How to Implement US IP Filtering with iptables Using CIDR Blocks


2 views

When you need to restrict access to specific geographic regions at the network layer, iptables provides a robust solution. For US-only access control, we'll leverage CIDR notation blocks covering all US IP ranges. This method is more efficient than individual IP filtering and operates at the kernel level.

First, obtain an updated list of US IP ranges in CIDR notation. You can find these from organizations like ARIN or use curated lists from services like ip2location. Here's a sample of what your IP block file might contain:

# US IP blocks (sample)
3.0.0.0/8
4.0.0.0/8
8.0.0.0/8
...

We'll create a whitelist approach where we first block all traffic, then allow only US IP blocks. Here's the basic structure:

#!/bin/bash
# Flush existing rules
iptables -F

# Set default policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# Allow localhost
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Allow established connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

Now we'll process the CIDR file and create allow rules for each block:

# Read US IP blocks from file and create rules
while read -r cidr; do
    iptables -A INPUT -s "$cidr" -j ACCEPT
done < us_ip_blocks.txt

# Optional: Log rejected attempts
iptables -A INPUT -j LOG --log-prefix "IPTABLES-BLOCKED: "

For better performance with large IP block lists:

# Create a custom chain for US IP filtering
iptables -N US_FILTER
iptables -A INPUT -j US_FILTER

# Add rules to the custom chain
while read -r cidr; do
    iptables -A US_FILTER -s "$cidr" -j RETURN
done < us_ip_blocks.txt

# Drop non-matching traffic
iptables -A US_FILTER -j DROP

To make your rules persistent across reboots on Debian/Ubuntu systems:

# Install persistence package
apt-get install iptables-persistent

# Save current rules
netfilter-persistent save

Verify your rules with:

iptables -L -n -v

And check your logs for blocked attempts:

tail -f /var/log/syslog | grep IPTABLES-BLOCKED

When restricting web services to specific geographic regions, IP-based filtering remains the most reliable method at the network layer. The United States has over 20,000 registered IP blocks (as of ARIN's 2023 data), making manual entry impractical. Here's how to implement this efficiently:

First, acquire an authoritative US IP range list. I recommend using ARIN's delegated data:

wget https://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest
grep "US|ipv4" delegated-arin-extended-latest | awk -F'|' '{print $4 "/" 32-log($5)/log(2)}' > us-cidr.txt

The naive approach would create thousands of individual rules, causing performance issues. Instead, use ipset for efficient matching:

# Create ipset hash
ipset create us-ips hash:net

# Populate from CIDR file
while read cidr; do
  ipset add us-ips $cidr
done < us-cidr.txt

With the ipset prepared, create iptables rules that reference it:

# Basic rule structure
iptables -A INPUT -p tcp --dport 80 -m set ! --match-set us-ips src -j DROP
iptables -A INPUT -p tcp --dport 443 -m set ! --match-set us-ips src -j DROP

# Alternative logging version for debugging
iptables -N US_FILTER
iptables -A US_FILTER -m set ! --match-set us-ips src -j LOG --log-prefix "Non-US Access: "
iptables -A US_FILTER -j DROP
iptables -A INPUT -p tcp --dport 80 -j US_FILTER
iptables -A INPUT -p tcp --dport 443 -j US_FILTER

For high-traffic servers, these optimizations help:

  • Place the rules early in your iptables chain
  • Consider using nftables instead for better performance with large sets
  • Monitor CPU usage during peak traffic

IP allocations change frequently. Set up a monthly cron job:

0 3 1 * * /usr/sbin/ipset flush us-ips && \
wget -qO- https://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest | \
grep "US|ipv4" | awk -F'|' '{print $4 "/" 32-log($5)/log(2)}' | \
while read cidr; do ipset add us-ips $cidr; done