pf GeoIP Blocking
Why?
Sometimes you see strange calls in your server logs, regardless of whether they are access logs from Apache or nginx, or messages from sshd, for example. However, you may also be under active attack from someone and want to block that person, but different IP addresses keep appearing. If you look them up, you will find that they always come from the same country or even the same ISP. You can find this out using tools such as whois on the command line.
However, you may also want to make your content available only in certain countries, which can also be achieved with geo-blocking.
There are various reasons why you might want to block certain people from your server. However, you should always remember not to accidentally block yourself. So before you travel to one of the countries you have blocked on your server, you should unlock yourself (or use a VPN).
Installation
In order to assign IP addresses to a country, we need a database that can provide the necessary data. The FreeBSD Ports collection contains the package ipdbtools. This package provides you with a few tools for storing an IP database locally and keeping it up to date. The data comes from the RIR, so it is identical to the results that a whois would output, for example.
To install the package run following command:
sudo pkg install ipdbtools
I will not go into further detail here about the use of the individual tools provided in this package, as the man pages provide sufficient information. If you would like to know more, you can find the information on your shell with:
apropos ipdb
I update my local database regularly using a cron job. To do this, I wrote a small script that executes the necessary commands. I store all data in the /etc/pf-tables/ directory. The script is called geoipblocker.sh:
#!/bin/sh
#Update Database
echo "Last Update:" >/etc/pf-tables/geoipupdate.log
date >> /etc/pf-tables/geoipupdate.log
echo "\n">>/etc/pf-tables/geoipupdate.log
ipdb-update.sh >> /etc/pf-tables/geoipupdate.log 2>&1 
# Write IP addresses to file, followed by -t and the respective countries.
# For new countries, simply add a line
ipup -t RU -p  > /etc/pf-tables/geoipblocker.table
ipup -t CN -p  >> /etc/pf-tables/geoipblocker.table
ipup -t MN -p  >> /etc/pf-tables/geoipblocker.table
# Load the file into a pf table
pfctl -t geoipblocker -T replace -f /etc/pf-tables/geoipblocker.table >> /etc/pf-tables/geoipupdate.log 2>&1
Now we need to adjust our firewall rules. Everyone has a different number of items already listed here. If the file is still empty, I recommend starting with a basic configuration and then adding to it.
The following must then be added to /etc/pf.conf:
# Create a table
table <geoipblocker> persist file "/etc/pf-tables/geoipblocker.table"
# block all IPs from the table
block quick on $external_if from <geoipblocker> to any
Please remember to replace $external_if with your appropriate interface or variable. The “quick” in the rule ensures that packets matching this rule are not evaluated further. If you have already set up port forwarding in your rule set, you should check that it does not already contain a “pass.” Since NAT and redirects are performed before the filters, this would ensure that your internal services can still be reached from the filtered addresses.
For example, the following rule would be problematic:
rdr pass on $external_if proto tcp to $server_ext_ip port {http,https} -> $internal_server_ip
Instead, you should remove the pass and perform the release in a separate rule:
pass in on $external_if proto tcp from any to $server_ext_ip port {http,https}
pass in on $external_if proto tcp from any to $internal_server_ip port {http,https}
After adding the rules to /etc/pf.conf, the script created earlier must be made executable and run manually once. Then you can reload the firewall:
chmox +x /etc/pf-tables/geoipblocker.sh
/etc/pf-tables/geoipblocker.sh
pfctl -vf /etc/pf.conf
When the script is executed, the IP database is updated and the addresses belonging to the selected countries are written to a file. The script loads these addresses into the geoipblocker table. To ensure that these addresses are also loaded when the firewall is restarted, the table is defined as persistent with the data file in the rule set.
To check whether the table has been loaded correctly, you can use the following command line:
pfctl -t geoipblocker -T show
This should output a long list of prefixes. If you want to filter, you should not use grep, as it does not allow you to query subnets. Instead, use the following command to test whether an address is contained in the table:
pfctl -t geoipblocker -Tt 1.2.3.4
Now all that remains is to update the IP data regularly, for which we create a crontab with crontab -e. This opens an editor in which you can then write the following content:
0       3       *       *       1       /etc/pf-tables/geoipblocker.sh
This ensures that the data is updated every Monday at 3 a.m.
Fazit
With ipdbtools, you can block your network for certain countries in conjunction with pf. Of course, you should be careful never to lock yourself out, for example, when you are on vacation. Of course, the rules added here also work for IPv6! Since products such as pfSense use the pf firewall, this setup also works there.