Using ipset and iptables to block full bogons
From Team Cymru:
A bogon prefix is a route that should never appear in the Internet routing table. A packet routed over the public Internet (not including over VPNs or other tunnels) should never have a source address in a bogon range. These are commonly found as the source addresses of DDoS attacks.
In this tutorial, we will be using the full bogons list of Team Cymru to block martians (private and reserved addresses defined by RFC 1918, RFC 5735 and RFC 6598) and netblocks that have been allocated to a regional internet registry (RIR) by IANA, but were not assigned to an actual ISP or other end user.
Why should I bother filtering it ?
As stated by Team Cymru, a bogon is something that is never meant to appear on internet. As such they are most often used in DDoS attacks, but can also appear for various reason (e.g: misconfigured network). Hence, it is not harmful and even recommended to filter these so that they never reach your own network and more importantly, never leave it to go on internet. So, lets get started.
- An ipset capable kernel.
Most Linux distributions nowadays includes support for ipset in their kernel, either entirely built-in or as a module. Regardless, here is how to enable it if you are building your own kernel or using a distribution such as gentoo.
[*] Networking support ---> Networking options ---> [*] Network packet filtering framework (Netfilter) ---> <M> IP set support ---> as well as Core Netfilter Configuration ---> <M> set target and match support
Notice that the IP set support is listed with a —> after itself, meaning it is a submenu. This is where you will also need to go to enable the various set types. It is recommended to enable them all, as modules, so that you will not have to rebuild because you noticed too late you are missing an important one later on.
- Raw table support in iptables and ip6tables (optional).
To be able to filter the bogons as early as possible, we will need to add the rules in the raw table of iptables, and optionally ip6tables if you chose to enable it as well. It is the first table in the entire netfilter framework, and hence ideal to filter packets before they ever get to traverse the nat or filter table. You can find support for raw table under networking support -> networking options -> Network packet filtering framework (Netfilter). In this menu, choose either ‘IP: netfilter configuration’ or ‘IPv6: netfilter configuration’ for ipv4 and ipv6 support, respectively, then enable the raw table as a module.
- The ipset tools for userspace.
Most distributions offer the ipset tools as a package that you can simply install through your package manager. I will not explain how to build this from source, as I have never done it and wish to avoid causing my readers to make mistakes.
Using a set of scripts to maintain the IP sets
For this tutorial, I provide two scripts to create and maintain the sets in ipset:
- The updater, which is used to periodically verify the last time when the lists were downloaded to disk, and download them again if required.
- The installer which is used to do the actual work with ipset, creating the sets and swapping them.
It is important to realize that the bogons and full bogons lists provided by Team Cymru are far from being static. They can evolve several times a day, and it is important to periodically download a fresh copy or you could end up blocking legit traffic over time. It is also important to not overdo it so that everyone has a chance at pulling the new lists. Team Cymru recommends 4 hours for the full bogons lists so that is what we’ll be using.
Of course, keep in mind these scripts that I offer as example were designed with my own setup in mind so there will be a few things you will need to change, most notably the paths where the file(s) are stored, the sets’ names if you so wish, and the path to the bogons-install script that is referenced in bogons-update.
Once this is done, be sure you have appropriately placed them both where you want them, and make sure all the data you have modified is correct.
- If you didn’t modify the scripts, then they should both be placed in /usr/local/bin.
- You will have to create the directory to store the bogons file, the scripts do not create it as needed.
For our example, we will use /data/bogons.
mkdir -p /data/bogons
Then go ahead and execute the bogons-update script.
- For IPv4, do the following:
- For IPv6, run this instead:
You should see the output of the curl command in both case, and nothing more before you are returned to your terminal prompt. It is worth noting that if the script fails to install properly, it will abort. Type the following, and confirm that both sets have been created and the number of entries should match the number of lines in your bogon file(s), minus one which is the header Team Cymru adds with the last updated timestamp. Your terminal output should look like this:
[[email protected] ~]# ipset -t list Name: bogons4 Type: hash:net Revision: 6 Header: family inet hashsize 2048 maxelem 65536 Size in memory: 103064 References: 0 Number of entries: 3029 Name: bogons6 Type: hash:net Revision: 6 Header: family inet6 hashsize 65536 maxelem 2000000 Size in memory: 348776 References: 0 Number of entries: 106649
Automating the process
Now that your ipset has been confirmed to work, we still need to automate the running of the bogons-update and bogons-install scripts.
- The bogons-update should be ran by a cron job or systemd timer every 5 minutes or so.
- The bogons-install should be ran only once at boot, before network comes up. It will be called automatically by bogons-update the rest of the time.
The bogons-update script only verifies the last modification time of the file(s) before acting on this value, either aborting because the file(s) are not at least 4 hours old, or downloading a fresh coppy if they are at least 4 hours old or more. But we still need to automate the execution of this script with some kind of timer for this to work as intended.
So since I am using systemd on my machine I will provide all the required files you will need in order to set this up properly, the only things you should need to modify being the paths to the updater and installer that systemd will use, in case you modified them, and the path to the ipset binary in bogons-install.service.
- It is advised that you add the RandomizedDelaySec= setting to your timer unit, especially if you intend to use the bogons listing on several hosts or routers. It will avoid your machine being overloaded with many timers running all at once, and will also avoid everyone having the timer to go off exactly every 4 hours after the file is considered stale hitting the server of Team Cymru all at the same time, all across the internet, thus reducing the constant load it is under. A randomness of 5 minutes for a timer while another one has a 10 minutes randomness might be a good starting point. Gives a chance for everyone to download the new listings. See your local manpage for systemd.timer for the explanation and usage of RandomizedDelaySec= setting. If you plan on using it on more than 5 machines, I recommend you make use of a cdn which will grab the listings once and then redistribute it to the machines it is used on, but this is beyond what this post will cover.
Once they are modified according to your needs, place them all under /etc/systemd/system/ and run:
sudo systemctl enable bogons-update.timer bogons-install.service sudo systemctl start bogons-update.timer
From now on the timer should trigger every 5 minutes and only the bogons-update script will know what to do if the age of the file(s) is under or above 4 hours old. As for the bogons-install.service it is only meant to run once per boot, before the network is brought up, so that it may restore the set(s) from the previously downloaded listing, which prevent your machine from downloading the list(s) every time you reboot it, and only when necessary due to the timer.
Blocking with iptables / ip6tables
Now that everything is organized on the ipset side of things, we need to configure iptables and optionally ip6tables if you chose to go for both full bogons listings. Lets add these lines to your /etc/iptables/iptables.rules, or whereever your distribution stores them:
*raw :PREROUTING ACCEPT :OUTPUT ACCEPT -A PREROUTING -i wan -m set --match-set bogons4 src -j DROP -A OUTPUT -o wan -m set --match-set bogons4 dst -j DROP COMMIT *filter -A FORWARD -i br0 -o wan -s 192.168.1.0/24 -m set --match-set bogons4 dst -j DROP COMMIT
Let me explain these rules. First in the *raw table, we add a rule to block bogons coming from the outside world, so that they will never get through. Still in the *raw table, the second rule prevent bogons from getting out of the router itself. We commit this table and move onto the *filter table, which contains a rule to prevent bogons from leaving your LAN, so from machines behind the router itself. We also commit this table.
- Be careful that the –match-set option of the set module matches the name of the actual IP set you created earlier, or iptables will not run properly!
- Make sure the interface names are the correct ones, or you could bring the entire network down.
- Make sure you are using the correct LAN address range, this is provided as example only.
- Be mindful of the fact the ip set needs to exist before you run iptables-restore at boot, or it will fail to run because of a missing ip set.
Ready? Good. Now try a iptables-restore < /etc/iptables/iptables.rules, only if you have physical access to the machine and can rollback independently of network connectivity. Otherwise I recommend iptables-failsafe or iptables-apply, which I briefly mentioned in my previous post, using iptables the right way. If you've really messed up, they will allow you to recover, in case you have no physical access to the machine you are working on.
As for IPv6, this is basically the same, with at least four obvious differences:
- It is done through /etc/iptables/ip6tables.rules instead, or whereever your distribution stores the IPv6 firewall configuration.
- The set name is not the same, in my case bogons6 instead of bogons4.
- The interface names for IPv6 might be different from your IPv4 ones. Be sure to verify this before applying the rules.
- There should be no need for a rule to accept a LAN in IPv6, since you most likely have a global prefix.
Once again, be careful with the parameters, and remember to use iptables-apply or iptables-failsafe if you are on a remote host.