rss logo

How to Implement Geo-Filtering on OpenBSD with PacketFilter

OpenBSD Logo

I regularly rely on the robust and secure OpenBSD operating system, paired with its powerful PacketFilter firewall, to protect my services. Since many commercial solutions offer Geo-Filtering, I was interested in implementing a similar feature directly within this system.

By enabling Geo-Filtering, we can restrict access to our services based on geographic location, effectively hiding them from specific regions and significantly enhancing our overall security.

To achieve this, we'll use PacketFilter's table mechanism, combined with Country IP block lists provided by ipdeny.com.

Country List Format

The country-based IP block lists provided by ipdeny.com follow a consistent URL pattern: https://www.ipdeny.com/ipblocks/data/countries/COUNTRY_CODE.zone

For instance, to retrieve the list for the United States (country code us), you can access it via the following URL: https://www.ipdeny.com/ipblocks/data/countries/us.zone.

  • To download a country-specific IP block list into the /etc/tables directory, use the wget command as follows:
root@host:~# wget --no-check-certificate https://www.ipdeny.com/ipblocks/data/countries/us.zone -O /etc/tables/us.zone
  • You can check the number of entries in the file with the following command:
root@host:~# wc -l /etc/tables/us.zone
65296 /etc/tables/us.zone

This count is important because it reflects the number of IP blocks loaded into the table. Fortunately, it remains well below the default table-entries limit of 200000 defined in OpenBSD's PF configuration (see the official documentation here).

Defining PacketFilter Rules

Now that we have our Geo-Filtering list in place, we can define the appropriate filtering rules in PacketFilter.

  • For example, the following configuration allows only US-based IP addresses to access our web server:
wan = "em0"  # Assigns the network interface 'em0' to the variable 'wan'

# Sets the maximum number of entries allowed in the PF table to 200,000 (which is already the default)
set limit table-entries 200000

# Creates a new PF table named 'USA' and loads its contents from the file '/etc/tables/us.zone'
table <USA> persist file "/etc/tables/us.zone"

# Allows incoming HTTP traffic from the 'USA' table to the IP address 192.168.1.10 on ports 80 and 443
pass in quick on { $wan } proto tcp from <USA> to 192.168.1.10 port { 80, 443 }

# Blocks incoming HTTP traffic from any source to the IP address 192.168.1.10 on ports 80 and 443
block in quick on { $wan } proto tcp from any to 192.168.1.10 port { 80, 443 }
  • Reload the PacketFilter rules to apply the changes:
root# pfctl -f /etc/pf.conf

What Next?

To keep the /etc/tables/us.zone file up to date, you can automate its retrieval using a crontab entry. For detailed instructions, refer to my separate tutorial here.