Dynamic Rule Generation

To set dynamic rules, based on recent traffic, define a function called maintenance() in Lua. 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 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:

function maintenance()
    addDynBlocks(exceedQRate(20, 10), "Exceeded query rate", 60)

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.

Dynamic blocks in force are displayed with showDynBlocks() and can be cleared with clearDynBlocks(). They return a table whose key is a 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 exceedQTypeRate(), rcode for exceedServFails()).

All exceed-functions are documented in the Configuration Reference.

Dynamic blocks drop matched queries by default, but this behavior can be changed with setDynBlocksAction(). For example, to send a REFUSED code instead of dropping the query:


Please see the documentation for setDynBlocksAction() to confirm which actions are supported.


Starting with dnsdist 1.3.0, a new DynBlockRulesGroup function can be used to return a 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:

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)

The new syntax would be:

local dbr = dynBlockRulesGroup()
dbr:setQueryRate(30, 10, "Exceeded query rate", 60)
dbr:setRCodeRate(DNSRCode.NXDOMAIN, 20, 10, "Exceeded NXD rate", 60)
dbr:setRCodeRate(DNSRCode.SERVFAIL, 20, 10, "Exceeded ServFail rate", 60)
dbr:setQTypeRate(DNSQType.ANY, 5, 10, "Exceeded ANY rate", 60)
dbr:setResponseByteRate(10000, 10, "Exceeded resp BW rate", 60)

function maintenance()

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.

DynBlockRulesGroup also offers the ability to specify that some network ranges should be excluded from dynamic blocking:

-- do not add dynamic blocks for hosts in the and 2001:db8::/32 ranges
dbr:excludeRange({"", "2001:db8::/32" })
-- except for

Since 1.3.3, it’s also possible to define a warning rate. When the query or response rate raises above the warning level but below the trigger level, a warning message will be issued along with a no-op block. If the rate reaches the trigger level, the regular action is applied.

local dbr = dynBlockRulesGroup()
-- Generate a warning if we detect a query rate above 100 qps for at least 10s.
-- If the query rate raises above 300 qps for 10 seconds, we'll block the client for 60s.
dbr:setQueryRate(300, 10, "Exceeded query rate", 60, DNSAction.Drop, 100)

Since 1.6.0, if a default eBPF filter has been set via setDefaultBPFFilter() dnsdist will automatically try to use it when a “drop” dynamic block is inserted via a DynBlockRulesGroup. eBPF blocks are applied in kernel space and are much more efficient than user space ones. Note that a regular block is also inserted so that any failure will result in a regular block being used instead of the eBPF one.