dnsproxy – Tiny DNS proxy¶
The module forwards all queries, or all specific zone queries if configured per zone, to the indicated server for resolution. If configured in the fallback mode, only localy unsatisfied queries are forwarded. I.e. a tiny DNS proxy. There are several uses of this feature:
A substitute public-facing server in front of the real one
Local zones (poor man’s “views”), rest is forwarded to the public-facing server
Using the fallback to forward queries to a resolver
The module does not alter the query/response as the resolver would, and the original transport protocol is kept as well.
The configuration is straightforward and just a single remote server is required:
remote: - id: hidden address: 10.0.1.1 mod-dnsproxy: - id: default remote: hidden fallback: on template: - id: default global-module: mod-dnsproxy/default zone: - domain: local.zone
When clients query for anything in the
local.zone, they will be responded to locally. The rest of the requests will be forwarded to the specified server (
10.0.1.1 in this case).
mod-dnsproxy: - id: STR remote: remote_id timeout: INT fallback: BOOL catch-nxdomain: BOOL
A remote response timeout in milliseconds.
If enabled, localy unsatisfied queries leading to REFUSED (no zone) are forwarded. If disabled, all queries are directly forwarded without any local attempts to resolve them.
If enabled, localy unsatisfied queries leading to NXDOMAIN are forwarded. This option is only relevant in the fallback mode.
dnstap – Dnstap traffic logging¶
A module for query and response logging based on the dnstap library. You can capture either all or zone-specific queries and responses; usually you want to do the former.
The configuration comprises only a sink path parameter, which can be either a file or a UNIX socket:
mod-dnstap: - id: capture_all sink: /tmp/capture.tap template: - id: default global-module: mod-dnstap/capture_all
To be able to use a Unix socket you need an external program to create it. Knot DNS connects to it as a client using the libfstrm library. It operates exactly like syslog.
Dnstap log files can also be created or read using kdig.
For all queries logging, use this module in the default template. For zone-specific logging, use this module in the proper zone configuration.
mod-dnstap: - id: STR sink: STR identity: STR version: STR log-queries: BOOL log-responses: BOOL
A sink path, which can be either a file or a UNIX socket when prefixed with
File is overwritten on server startup or reload.
A DNS server identity. Set empty value to disable.
Default: FQDN hostname
A DNS server version. Set empty value to disable.
Default: server version
If enabled, query messages will be logged.
If enabled, response messages will be logged.
geoip — Geography-based responses¶
This module offers response tailoring based on client’s subnet, geographic location, or a statistical weight. It supports GeoIP databases in the MaxMind DB format, such as GeoIP2 or the free version GeoLite2.
The module can be enabled only per zone.
If EDNS Client Subnet support is enabled and if a query contains this option, the module takes advantage of this information to provide a more accurate response.
There are several ways to enable DNSSEC signing of tailored responses.
Full zone signing¶
If automatic DNSSEC signing is enabled, the whole zone is signed by the server and all alternative RRsets, which are responded by the module, are pre-signed when the module is loaded.
This has a speed benefit, however note that every RRset configured in the module should have a default RRset of the same type contained in the zone, so that the NSEC(3) chain can be built correctly. Also, it is STRONGLY RECOMMENDED to use manual key management in this setting, as the corresponding zone has to be reloaded when the signing key changes and to have better control over key synchronization to all instances of the server.
DNSSEC keys for computing record signatures MUST exist in the KASP database or be generated before the module is launched, otherwise the module fails to compute the signatures and does not load.
If automatic DNSSEC signing is disabled, it’s possible to combine externally pre-signed zone with module pre-signing of the alternative RRsets when the module is loaded. In this mode, only ZSK has to be present in the KASP database. Also in this mode every RRset configured in the module should have a default RRset of the same type contained in the zone.
policy: - id: presigned_zone manual: on unsafe-operation: no-check-keyset mod-geoip: - id: geo_dnssec ... dnssec: on policy: presigned_zone zone: - domain: example.com. module: mod-geoip/geo_dnssec
If the GeoIP module is used with online signing, it is recommended to set the nsec-bitmap option of the onlinesign module to contain all Resource Record types potentially generated by the module.
An example configuration:
mod-geoip: - id: default config-file: /path/to/geo.conf ttl: 20 mode: geodb geodb-file: /path/to/GeoLite2-City.mmdb geodb-key: [ country/iso_code, city/names/en ] zone: - domain: example.com. module: mod-geoip/default
Every instance of the module requires an additional config-file in which the desired responses to queries from various locations are configured. This file has the following simple format:
domain-name1: - geo|net|weight: value1 RR-Type1: RDATA RR-Type2: RDATA ... - geo|net|weight: value2 RR-Type1: RDATA ... domain-name2: ...
Module configuration examples¶
This section contains some examples for the module’s config-file.
foo.example.com: - net: 10.0.0.0/24 A: [ 192.168.1.1, 192.168.1.2 ] AAAA: [ 2001:DB8::1, 2001:DB8::2 ] TXT: "subnet\ 10.0.0.0/24" ... bar.example.com: - net: 2001:DB8::/32 A: 192.168.1.3 AAAA: 2001:DB8::3 TXT: "subnet\ 2001:DB8::/32" ...
Clients from the specified subnets will receive the responses defined in the module config. Others will receive the default records defined in the zone (if any).
If a space or a quotation mark is a part of record data, such a character must be prefixed with a backslash. The following notations are equivalent:
Multi-word\ string "Multi-word\ string" "\"Multi-word string\""
Using geographic locations¶
foo.example.com: - geo: "CZ;Prague" CNAME: cz.foo.example.com. - geo: "US;Las Vegas" CNAME: vegas.foo.example.net. - geo: "US;*" CNAME: us.foo.example.net. ...
Clients from the specified geographic locations will receive the responses defined in the module config. Others will receive the default records defined in the zone (if any). See geodb-key for the syntax and semantics of the location definitions.
Using weighted records¶
foo.example.com: - weight: 1 CNAME: canary.foo.example.com. - weight: 10 CNAME: prod1.foo.example.com. - weight: 10 CNAME: prod2.foo.example.com. ...
Each response is generated through a random pick where each defined record has a likelyhood of its weight over the sum of all weights for the requested name to. Records defined in the zone itself (if any) will never be served.
$ for i in $(seq 1 100); do kdig @192.168.1.242 CNAME foo.example.com +short; done | sort | uniq -c 3 canary.foo.example.com.foo.example.com. 52 prod1.foo.example.net.foo.example.com. 45 prod2.foo.example.net.foo.example.com.
mod-geoip: - id: STR config-file: STR ttl: TIME mode: geodb | subnet | weighted dnssec: BOOL policy: policy_id geodb-file: STR geodb-key: STR ...
Full path to the response configuration file as described above.
The time to live of Resource Records returned by the module.
The mode of operation of the module.
subnet– Responses are tailored according to subnets.
geodb– Responses are tailored according to geographic data retrieved from the configured database.
weighted– Responses are tailored according to a statistical weight.
If explicitly enabled, the module signs positive responses based on the module policy (policy). If explicitly disabled, positive responses from the module are not signed even if the zone is pre-signed or signed by the server (dnssec-signing).
This configuration must be used carefully. Otherwise the zone responses can be bogus. DNSKEY rotation isn’t supported. So manual mode is highly recommended.
Default: an imaginary policy with all default values
Full path to a .mmdb file containing the GeoIP database.
Required if mode is set to geodb
Multi-valued item, can be specified up to 8 times. Each geodb-key specifies a path to a key in a node in the supplied GeoIP database. The module currently supports two types of values: string or 32-bit unsigned int. In the latter case, the key has to be prefixed with (id). Common choices of keys include:
The exact keys available depend on the database being used. To get the full list of keys available, you can e.g. do a sample lookup on your database with the mmdblookup tool.
In the zone’s config file for the module the values of the keys are entered in the same order as the keys in the module’s configuration, separated by a semicolon. Enter the value “*” if the key is allowed to have any value.
noudp — No UDP response¶
The module sends empty truncated reply to a query over UDP. Replies over TCP are not affected.
To enable this module for all configured zones and every UDP reply:
template: - id: default global-module: mod-noudp
Or with specified UDP allow rate:
mod-noudp: - id: sometimes udp-allow-rate: 1000 # Don't truncate every 1000th UDP reply template: - id: default module: mod-noudp/sometimes
mod-noudp: - id: STR udp-allow-rate: INT udp-truncate-rate: INT
Both udp-allow-rate and udp-truncate-rate cannot be specified together.
Specifies frequency of UDP replies that are not truncated. A non-zero value means that every Nth UDP reply is not truncated.
The rate value is associated with one UDP worker. If more UDP workers are configured, the specified value may not be obvious to clients.
Default: not set
Specifies frequency of UDP replies that are truncated (opposite of udp-allow-rate). A non-zero value means that every Nth UDP reply is truncated.
The rate value is associated with one UDP worker. If more UDP workers are configured, the specified value may not be obvious to clients.
onlinesign — Online DNSSEC signing¶
The module provides online DNSSEC signing. Instead of pre-computing the zone signatures when the zone is loaded into the server or instead of loading an externally signed zone, the signatures are computed on-the-fly during answering.
The main purpose of the module is to enable authenticated responses with zones which use other dynamic module (e.g., automatic reverse record synthesis) because these zones cannot be pre-signed. However, it can be also used as a simple signing solution for zones with low traffic and also as a protection against zone content enumeration (zone walking).
In order to minimize the number of computed signatures per query, the module produces a bit different responses from the responses that would be sent if the zone was pre-signed. Still, the responses should be perfectly valid for a DNSSEC validating resolver.
Differences from statically signed zones:
The NSEC records are constructed as Minimally Covering NSEC Records (RFC 7129#appendix-A). Therefore the generated domain names cover the complete domain name space in the zone’s authority.
NXDOMAIN responses are promoted to NODATA responses. The module proves that the query type does not exist rather than that the domain name does not exist.
Domain names matching a wildcard are expanded. The module pretends and proves that the domain name exists rather than proving a presence of the wildcard.
Records synthesized by the module:
DNSKEY record is synthesized in the zone apex and includes public key material for the active signing key.
NSEC records are synthesized as needed.
RRSIG records are synthesized for authoritative content of the zone.
CDNSKEY and CDS records are generated as usual to publish valid Secure Entry Point.
Due to limited interaction between the server and the module, after any change to KASP DB (including knotc zone-ksk-submitted command) or when a scheduled DNSSEC event shall be processed (e.g. transition to next DNSKEY rollover state) the server must be reloaded or queried to the zone (with the DO bit set) to apply the change or to trigger the event. For optimal operation, the recommended query frequency is at least ones per second for each zone configured.
The NSEC records may differ for one domain name if queried for different types. This is an implementation shortcoming as the dynamic modules cooperate loosely. Possible synthesis of a type by other module cannot be predicted. This dissimilarity should not affect response validation, even with validators performing aggressive negative caching (RFC 8198).
The module isn’t compatible with the Offline KSK mode yet.
Configure the module with an explicit signing policy which has the rrsig-lifetime value in the order of hours.
Note that single-type-signing should be set explicitly to avoid fallback to backward-compatible default.
Enable the module in the zone configuration with the default signing policy:
zone: - domain: example.com module: mod-onlinesign
Or with an explicit signing policy:
policy: - id: rsa algorithm: RSASHA256 ksk-size: 2048 rrsig-lifetime: 25h rrsig-refresh: 20h mod-onlinesign: - id: explicit policy: rsa zone: - domain: example.com module: mod-onlinesign/explicit
Or use manual policy in an analogous manner, see Manual key management.
Make sure the zone is not signed and also that the automatic signing is disabled. All is set, you are good to go. Reload (or start) the server:
$ knotc reload
The following example stacks the online signing with reverse record synthesis module:
mod-synthrecord: - id: lan-forward type: forward prefix: ip- ttl: 1200 network: 192.168.100.0/24 zone: - domain: corp.example.net module: [mod-synthrecord/lan-forward, mod-onlinesign]
mod-onlinesign: - id: STR policy: policy_id nsec-bitmap: STR ...
A reference to DNSSEC signing policy. A special default value can be used for the default policy setting.
Default: an imaginary policy with all default values
probe — DNS traffic probe¶
The module allows the server to send simplified information about regular DNS traffic through UNIX sockets. The exported information consists of data blocks where each data block (datagram) describes one query/response pair. The response part can be empty. The receiver can be an arbitrary program using libknot interface (C or Python). In case of high traffic, more channels (sockets) can be configured to allow parallel processing.
Default module configuration:
template: - id: default global-module: mod-probe
Per zone probe with 8 channels and maximum 1M logs per second limit:
mod-probe: - id: custom prefix: /tmp/knot-probe channels: 8 max-rate: 1000000 zone: - domain: example.com. module: mod-probe/custom
mod-probe: - id: STR path: STR channels: INT max-rate: INT
A directory path the UNIX sockets are located.
It’s recommended to use a directory with the execute permission resctricted to the intended probe consumer process owner only.
Number of channels (UNIX sockets) the traffic is distributed to. In case of high DNS traffic which is beeing processed by many UDP/XDP/TCP workers, using more channels reduces the module overhead.
Maximum number of queries/replies per second the probe is allowed to transfer. If the limit is exceeded, the over-limit traffic is ignored. Zero value means no limit.
queryacl — Limit queries by remote address or target interface¶
This module provides a simple way to whitelist incoming queries according to the query’s source address or target interface. It can be used e.g. to create a restricted-access subzone with delegations from the corresponding public zone. The module may be enabled both globally and per-zone.
The module limits only regular queries. Notify, transfer and update are handled by ACL.
mod-queryacl: - id: default address: [192.0.2.73-192.0.2.90, 203.0.113.0/24] interface: 198.51.100 zone: - domain: example.com module: mod-queryacl/default
mod-queryacl: - id: STR address: ADDR[/INT] | ADDR-ADDR ... interface: ADDR[/INT] | ADDR-ADDR ...
An optional list of allowed ranges and/or subnets for query’s source address. If the query’s address does not fall into any of the configured ranges, NOTAUTH rcode is returned.
Default: not set
An optional list of allowed ranges and/or subnets for query’s target interface. If the interface does not fall into any of the configured ranges, NOTAUTH rcode is returned. Note that every interface used has to be configured in listen.
Don’t use values 0.0.0.0 and ::0. These values are redundant and don’t work as expected.
Default: not set
rrl — Response rate limiting¶
Response rate limiting (RRL) is a method to combat DNS reflection amplification attacks. These attacks rely on the fact that source address of a UDP query can be forged, and without a worldwide deployment of BCP38, such a forgery cannot be prevented. An attacker can use a DNS server (or multiple servers) as an amplification source and can flood a victim with a large number of unsolicited DNS responses. The RRL lowers the amplification factor of these attacks by sending some of the responses as truncated or by dropping them altogether.
The module introduces two statistics counters. The number of slipped and dropped responses.
If the Cookies module is active, RRL is not applied for responses with a valid DNS cookie.
You can enable RRL by setting the module globally or per zone.
mod-rrl: - id: default rate-limit: 200 # Allow 200 resp/s for each flow slip: 2 # Approximately every other response slips template: - id: default global-module: mod-rrl/default # Enable RRL globally
mod-rrl: - id: STR rate-limit: INT slip: INT table-size: INT whitelist: ADDR[/INT] | ADDR-ADDR ...
Rate limiting is based on the token bucket scheme. A rate basically represents a number of tokens available each second. Each response is processed and classified (based on several discriminators, e.g. source netblock, query type, zone name, rcode, etc.). Classified responses are then hashed and assigned to a bucket containing number of available tokens, timestamp and metadata. When available tokens are exhausted, response is dropped or sent as truncated (see slip). Number of available tokens is recalculated each second.
Size of the hash table in a number of buckets. The larger the hash table, the lesser the probability of a hash collision, but at the expense of additional memory costs. Each bucket is estimated roughly to 32 bytes. The size should be selected as a reasonably large prime due to better hash function distribution properties. Hash table is internally chained and works well up to a fill rate of 90 %, general rule of thumb is to select a prime near 1.2 * maximum_qps.
As attacks using DNS/UDP are usually based on a forged source address, an attacker could deny services to the victim’s netblock if all responses would be completely blocked. The idea behind SLIP mechanism is to send each Nth response as truncated, thus allowing client to reconnect via TCP for at least some degree of service. It is worth noting, that some responses can’t be truncated (e.g. SERVFAIL).
Setting the value to 0 will cause that all rate-limited responses will be dropped. The outbound bandwidth and packet rate will be strictly capped by the rate-limit option. All legitimate requestors affected by the limit will face denial of service and will observe excessive timeouts. Therefore this setting is not recommended.
Setting the value to 1 will cause that all rate-limited responses will be sent as truncated. The amplification factor of the attack will be reduced, but the outbound data bandwidth won’t be lower than the incoming bandwidth. Also the outbound packet rate will be the same as without RRL.
Setting the value to 2 will cause that approximately half of the rate-limited responses will be dropped, the other half will be sent as truncated. With this configuration, both outbound bandwidth and packet rate will be lower than the inbound. On the other hand, the dropped responses enlarge the time window for possible cache poisoning attack on the resolver.
Setting the value to anything larger than 2 will keep on decreasing the outgoing rate-limited bandwidth, packet rate, and chances to notify legitimate requestors to reconnect using TCP. These attributes are inversely proportional to the configured value. Setting the value high is not advisable.
A list of IP addresses, network subnets, or network ranges to exempt from rate limiting. Empty list means that no incoming connection will be white-listed.
Default: not set
stats — Query statistics¶
The module extends server statistics with incoming DNS request and corresponding response counters, such as used network protocol, total number of responded bytes, etc (see module reference for full list of supported counters). This module should be configured as the last module.
Server initiated communication (outgoing NOTIFY, incoming *XFR,…) is not counted by this module.
Leading 16-bit message size over TCP is not considered.
Common statistics with default module configuration:
template: - id: default global-module: mod-stats
Per zone statistics with explicit module configuration:
mod-stats: - id: custom edns-presence: on query-type: on template: - id: default module: mod-stats/custom
mod-stats: - id: STR request-protocol: BOOL server-operation: BOOL request-bytes: BOOL response-bytes: BOOL edns-presence: BOOL flag-presence: BOOL response-code: BOOL request-edns-option: BOOL response-edns-option: BOOL reply-nodata: BOOL query-type: BOOL query-size: BOOL reply-size: BOOL
If enabled, all incoming requests are counted by the network protocol:
udp4 - UDP over IPv4
tcp4 - TCP over IPv4
udp6 - UDP over IPv6
tcp6 - TCP over IPv6
udp4-xdp - UDP over IPv4 through XDP
tcp4-xdp - TCP over IPv4 through XDP
udp6-xdp - UDP over IPv6 through XDP
tcp6-xdp - TCP over IPv6 through XDP
If enabled, all incoming requests are counted by the server operation. The server operation is based on message header OpCode and message query (meta) type:
query - Normal query operation
update - Dynamic update operation
notify - NOTIFY request operation
axfr - Full zone transfer operation
ixfr - Incremental zone transfer operation
invalid - Invalid server operation
If enabled, all incoming request bytes are counted by the server operation:
query - Normal query bytes
update - Dynamic update bytes
other - Other request bytes
If enabled, outgoing response bytes are counted by the server operation:
reply - Normal response bytes
transfer - Zone transfer bytes
other - Other response bytes
Dynamic update response bytes are not counted by this module.
If enabled, EDNS pseudo section presence is counted by the message direction:
request - EDNS present in request
response - EDNS present in response
If enabled, some message header flags are counted:
TC - Truncated Answer in response
DO - DNSSEC OK in request
If enabled, outgoing response code is counted:
other - All other codes
In the case of multi-message zone transfer response, just one counter is incremented.
Dynamic update response code is not counted by this module.
If enabled, EDNS options in requests are counted by their code:
other - All other codes
If enabled, EDNS options in responses are counted by their code. See request-edns-option.
If enabled, normal query type is counted:
other - All other types
Not all assigned meta types (IXFR, AXFR,…) have their own counters, because such types are not processed as normal query.
If enabled, normal query message size distribution is counted by the size range in bytes:
synthrecord – Automatic forward/reverse records¶
This module is able to synthesize either forward or reverse records for a given prefix and subnet.
Records are synthesized only if the query can’t be satisfied from the zone. Both IPv4 and IPv6 are supported.
Automatic forward records¶
mod-synthrecord: - id: test1 type: forward prefix: dynamic- ttl: 400 network: 2620:0:b61::/52 zone: - domain: test. file: test.zone # Must exist module: mod-synthrecord/test1
$ kdig AAAA dynamic-2620-0-b61-100--1.test. ... ;; QUESTION SECTION: ;; dynamic-2620-0-b61-100--1.test. IN AAAA ;; ANSWER SECTION: dynamic-2620-0-b61-100--1.test. 400 IN AAAA 2620:0:b61:100::1
You can also have CNAME aliases to the dynamic records, which are going to be further resolved:
$ kdig AAAA alias.test. ... ;; QUESTION SECTION: ;; alias.test. IN AAAA ;; ANSWER SECTION: alias.test. 3600 IN CNAME dynamic-2620-0-b61-100--2.test. dynamic-2620-0-b61-100--2.test. 400 IN AAAA 2620:0:b61:100::2
Automatic reverse records¶
mod-synthrecord: - id: test2 type: reverse prefix: dynamic- origin: test ttl: 400 network: 2620:0:b61::/52 zone: - domain: 1.6.b.0.0.0.0.0.0.2.6.2.ip6.arpa. file: 1.6.b.0.0.0.0.0.0.2.6.2.ip6.arpa.zone # Must exist module: mod-synthrecord/test2
$ kdig -x 2620:0:b61::1 ... ;; QUESTION SECTION: ;; 22.214.171.124.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.6.b.0.0.0.0.0.0.2.6.2.ip6.arpa. IN PTR ;; ANSWER SECTION: 126.96.36.199.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.6.b.0.0.0.0.0.0.2.6.2.ip6.arpa. 400 IN PTR dynamic-2620-0-b61--1.test.
Known bugs, limitations¶
The queries to the empty-non-terminals being parent to synthesized reverse records, e.g. 0.0.0.0.1.6.b.0.0.0.0.0.0.2.6.2.ip6.arpa. IN PTR with previous example, are answered wrongly NXDOMAIN instead of correct NODATA.
mod-synthrecord: - id: STR type: forward | reverse prefix: STR origin: DNAME ttl: INT network: ADDR[/INT] | ADDR-ADDR ... reverse-short: BOOL
The type of generated records.
forward– Forward records
reverse– Reverse records
A record owner prefix.
The value doesn’t allow dots, address parts in the synthetic names are separated with a dash.
Time to live of the generated records.
An IP address, a network subnet, or a network range the query must match.
If enabled, a shortened IPv6 address can be used for reverse record rdata synthesis.
whoami — Whoami response¶
The module synthesizes an A or AAAA record containing the query source IP address, at the apex of the zone being served. It makes sure to allow Knot DNS to generate cacheable negative responses, and to allow fallback to extra records defined in the underlying zone file. The TTL of the synthesized record is copied from the TTL of the SOA record in the zone file.
Because a DNS query for type A or AAAA has nothing to do with whether the query occurs over IPv4 or IPv6, this module requires a special zone configuration to support both address families. For A queries, the underlying zone must have a set of nameservers that only have IPv4 addresses, and for AAAA queries, the underlying zone must have a set of nameservers that only have IPv6 addresses.
To enable this module, you need to add something like the following to the Knot DNS configuration file:
zone: - domain: whoami.domain.example file: "/path/to/whoami.domain.example" module: mod-whoami zone: - domain: whoami6.domain.example file: "/path/to/whoami6.domain.example" module: mod-whoami
The whoami.domain.example zone file example:
$TTL 1 @ SOA ( whoami.domain.example. ; MNAME hostmaster.domain.example. ; RNAME 2016051300 ; SERIAL 86400 ; REFRESH 86400 ; RETRY 86400 ; EXPIRE 1 ; MINIMUM ) $TTL 86400 @ NS ns1.whoami.domain.example. @ NS ns2.whoami.domain.example. @ NS ns3.whoami.domain.example. @ NS ns4.whoami.domain.example. ns1 A 198.51.100.53 ns2 A 192.0.2.53 ns3 A 203.0.113.53 ns4 A 198.19.123.53
The whoami6.domain.example zone file example:
$TTL 1 @ SOA ( whoami6.domain.example. ; MNAME hostmaster.domain.example. ; RNAME 2016051300 ; SERIAL 86400 ; REFRESH 86400 ; RETRY 86400 ; EXPIRE 1 ; MINIMUM ) $TTL 86400 @ NS ns1.whoami6.domain.example. @ NS ns2.whoami6.domain.example. @ NS ns3.whoami6.domain.example. @ NS ns4.whoami6.domain.example. ns1 AAAA 2001:db8:100::53 ns2 AAAA 2001:db8:200::53 ns3 AAAA 2001:db8:300::53 ns4 AAAA 2001:db8:400::53
The parent domain would then delegate whoami.domain.example to ns[1-4].whoami.domain.example and whoami6.domain.example to ns[1-4].whoami6.domain.example, and include the corresponding A-only or AAAA-only glue records.
This module is not configurable.