├── .gitignore ├── .gitmodules ├── dagg-ffi ├── libdagg │ ├── Makefile │ ├── libdagg.h │ └── libdagg.c ├── README.md └── dagg-ffi.lua ├── misc ├── addDomainBlock │ ├── addDomainBlock.lua │ └── README.md ├── suffixMatchSpoof │ ├── suffixMatchSpoof.lua │ └── README.md └── blockFilter │ ├── blockFilter.lua │ └── README.md ├── LICENSE ├── dagg ├── README.md └── dagg.lua └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | compile_flags.txt 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "filter/filters"] 2 | path = dagg-ffi/libdagg/filters 3 | url = https://github.com/FastFilter/xor_singleheader.git 4 | [submodule "filter/xxhash"] 5 | path = dagg-ffi/libdagg/xxhash 6 | url = https://github.com/Cyan4973/xxHash.git 7 | -------------------------------------------------------------------------------- /dagg-ffi/libdagg/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : clean 2 | 3 | SOURCES = $(shell echo *.c) 4 | HEADERS = $(shell echo *.h) 5 | OBJECTS = $(SOURCES:.c=.o) 6 | 7 | INCSRC = -Ifilters/include 8 | INCSRC += -Ixxhash 9 | 10 | CFLAGS = -fPIC -O2 -shared -fkeep-inline-functions -Wall -Werror $(INCSRC) 11 | LDFLAGS = $(LDPATH) $(LDLIBS) 12 | 13 | TARGET=libdagg.so 14 | 15 | all: $(TARGET) 16 | 17 | clean: 18 | rm -f $(OBJECTS) $(TARGET) 19 | 20 | $(TARGET) : $(OBJECTS) 21 | $(CC) $(CFLAGS) $(LDFLAGS) $(OBJECTS) -o $@ 22 | -------------------------------------------------------------------------------- /misc/addDomainBlock/addDomainBlock.lua: -------------------------------------------------------------------------------- 1 | local blocklistPath = "/tmp/path/to/my/block.list" 2 | 3 | -- addDomainBlock all domains listed in the file 4 | local function loadBlocklist(file) 5 | local f = io.open(file, "rb") 6 | 7 | -- verify that the file exists and it is accessible 8 | if f~=nil 9 | then 10 | for domain in io.lines(file) do 11 | addDomainBlock(domain) 12 | end 13 | 14 | f:close() 15 | else 16 | errlog("The blocklist file is missing or inaccessible!") 17 | end 18 | end 19 | 20 | infolog("[addDomainBlock] loading blocklist...") 21 | 22 | -- Warning: SLOW 23 | loadBlocklist(blocklistPath) 24 | 25 | infolog("[addDomainBlock] loading done.") 26 | -------------------------------------------------------------------------------- /misc/suffixMatchSpoof/suffixMatchSpoof.lua: -------------------------------------------------------------------------------- 1 | local blocklistPath = "/tmp/path/to/my/block.list" 2 | 3 | BlockNode = newSuffixMatchNode() 4 | 5 | -- read all the domains in a set 6 | local function loadBlocklist(smn, file) 7 | local f = io.open(file, "rb") 8 | 9 | -- verify that the file exists and it is accessible 10 | if f ~= nil then 11 | for domain in io.lines(file) do 12 | smn:add(newDNSName(domain)) 13 | end 14 | 15 | f:close() 16 | else 17 | errlog "The domain list is missing or inaccessible!" 18 | end 19 | end 20 | 21 | infolog "[suffixMatchSpoof] loading blocklist..." 22 | 23 | loadBlocklist(BlockNode, blocklistPath) 24 | 25 | infolog "[suffixMatchSpoof] loading done." 26 | 27 | -- Action to take against the domains in the blocklist 28 | -- 29 | -- It is recommended to return an IP, as some apps have 30 | -- apply workarounds when the response is NXDOMAIN 31 | addAction(AndRule { SuffixMatchNodeRule(BlockNode), QTypeRule "A" }, SpoofAction "127.0.0.1") 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 enilfodne 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /misc/blockFilter/blockFilter.lua: -------------------------------------------------------------------------------- 1 | local blocklistPath = "/tmp/path/to/my/block.list" 2 | 3 | -- load the blocked domain list 4 | local function loadBlockedDomains(file) 5 | local domains = {} 6 | local f = io.open(file, "rb") 7 | 8 | -- verify that the file exists and it is accessible 9 | if f ~= nil then 10 | for domain in io.lines(file) do 11 | -- as public lists do not have proper domain notation 12 | -- (ending with dot), remove the one in the query 13 | domains[domain .. "."] = true 14 | end 15 | 16 | f:close() 17 | else 18 | errlog "The domain list is missing or inaccessible!" 19 | end 20 | 21 | return domains 22 | end 23 | 24 | infolog "[blockFilter] loading blocklist..." 25 | 26 | Blocklist = loadBlockedDomains(blocklistPath) 27 | 28 | infolog "[blockFilter] loading done." 29 | 30 | -- blockFilter is a built-in function in dnsdist 31 | -- it gets called whenever a query is received 32 | function blockFilter(dq) 33 | local qname = dq.qname:toString() 34 | 35 | if Blocklist[qname] then 36 | -- this drops the query COMPLETELY 37 | -- the client will timeout (hang) 38 | return true 39 | end 40 | 41 | return false 42 | end 43 | -------------------------------------------------------------------------------- /misc/addDomainBlock/README.md: -------------------------------------------------------------------------------- 1 | addDomainBlock 2 | --- 3 | 4 | This variant utilizes, `dnsdist` built-in method - `addDomainBlock`. 5 | 6 | Pros 7 | ---- 8 | - Simple 9 | - Uses built-in method 10 | - Load on startup 11 | 12 | Cons 13 | ---- 14 | - Really slow at startup (especially with more than a few thousand domains). So slow, that the default configuration times-out 15 | - No control over the response 16 | 17 | Installation 18 | --- 19 | 20 | 1. Create a `conf.d` folder in your `dnsdist` configuration folder (the default is `/etc/dnsdist`) 21 | 2. Copy `addDomainBlock.lua` (from `dagg/addDomainBlock.lua`), rename it to `.conf` (e.g. `/etc/dnsdist/conf.d/addDomainBlock.conf`) 22 | 3. Customize it to match your setup: 23 | 3.1. Change the path to your blocklist by modifying the `blocklistPath` variable 24 | 4. Add the following snippet at the end of your `dnsdist` configuration file (usually `/etc/dnsdist/dnsdist.conf`) 25 | 26 | ```lua 27 | -- Include additional configuration 28 | includeDirectory("/etc/dnsdist/conf.d") 29 | ``` 30 | 31 | _Note: Change the path of the directory, accordingly on your system!_ 32 | 33 | 5. Restart `dnsdist` (e.g. `systemctl restart dnsdist` or `service dnsdist restart`) 34 | -------------------------------------------------------------------------------- /misc/suffixMatchSpoof/README.md: -------------------------------------------------------------------------------- 1 | suffixMatchSpoof 2 | --- 3 | 4 | This variant utilizes, `dnsdist` built-in method - `SuffixMatchNode`. 5 | 6 | Pros 7 | ---- 8 | - Simple 9 | - Uses built-in methods 10 | - Customizable - by changing the parameters passed to `addAction` you can change the behaviour (block,allow,modify) for a matched domain. 11 | 12 | Cons 13 | ---- 14 | - As we create a new object for every domain, it's not very efficient. 15 | 16 | 17 | Installation 18 | --- 19 | 20 | 1. Create a `conf.d` folder in your `dnsdist` configuration folder (the default is `/etc/dnsdist`) 21 | 2. Copy `suffixMatchSpoof.lua` (from `dagg/suffixMatchSpoof.lua`), rename it to `.conf` (e.g. `/etc/dnsdist/conf.d/suffixMatchSpoof.conf`) 22 | 3. Customize it to match your setup: 23 | 3.1. Change the path to your blocklist by modifying the `blocklistPath` variable 24 | 4. Add the following snippet at the end of your `dnsdist` configuration file (usually `/etc/dnsdist/dnsdist.conf`) 25 | 26 | ```lua 27 | -- Include additional configuration 28 | includeDirectory("/etc/dnsdist/conf.d") 29 | ``` 30 | 31 | _Note: Change the path of the directory, accordingly on your system!_ 32 | 33 | 5. Restart `dnsdist` (e.g. `systemctl restart dnsdist` or `service dnsdist restart`) 34 | -------------------------------------------------------------------------------- /misc/blockFilter/README.md: -------------------------------------------------------------------------------- 1 | blockFilter 2 | --- 3 | 4 | This variant utilizes, `dnsdist` built-in method - `blockFilter`. 5 | 6 | Pros 7 | ---- 8 | - Simple 9 | - Uses built-in method 10 | - Loads on startup 11 | - Fastest? 12 | 13 | Cons 14 | ---- 15 | - `dnsdist` will send the query upstream 16 | - No control over the response - this means that `dnsdist` will not respond to a matched query, which will leave clients hanging for a long periods of time (5-15s), until the software decides to timeout. 17 | 18 | Installation 19 | --- 20 | 21 | 1. Create a `conf.d` folder in your `dnsdist` configuration folder (the default is `/etc/dnsdist`) 22 | 2. Copy `blockFilter.lua` (from `dagg/blockFilter.lua`), rename it to `.conf` (e.g. `/etc/dnsdist/conf.d/blockFilter.conf`) 23 | 3. Customize it to match your setup: 24 | 3.1. Change the path to your blocklist by modifying the `blocklistPath` variable 25 | 4. Add the following snippet at the end of your `dnsdist` configuration file (usually `/etc/dnsdist/dnsdist.conf`) 26 | 27 | ```lua 28 | -- Include additional configuration 29 | includeDirectory("/etc/dnsdist/conf.d") 30 | ``` 31 | 32 | _Note: Change the path of the directory, accordingly on your system!_ 33 | 34 | 5. Restart `dnsdist` (e.g. `systemctl restart dnsdist` or `service dnsdist restart`) 35 | -------------------------------------------------------------------------------- /dagg-ffi/libdagg/libdagg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 enilfodne 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | 25 | #include "binaryfusefilter.h" 26 | 27 | #define XXH_INLINE_ALL 28 | #include "xxhash.h" 29 | 30 | int init(uint64_t *set, size_t set_size, binary_fuse16_t filter); 31 | 32 | binary_fuse16_t initialize(uint64_t *set, size_t set_size); 33 | 34 | void deinit(binary_fuse16_t filter); 35 | 36 | bool contains(const char *value, binary_fuse16_t filter); 37 | 38 | uint64_t xxhash(const char *str); 39 | -------------------------------------------------------------------------------- /dagg/README.md: -------------------------------------------------------------------------------- 1 | dagg 2 | --- 3 | 4 | This is a very basic approach to domain filtering. It's reads the blocklist upon reload and keeps the domains in memory. Thus it is not feasible for large lists or multiple DAG lists. 5 | 6 | Pros 7 | ---- 8 | - Simple, no external dependencies (other than `sed`) 9 | 10 | Cons 11 | ---- 12 | - High memory usage (depends on the size of the blocklist, roughly a million entries consumes > 150MiB) 13 | 14 | Requirements 15 | --- 16 | 17 | - `sed` as a post-processing workaround 18 | 19 | Installation 20 | --- 21 | 22 | 1. Create a `conf.d` folder in your `dnsdist` configuration folder (the default is `/etc/dnsdist`) 23 | 2. Copy `dagg.lua` (from `dagg/dagg.lua`), rename it to `.conf` (e.g. `/etc/dnsdist/conf.d/dagg.conf`) 24 | 3. Customize it to match your setup, 25 | - Set `Dagg.config.blocklist.path` 26 | - Set `Dagg.config.reload.target` to a non-existant FQDN, in order to trigger re/load of the blocklist 27 | 4. Add the following snippet at the end of your `dnsdist` configuration file (usually `/etc/dnsdist/dnsdist.conf`) 28 | 29 | ```lua 30 | -- Include additional configuration 31 | includeDirectory("/etc/dnsdist/conf.d") 32 | ``` 33 | 34 | _Note: Change the path of the directory, accordingly on your system!_ 35 | 36 | 5. Restart `dnsdist` (e.g. `systemctl restart dnsdist` or `service dnsdist restart`) 37 | 6. Reload the blocklist (by sending a query to the FQDN you set as `Dagg.config.reload.target`) to your `dnsdist` instance 38 | 39 | Re/Loading 40 | --- 41 | 42 | 1. To prevent DNS outages, the blocklist is *NOT* automatically loaded at startup, this has to be done manually or via script 43 | 2. It's recommended to query the `reload` domain, every-time you update your blocklist 44 | 3. You can specify anything for `reload` domain, however sticking to pre-defined reserved-use domains (like `.local`) is recommended 45 | -------------------------------------------------------------------------------- /dagg-ffi/README.md: -------------------------------------------------------------------------------- 1 | dagg-ffi 2 | --- 3 | 4 | Using a [binary fuse filter](https://github.com/FastFilter/xor_singleheader) and [xxhash](https://github.com/Cyan4973/xxHash), this should allow for a more scalable in-memory blocklist. 5 | 6 | Pros 7 | --- 8 | 9 | - (should be) faster 10 | - (should) scale further 11 | 12 | Cons 13 | --- 14 | 15 | - More complex, requires an external library (`libdagg`, part of this project) 16 | 17 | Requirements 18 | --- 19 | 20 | 1. `LuaJIT` with `ffi` built-in and enabled 21 | 2. `gcc` 22 | 3. `make` 23 | 4. `sed` as post-processing workaround 24 | 25 | Installation 26 | --- 27 | 28 | 1. Create a `conf.d` folder in your `dnsdist` configuration folder (the default is `/etc/dnsdist`) 29 | 2. Copy `dagg-ffi.lua` (from `dagg-ffi/dagg-ffi.lua`), rename it to `.conf` (e.g. `/etc/dnsdist/conf.d/dagg-ffi.conf`) 30 | 3. Customize it to match your setup, 31 | - Set `Dagg.config.blocklist.path` 32 | - Set `Dagg.config.reload.target` to a non-existant FQDN, in order to trigger re/load of the blocklist 33 | 4. Add the following snippet at the end of your `dnsdist` configuration file (usually `/etc/dnsdist/dnsdist.conf`) 34 | 35 | ```lua 36 | -- Include additional configuration 37 | includeDirectory("/etc/dnsdist/conf.d") 38 | ``` 39 | 40 | _Note: Change the path of the directory, accordingly on your system!_ 41 | 42 | 5. Compile `libdagg` (there's a paragraph below on how to do this) 43 | 6. Place `libdagg.so` in `/usr/local/lib/` (or the equivalent on your system) 44 | 7. Restart `dnsdist` (e.g. `systemctl restart dnsdist` or `service dnsdist restart`) 45 | 8. Reload the blocklist (by sending a query to the FQDN you set as `Dagg.config.reload.target`) to your `dnsdist` instance 46 | 47 | `libdagg` Compilation 48 | --- 49 | 50 | 1. Clone this project 51 | 2. Run `git submodule init` and `git submodule update` 52 | 3. `cd` into `dagg-ffi/libdagg` 53 | 4. Run `make clean` and `make` 54 | 5. Copy the resulting file (`libdagg.so`), to a directory within your `LD_LIBRARY_PATH` 55 | 56 | Re/Loading 57 | --- 58 | 59 | 1. To prevent DNS outages, the blocklist is *NOT* automatically loaded at startup, this has to be done manually or via script 60 | 2. It's recommended to query the `reload` domain, every-time you update your blocklist 61 | 3. You can specify anything for `reload` domain, however sticking to pre-defined reserved-use domains (like `.local`) is recommended 62 | -------------------------------------------------------------------------------- /dagg-ffi/libdagg/libdagg.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 enilfodne 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include "libdagg.h" 24 | 25 | int init(uint64_t *set, size_t set_size, binary_fuse16_t filter) { 26 | if (!binary_fuse16_allocate(set_size, &filter)) { 27 | // you may have run out of memory 28 | return ENOMEM; 29 | } 30 | 31 | if (!binary_fuse16_populate(set, set_size, &filter)) { 32 | // it should not fail in practice unless you have 33 | // many duplicated hash values 34 | return EINVAL; 35 | } 36 | 37 | return 0; 38 | } 39 | 40 | binary_fuse16_t initialize(uint64_t *set, size_t set_size) { 41 | binary_fuse16_t filter; 42 | 43 | // printf("[I] initialize - allocate\n"); 44 | 45 | if (!binary_fuse16_allocate(set_size, &filter)) { 46 | // you may have run out of memory 47 | printf("[E] out of memory"); 48 | // return ENOMEM; 49 | } 50 | 51 | // printf("[I] initialize - populate\n"); 52 | 53 | if (!binary_fuse16_populate(set, set_size, &filter)) { 54 | // it should not fail in practice unless you have 55 | // many duplicated hash values 56 | printf("[E] invalid args"); 57 | // return EINVAL; 58 | } 59 | 60 | return filter; 61 | } 62 | 63 | void deinit(binary_fuse16_t filter) { 64 | // printf("[I] free\n"); 65 | binary_fuse16_free(&filter); 66 | } 67 | 68 | bool contains(const char *value, binary_fuse16_t filter) { 69 | uint64_t hash = xxhash(value); 70 | // printf("[%s] - %lx\n", value, hash); 71 | // printf("al: %d", filter.ArrayLength); 72 | return binary_fuse16_contain(hash, &filter); 73 | } 74 | 75 | uint64_t xxhash(const char *str) { 76 | return (uint64_t)XXH64(str, strlen(str), 0x00000000); 77 | } 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | What is this? 2 | --- 3 | 4 | [dnsdist](http://dnsdist.org/) is an amazing piece of software, and it did everything I asked for (and more). 5 | 6 | However, after lots of searching, I haven't found a good example of someone doing domain blocking in an automated manner. 7 | 8 | Usually the examples are referring to a single domain or wildcard blocking, but I wanted to do something more robust. 9 | 10 | Intro 11 | --- 12 | 13 | This repository contains scripts that can be used for automated & aggregated domain blocking/spoofing (usually used for ad/malware/phishing filtering). 14 | 15 | Format 16 | --- 17 | 18 | - The scripts expect a blocklist in the following format - single `IDNA`-encoded domain per line, followed by a UNIX newline - `0xA`. 19 | 20 | For example: 21 | 22 | ``` 23 | github.com 24 | www.github.com 25 | ``` 26 | 27 | Variants 28 | --- 29 | 30 | - dagg 31 | 32 | This is a simple, plain-text comparison, where the domains are being loaded in RAM. Simple, but consumes lots of memory and it can only do direct comparisons. 33 | 34 | - dagg-ffi 35 | 36 | More complex version (it needs external library) of `dagg`. Introduces the usage of probabilistic filters (binary fuse filter). 37 | 38 | This version uses far less resources (theoretically, it should use about 8 MiB per million entries), though the probabilistic nature of the filters may not 39 | be suitable for everyone 40 | 41 | - misc/addDomainBlock 42 | 43 | Uses built-in methods within `dnsdist`. This offers more customization and flexibility, but it's not very efficient and it can't handle larger blocklists. 44 | 45 | - misc/blockFilter 46 | 47 | Uses built-in methods within `dnsdist`. This offers more customization and flexibility, but it's not very efficient and it can't handle larger blocklists. 48 | It also sinkholes domains without responding to the client, allowing them to timeout. Useful when you want to drop queries. 49 | 50 | - misc/suffixMatchSpoof 51 | 52 | Uses built-in methods within `dnsdist`. This is being used as part of `dagg`, in order to handle wildcard domains. 53 | 54 | What variant should i pick? 55 | --- 56 | 57 | It depends on your use-case (and functionality), but, in general, if all you care about is domain count: 58 | 59 | - 1 - 100000 domains 60 | 61 | Pick any of the `misc` approaches. This gives you the most flexibility and are generally the easiest to use / implement 62 | 63 | - 100,000 - 1,000,000 domains 64 | 65 | Pick `dagg`, it'll need a few hundred MiB of RAM, but you don't have to compile additional libraries and it's a bit faster, per-query 66 | 67 | - more than 1,000,000 domains 68 | 69 | Pick `dagg-ffi`. It's the most restrictive, but with so much domains, you could drastically slow-down `dnsdist` if you're using any 70 | of the previous methods. 71 | 72 | These are just generic suggestions. It's best to try out the different methods and see which suits you best. 73 | 74 | What does `dagg` mean? 75 | --- 76 | 77 | Nothing. It is a portmanteau of domain aggregator. 78 | 79 | TODO 80 | --- 81 | 82 | - There are probably, more efficient ways for domain comparison, within `dnsdist` engine 83 | - Use built-in methods, but reduce memory usage. Built-in methods are a more flexible approach, when it comes to a domain declaration (usage of wildcard etc.) 84 | 85 | -------------------------------------------------------------------------------- /dagg/dagg.lua: -------------------------------------------------------------------------------- 1 | Dagg = { 2 | -- config 3 | config = { 4 | actionlog = { 5 | path = "/tmp/path/to/my/action.log", 6 | }, 7 | blocklist = { 8 | path = "/tmp/path/to/my/block.list", 9 | }, 10 | reload = { 11 | target = "reload.change.me.to.something.local.", 12 | }, 13 | unload = { 14 | target = "unload.hange.me.to.something.local.", 15 | }, 16 | }, 17 | -- table storing the domains that need to be blocked 18 | table = { 19 | -- only used for wildcard domains 20 | smn = newSuffixMatchNode(), 21 | -- default - fast string comparison 22 | str = {}, 23 | }, 24 | } 25 | 26 | -- read all the domains in a set 27 | function DaggLoadDomainsFromFile(file) 28 | local f = io.open(file, "rb") 29 | 30 | -- verify that the file exists and it is accessible 31 | if f ~= nil then 32 | for domain in f:lines() do 33 | if string.find(domain, "*") then 34 | local suffix = domain:gsub("*.", "") 35 | Dagg.table.smn:add(suffix) 36 | else 37 | Dagg.table.str[domain] = 1 38 | end 39 | end 40 | 41 | f:close() 42 | end 43 | end 44 | 45 | -- verbose, but clear 46 | function DaggLoadBlocklist() 47 | -- no reason, just for clarity 48 | local file = Dagg.config.blocklist.path 49 | -- not really necessary, but keep similarity to other versions 50 | 51 | local f = io.open(file, "rb") 52 | 53 | if f ~= nil then 54 | -- file exists, close and proceed with sed 55 | f:close() 56 | 57 | -- it appears that even when using: 58 | -- 59 | -- 'local var = str2 .. str2' 60 | -- 61 | -- the variable is not being garbage-collected 62 | -- and it ends up looking like a memory leak. 63 | -- 64 | -- let me know if if there's a better way 65 | os.execute("sed '/\\.$/ ! s/$/\\./' -i " .. file) 66 | else 67 | errlog "[Dagg] the blocklist file is missing or inaccessible!" 68 | end 69 | 70 | DaggLoadDomainsFromFile(file) 71 | end 72 | 73 | -- clear the table from memory 74 | function DaggClearTable() 75 | Dagg.table = { 76 | smn = newSuffixMatchNode(), 77 | str = {}, 78 | } 79 | end 80 | 81 | -- write down a query to the action log 82 | function DaggWriteToActionLog(dq) 83 | -- write-down the query 84 | local f = io.open(Dagg.config.actionlog.path, "a") 85 | 86 | if f ~= nil then 87 | local query_name = dq.qname:toString() 88 | local remote_addr = dq.remoteaddr:toString() 89 | 90 | local msg = string.format("[%s][%s] %s", os.date("!%Y-%m-%dT%TZ", t), remote_addr, query_name) 91 | 92 | f:write(msg, "\n") 93 | f:close() 94 | end 95 | end 96 | 97 | -- main query action 98 | function DaggIsDomainBlocked(dq) 99 | local qname = dq.qname:toString():lower() 100 | 101 | if Dagg.table.str[qname] or Dagg.table.smn:check(dq.qname) then 102 | -- set QueryResponse, so the query never goes upstream 103 | dq.dh:setQR(true) 104 | 105 | -- set a CustomTag 106 | -- you can optionally set a tag and process 107 | -- this request with other actions/pools 108 | -- dq:setTag("Dagg", "true") 109 | 110 | -- WARNING: it (may?) affect(s) performance 111 | -- DaggWriteToActionLog(dq) 112 | 113 | -- return NXDOMAIN - its fast, but apparently 114 | -- some apps resort to hard-coded entries if NX 115 | -- try spoofing in this instance. 116 | 117 | return DNSAction.Nxdomain, "" 118 | 119 | -- return Spoof - you can spoof the response 120 | -- instead of NX, but this may lead to time-outs 121 | -- 122 | -- return DNSAction.Spoof, "127.0.0.1" 123 | end 124 | 125 | return DNSAction.None, "" 126 | end 127 | addAction(AllRule(), LuaAction(DaggIsDomainBlocked)) 128 | 129 | -- reload action 130 | function DaggReloadBlocklist(dq) 131 | infolog "[Dagg] re/loading blocklist..." 132 | 133 | -- prevent the query from going upstream 134 | dq.dh:setQR(true) 135 | 136 | -- clear 137 | DaggClearTable() 138 | 139 | -- clean-up 140 | collectgarbage "collect" 141 | 142 | -- load 143 | DaggLoadBlocklist() 144 | 145 | -- clean-up 146 | collectgarbage "collect" 147 | 148 | -- respond with a local address just in case 149 | return DNSAction.Spoof, "127.0.0.127" 150 | end 151 | addAction(Dagg.config.reload.target, LuaAction(DaggReloadBlocklist)) 152 | 153 | -- unload action 154 | function DaggUnloadBlocklist(dq) 155 | infolog "[Dagg] unloading blocklist..." 156 | 157 | -- prevent the query from going upstream 158 | dq.dh:setQR(true) 159 | 160 | -- clear 161 | DaggClearTable() 162 | 163 | -- clean-up 164 | collectgarbage "collect" 165 | 166 | -- respond with a local address just in case 167 | return DNSAction.Spoof, "127.0.0.127" 168 | end 169 | addAction(Dagg.config.unload.target, LuaAction(DaggUnloadBlocklist)) 170 | -------------------------------------------------------------------------------- /dagg-ffi/dagg-ffi.lua: -------------------------------------------------------------------------------- 1 | Dagg = { 2 | -- config 3 | config = { 4 | actionlog = { 5 | path = "/tmp/path/to/my/action.log", 6 | }, 7 | blocklist = { 8 | path = "/tmp/path/to/my/block.list", 9 | }, 10 | reload = { 11 | target = "reload.change.me.to.something.local.", 12 | }, 13 | unload = { 14 | target = "unload.change.me.to.something.local.", 15 | }, 16 | }, 17 | -- table storing the domains that need to be blocked 18 | table = { 19 | -- struct of the filter 20 | fuse16 = nil, 21 | -- only used for wildcard domains 22 | suffix = nil, 23 | }, 24 | -- keep a local ffi copy to avoid API conflict 25 | ffi = require "ffi", 26 | -- C-library containing the filter implementation 27 | lib = {}, 28 | } 29 | 30 | Dagg.ffi.cdef [[ 31 | typedef struct binary_fuse16_s { 32 | uint64_t Seed; 33 | uint32_t SegmentLength; 34 | uint32_t SegmentLengthMask; 35 | uint32_t SegmentCount; 36 | uint32_t SegmentCountLength; 37 | uint32_t ArrayLength; 38 | uint16_t *Fingerprints; 39 | } binary_fuse16_t; 40 | 41 | binary_fuse16_t initialize(uint64_t *set, size_t set_size); 42 | void deinit(binary_fuse16_t filter); 43 | bool contains(const char* value, binary_fuse16_t filter); 44 | uint64_t xxhash(const char* str); 45 | ]] 46 | 47 | Dagg.lib = Dagg.ffi.load "libdagg" 48 | 49 | -- read all the domains in a set 50 | function DaggLoadDomainsFromFile(file, lineCount) 51 | local f = io.open(file, "rb") 52 | 53 | -- make sure the file has opened successfully 54 | -- yes, we just counted the lines, but still 55 | if f ~= nil then 56 | -- initialize filter 57 | -- 58 | -- yes, the array is larger, than the set, it's easier 59 | -- instead of running another loop, just to count the domains 60 | local set = Dagg.ffi.new("uint64_t[?]", lineCount) 61 | 62 | -- initialize suffix for wildcard domains 63 | Dagg.table.suffix = newSuffixMatchNode() 64 | 65 | -- set index 66 | local _ = 0 67 | 68 | for domain in f:lines() do 69 | -- handle wildcard domains 70 | if string.find(domain, "*") then 71 | local suffix = domain:gsub("*.", "") 72 | Dagg.table.suffix:add(suffix) 73 | -- everything else 74 | else 75 | set[_] = Dagg.lib.xxhash(domain) 76 | _ = _ + 1 77 | end 78 | end 79 | 80 | -- close the file 81 | f:close() 82 | 83 | -- initialize the filter set 84 | Dagg.table.fuse16 = Dagg.ffi.gc(Dagg.lib.initialize(set, lineCount), Dagg.lib.deinit) 85 | end 86 | end 87 | 88 | -- verbose, but clear 89 | function DaggLoadBlocklist() 90 | -- no reason, just for clarity 91 | local file = Dagg.config.blocklist.path 92 | 93 | local f = io.open(file, "rb") 94 | 95 | -- no built-in method for counting lines 96 | -- so do it, really verbose 97 | local lineCount = 0 98 | 99 | -- verify that the file exists and it is accessible 100 | if f ~= nil then 101 | for _ in f:lines() do 102 | lineCount = lineCount + 1 103 | end 104 | 105 | -- close before modifying the file with sed 106 | f:close() 107 | 108 | -- it appears that even when using: 109 | -- 110 | -- 'local var = str2 .. str2' 111 | -- 112 | -- the variable is not being garbage-collected 113 | -- and it ends up looking like a memory leak. 114 | -- 115 | -- let me know if if there's a better way 116 | os.execute("sed '/\\.$/ ! s/$/\\./' -i " .. file) 117 | else 118 | errlog "[Dagg] the blocklist file missing or inaccessible!" 119 | end 120 | 121 | DaggLoadDomainsFromFile(file, lineCount) 122 | end 123 | 124 | -- clear the table from memory 125 | function DaggClearTable() 126 | -- zero-out the tables 127 | Dagg.table.fuse16 = nil 128 | Dagg.table.suffix = nil 129 | end 130 | 131 | -- write down a query to the action log 132 | function DaggWriteToActionLog(dq) 133 | -- write-down the query 134 | local f = io.open(Dagg.config.actionlog.path, "a") 135 | 136 | if f ~= nil then 137 | local query_name = dq.qname:toString() 138 | local remote_addr = dq.remoteaddr:toString() 139 | 140 | local msg = string.format("[%s][%s] %s", os.date("!%Y-%m-%dT%TZ", t), remote_addr, query_name) 141 | 142 | f:write(msg, "\n") 143 | f:close() 144 | end 145 | end 146 | 147 | -- main query action 148 | function DaggIsDomainBlocked(dq) 149 | local block = false 150 | 151 | if Dagg.table.fuse16 and Dagg.lib["contains"] then 152 | block = block or Dagg.lib.contains(dq.qname:toString():lower(), Dagg.table.fuse16) 153 | end 154 | 155 | if Dagg.table.suffix then 156 | block = block or Dagg.table.suffix:check(dq.qname) 157 | end 158 | 159 | if block then 160 | -- set QueryResponse, so the query never goes upstream 161 | dq.dh:setQR(true) 162 | 163 | -- set a CustomTag 164 | -- you can optionally set a tag and process 165 | -- this request with other actions/pools 166 | -- 167 | -- dq:setTag("Dagg", "true") 168 | 169 | -- WARNING: it (may) affect(s) performance 170 | -- 171 | -- DaggWriteToActionLog(dq) 172 | 173 | -- return NXDOMAIN - its fast, but apparently 174 | -- some apps resort to hard-coded entries if NX 175 | -- try spoofing in this instance. 176 | -- 177 | return DNSAction.Nxdomain, "" 178 | 179 | -- return Spoof - you can spoof the response 180 | -- instead of NX, but this may lead to time-outs 181 | -- 182 | -- return DNSAction.Spoof, "127.0.0.1" 183 | end 184 | 185 | return DNSAction.None, "" 186 | end 187 | addAction(AllRule(), LuaAction(DaggIsDomainBlocked)) 188 | 189 | -- reload action 190 | function DaggReloadBlocklist(dq) 191 | infolog "[Dagg] re/loading blocklist..." 192 | 193 | -- prevent the query from going upstream 194 | dq.dh:setQR(true) 195 | 196 | -- clear 197 | DaggClearTable() 198 | 199 | -- clean-up 200 | collectgarbage "collect" 201 | 202 | -- load 203 | DaggLoadBlocklist() 204 | 205 | -- clean-up 206 | collectgarbage "collect" 207 | 208 | -- respond with a local address just in case 209 | return DNSAction.Spoof, "127.0.0.127" 210 | end 211 | addAction(Dagg.config.reload.target, LuaAction(DaggReloadBlocklist)) 212 | 213 | -- unload action 214 | function DaggUnloadBlocklist(dq) 215 | infolog "[Dagg] unloading blocklist..." 216 | 217 | -- prevent the query from going upstream 218 | dq.dh:setQR(true) 219 | 220 | -- clear 221 | DaggClearTable() 222 | 223 | -- clean-up 224 | collectgarbage "collect" 225 | 226 | -- respond with a local address just in case 227 | return DNSAction.Spoof, "127.0.0.127" 228 | end 229 | addAction(Dagg.config.unload.target, LuaAction(DaggUnloadBlocklist)) 230 | --------------------------------------------------------------------------------