In times when the internet is full of shitty traffic like port scanners, bots, brute forces, script kiddies and lots of scrapers - irrelevant countries for which you most likely do not want to offer your service and from which a lot of stupid traffic comes should be filtered out by default in order to minimize risks.
I often block everything on the relevant ports such as SSH (port 22) except my fixed IP address. Let's say you don't have a fixed IP available and still want to limit the possible sources for your cloud VPS. For example, you can say "I only allow connections from Germany" for my SSH port. How? I'll show you now!
I use debian / ubuntu based server. We fully rely on standard which are all available in the standard apt. As geo-fence firewall we use nftables.
Install all packages we need on your server.
apt install nftables python3 git
Get IP address definition and keep it up to date
First we need the definitions of IP address ranges, which are assigned to the countries. Thank god (🙏) someone has already done the work, and so we can use the following git repo pvxe/nftables-geoip to map all IP addresses to countries via the db-ip.com service.
git clone https://github.com/pvxe/nftables-geoip
cd nftables-geoip
Download all IP definitions.
python3 nft_geoip.py --download --file-location location.csv
Make the downloads includeable
Now we need to find out the import path of nftables. The easiest way is to type the following:
nft --help
In the output check the line:
-I, --includepath Add to the paths searched for include files. Default is: /etc
You will find here the default include path. Copy the following downloaded files there:
cp geoip-def-all.nft /etc/
cp geoip-ipv4.nft /etc/
cp geoip-ipv6.nft /etc/
Replace /etc/ with your include path if needed.
Later, a cronjob bash-script should be implemented here that regularly updates the definitions.
Define nftables filter file
nano /etc/nftables.conf
Replace the default config with the following.
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
include "geoip-def-all.nft"
include "geoip-ipv4.nft"
include "geoip-ipv6.nft"
# all countries
define allcountries = { $AF ,$AX ,$AL ,$DZ ,$AS ,$AD ,$AO ,$AI ,$AQ ,$AG ,$AR ,$AM ,$AW ,$AU ,$AT ,$AZ ,$BS ,$BH ,$BD ,$BB ,$BY ,$BE ,$BZ ,$BJ ,$BM ,$BT ,$BO ,$BQ ,$BA ,$BW ,$BV ,$BR ,$IO ,$BN ,$BG ,$BF ,$BI ,$CV ,$KH ,$CM ,$CA ,$KY ,$CF ,$TD ,$CL ,$CN ,$CX ,$CC ,$CO ,$KM ,$CG ,$CD ,$CK ,$CR ,$CI ,$HR ,$CU ,$CW ,$CY ,$CZ ,$DK ,$DJ ,$DM ,$DO ,$EC ,$EG ,$SV ,$GQ ,$ER ,$EE ,$SZ ,$ET ,$FK ,$FO ,$FJ ,$FI ,$FR ,$GF ,$PF ,$TF ,$GA ,$GM ,$GE ,$DE ,$GH ,$GI ,$GR ,$GL ,$GD ,$GP ,$GU ,$GT ,$GG ,$GN ,$GW ,$GY ,$HT ,$HM ,$VA ,$HN ,$HK ,$HU ,$IS ,$IN ,$ID ,$IR ,$IQ ,$IE ,$IM ,$IL ,$IT ,$JM ,$JP ,$JE ,$JO ,$KZ ,$KE ,$KI ,$KP ,$KR ,$KW ,$KG ,$LA ,$LV ,$LB ,$LS ,$LR ,$LY ,$LI ,$LT ,$LU ,$MO ,$MG ,$MW ,$MY ,$MV ,$ML ,$MT ,$MH ,$MQ ,$MR ,$MU ,$YT ,$MX ,$FM ,$MD ,$MC ,$MN ,$ME ,$MS ,$MA ,$MZ ,$MM ,$NA ,$NR ,$NP ,$NL ,$NC ,$NZ ,$NI ,$NE ,$NG ,$NU ,$NF ,$MK ,$MP ,$NO ,$OM ,$PK ,$PW ,$PS ,$PA ,$PG ,$PY ,$PE ,$PH ,$PN ,$PL ,$PT ,$PR ,$QA ,$RE ,$RO ,$RU ,$RW ,$BL ,$SH ,$KN ,$LC ,$MF ,$PM ,$VC ,$WS ,$SM ,$ST ,$SA ,$SN ,$RS ,$SC ,$SL ,$SG ,$SX ,$SK ,$SI ,$SB ,$SO ,$ZA ,$GS ,$SS ,$ES ,$LK ,$SD ,$SR ,$SJ ,$SE ,$CH ,$SY ,$TW ,$TJ ,$TZ ,$TH ,$TL ,$TG ,$TK ,$TO ,$TT ,$TN ,$TR ,$TM ,$TC ,$TV ,$UG ,$UA ,$AE ,$GB ,$US ,$UM ,$UY ,$UZ ,$VU ,$VE ,$VN ,$VG ,$VI ,$WF ,$EH ,$YE ,$ZM ,$ZW }
# all countries without $DE $AT $CH
define allcountries_without_dach = { $AF ,$AX ,$AL ,$DZ ,$AS ,$AD ,$AO ,$AI ,$AQ ,$AG ,$AR ,$AM ,$AW ,$AU ,$AZ ,$BS ,$BH ,$BD ,$BB ,$BY ,$BE ,$BZ ,$BJ ,$BM ,$BT ,$BO ,$BQ ,$BA ,$BW ,$BV ,$BR ,$IO ,$BN ,$BG ,$BF ,$BI ,$CV ,$KH ,$CM ,$CA ,$KY ,$CF ,$TD ,$CL ,$CN ,$CX ,$CC ,$CO ,$KM ,$CG ,$CD ,$CK ,$CR ,$CI ,$HR ,$CU ,$CW ,$CY ,$CZ ,$DK ,$DJ ,$DM ,$DO ,$EC ,$EG ,$SV ,$GQ ,$ER ,$EE ,$SZ ,$ET ,$FK ,$FO ,$FJ ,$FI ,$FR ,$GF ,$PF ,$TF ,$GA ,$GM ,$GE ,$GH ,$GI ,$GR ,$GL ,$GD ,$GP ,$GU ,$GT ,$GG ,$GN ,$GW ,$GY ,$HT ,$HM ,$VA ,$HN ,$HK ,$HU ,$IS ,$IN ,$ID ,$IR ,$IQ ,$IE ,$IM ,$IL ,$IT ,$JM ,$JP ,$JE ,$JO ,$KZ ,$KE ,$KI ,$KP ,$KR ,$KW ,$KG ,$LA ,$LV ,$LB ,$LS ,$LR ,$LY ,$LI ,$LT ,$LU ,$MO ,$MG ,$MW ,$MY ,$MV ,$ML ,$MT ,$MH ,$MQ ,$MR ,$MU ,$YT ,$MX ,$FM ,$MD ,$MC ,$MN ,$ME ,$MS ,$MA ,$MZ ,$MM ,$NA ,$NR ,$NP ,$NL ,$NC ,$NZ ,$NI ,$NE ,$NG ,$NU ,$NF ,$MK ,$MP ,$NO ,$OM ,$PK ,$PW ,$PS ,$PA ,$PG ,$PY ,$PE ,$PH ,$PN ,$PL ,$PT ,$PR ,$QA ,$RE ,$RO ,$RU ,$RW ,$BL ,$SH ,$KN ,$LC ,$MF ,$PM ,$VC ,$WS ,$SM ,$ST ,$SA ,$SN ,$RS ,$SC ,$SL ,$SG ,$SX ,$SK ,$SI ,$SB ,$SO ,$ZA ,$GS ,$SS ,$ES ,$LK ,$SD ,$SR ,$SJ ,$SE ,$SY ,$TW ,$TJ ,$TZ ,$TH ,$TL ,$TG ,$TK ,$TO ,$TT ,$TN ,$TR ,$TM ,$TC ,$TV ,$UG ,$UA ,$AE ,$GB ,$US ,$UM ,$UY ,$UZ ,$VU ,$VE ,$VN ,$VG ,$VI ,$WF ,$EH ,$YE ,$ZM ,$ZW }
chain geoip-mark-input {
type filter hook input priority -1; policy accept;
# Mark incomming packages with the country
meta mark set ip saddr map @geoip4
meta mark set ip6 saddr map @geoip6
}
chain input {
type filter hook input priority 0; policy accept;
# example: traffic outside $DE, $AT, $CH will be blocked on port 22
tcp dport 22 meta mark $allcountries_without_dach drop
# example: traffic outside $DE, $AT, $CH will be completly blocked
meta mark $allcountries_without_dach drop
# example: traffic from $UK will be blocked for port 443
tcp dport 443 meta mark $UK drop
}
}
You have all the country codes in the ISO 3166-1 standard available. i.e. $US = USA, $DE = Germany, $RU = RUSSIA etc. ... Incoming traffic will be tagged with the country code.
And here you go.
systemctl restart nftables
Now you can test your geo-fence with an VPN connection or similar.
More advanced
To make it more readable and easier to maintain, you can of course also block everything by default by replacing the following line in the input chain
type filter hook input priority 0; policy accept;
with
type filter hook input priority 0; policy drop;
Then use the following filter rule.
tcp dport 22 meta mark $DE accept
This will allow only German traffic on port 22, for example.
Please take care, so that you do not exclude yourself. You also have to release your internal ipnet from the local LAN or similar depending on the environment you have.
Thanks for reading and stay safe.
Top comments (1)
Wow, this is an incredibly detailed guide! Thank you for breaking down the steps for setting up geo-fencing with nftables. As someone who has struggled with unwanted traffic on my servers, this solution looks both robust and straightforward. The script for regularly updating the IP definitions is a great touch, ensuring the firewall remains effective over time. I'll definitely be giving this a try on my own setup. Cheers!