Build Internet-Connection sharing firewall based on a dynamic WAN IP address

Maciej
5 min readDec 13, 2019

--

Your Linux firewall box is assembled and ready to go to work. But first, you must setup a firewall and Internet connection sharing. You’re still on IPv4, and your LAN uses mostly nonroutable private IP addresses, so you want a Network Address Translation (NAT) firewall. You have a dynamically assigned WAN address.

It’s all done with iptables. Don’t connect the WAN interface yet. Make sure there are no open ports on your firewall machine. Test this by running netstat on the firewall box. This command shows all listening and TCP and UDP sockets and established connections:

admin@firewall:~# netstat -untap

If you find any open ports, close them. Any services you want to run can be restarted later, but for now, it’s safer to shut them off, with one exception: you need a DHCP client running so the WAN interface will work correctly. DHCP clients run by default on all Linux distributions, so you shouldn’t have to enable it. Next, edit /etc/sysctl.conf so that it has these kernel parameters. The first one is the most important because you must have it to enable sharing your Internet connection:

net.ipv4.ip_forward = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.accept_source_route = 0

Next, copy the following script, call it /usr/local/bin/fw_nat, and make it read/write/ executable for root only, mode 0700:

#!/bin/sh
##/usr/local/bin/fw_nat
#iptables firewall script for sharing
#broadband Internet, with no public services
#define variables
ipt=”/sbin/iptables”
mod=”/sbin/modprobe”
LAN_IFACE=”eth0"
WAN_IFACE=”eth1"
#basic set of kernel modules
$mod ip_tables
$mod ip_conntrack
$mod iptable_filter
$mod iptable_nat
$mod iptable_mangle
$mod ipt_LOG
$mod ipt_limit
$mod ipt_state
$mod ipt_MASQUERADE
#add these for IRC and FTP
$mod ip_nat_ftp
$mod ip_nat_irc
$mod ip_conntrack_ftp
$mod ip_conntrack_irc
# Flush all active rules and delete all custom chains
$ipt -F
$ipt -t nat -F
$ipt -t mangle -F
$ipt -X
$ipt -t nat -X
$ipt -t mangle -X
#Set default policies
$ipt -P INPUT DROP
$ipt -P FORWARD DROP
$ipt -P OUTPUT ACCEPT
$ipt -t nat -P OUTPUT ACCEPT
$ipt -t nat -P PREROUTING ACCEPT
$ipt -t nat -P POSTROUTING ACCEPT
$ipt -t mangle -P PREROUTING ACCEPT
$ipt -t mangle -P POSTROUTING ACCEPT
#this line is necessary for the loopback interface
#and internal socket-based services to work correctly
$ipt -A INPUT -i lo -j ACCEPT
#Enable IP masquerading
$ipt -t nat -A POSTROUTING -o $WAN_IFACE -j MASQUERADE
#Enable unrestricted outgoing traffic, incoming
#is restricted to locally-initiated sessions only
$ipt -A INPUT -m state — state RELATED,ESTABLISHED -j ACCEPT
$ipt -A FORWARD -i $WAN_IFACE -o $LAN_IFACE -m state — state ESTABLISHED,RELATED -j
ACCEPT
$ipt -A FORWARD -i $LAN_IFACE -o $WAN_IFACE -m state — state NEW,ESTABLISHED,RELATED
-j ACCEPT
# Accept important ICMP messages
$ipt -A INPUT -p icmp — icmp-type echo-request -j ACCEPT
$ipt -A INPUT -p icmp — icmp-type time-exceeded -j ACCEPT
$ipt -A INPUT -p icmp — icmp-type destination-unreachable -j ACCEPT
#Reject connection attempts not initiated from inside the LAN
$ipt -A INPUT -p tcp — syn -j DROP

Now, load the new sysctl settings and execute the fw_nat script as root:

# /sbin/sysctl -p
# fw_nat

Then, connect the WAN interface to your broadband modem, and bring up the WAN interface:

# /sbin/ifup eth1

You should see some messages from your DHCP client and see your new address.

Now, connect a second PC to your LAN port, either with a switch or a crossover cable. It needs a static address on the same network as the firewall’s LAN port, using the firewall’s LAN address as the gateway. You should be able to web surf, ping remote sites, and ping each other. Once everything is working correctly, go to to learn how to start your iptables script at boot, and how to stop and restart your firewall.

If running /sbin/ifup eth1 gives you this message:

ifup: interface eth1 already configured

Run /sbin/ifdown eth1, then /sbin/ifup eth1. A typical response to running /sbin/ifup eth1 looks like this:

# ifup eth1
Internet Systems Consortium DHCP Client V3.0.2
Copyright 2004 Internet Systems Consortium.
All rights reserved.
For info, please visit http://www.isc.org/products/DHCP
sit0: unknown hardware address type 776
sit0: unknown hardware address type 776
Listening on LPF/eth1/00:01:02:03:04:05
Sending on LPF/eth1/00:01:02:03:04:05
Sending on Socket/fallback
DHCPDISCOVER on eth1 to 255.255.255.255 port 67 interval 3
DHCPOFFER from 1.2.3.4
DHCPREQUEST on eth1 to 255.255.255.255 port 67
DHCPACK from 1.2.3.4
bound to 1.2.3.44 — renewal in 34473 seconds.

If none of this happens, make sure your cables are connected correctly. If they are, try rebooting. It’s usually quicker than dinking around with the network starting/stopping peculiarities of your particular Linux distribution. The RELATED,ESTABLISHED rules are examples of the power of stateful packet filtering. iptables’ connection tracking knows which TCP packets belong to an established connection, so we can lock down incoming traffic tightly and still have unfettered functionality with just a few rules. The default policies apply when no specific rules apply to a packet. The NAT and mangle tables should default to ACCEPT because packets traverse these tables before the filter table. If your NAT and mangle policies are DROP, you will have to create additional rules to allow packets to reach the filter table. Setting OUTPUT ACCEPT as the default is somewhat controversial. Some admins advocate locking this down with OUTPUT DROP, and writing allow rules only as needed. If you use OUTPUT ACCEPT, see Recipe 3.18 for some tips on writing egress rules for blocking known bad ports, and for adding some other basic precautions. iptables does not run as a daemon, but operates at the kernel level. The rules are loaded into memory by the iptables command. You may run all the commands in the above script from the command line, which is one way of testing. However, they will not survive a reboot. My preference is to script all rules even for testing; it’s easy enough to edit and rerun the script. If things go excessively haywire, run the flush script from Recipe 3.8 to delete all rules and reset everything to ACCEPT. If for some reason that does not work, rebooting will clear out everything, provided you have no firewall scripts that run at boot. Then, you need to reexamine your scripts to figure out what went wrong. Because iptables is implemented in the kernel, stock kernels vary in how many modules are built-in, and how many are loadable modules. Check your /boot/config-* file to see how yours was built. It’s unnecessary to include kernel modules in your firewall script that are built-in to the kernel, though it doesn’t hurt anything. You may wish to build a custom kernel with all the iptables modules you need built-in to save the hassle of managing modules. There are no performance differences either way, it’s just a matter of personal preference. It is common to see kernel parameters set in iptables scripts, like this:

echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts
echo 0 > /proc/sys/net/ipv4/conf/all/accept_redirects
echo 0 > /proc/sys/net/ipv4/conf/all/accept_source_route

I prefer to control these options with sysctl because that is what it is designed to do, and I prefer that they operate independently of my iptables script. The echo commands are nice for command-line testing, as they override configuration files. They won’t survive a reboot, so any settings you want to keep permanently should go in /etc/sysctl.conf. A common point of confusion is dots and slashes. You may use either, like this:

net.ipv4.tcp_syncookies = 1
net/ipv4/tcp_syncookies = 1

--

--

Maciej
Maciej

Written by Maciej

DevOps Consultant. I’m strongly focused on automation, security, and reliability.

No responses yet