rbldnsd(8)

๐Ÿ“ฆ
version 1.0~20210120 ยท 2 versions
, in debian bookworm, debian trixie, epel 10, fedora 43, fedora 44, ubuntu noble, ubuntu questing, ubuntu resolute, ubuntu stonking

๐Ÿ’– rbldnsd(8) ๐Ÿ’–

okay so bestie, welcome to the manual for rbldnsd โ€” that's section 8 of the System Manager's Manual, for the record, so you know exactly which page you're vibing with. it's a daemon, it's iconic, let's get into it ๐Ÿฆ‹


โœจ NAME โœจ

rbldnsd โ€” a DNS daemon that's literally built for running DNS-based blocklists ๐Ÿ‘‘


๐ŸŒธ SYNOPSIS ๐ŸŒธ

rbldnsd options zone:dataset...

๐Ÿ’… DESCRIPTION ๐Ÿ’…

okay so here's the tea: rbldnsd is a small DNS-protocol daemon, and it was designed with exactly one glow-up goal โ€” handling queries to DNS-based IP-listing or NAME-listing services. and like, why do those services exist? because they're a super simple way to share and publish a list of IP addresses or (domain) names that are "listed" for some reason. classic use case: you wanna be able to refuse service to a client that shows up on some blocklist. that's it. that's the whole vibe check ๐ŸŽ€

but listen โ€” rbldnsd is not a general-purpose nameserver, and it's not trying to be. it will answer A and TXT queries (and SOA and NS if such RRs are specified), and it has limited ability to answer to some other types of queries. that's the whole menu. no cap.

rbldnsd tries to handle data from two different perspectives: give it a set (or several) of "listed entries" โ€” like IP address ranges or domain names โ€” and it builds and serves a DNS zone. and bestie, the way those two things are not the same is the whole plot twist:

  • a list of spammer's IPs is NOT a DNS zone
  • but it may be represented and used as one, provided that some additional information necessary to build a complete DNS zone is available โ€” like NS and SOA records, and maybe A records necessary for http to work

and this is exactly where rbldnsd is very different from other general-purpose nameservers such as BIND or NSD:

  • rbldnsd operates with datasets โ€” sets of entries, IP addresses or domain names, logically grouped together
  • other general-purpose nameservers operate with zones

so yeah, the way rbldnsd operates may be somewhat confusing to BIND experts. sorry not sorry, BIND experts ๐Ÿ™ˆ


for rbldnsd, the building block is a dataset. examples, bestie:

  • set of insecure/abuseable hosts (IP addresses)
  • set of network ranges that belongs to various spam operations (IP ranges)
  • domain names that belong to spammers (RHSBL)
  • and so on, you get the picture โœจ

usually, different kind of information is placed into a separate file, because easy maintenance is the moment. and from a number of such datasets, rbldnsd constructs a number of DNS zones as specified on the command line. the flexibility?? unmatched:

  • a single dataset may be used for several zones
  • a single zone may be constructed from several datasets

rbldnsd will answer queries to DNS zones specified on the command line as a set of zone specifications. each zone specification consists of:

  • the zone basename
  • the dataset type
  • a comma-separated list of files that forms a given dataset

like so: zone:type:file,file,... ๐ŸŒˆ

several zones may be specified in the command line, so that rbldnsd will answer queries to any of them. also, a single zone may be specified several times with different datasets, so it's possible to form a zone from a combination of several different datasets. and the same dataset may be reused for several zones too โ€” and in this case, it will be read into memory only once. efficiency queen ๐Ÿ’…


๐Ÿงš dataset types, grouped by category ๐Ÿงš

there are several dataset formats available, and each one is suitable and optimized (in terms of memory, speed and ease of use) for a specific task. the available dataset types may be grouped into the following categories:

  • lists of IP addresses. when a query is done to a zone with such data, the query is interpreted as an IP address in a reverse form (similar to in-addr.arpa zone). if the address is found in dataset data, rbldnsd will return A and TXT records specified in data for that IP. this is a classical IP-based blocklist ๐Ÿ“
  • lists of domain names. similar to list of IP addresses, but with generic domain names instead of IPs (wildcards allowed). this type of data may be used to form a blocklist of e.g. sender domain names.
  • generic list of various types of records, as an auxilary data to form a complete nameserver. this format is similar to bind-style datafiles, but very simplified. one may specify A, TXT, NS and MX records here.
  • combined set, different datasets from the list above combined in the single (set of) source files, for easy maintenance.
  • acl, or Access Control List. this is a pseudo dataset, that works by overwriting query results based on the requestor (peer) IP address. ๐Ÿ”ฅ

quick vibe recap: small daemon, A and TXT (plus SOA and NS when specified), datasets not zones, and acl is a pseudo dataset that rewrites results per requestor IP. got it? slay. ๐Ÿ’–

(and bestie, this was only part 1 โ€” there's more, don't go anywhere) ๐ŸŒธ

โœจ OPTIONS โœจ

Okay bestie, here's the whole menu of flags. The following options may be specified:

  • -u user[:group] โ€” rbldnsd switches its userid over to user (which defaults to rbldns) and to group (which defaults to the primary group of that user). And heads up โ€” rbldnsd will straight up refuse to run as the root user, because that's insecure, no cap. ๐Ÿ™…โ€โ™€๏ธ
  • -r rootdir โ€” rbldnsd will chroot into rootdir if you specify it. Your data files need to be available inside rootdir, so plan accordingly. ๐Ÿ 
  • -w workdir โ€” rbldnsd changes its working directory to workdir (and it does this after chrooting into rootdir if you also passed -r). Handy for shortening up those filename paths. ๐Ÿ“
  • -b address/port โ€” this one is required, periodt. rbldnsd binds to the address and port you give it (port defaults to port 53, i.e. domain). You can hand it either a numeric IP address or a hostname, and either a port number or a service name โ€” both are accepted. Want several addresses to listen on? Just repeat the -b option. Also, if a hostname resolves to several addresses, rbldnsd will listen on all of them. Note that rbldnsd runs slightly faster if only one listening address is specified. And the delimiter between host and port is a slash (/), not a colon โ€” that's so IPv6 addresses get handled correctly. ๐Ÿ’…
  • -4 โ€” use an IPv4 listening socket/transport and don't even attempt IPv6. (This gets ignored if rbldnsd was built without IPv6 support.) 4๏ธโƒฃ
  • -6 โ€” use an IPv6 listening socket/transport and don't attempt IPv4. (If IPv6 support wasn't compiled in, this option will be reported as an error.) 6๏ธโƒฃ
  • -t defttl:minttl:maxttl โ€” sets the default reply time-to-live (TTL) to defttl, and sets the constraints for TTL to minttl and maxttl. The default kicks in when there's no TTL defined in a given scope (in a data file), and the constraints get applied when such a value is provided in the data. You can omit any of the values, including the trailing colon (:) characters โ€” like, -t 30 sets the default TTL to 30 seconds, and -t ::120 or -t ::2m sets the maximum allowed TTL to 2 minutes. All 3 values are in time units, with an optional suffix: s (seconds, the default), m (minutes), h (hours), d (days) or w (weeks). A zero minttl or maxttl means no corresponding constraint gets enforced at all. The default defttl is 35m. โฐ
  • -c check โ€” sets the interval between checks for zone file changes to check, default is 1m (one minute). rbldnsd checks the zone file's last modification time every so often, and if it spots a change, the zone gets automatically reloaded. Setting this value to 0 disables automatic zone change detection entirely. This whole procedure can also be triggered by sending a SIGHUP signal to rbldnsd (peep the SIGNALS section below). ๐Ÿ”„
  • -e โ€” allows non-network addresses to be used in CIDR ranges. Normally rbldnsd rejects addresses like 127.2.3.4/24, where the prefix isn't within the network mask derived from the bit number (in that case the correct form is 127.2.3.0/24 โ€” note the trailing zero in the prefix). It does this to catch possible typos in zones (rbldnsd will warn about the problem and ignore such an entry). This option disables the check on whether the CIDR prefix fits within the network mask. ๐Ÿ•ต๏ธโ€โ™€๏ธ
  • -q โ€” quick and quiet start. Normally rbldnsd does socket initialization and zone load in the foreground, writing progress and statistic to standard output, and aborts if there are any errors. With this flag, nothing gets printed and the first zone load happens in the background (unless the -n option is also given), and zone load errors become non-fatal. ๐Ÿคซ
  • -p pidfile โ€” writes rbldnsd's pid to the specified pidfile, so it's easy to find. This file gets written before entering a chroot jail (if one is specified) and before changing userid, so it's totally fine to specify something like /var/run/rbldnsd.pid here. ๐Ÿ“Œ
  • -l logfile โ€” specifies a file to which to log all requests made. This file is created after entering a chroot jail and becoming a user. Logfiles can get quite large, esp. on busy sites (rbldnsd will log every recognized request if this option is specified). This option is mainly intended for debugging purposes. Upon receiving a SIGHUP signal, rbldnsd reopens its logfile. If logfile is prefixed with a plus sign (+), logging will not be buffered (i.e. each line gets flushed to disk); by default, logging is buffered to reduce system load. Specify a single hyphen (-) as the filename to log to standard output (filedescriptor 1), either buffered by default, or line-buffered if specified as +- (standard output will not be "reopened" upon receiving a SIGHUP signal, but will be flushed in case logging is buffered). ๐Ÿ“
  • -s statsfile โ€” specifies a file where rbldnsd writes a line with a short statistic summary of queries made per zone, every check (-c) interval. Format of each line is:
  timestamp zone:qtot:qok:qnxd:bin:bout zone:...

where timestamp is unix time (seconds since epoch), zone is the name of the base zone, qtot is the total number of queries received, qok is the number of positive replies, qnxd is the number of NXDOMAIN replies, bin is the total number of bytes read from the network (excluding IP/UDP overhead and dropped packets), and bout is the total number of bytes written to the network. There are as many such tuples as there are zones, plus one extra total tuple at the end, with zone being "*", like:

  1234 bl1.ex:10:5:4:311:432 bl2.ex:24:13:7:248:375 *:98:35:12:820:987

Note that the total values may be larger than the sum of the per-zone values, because of queries made against unlisted zones, or bad/broken packets. ๐Ÿงฎ

rbldnsd writes a bare timestamp to statsfile when it's starting up, shutting down, or when statistic counters are being reset after receiving a SIGUSR2 signal (see below) โ€” that marks the points where the counters are starting back from zero.

By default, rbldnsd writes absolute counter values into statsfile (number of packets (bytes) since startup or last reset). statsfile may be prefixed with a plus sign (+), in which case rbldnsd writes delta values instead โ€” that is, the number of packets or bytes since the last write, or the number of packets (bytes) per unit of time ("incremental" mode, hence the + sign). โž•

  • -n โ€” do not become a daemon. Normally rbldnsd forks and goes to the background after successful initialization. This option disables that behaviour. ๐Ÿ›‘
  • -f โ€” asks rbldnsd to keep processing requests during data reloads. rbldnsd forks a child process to handle requests while the parent reloads the data. This keeps operations smooth, but it needs more memory, since two copies of the data are kept in memory during the reload process. ๐Ÿซถ
  • -d โ€” dump all zones to stdout in BIND format and exit. This may be suitable for converting easily editable rbldnsd-style data into a BIND zone. rbldnsd dumps all zones as one stream, so you may want to specify only one zone with -d. The zone file will have appropriate $ORIGIN tags. Note that the data generated may be really huge (since BIND format isn't appropriate for this sort of thing), and some entries may not really be the same in BIND as in rbldnsd (e.g., IP netblocks of large size will be represented as wildcard entries โ€” 10.0.0.0/8 will become *.10; excluded entries will be represented by a CNAME to excluded name, so such a name should not be present in a data set). In this mode, rbldnsd ignores the -r (root directory) option. ๐Ÿ—‚๏ธ
  • -v โ€” do not show exact version information in response to version.bind CH TXT queries (by default rbldnsd responds to such queries since version 0.98). With a single -v, rbldnsd will only return "rbldnsd" to the caller, without the version number. A second -v disables providing any information in response to such requests โ€” i.e. rbldnsd will return the REFUSE code. ๐Ÿค
  • -C โ€” disable automatic on-the-fly uncompression of data files, if that feature is compiled in (see below). ๐Ÿšซ
  • -A โ€” (its whole deal is described together with -a right below ๐Ÿ‘‡)
  • -a โ€” controls how "lazy" rbldnsd is when constructing replies. With -a specified, rbldnsd does not add the AUTH section (containing NS records) to replies unless NS records are explicitly asked for. This is equivalent to the BIND9 "minimal-answers" configuration setting. While with -A specified, rbldnsd will always fill in the AUTH section, increasing the size of replies dramatically but letting (caching) resolver clients react faster to changes in nameserver lists. Currently (as of 0.998 version), the non-lazy (as with -A) mode is the default, but it will change in a future release. โš–๏ธ
  • -x extension โ€” load the given extension file (a dynamically-linked library, usually with a ".so" suffix). This allows gathering custom statistics or performing other custom tasks. See the separate document for details about building and using extensions. This feature is not available on all platforms, and can be disabled at compile time. ๐Ÿ”Œ
  • -X extarg โ€” pass the given argument, extarg, to the extension loaded with -x. ๐Ÿ“จ

๐Ÿฆ‹ DATASET TYPES AND FORMATS ๐Ÿฆ‹

Dataset files are text files which get interpreted depending on the type specified in the command line. Empty lines and lines starting with a hash character (#) or semicolon (;) are ignored โ€” except for one special case outlined below in the section titled "Special Entries". ๐Ÿ™ˆ

A (comma-separated) list of files in a dataset specification (in type:file,file,...) is interpreted as if all the files were logically combined into one single file. ๐Ÿงฉ

When compiled with zlib support, rbldnsd is able to read gzip-compressed data files. So every file in a dataset specification can be compressed with gzip(1), and rbldnsd will read such a file decompressing it on-the-fly. This feature may be turned off by specifying the -C option. ๐Ÿ—œ๏ธ

rbldnsd is designed to service a DNSBL, where each entry has a single A record and an optional TXT record associated with it. rbldnsd lets you specify the A value and TXT template either for each entry individually, or use a default A value and TXT template pair for a group of entries. See the section "Resulting A values and TXT templates" below for the way to specify them. ๐Ÿ‘‘

Special Entries โœจ

So like, sometimes a line wants to be โœจextraโœจ and not just be a plain old entry. If a line starts with a dollar sign ($), or a hash character and a dollar sign (#$), or a semicolon and dollar sign (;#), or a colon and a dollar sign (:$), it gets interpreted in a special way, no matter what dataset type you're in. This is the one single exception where a line starting with a hash character is NOT ignored โ€” and that's on purpose, bestie, because it means you can use the same zone files for both rbldnsd and for DJB's rbldns. Iconic, right? ๐Ÿ’…

The following keywords, following a dollar sign, are recognized:

  • $SOA ttl origindn persondn serial refresh retry expire minttl โ€” Specifies the SOA (Start Of Authority) record for all zones using this dataset. Only the first $SOA record is interpreted. This is the only way to specify SOA โ€” by default, rbldnsd will not add any SOA record into answers, and will REFUSE to answer certain queries (notably, an SOA query to the zone's base domain name ๐Ÿ˜ญ). It's recommended, but not mandatory, to specify an SOA record for every zone. If no SOA is given, negative replies will not be cacheable by caching nameservers. Only one, first $SOA line is recognized in every dataset (all subsequent $SOA lines encountered in the same dataset are silently ignored ๐Ÿ™ˆ). When constructing a zone, SOA will be taken from the first dataset where a $SOA line is found, in an order as specified in the command line; subsequent $SOA lines, if any, are ignored. This way, you can overwrite a $SOA found in 3rd party data by prepending a small local file to the dataset in question, listing it before any other files. ๐Ÿ‘‘

If the serial value specified is zero, the timestamp of the most recent modified file will be substituted as the serial.

If the ttl field is zero, the default ttl (-t option or last $TTL value, see below) will be used.

All time fields (ttl, refresh, retry, expire, minttl) may be specified in time units. See the -t option for details.

  • $NS ttl nameserverdn nameserverdn... โ€” Specifies NS (Name Server) records for all zones using this dataset. Only the first $NS line in a dataset is recognized, all subsequent lines are silently ignored. When constructing a zone from several datasets, rbldnsd uses nameservers from the $NS line in only the first dataset where a $NS line is given, in command-line order, just like for the $SOA record. Only the first 32 nameservers are recognized. Individual nameserver(s) may be prefixed with a minus sign (-), which means this single nameserver will be ignored by rbldnsd. This is useful to temporarily comment out one nameserver entry without removing it from the list. ๐Ÿ’ก If ttl is zero, the default ttl will be used. The list of NS records, just like the $SOA value, is taken from the first data file in a dataset where the $NS line is found; subsequent $NS lines, if any, are ignored.
  • $TTL time-to-live โ€” Specifies the TTL (time-to-live) value for all records in the current dataset. See also the -t option. $TTL special overrides the -t value on a per-dataset basis. ๐ŸŽ€
  • $TIMESTAMP dstamp [expires] โ€” (experimental ๐Ÿ‘ฉโ€๐Ÿ”ฌ) Specifies the data timestamp dstamp for when the data has been generated, and optionally when it will expire. The timestamps are in the form yyyy:mm:dd[:hh[:mi[:ss]]], where yyyy is the year like 2005, mm is the month number (01..12), dd is the month day number (01..31), hh is hour (00..23), mi and ss are minutes and seconds (00..59); hours, minutes and seconds are optional and default to 0; the delimiters (either colon or dash may be used) are optional too, but are allowed for readability. Also, a single zero (0) or dash (-) may be used as dstamp and/or expires, indicating the value is not given. expires may also be specified as +rel, where rel is a time specification (probably with a suffix like s, m, h, d) as an offset to dstamp. rbldnsd compares dstamp with the current timestamp and refuses to load the file if dstamp specifies a time in the future. And if expires is specified, rbldnsd will refuse to service requests for that data if the current time is greater than the value specified in the expires field.
Note that rbldnsd will check the data expiry time every time it checks for data file updates (when receiving SIGHUP signal or every -c interval). If the automatic data reload timer (-c option) is disabled, zones will not be expired automatically. ๐Ÿ˜ณ
  • $MAXRANGE4 range-size โ€” Specifies the maximum size of an IPv4 range allowed for IPv4-based datasets. If an entry covers more IP addresses than range-size, it will be ignored (and a warning will be logged). range-size may be specified as a number of hosts, like 256, or as a network prefix length, like /24 (the two are the same):
$MAXRANGE4 /24
$MAXRANGE4 256

This constraint is active for the dataset it is specified in, and can be overwritten (by a subsequent $MAXRANGE statement) by a smaller value, but can not be increased. ๐Ÿšซ

  • $n text โ€” (n is a single digit). Specifies a substitution variable for use as $n placeholders (the $n entries are ignored in generic dataset). See the section "Resulting A values and TXT templates" below for description and usage examples.
  • $= text โ€” Set the base template for all individual TXT records. See the section "Resulting A values and TXT templates" below for more information.

๐ŸŒˆ ip4set Dataset

A set of IP addresses or CIDR address ranges, together with A and TXT resulting values. IP addresses are specified one per line, by an IP address prefix (initial octets), a complete IP address, a CIDR range, or an IP prefix range (two IP prefixes or complete addresses delimited by a dash, inclusive). ๐Ÿฆ‹

Examples, to specify 127.0.0.0/24:

127.0.0.0/24
127.0.0
127/24
127-127.0.0
127.0.0.0-127.0.0.255
127.0.0.1-255

to specify 127.16.0.0-127.31.255.255:

127.16.0.0-127.31.255.255
127.16.0-127.31.255
127.16-127.31
127.16-31
127.16.0.0/12
127.16.0/12
127.16/12

Note that in a prefix range, the last boundary is completed with all-ones (255), not all-zeros like the first boundary and a prefix alone. In prefix ranges, if the last boundary is only one octet (127.16-31), it is treated as a "suffix", as the value of the last specified octet of the first boundary prefix (127.16.0-31 is treated as 127.16.0.0-127.16.31.255, i.e. 127.16.0.0/19).

After an IP address range, A and TXT values for a given entry may be specified. If none given, default values in the current scope (see below) apply. If a value starts with a colon, it is interpreted as a pair of A record and TXT template, delimited by colon (:127.0.0.2:This entry is listed). If a value does not start with a colon, it is interpreted as a TXT template only, with the A record defaulting to the default A value in the current scope.

An IP address range may be followed by a comment char (either hash character (#) or semicolon (;)), e.g.:

127/8 ; loopback network

In this case all characters up to the end of line are ignored, and default A and TXT values will be used for this IP range.

Every IP address that fits within any of the specified ranges is "listed", and rbldnsd will respond to reverse queries against it within the specified zone with positive results. In contrast, if an entry starts with an exclamation sign (!), this is an exclusion entry, i.e. the corresponding address range is excluded from being listed (and any value for this record is ignored). This may be used to specify a large range except some individual addresses, in a compact form. ๐Ÿ’–

If a line starts with a colon (:), this line specifies the default A value and TXT template to return (see below) for all subsequent entries up to the end of the current file. If no default entry is specified, and no value is specified for a given record, rbldnsd will return 127.0.0.2 for matching A queries and no record for matching TXT queries. If a TXT record template is specified and contains occurrences of a dollar sign ($), every such occurrence is replaced with the IP address in question, so a single TXT template may be used to e.g. refer to a webpage for additional information for a specific IP address. โœจ


๐ŸŽ€ ip4trie Dataset

Set of IP4 CIDR ranges with corresponding (A, TXT) values. This dataset is similar to ip4set, but uses a different internal representation. It accepts CIDR ranges only (not a.b.c.d-e.f.g.h), and allows for the specification of A/TXT values on a per-CIDR-range basis. (If multiple CIDR ranges match a query, the value for the longest matching prefix is returned.) Exclusions are supported too.

This dataset is not particularly memory-efficient for storing many single IP addresses โ€” it uses about 50% more memory than the ip4set dataset in that case. The ip4trie dataset is better adapted, however, for listing CIDR ranges (whose lengths are not a multiple of 8 bits).


๐Ÿงš ip4tset Dataset

"trivial" ip4set: a set of single IP addresses (one per line), with the same A+TXT template. This dataset type is more efficient than ip4set (in both memory usage and access times), but has an obvious limitation. It is intended for DNSBLs like DSBL.org, ORDB.org and similar, where each entry uses the same default A+TXT template. This dataset uses only half the memory for the same list of IP addresses compared to ip4set. ๐Ÿ’œ


๐Ÿ’€ ip6trie Dataset

Set of IP6 CIDR ranges. This is the IP6 equivalent of the ip4trie dataset. It allows the specification of individual A/TXT values for each CIDR range and supports exclusions. Compressed (::) ip6 notation is supported.

Example zone data:

# Default A and TXT template values
:127.0.1.2: Listed, see http://example.com/lookup?$

# A listing, note that trailing :0s can be omitted
2001:21ab:c000/36

# /64 range with non-default A and TXT values
2001:21ab:def7:4242 :127.0.1.3: This one smells funny

# compressed notation
2605:6001:42::/52
::1                   # localhost
!2605:6001:42::bead   # exclusion

๐ŸŒธ ip6tset Dataset

"Trivial" ip6 dataset: a set of /64 IP6 CIDR ranges (one per line), all sharing a single A+TXT template. Exclusions of single IP6 (/128) addresses are also supported. This dataset type is quite memory-efficient โ€” it uses about 40% of the memory that the ip6trie dataset would use โ€” but has obvious limitations. ๐Ÿ˜ญ

This dataset wants the /64s listed as four ip6 words, for example:

2001:20fe:23:41ed
abac:adab:ad00:42f

Exclusions are denoted with a leading exclamation mark. You may also use compressed :: notation for excluded addresses. E.g.:

!abac:adab:ad00:42f:face:0f:a:beef
!abac:adab:ad00:42f::2

๐ŸŒธ dnset Dataset ๐ŸŒธ

Okay bestie, this one is a set of domain names (possibly wildcarded!!) that each carry an A value and a TXT value. It's giving ip4set energy, except instead of IP addresses the data is domain names โ€” and not in reverse form, we're not doing that here. ๐Ÿฆ‹

One domain name per line. A line can start with a wildcard, either star-dot (*.) or just a dot (.). A line starting with an exclamation sign is an exclusion. And if a line starts with a colon, that sets the default value for all the subsequent lines. ๐Ÿ’…

Wildcards get read like this, and this is important so pay attention:

  • example.com โ€” only the example.com domain itself is listed, not its subdomains. This is not a wildcard entry. periodt.
  • *.example.com โ€” all subdomains of example.com are listed, but not example.com itself. ๐Ÿ™ˆ
  • .example.com โ€” all subdomains of example.com and example.com itself are listed. It's a shortcut, bestie: to list a domain and all its subdomains you either write two lines (example.com and *.example.com) or one line (.example.com). slay. โœจ

You can use this dataset type instead of ip4set, as long as all your CIDR ranges are expanded and reversed (but heads up โ€” in that case the TXT template will be expanded differently).


๐ŸŽ€ generic Dataset ๐ŸŽ€

Generic type: it's a simplified bind-style format, iykyk.

  • Every record goes on one line โ€” line continuations are not supported, sorry.
  • Records must be specified completely, meaning all domain names in values have to be fully-qualified, and the entry name may not be omitted.
  • No wildcards accepted. ๐Ÿ’€
  • Only A, TXT, and MX records are recognized.
  • A TTL value may be specified before the record type.

Examples, look:

# bl.ex.com
# specify some values for current zone
$NS 0 ns1.ex.com ns2.ex.com
# record with TTL
www 3000 A 127.0.0.1
about TXT "ex.com combined blocklist"

๐Ÿซถ combined Dataset ๐Ÿซถ

This is a special dataset and it stores no data by itself. Instead it acts like a container for several other datasets of any type โ€” except combined itself, no nesting bestie, we said what we said. ๐Ÿ’…

The data file contains:

  1. An optional common section, where specials like $NS, $SOA, $TTL are recognized (see above).
  2. A series of sections, each defining one (nested) dataset plus several subzones of the base zone where that dataset should be consulted.

A new (nested) dataset starts with a line that looks like:

$DATASET type[:name] subzone subzone...

And every line after that, up until the end of the current file or the next $DATASET line, is interpreted as part of a dataset of type type, with an optional name. The name is only used for logging purposes, and the whole :name part (no quotes, no square brackets) is optional. ๐Ÿ“

Heads up: combined datasets cannot be nested. ๐Ÿšซ

Every subzone is always relative to the base zone name given on the command line. And if a subzone is specified as the single character @, the dataset gets connected to the base zone itself. โœจ

This dataset type exists to make subzone maintenance easier โ€” so you can include several subzones in one file, which means easy data transfer, atomic operations, and being able to modify the list of subzones on remote secondary nameservers. it's giving organized. ๐Ÿ‘‘

Example of a complete dataset containing a subzone proxies with a list of open proxies, a subzone relays with a list of open relays, a subzone multihop with output IPs of multihop open relays, and the base zone itself including proxies and relays but not multihops:

# common section
$NS 1w ns1.ex.com ns2.ex.com
$SOA 1w ns1.ex.com admin.ex.com 0 2h 2h 1w 1h
# list of open proxies,
# in `proxies' subzone and in base zone
$DATASET ip4set:proxy proxies @
:2:Open proxy, see http://bl.ex.com/proxy/$
127.0.0.2
127.0.0.10
# list of open relays,
# in `relays' subzone and in base zone
$DATASET ip4set:relay relays @
:3:Open relay, see http://bl.ex.com/relay/$
127.0.0.2
127.0.2.10
# list of optputs of multistage relays,
# in `multihop' subzone only
$DATASET ip4set:multihop-relay multihop
:4:Multihop open relay, see http://bl.ex.com/relay/$
127.0.0.2
127.0.9.12
# for the base zone and all subzones,
# include several additional records
$DATASET generic:common proxies relays multihop @
@ A 127.0.0.8
www A 127.0.0.8
@ MX 10 mx.ex.com
# the above results in having the following records
# (provided that the base zone specified is bl.ex.com):
#  proxies.bl.ex.com A 127.0.0.8
#  www.proxies.bl.ex.com 127.0.0.8
#  relays.bl.ex.com A 127.0.0.8
#  www.relays.bl.ex.com 127.0.0.8
#  multihop.bl.ex.com A 127.0.0.8
#  www.multihop.bl.ex.com 127.0.0.8
#  bl.ex.com A 127.0.0.8
#  www.bl.ex.com 127.0.0.8

Note bestie: $NS and $SOA values apply to the base zone only, no matter where in the file you put them. Unlike the $TTL values and $n substitutions, which can be both global and local for a given (sub-)dataset. ๐Ÿฆ‹


๐Ÿ’– Resulting A values and TXT templates ๐Ÿ’–

In all zone file types except generic, A values and TXT templates are specified like this:

:127.0.0.2:Blacklisted: http://example.com/bl?$

If a line starts with a colon, it specifies the default A and TXT for all subsequent entries in this dataset. Similar format is used for individual records, with the A value (enclosed by colons) being optional:

127.0.0.2 :127.0.0.2:Blacklisted: http://example.com/bl?$

Or, with no specific A value:

127.0.0.2 Blacklisted: http://example.com/bl?$

Two parts of a line, delimited by the second colon, specify the A and TXT record values. Both are optional. By default โ€” either when no default line is specified, or when there's no IP address in that line โ€” rbldnsd returns 127.0.0.2 as the A record. The 127.0.0 prefix for an A value may be omitted, so the example above can be simplified to:

:2:Blacklisted: http://example.com/bl?$

There is no default TXT value, so rbldnsd won't return anything for TXT queries unless TXT is specified.

When an A value is specified for a given entry but the TXT template is omitted, there are two cases and they're interpreted differently โ€” it all depends on whether there's a second colon (:) after the A value. (Yes, the original calls it a semicolon. We keep the chaos. ๐Ÿ˜ญ)

  • If there's no second colon โ†’ the default TXT value for this scope is used.
  • If the second colon is present โ†’ no TXT template will be generated at all.

All possible cases, outlined:

# default A value and TXT template
:127.0.0.2:IP address $ is listed
# 127.0.0.4 will use default A and TXT
127.0.0.4
# 127.0.0.5 will use specific A and default TXT
127.0.0.5 :5
# 127.0.0.6 will use specific a and no TXT
127.0.0.6 :6:
# 127.0.0.7 will use default A and specific TXT
127.0.0.7 IP address $ running an open relay

In a TXT template, references to substitution variables get replaced with the values of those variables. Specifically:

  • A single dollar sign ($) is replaced by the listed entry โ€” the IP address in question for IP-based datasets, and the domain name for domain-based datasets. ๐Ÿ’ซ
  • $n-style constructs, where n is a single digit, are replaced by the substitution variable $n defined for this dataset in the current scope (see the "Special Entries" section above).
  • To specify a dollar sign as-is, use $$. ๐Ÿค‘

For example, these lines:

$1 See http://www.example.com/bl
$2 for details
127.0.0.2  $1/spammer/$ $2
127.0.0.3  $1/relay/$ $2
127.0.0.4  This spammer wants some $$$$.  $1/$

will result in the following text being generated:

See http://www.example.com/bl/spammer/127.0.0.2 for details
See http://www.example.com/bl/relay/127.0.0.3 for details
This spammer wants some $$.  See http://www.example.com/bl/127.0.0.4

If the "base template" ($= variable) is defined, that template is used for expansion instead of the one specified for the entry being queried. Inside the base template, the $= construct is substituted with the text given for individual entries. To stop using the base template $= for a single record, start it with = (which will be omitted from the resulting TXT value). For example:

$= See http://www.example.com/bl?$= ($) for details
127.0.0.2 r123
127.0.0.3
127.0.0.4 =See other blocklists for details about $

produces these TXT records:

See http://www.example.com/bl?r123 (127.0.0.2) for details
See http://www.example.com/bl?127.0.0.3 (127.0.0.3) for details
See other blocklists for details about 127.0.0.4

๐ŸŒˆ acl Dataset ๐ŸŒˆ

Okay this one is not a real dataset, even though the syntax and usage are the same as with the other datasets. Instead of defining which records exist in a given zone and which don't, the acl dataset specifies which client hosts (peers) are allowed to query the given zone. ๐Ÿ‘‘

The dataset specifies a set of IPv4 and/or IPv6 CIDR ranges (syntax exactly the same as what ip4trie and ip6trie datasets understand), together with action specifiers. When a query comes from an IP address that's listed (not for the IP address, careful), the specified action changes the rules used to construct the reply. ๐Ÿ’…

Possible actions and what they mean:

  • :ignore โ€” ignore all queries from this IP address altogether. rbldnsd acts like no query was ever received. This is the default action.
  • :refuse โ€” refuse all queries from the IP in question. rbldnsd will always return the REFUSED DNS response code. ๐Ÿšซ
  • :empty โ€” pretend there's no data in all other datasets for the given zone. That means all the clients in question will always get a reply from rbldnsd saying the requested IP address or domain name is not listed in the given DNSBL. rbldnsd still replies to metadata queries (SOA and NS records, and to all queries satisfied by a generic dataset if one is specified for the given zone) as usual. ๐Ÿซง
  • :pass โ€” process the request as usual. You'd use this to add a "whitelisting" entry for a network/host blocked by another (larger) ACL entry. โœจ
  • a_txt_template โ€” the usual A+TXT template, same as used by other datasets. This means rbldnsd will reply to any valid DNSBL query with an "it is listed" answer, so the client in question will see every IP address or domain name as listed in the given DNSBL. The TXT record used in the reply is taken from the acl dataset instead of the real datasets. And again, just like with the empty case, rbldnsd keeps replying to metadata queries (including generic datasets, if any) as usual. ๐Ÿ’–

Only one ACL dataset per zone, bestie, and every zone needs at least one non-acl dataset too โ€” that's the rule, no exceptions ๐Ÿ’…

You can also set up one global ACL dataset, and the trick is that you give it an empty zone name (which literally no other dataset type is allowed to do), like this:

rbldnsd ... :acl:filename...

In that case the ACL in filename applies to every single zone โœจ And if you've got both a global ACL and a zone-specific one? Both get consulted, and the actions happen in the order we already talked about, so:

  • if either ACL says ignore for that IP โ†’ the request gets ignored
  • else if either ACL says refuse โ†’ the query gets refused
  • and so on down the chain

And if both ACLs hand back an "always listed" A+TXT template? The reply carries the A+TXT from the global ACL. That's the one that wins. ๐Ÿซถ

For this dataset type, only a few of the $-style specials are recognized. Specifically: $SOA and $NS keywords are not allowed, periodt. ๐Ÿ™… And when rbldnsd does its $ substitution inside the TXT template that came back from an ACL dataset, it uses the client IP address for that single $ character โ€” not the IP address or domain name from the original query. Different vibe entirely, iykyk.


โœจ SIGNALS โœจ

Rbldnsd handles these signals, and each one has its own whole personality:

  • SIGHUP โ€” rechecks zone files and reloads any that are outdated. This also happens automatically if you enabled it (peep the -c option ๐Ÿ‘€). Bonus: on SIGHUP rbldnsd will also reopen the logfile, if one was specified with the -l option.
  • SIGTERM, SIGINT โ€” terminate the process. bye bestie ๐Ÿ’€
  • SIGUSR1 โ€” logs the current statistic counters into syslog. Rbldnsd keeps track of how many packets it handled, how many bytes were received and sent, how many OK requests/replies (and how many answer records) were received/sent, how many NXDOMAIN answers got sent, and how many errors/refusals/etc. got sent โ€” all over some period of time. ๐Ÿ“Š
  • SIGUSR2 โ€” same as SIGUSR1, but it resets all the counters and starts a brand new sample period. fresh start energy โœจ

โœจ NOTES โœจ

Some unsorted usage notes follow. Grab a snack ๐Ÿ“

Generating and transferring data files

When you're creating a data file for rbldnsd (and honestly, for anything else โ€” this is general advice, take it), the move is: build the data in a temporary file and rename that temp file once everything's done. Do not write straight to the main file. Because at that exact same moment rbldnsd might be trying to read it, and it'll get incomplete data as a result. ๐Ÿ˜ญ

Same energy applies to copying data with cp(1) and similar utilities (including scp(1)) that copy over existing data. And even if you're 100% sure nobody's reading while you copy or generate โ€” imagine what happens if you can't finish the process for literally any reason (interrupt, filesystem full, an endless parade of other reasons...). In most cases it is way better to keep older-but-correct data than to leave incomplete/corrupt data sitting in place. ๐Ÿซ 

Right:

scp remote:data target.tmp && mv target.tmp target

Wrong:

scp remote:data target

Right:

./generate.pl > target.tmp && mv target.tmp target

Wrong:

./generate.pl > target

From this angle, the rsync(1) command looks safe โ€” it always creates a temporary file and renames it to the destination only when all is ok. (But watch out for the --partial option! It's great for downloading stuff but may be wrong for transferring data files โ€” usually you do not want partial files to get loaded. ๐Ÿ™ˆ) In contrast, scp(1) is not safe, since it does direct copying. You can still use scp(1) safely though, like in the example up there. ๐Ÿ‘†

Also: try to eliminate the case where two (or more) processes are doing data copying/generation at the same time to the same destination. If your data comes from a cron job, use file locking before you attempt any other file manipulation โ€” create a separate lock file (which should never be removed) and flock/fcntl it in exclusive mode without waiting, exiting if the lock fails.

Absolute vs relative domain names

All keys specified in dataset files are always relative to the zone base DN. But all the values (NS and SOA records, MX records in generic dataset) are absolute. Yes, this is different from BIND behaviour, where a trailing dot tells you whether it's an absolute or relative DN. Rbldnsd just ignores trailing dots in domain names. ๐Ÿ’…

Aggregating datasets

Several zones may be served by rbldnsd, and every zone may consist of several datasets. There are so many ways to combine several data files into several zones. For example: suppose you have a list of dialup ranges in a file named dialups, and a list of spammer's ip addresses in a file named spammers, and you want to serve 3 zones with rbldnsd: dialups.bl.ex.com, spam.bl.ex.com and bl.ex.com โ€” which is a combination of the two. Two ways to do it:

rbldnsd options... \
  dialups.bl.ex.com:ip4set:dialups \
  spam.bl.ex.com:ip4set:spammers \
  bl.ex.com:ip4set:dialups,spammers

or:

rbldnsd options... \
  dialups.bl.ex.com:ip4set:dialups \
  spam.bl.ex.com:ip4set:spammers \
  bl.ex.com:ip4set:dialups \
  bl.ex.com:ip4set:spammers
Heads up: you should specify the combined bl.ex.com zone after all its subzones on the command line, or else the subzones will not be consulted at all. ๐Ÿ‘‘

In the first form there are 3 independent data sets, and every record gets stored 2 times in memory, but only one search in the internal data structures is needed to resolve queries for the aggregate bl.ex.com. In the second form there are only 2 data sets, every record gets stored only once (both datasets get reused), but rbldnsd performs 2 searches to answer queries against the aggregate zone โ€” though honestly the difference in speed is almost unnoticeable. ๐Ÿฆ‹

Note that when aggregating several data files into one dataset, an exclusion entry in one file becomes an exclusion entry in the whole dataset. That may be a problem when aggregating dialups (where exclusions are common) with open relays/proxies (where exclusions are rare, if used at all). ๐Ÿ˜ฌ

A similar effect may be achieved by using the combined dataset type, and sometimes it's way easier. The combined dataset results in every nested dataset being used independently, like in the second form above.

The combined dataset requires rbldnsd to be the authoritative nameserver for the whole base zone. Most importantly, you may specify SOA and NS records for the base zone only. So some DNSBLs that don't use a common subzone for the data just can't use this dataset. One example: DSBL.org DNSBL, where each of list.dsbl.org, multihop.dsbl.org and unconfirmed.dsbl.org are separate, independent zones with different sets of nameservers. But for DSBL.org, where each dataset is really independent and used only once (there's no (sub)zone that acts as a combination of other zones), the combined dataset isn't necessary. In contrast, for SORBS.net zones, where several subzones are used and the main zone is a combination of several subzones, the combined dataset is the way to go. โœจ

All authoritative nameservers should be set up similarily

When you have several nameservers for your zone, set them all up in a similar way. Namely: if one is set up using a combined dataset, all the rest should be too โ€” otherwise the DNS meta-data will be broken. ๐Ÿ’€

Why? Because metadata (SOA and NS) records returned by nameservers using combined vs. other datasets will have a different origin. With a combined dataset, rbldnsd returns NS and SOA records for the base zone, not for any subzone defined inside the dataset.

Take the example above with dialups.bl.ex.com, spammers.bl.ex.com and aggregate bl.ex.com zones, plus two nameservers: the first is set up in any of the ways described above (using individual datasets for each of the 3 zones), and the second is set up for the whole bl.ex.com zone using a combined dataset. In that case, for queries against dialups.bl.ex.com, the first nameserver returns NS records like

dialups.bl.ex.com. IN NS a.ns.ex.com.

while the second always uses the base zone, so its NS records look like

bl.ex.com. IN NS a.ns.ex.com.

All authoritative nameservers for a zone must have consistent metadata records. The only way to achieve that is to use a similar configuration (combined or not) on all nameservers. Keep this in mind when you're using other software for a nameserver too. ๐Ÿซถ

Generic dataset usage

The generic dataset type is very rudimentary. Its purpose is to complement all the other types so you get a complete nameserver that can answer A, TXT and MX queries. This is useful mostly to define A records for HTTP access (relays.bl.example.com A, www.bl.example.com A just in case) and maybe some descriptive texts as a TXT record. ๐ŸŒธ

Since rbldnsd only searches one (the most closely matching) (sub)zone for every request, you cannot specify a single e.g. generic dataset in the form

proxies      TXT list of open proxies
www.proxies  A 127.0.0.8
relays       TXT list of open relays
www.relays   A 127.0.0.9

for several (sub)zones that are each represented as a zone too (either on the command line or as a combined dataset). Instead, you should specify several generic datasets โ€” a separate one for every (sub)zone. And if the data for every subzone is the same, then the same single dataset may be used, but it should be specified for every zone it should apply to (see the combined dataset usage example above ๐Ÿ‘†).

๐Ÿ’€ BUGS ๐Ÿ’€

okay bestie, listen up โ€” most of the stuff in this section isn't really a bug, it's just the vibe of a nameserver that serves a DNSBL zone, where nobody ever standardised what "expected behaviour" even means. rbldnsd matches BIND runtime behaviour where it makes sense, but likeโ€ฆ not always. ๐Ÿ™ˆ


  • the lowercase thing โ€” rbldnsd lowercases some domain names when it's loading them (specifically the ones that are lookup keys, like in the generic and dnset datasets), purely to make lookups faster. In most cases this is a whole non-issue.
  • no TCP mode, at all โ€” nope, doesn't exist. If a resource record doesn't fit in a UDP packet (512 bytes), it gets silently ignored. For most usages this is fine, because there should only be a few RRs in an answer anyway, and because one record is usually enough to decide whether something is "listed" or not. rbldnsd is not a full-featured nameserver, bestie โ€” it's not trying to be. ๐Ÿ’…
  • the AUTHORITY section situation โ€” rbldnsd will not always return a list of nameserver records in the AUTHORITY section of every positive answer. NS records get provided (if given) only if there's room for them in a single UDP packet. If the records don't fit, the AUTHORITY section is justโ€ฆ empty. ๐Ÿซฅ
  • no AXFR, and honestly, good โ€” rbldnsd does not allow AXFR operations. For DNSBLs, AXFR is the stupidiest yet most common thing to do โ€” use rsync for zone transfers instead. This isn't a bug in rbldnsd itself, it's a bug in the common practice of using AXFR and friends to transfer huge zones in a format that is genuinely not suitable for the task. Maybe someday, if there's real demand, AXFR "server" support will get implemented (so rbldnsd could act as master for BIND nameservers, but not as secondary), but the note stands: use rsync. โœจ
  • TXT truncation, 255 bytes max โ€” rbldnsd truncates all TXT records to be at most 255 bytes. DNS specs allow longer TXTs, but long TXTs should be avoided as much as possible โ€” a TXT record is used as an SMTP rejection string, and also, reminder: DNS UDP packet is limited to 512 bytes. rbldnsd will log a warning when such truncation occurs. ๐Ÿ“

๐ŸŒˆ VERSION ๐ŸŒˆ

This manpage corresponds to rbldnsd version 0.998.


๐Ÿ‘‘ AUTHOR ๐Ÿ‘‘

The rbldnsd daemon was written by Michael Tokarev <mjt+rbldnsd@corpit.ru>, based on ideas by Dan Bernstein and his djbdns package, with excellent contributions by Geoffrey T. Dairiki <dairiki@dairiki.org>.

Like, absolute icons, no cap. ๐Ÿซถ


๐ŸŽ€ LICENCE ๐ŸŽ€

Mostly GPL, with some code licensed under the 3-clause BSD license.


and that's the whole page, bestie!! ๐Ÿฆ‹ this was rbldnsd(8) from Dec 2015, matching rbldnsd version 0.998 โ€” go forth and serve those DNSBL zones, you're literally the main character of your own nameserver now ๐Ÿ’–โœจ