├── .gitignore ├── README.md ├── process.custom.php ├── process.dan-pollock.php ├── process.hphosts.php ├── process.malwaredomainlist.com.php ├── process.malwaredomains.com.php ├── process.mvps.php ├── process.peter-lowe.php ├── process.php ├── process.spam404.php ├── source.custom.txt ├── source.dan-pollock.txt ├── source.hphosts.txt ├── source.malwaredomainlist.com.txt ├── source.malwaredomains.com.txt ├── source.mvps.txt ├── source.peter-lowe.txt └── source.spam404.txt /.gitignore: -------------------------------------------------------------------------------- 1 | script.*-*.rsc 2 | named.conf.* 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RouterOS DNS Server AdBlock Configuration 2 | 3 | This repository contains a PHP script that will convert some of the filter lists used by AdBlock and uBlock into RouterOS commands to add static entries to the router's DNS server (`/ip dns static`). Combined with an additional firewall (`/ip firewall filter`) rule, shown below, this has the effect of blocking adverts (and/or malware, scams, etc., depending on the chosen lists) for any clients which are configured to use the router as their DNS server. 4 | 5 | By blocking ads at the DNS server level, adverts are blocked even for locked-down devices such as smartphones and games consoles! 6 | 7 | Note that it's generally easiest to configure your DHCP server to push DNS server entries to your clients, where those server entries point back to your router. That way, everything on your network will automatically use the ad-blocking DNS server. 8 | 9 | ## Usage ## 10 | 1. Add a firewall filter rule to block outbound access to the **240.0.0.0/4** IPv4 range, rejecting those attempts with a **TCP Reset**. If a reject option besides `tcp-reset` is chosen, browsers attempting to load an advert will wait several seconds before timing out; `tcp-reset` forces them to fail instantly instead. *Remember to change the `in-interface` below to match your configuration!* 11 | `/ip firewall filter add chain=forward in-interface=LAN connection-state=new protocol=tcp dst-address=240.0.0.0/4 action=reject reject-with=tcp-reset` 12 | 2. Ensure your router's DNS server is configured as desired. 13 | 3. Edit `process.php`, commenting-out the filter list files that you don't want to use. **NOTE**: my experience is the MikroTik RouterBoard devices (especially the consumer-level ones) aren't sufficiently powerful to use more than a few of these lists simultaneously. My own RouterBoard (RB450G), when presented with 60,000 static DNS entries, kept working quite happily for several hours until it tried reloading the static DNS entry list, which took it 16 minutes, during which time the DNS server wouldn't respond and even basic traffic forwarding performance was impacted. I **strongly suggest** that you only use one or two of these lists! 14 | 4. If you have any specific hosts that you want to block, add them to `source.custom.txt`. 15 | 5. Note that if you decide to enable the "remove duplicates" option in `process.php`, you will find that the script runs *significantly* faster under HHVM rather than standard PHP. In my tests, HHVM processed all of the input files in 20 seconds while standard PHP took 275 seconds! 16 | 6. Execute `process.php`: 17 | `php process.php` or `hhvm process.php` 18 | 7. The script will create a number of script.*.rsc files. Since my RouterBoard's input buffer was quite limited, I had the script split the output files into separate files. You can now copy these files, one at a time (allowing each file to finish being processed before loading the next one!), into a RouterOS terminal. 19 | 8. Configure your router's DHCP server to push DNS settings that include using the router as clients' main DNS resolver. 20 | 21 | Note: the 240.0.0.0/4 range is listed as as reserved range, which is why that range was chosen to redirect advert requests to. I chose to have the DNS server respond with a 240.0.0.x address rather than something like 0.0.0.1 (RouterOS itself refuses to let you have a static DNS entry pointing to 0.0.0.0) because some devices/software seem to actually try making a connection to 0.0.0.x addresses and will wait until they time out before the web page finishes loading. Similarly, the loopback range (127.0.0.0/8) wasn't chosen because some devices will be listening on port 80 for one reason or another (weirdly, Skype on Windows does this, seemingly as a way around firewalls, but mostly as a way to annoy anyone trying to set up Apache), and making a request that a local web server has to handle, thus increasing the load on the device, would be going against one of the primary reasons for blocking ads in the first place! 22 | -------------------------------------------------------------------------------- /process.custom.php: -------------------------------------------------------------------------------- 1 | ["240.0.0.1"], 5 | "mvps" => ["240.0.0.2"], 6 | "hphosts" => ["240.0.0.3"], 7 | "dan-pollock" => ["240.0.0.4"], 8 | "spam404" => ["240.0.0.5"], 9 | "malwaredomains.com" => ["240.0.0.6"], 10 | "malwaredomainlist.com" => ["240.0.0.7"], 11 | "custom" => ["240.0.0.255"], 12 | ]; 13 | 14 | // Might be a bit memory-intensive/slow... not strictly necessary, as RouterOS will just display a warning on duplicates. Only applicable in RouterOS mode 15 | define('SKIP_DUPLICATES', true); 16 | 17 | // Seems to be faster - use integer (CRC32 hash) keys for matching duplicates, rather than strings 18 | define('SKIP_DUPLICATES_CRC32', true); 19 | 20 | // Only applies to RouterOS output 21 | define('PER_FILE_LIMIT', 3000); 22 | 23 | // Enables output of bind9 zone files instead of RouterOS scripts. Forces skip_duplicates to ON 24 | define('BIND9_OUTPUT', true); 25 | 26 | // Name of Bind9 "null" zone file 27 | define('BIND9_NULL_ZONEFILE_NAME', '/etc/bind/db.null'); 28 | 29 | define('IN_PROCESS', 1); 30 | $totalTimeStart = microtime(true); 31 | $totalHosts = 0; 32 | $totalFiles = 0; 33 | $hostsList = []; 34 | 35 | if (BIND9_OUTPUT) { 36 | echo "NOTE: Generating Bind9 zone output instead of RouterOS commands. Forcing SKIP_DUPLICATES to be ON"; 37 | if (SKIP_DUPLICATES_CRC32) { 38 | echo " (via crc32)"; 39 | } 40 | echo ".\r\n\r\n"; 41 | } else { 42 | echo "NOTE: Removing duplicate hosts is " . (SKIP_DUPLICATES ? "ENABLED" : "DISABLED") . (SKIP_DUPLICATES_CRC32 ? ' (via crc32)' : '') . ".\r\n\r\n"; 43 | } 44 | 45 | foreach ($files as $type => $details) { 46 | list($destIp) = $details; 47 | 48 | $startTime = microtime(true); 49 | echo 50 | str_pad($type, 39, " ", STR_PAD_RIGHT) . 51 | " => " . 52 | str_pad($destIp, 15, " ", STR_PAD_RIGHT) . 53 | " ... "; 54 | 55 | $hosts = 0; 56 | $hostsInThisFile = 0; 57 | $fileNum = 0; 58 | $outputFilename = (BIND9_OUTPUT) ? "named.conf.adblock-$type" : "script.$type-$fileNum.rsc"; 59 | $fpRead = fopen("source.$type.txt", 'rb'); 60 | $fpWrite = fopen($outputFilename, 'wb'); 61 | 62 | $addLn = function($name, $comment = null) use ($type, $destIp, &$fpWrite, &$hosts, &$hostsList, &$hostsInThisFile, &$fileNum) { 63 | if (BIND9_OUTPUT || SKIP_DUPLICATES) { 64 | $searchName = strtolower($name); 65 | if (SKIP_DUPLICATES_CRC32) { 66 | $searchName = crc32($searchName); 67 | } 68 | if (in_array($searchName, $hostsList)) { 69 | return; 70 | } else { 71 | $hostsList[] = $searchName; 72 | } 73 | } 74 | 75 | if (!BIND9_OUTPUT && $hostsInThisFile >= PER_FILE_LIMIT) { 76 | // Switch to a new file 77 | fclose($fpWrite); 78 | ++$fileNum; 79 | $hostsInThisFile = 0; 80 | $fpWrite = fopen("script.$type-$fileNum.rsc", 'wb'); 81 | fputs($fpWrite, "# Continuation...\r\n\r\n"); 82 | fputs($fpWrite, "/ip dns static\r\n\r\n"); 83 | } 84 | 85 | if (BIND9_OUTPUT) { 86 | if (!empty($comment)) { 87 | // Includes a comment 88 | fputs($fpWrite, sprintf( 89 | "zone \"%s\" { type master; notify no; file \"%s\"; }; // %s\r\n", 90 | $name, 91 | BIND9_NULL_ZONEFILE_NAME, 92 | addcslashes($comment, '"') 93 | )); 94 | } else { 95 | // No comment 96 | fputs($fpWrite, sprintf( 97 | "zone \"%s\" { type master; notify no; file \"%s\"; };\r\n", 98 | $name, 99 | BIND9_NULL_ZONEFILE_NAME 100 | )); 101 | } 102 | } else { 103 | if (!empty($comment)) { 104 | // Includes a comment 105 | fputs($fpWrite, sprintf( 106 | "add address=%s name=\"%s\" comment=\"%s\"\r\n", 107 | $destIp, 108 | $name, 109 | addcslashes($comment, '?"') 110 | )); 111 | } else { 112 | // No comment 113 | fputs($fpWrite, sprintf( 114 | "add address=%s name=\"%s\"\r\n", 115 | $destIp, 116 | $name 117 | )); 118 | } 119 | } 120 | ++$hosts; 121 | ++$hostsInThisFile; 122 | }; 123 | 124 | include "process.$type.php"; 125 | 126 | fclose($fpRead); 127 | fclose($fpWrite); 128 | 129 | $duration = (microtime(true) - $startTime) * 1000; 130 | printf("%d hosts (%.2fms) (%d files)\r\n", $hosts, $duration, ($fileNum+1)); 131 | 132 | $totalHosts += $hosts; 133 | $totalFiles += ($fileNum + 1); 134 | } 135 | 136 | echo "\r\n"; 137 | $totalDuration = (microtime(true) - $totalTimeStart) * 1000; 138 | printf("Total duration: %.2fms\r\n", $totalDuration); 139 | printf("Total hosts: %d\r\n", $totalHosts); 140 | printf("Total files: %d\r\n", $totalFiles); 141 | printf("Peak RAM use: %.2f MB\r\n", (memory_get_peak_usage(true) / (1024*1024))); 142 | -------------------------------------------------------------------------------- /process.spam404.php: -------------------------------------------------------------------------------- 1 |