How to restrict your firewall by country firewalld (ie iptable)

About

This article is a step-by-step tutorial that shows you how to configure your iptable firewall with firewalld to allow only requests originating from a country

firewalld is based on iptable and therefore supports the same concept such as:

To filter by country, you need to:

  • create an ipset which is a set of IP.
  • and apply your rule on this IP set.

This article gives you a detailed step-by-step on how to do it.

GeoDNS is also another way to block by country.

Steps

Create your file

Each set of ip has a storage format called the type that defines :

  • the type of input file used to define the set.
  • the storage type and therefore the lookup time

You can't change this format once the ip set is created.

To store the IP of a country, we will use the hash:net format because it store the information by range ( not by ip as the hash:ip format is doing)

The file format for a hash:net type is:

ip
ip[/cidr]
fromaddr-toaddr

You create them from a IP geolocalization database.

For example from the GeoIp database, in Postgress, we have just run this command to get all Netherlands IP subnet.

SELECT 
    inet_merge(
       '0.0.0.0'::inet + ip_from, 
       '0.0.0.0'::inet + ip_to
   ) from ip 
where ctry = 'NL'

The output was used to create this file called ipset_nl.txt in this step by step.

2.56.16.0/22
2.56.32.0/22
2.56.56.0/22
2.56.100.0/22
...

Create a set of ip

The below command create a ipset name of the type hash:ip

firewall-cmd \
    --new-ipset=nl \
    --type=hash:net \
    --permanent \
    --option=family=inet  

where:

  • the option can be found in ipset documentation section of the type (in our case hash:ip)
    • family is the protocol family of the IP addresses to be stored in the set (default: inet = ipv4)
    • hashsize is the initial hash size for the set (default to 1024)
    • maxelem is the maximal number of element (default to 65536)

The command should return:

success

Load the file into the ipset

To load the file created from the geo database, you would performed this command.

firewall-cmd \
    --ipset=nl \
    --permanent \
    --add-entries-from-file=/tmp/ipset_nl.txt
success

Reload to load the ipset in memory and therefore make it visible to the ipset command

systemctl reload firewalld

Check that the ipset is not full

ipset -t list nl
Name: nl
Type: hash:net
Revision: 6
Header: family inet hashsize 2048 maxelem 65536
Size in memory: 135032
References: 0
Number of entries: 5139

Their is one line by entry (ie 5139) and the memory size is 135032/1024=131kb

While with a hash:ip, you're list would be full because the record is not at the net level but at the ip. It creates therefore all IP for each subnet.

Name: nl
Type: hash:ip
Revision: 4
Header: family inet hashsize 524288 maxelem 1000000
Size in memory: 22553880
References: 0
Number of entries: 1000000

Test the IP Set

You can test your IP.

  • A Canada IP should not be in the set.
ipset test nl 167.114.98.233
167.114.98.233 is NOT in set nl.

  • A Netherlands IP should be in the set.
ipset test nl 143.176.205.82
Warning: 143.176.205.82 is in set nl.

Add a rule to drop all packets that does not come from the IPSet

Adding a rule that drops all packet for the ssh Firewalld (port 22) if they are not from Netherlands

firewall-cmd \
    --zone=public \
    --permanent \
    --add-rich-rule='rule family="ipv4" source not ipset="nl" service name="ssh" drop'
success

Reload and Test

When your restart you are making the rules active. If you are locked out because of any manipulation, check this article that will help you recover your system.

Made the rule permanent.

systemctl stop firewalld
systemctl start firewalld
# systemctl reload firewalld

Test:

  • that you can connect
  • that you can't if you can use a VPN
  • or just check the log for bot

Check the log;

systemctl status firewalld
# or tail -20 /var/log/firewalld

Check that the rule is still present

firewall-cmd --list-rich-rule

Support

Hash is full

ERROR: COMMAND_FAILED: '/usr/sbin/ipset restore' failed: ipset v7.1: Error in line 45: Hash is full, cannot add more elements

If you use the ipset type hash:ip and that you use a subnet as entry, each subnet is transformed to create an entry for each IP and you can quickly be full.

Example:

ipset -t list nl
Name: nl
Type: hash:ip
Revision: 4
Header: family inet hashsize 32768 maxelem 65536
Size in memory: 1444728
References: 0
Number of entries: 65536

Solution:

  • use the format hash:net if you entries are subnet
  • or increase the size of your ipset with the maxlen option





Discover More
Firewalld - How to filter your traffic based on an IP, port or Region?

This page shows you how to filter your traffic based on a IP with firewalld rich rules. where: --permanent means that the rule will be used even after reboot. --add-rich-rule specifies the rich...
Firewalld - Howto's

A list of howto based on firewalld from country to subnet filtering as well as disabling it in rescue mode
Recaptcha
How can I protect myself from Bad Bot (Spambot, Attacker )?

Bad Bots are robots with bad intentions. They are also known as attackers. They walk through: web pages trying to find a form and to fill them trying: to send email in mass to create a fake...
Chrome Site Cant Be Reached
How to allow only the HTTP traffic from a subnet such as Cloudflare with Firewalld?

This article is a step by step that shows you how to configure the firewall Firewalld to allow the network packet traffic from a subnet such as Cloudflare



Share this page:
Follow us:
Task Runner