#+TITLE: blocker - Domain blocker plugin for CoreDNS
=blocker= is a CoreDNS plugin which can be used to block a list of domains provided in the AdBlock
Plus syntax format. The blocklist will be loaded into memory at start-up and the file's modified
time will be checked periodically. When the blocklist file is updated, the in-memory blocklist will
be updated by scanning the blocklist file line-by-line.
Updating the blocklist file itself is beyond the scope of this plugin. I recommend a bash script
which downloads [[https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts][common]] [[http://hosts.oisd.nl/][blocklists]] and updates them into a format without comments. The script
[[file:blocklist-file-preparer.sh]] included with this repository is an example of how this can be done
using bash and common GNU utilities.
*Example blocklist file:* ([[https://github.com/AdguardTeam/AdGuardHome/wiki/Hosts-Blocklists#adblock-style][AdBlock Plus syntax]])
#+begin_src text
||buyer.revsci.net^
||ww92.impfr.tradedoubler.com^
||next.chartboost.com^
||pl16442154.alternativecpmgate.com^
||denturesauaid.com^
||pdx-p-con-336.saas.appdynamics.com^
||cdn.ad.citynews.it^
||xxxxxxxamob.acs86.com^
||www.globalhotsale.su^
||zipuploads.com^
#+end_src
* Usage
** CoreDNS Binary
You can include blocker in the CoreDNS code just as you would include any other CoreDNS plugin.
#+begin_src sh
# Clone coredns to a local location
$ git clone git@github.com:coredns/coredns.git ~/dns-server/coredns
# Clone blocker plugin to a close location
$ git clone git@github.com:icyflame/blocker.git ~/dns-server/blocker
# Symlink blocker location into coredns/plugin/blocker
$ cd ~/dns-server/coredns/plugin
$ ln -s ../blocker ./blocker
# Update plugin.cfg and put the line "blocker:blocker" before the "forward:forward" line
# Build CoreDNS
$ cd ~/dns-server/coredns
$ go generate
$ make
$ ./coredns -conf Corefile
#+end_src
** Corefile
The =blocker= directive inside Corefile requires four arguments. The first argument is the absolute
path to the blocklist file. The second argument is the frequency at which the blocklist file is
checked for updates. The third argument is the type of blocklist file (=hosts= and =abp= are the
only two values which are supported at this time.) The fourth argument is the response type from
the plugin, either =empty= for a valid DNS response with 0.0.0.0 or ::6 or =nxdomain= to respond
with a DNS empty response.
The frequency is specified as a string and the value should be a valid argument of the
[[https://pkg.go.dev/time#ParseDuration][time.ParseDuration]] function.
#+begin_src conf
blocker /home/user/blocklist_file 1h abp empty
#+end_src
The following is a sample Corefile including the =blocker= directive. It will block domains that are
specified in the blocklist and forward everything else to a full DNS server.
#+begin_src conf
.:53 {
metadata
# prometheus records metrics regarding incoming requests
prometheus
# log writes 1 line to the log for every DNS request
# The last word in the log line will be YES if the request was blocked and NO if it was not
# blocked.
# This behaviour is supported by the metadata plugin.
log . "{common} {/blocker/request-blocked}"
# blocker blocks domains which are specified in the blocklist
blocker /home/user/blocklist_file 1h abp empty
# forward handles any request that is not blocked by blocker
forward . 127.0.0.1:9053
}
#+end_src
** plugin.cfg
This is a sample middleware configuration file. The order of plugins here is important. This is the
order in which plugins will be executed for incoming requests.
#+begin_src conf
metadata:metadata
prometheus:metrics
log:log
blocker:blocker
forward:forward
#+end_src
* Interaction with Other CoreDNS Plugins
** =metadata=
The blocker plugin will write the metadata value with the label =blocker/request-blocked=. This is a
boolean value whose value will be either =YES= (if the request was blocked and the empty IP address
was returned as a result to the user) and =NO= when the request was not blocked.
* Release Binaries
For tags which are published to this repository, the GitHub Actions workflow
=./.github/workflows/build-binary.yml= builds binaries using the latest Go version for Linux under
the three most popular architectures: AMD64, ARM (32 bit), and ARM64. The =tar.gz= files contain a
Checksum file which can be used together with =sha256sum= to verify the integrity of the binary.
#+begin_src sh
$ wget https://github.com/icyflame/blocker/releases/download/v0.0.1-alpha/coredns-linux-amd64.tar.gz
...
coredns-linux-amd64.tar.gz 100%[=======================================================================>] 5.16M 610KB/s in 14s
2024-07-13 12:26:05 (390 KB/s) - ‘coredns-linux-amd64.tar.gz’ saved [5414731/5414731]
$ tar tvf coredns-linux-amd64.tar.gz
-rwxr-xr-x runner/docker 14110872 2024-07-13 12:26 coredns-linux-amd64
-rw-r--r-- runner/docker 86 2024-07-13 12:26 coredns-linux-amd64.checksum
$ tar zxf coredns-linux-amd64.tar.gz
$ sha256sum -c coredns-linux-amd64.checksum
coredns-linux-amd64: OK
$ ./coredns-linux-amd64 -version
CoreDNS-1.11.1
linux/amd64, go1.22.5, Blocker plugin refs/tags/v0.0.1-alpha 1e6061ee8b7d2ad2ee5c632d3b91851c00481453
#+end_src
* Development
** Running Tests
This plugin contains unit tests. These unit tests are run as part of the unit tests for CoreDNS. The
following process should be followed to run these unit tests:
#+begin_src sh
# Clone CoreDNS
$ git clone git@github.com:coredns/coredns.git /tmp/dns-server/coredns
# Clone this plugin
git clone git@github.com:icyflame/blocker.git /tmp/dns-server/blocker
# Link this plugin into CoreDNS
ln -s /tmp/dns-server/blocker /tmp/dns-server/coredns/plugin/blocker
# Run tests for this plugin
cd /tmp/dns-server/coredns
go test -v -count=1 ./plugin/blocker
=== RUN TestIsDomainBlocked_ABP
=== RUN TestIsDomainBlocked_ABP/base_case
[snip]
PASS
ok github.com/coredns/coredns/plugin/blocker 0.005s
#+end_src
During development, making changes to the ~blocker~ plugin, after the above process, makes it easier
to run tests and use the Go language server features such as jumping to definition.
** Testing the DNS server
Apart from unit tests, benchmarking tools can be used to verify whether CoreDNS works with a large
volume of DNS requests. This is the setup that I use to run benchmarks using [[https://github.com/AskMediaGroup/dnsbench][dnsbench]].
First, follow the process in the ~Usage > CoreDNS Binary~ section to create a CoreDNS binary which
contains the blocker plugin.
Second, start the CoreDNS server using the following configuration:
#+begin_src conf
$ cat Corefile.benchmark.conf
.:5335 {
metadata
log . "{common} {/blocker/request-blocked}"
blocker /home/siddharth/code/open-source/coredns/blocklist.benchmark 1s abp nxdomain
forward . 8.8.8.8
}
$ touch blocklist.benchmark
$ ./coredns -conf Corefile.benchmark.conf
maxprocs: Leaving GOMAXPROCS=8: CPU quota undefined
[INFO] plugin/blocker: updated blocklist; blocked domains: before: 0, after: 0; last updated: before: 0001-01-01 00:00:00 +0000 UTC, after: 2025-08-09 12:10:31.479348779 +0900 JST m=+0.026230377
.:5335
CoreDNS-1.12.3
linux/amd64, go1.24.5, 463fd1c1b-dirty
#+end_src
Third, run the following one liner which simulates updating the block list:
#+begin_src sh
$ truncate --size 0 blocklist.benchmark; for i in `seq 101 10000`; do sleep 1; echo "||baddomain-$i.example.com^" >> blocklist.benchmark; echo "$(date): Iteration $i DONE"; done;
Sat 09 Aug 2025 12:11:30 PM JST: Iteration 1 DONE
Sat 09 Aug 2025 12:11:31 PM JST: Iteration 2 DONE
Sat 09 Aug 2025 12:11:32 PM JST: Iteration 3 DONE
[snip]
#+end_src
A new domain will be written to the blocklist file every second, and the Blocker plugin will
constantly update this file, printing logs such as this one:
#+begin_src sh
[INFO] plugin/blocker: updated blocklist; blocked domains: before: 58, after: 59; last updated: before: 2025-08-09 12:12:28.480451154 +0900 JST m=+117.027332732, after: 2025-08-09 12:12:29.47975636 +0900 JST m=+118.026637938
#+end_src
Finally, install and run the ~dnsbench~ tool against this CoreDNS server:
#+begin_src sh
# Prepare a list of domain names
$ truncate --size 0 domain-names.benchmark; for i in `seq 1 200`; do echo "baddomain-$i.example.com" >> domain-names.benchmark; done;
$ dnsbench run --nameserver '127.0.0.1:5335' --names domain-names.benchmark --count=10000
# requests errors min [ p50 p95 p99 p999] max qps
1421 382 4.71 [6.23 265.95 523.76 809.50] 809.50 284.20
1550 422 4.91 [6.09 253.89 475.27 740.82] 740.82 310.00
1562 426 4.84 [6.11 254.54 476.32 738.72] 738.72 312.40
1293 353 4.93 [6.14 268.30 529.53 771.23] 771.23 258.60
1403 383 4.81 [6.09 263.98 525.34 786.96] 786.96 280.60
1539 419 4.87 [6.06 263.19 508.82 743.96] 743.96 307.80
Finished 10000 requests
# latency summary
10000 2727 4.71 [6.11 264.90 493.09 809.50] 809.50 290.18
Concurrency level: 10
Time taken for tests: 34.464370221s
Completed Requests: 7273
Failed Requests: 2727
Requests per second: -0.0000 [#/sec] (mean)
Time per request: 34.04 [ms] (mean)
Fastest request: 4.71 [ms]
Slowest request: 809.50 [ms]
#+end_src