# Firewall ruleset for home network gateway

## bertvv

Hello all,

I just finished writing a Gentoo-friendly firewall script for the gateway of my home network and I thought it would be a good thing to share it with the rest of you. On the one hand, I hope some of you find it useful, on the other hand, I'd appreciate comments on the quality of the ruleset and hints on how to improve the script.

Relevant information about my gateway:

 Connections: Internet (cable modem, on eth1), local network (on eth0)

 Services for the local network:

 DHCP server

 NAT to allow local hosts to connect to the internet

 dnsmasq for handling DNS requests from local hosts

 There are no services accessible from the external network, netiher on the gateway, nor on any local host.

The ruleset is based on the IP Masquerading HOWTO and the Gentoo Linux Security Guide.

You can install the script e.g. with:

```
install -m 755 /path/to/firewall-script /etc/init.d/
```

But without further ado, here it is:

```

#!/sbin/runscript

# 

# Firewall ruleset for a home network gateway running Gentoo

# 

# Author:        Bert Van Vreckem <bertvv at advalvas dot be>

# Created:       2003-07-30 15:50:00

# Modified:      2004-06-21 12:01:06

# "Home network" means that no external services run on the gateway,

# nor on the local network. In other words, there's no DMZ and traffic

# initiated from the external network is always blocked. All traffic

# from the local network to the outside world is accepted. The ruleset

# supports NAT and a DHCP server for the internal network running on

# the gateway. Packets are dropped silently, i.e. no ICMP error

# message is sent to the sender of the offending packet.

# References:

#

# - The IP Masquerade HOWTO, section 6.4.1. 

#   <http://www.tldp.org/HOWTO/IP-Masquerade-HOWTO/> for

# - The Gentoo Linux Security Guide., section 10, 13

#   <http://www.gentoo.org/doc/en/gentoo-security.xml>

#---------------------------------------------------------------------

# Settings

#---------------------------------------------------------------------

#---------- General settings ------------------------------------------

# These should not be changed

opts="${opts} showstatus panic rules"

# Shell commands

IPTABLES="/sbin/iptables"

LSMOD="/sbin/lsmod"

DEPMOD="/sbin/depmod"

MODPROBE="/sbin/modprobe"

GREP="/bin/grep"

AWK="/bin/awk"

SED="/bin/sed"

IFCONFIG="/sbin/ifconfig"

ECHO="/bin/echo"

# status flag (used by eend)

status="0"

#---------- Specific settings ----------------------------------------

# Change according to your own situation

# Network interface card for the local network

INT_INTERFACE="eth0"

# Network interface card for the internet connection

EXT_INTERFACE="eth1"

# Gateway IP address on the external network (should not be changed)

# NOTE: when dhcp lease is renewed and the IP address is changed, the

# ruleset should be rerun. If you want this behaviour, you should

# configure the dhcp client accordingly.

EXT_ADDRESS="`$IFCONFIG $EXT_INTERFACE | $AWK \

 /$EXT_INTERFACE/'{next}//{split($0,a,":");split(a[2],a," ");print a[1];exit}'`"

# Network address of the internal network

INT_NETWORK="192.168.0.0/24"

# Gateway IP address on the local network

INT_ADDRESS="192.168.0.1/24"

# Wildcard for *all* IP addresses

UNIVERSE="0.0.0.0/0"

#---------------------------------------------------------------------

# Script proper

#---------------------------------------------------------------------

#---------- script dependencies --------------------------------------

depend() {

    need net

}

#---------- firewall ruleset -----------------------------------------

rules() {

    stop 

    ebegin "Setting internal rules"

    # Verify module dependencies

    einfo "Loading kernel modules"

    $DEPMOD -a || status="1"

    $MODPROBE ip_tables  || status="1"

    $MODPROBE ip_conntrack  || status="1"

    $MODPROBE ip_conntrack_ftp || status="1"

    $MODPROBE ip_conntrack_irc || status="1"

    $MODPROBE iptable_filter || status="1"

    $MODPROBE iptable_nat || status="1"

    $MODPROBE ip_nat_ftp || status="1"

    $MODPROBE ipt_LOG  || status="1"

    $MODPROBE ipt_limit  || status="1"

    $MODPROBE ipt_state  || status="1"

    einfo "Applying kernel security settings"

    # Enable IP Forwarding. 

    $ECHO "1" > /proc/sys/net/ipv4/ip_forward

    # Enable DynamicAddr.

    $ECHO "1" > /proc/sys/net/ipv4/ip_dynaddr

    # Enable Reverse Path Filtering. Makes sure packets use legitimate

    # source addresses. This prevents IP spoofing.

    for i in /proc/sys/net/ipv4/conf/*/rp_filter ; do

   $ECHO "1" > $i 

    done

    # Drop ping packets. It can be handy to enable pinging from the

    # local net for network testing, but there is no reason why an

    # outsider should be able to ping.

    #$ECHO "1" > /proc/sys/net/ipv4/icmp_echo_ignore_all

    # Ignore ICMP broadcasts. This prevents Smurf attacks.

    $ECHO "1" > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts

    # Protect against bogus error message responses

    $ECHO "1" > /proc/sys/net/ipv4/icmp_ignore_bogus_error_responses

    # Disable source routed packets, which can be used to compromise

    #your network

    $ECHO "0" > /proc/sys/net/ipv4/conf/all/accept_source_route

    # Don't accept redirects. This can be used to alter your routing

    # table.

    $ECHO "0" > /proc/sys/net/ipv4/conf/all/accept_redirects

    # Enable log_martians, logging of spoofed, source routed and

    # redirect packets.

    $ECHO "1" > /proc/sys/net/ipv4/conf/all/log_martians

    # Enable Syn Cookies.

    #$ECHO "1" > /proc/sys/net/ipv4/tcp_syncookies

    ##################################################################

    # Unless specified, the defaults for INPUT, OUTPUT, and FORWARD to

    # DROP

    #

    # You CANNOT change this to REJECT as it isn't a vaild policy

    # setting.  If you want REJECT, you must explictly REJECT at the

    # end of a giving INPUT, OUTPUT, or FORWARD chain

    #

    einfo "Setting default rules to drop"

    $IPTABLES -P INPUT DROP || status="1"

    $IPTABLES -F INPUT  || status="1"

    $IPTABLES -P OUTPUT DROP || status="1"

    $IPTABLES -F OUTPUT  || status="1"

    $IPTABLES -P FORWARD DROP || status="1"

    $IPTABLES -F FORWARD  || status="1"

    $IPTABLES -F -t nat || status="1"

    # Flush the user chain.. if it exists

    if [ -n "`$IPTABLES -L | $GREP drop-and-log-it`" ]; then

        $IPTABLES -F drop-and-log-it || status="1"

    fi

    

    # Delete all User-specified chains

    $IPTABLES -X || status="1"

    

    # Reset all IPTABLES counters

    $IPTABLES -Z || status="1"

    ##################################################################

    #Configuring specific CHAINS for later use in the ruleset

    #

    # NOTE: Some users prefer to have their firewall silently

    #       "DROP" packets while others prefer to use "REJECT"

    #       to send ICMP error messages back to the remote 

    #       machine.  The default is "REJECT" but feel free to

    #       change this below. [bert: done! packets now dropped]

    #

    # NOTE: Without the --log-level set to "info", every single

    #       firewall hit will goto ALL vtys.  This is a very big

    #       pain.

    #

    einfo "Creating drop chain"

    $IPTABLES -N drop-and-log-it || status="1"

    $IPTABLES -A drop-and-log-it -j LOG --log-level info || status="1"

    $IPTABLES -A drop-and-log-it -j DROP || status="1"

    ##################################################################

    # INPUT: Incoming traffic from various interfaces. All rulesets

    #        are already flushed and set to a default policy of DROP.

    einfo "Setting INPUT chain"

    # Loopback interfaces are valid.

    $IPTABLES -A INPUT -i lo -s $UNIVERSE -d $UNIVERSE -j ACCEPT \

        || status="1"

    # Local interface, local machines, going anywhere is valid

    $IPTABLES -A INPUT -i $INT_INTERFACE -s $INT_NETWORK \

        -d $UNIVERSE -j ACCEPT || status="1"

    # Remote interface, claiming to be local machines, IP spoofing,

    # get lost

    $IPTABLES -A INPUT -i $EXT_INTERFACE -s $INT_NETWORK \

        -d $UNIVERSE -j drop-and-log-it || status="1"

    # External interface, from any source, for ICMP traffic is valid

    # If you would like your machine to "ping" from the Internet,

    # enable this next line

    #$IPTABLES -A INPUT -i $EXT_INTERFACE -p ICMP -s $UNIVERSE \

    #    -d $EXT_ADDRESS -j ACCEPT || status="1"

    # Remote interface, any source, going to permanent PPP address is

    # valid

    #$IPTABLES -A INPUT -i $EXT_INTERFACE -s $UNIVERSE \

    #    -d $EXT_ADDRESS -j ACCEPT || status="1"

    # Allow any related traffic coming back to the MASQ server in

    $IPTABLES -A INPUT -i $EXT_INTERFACE -s $UNIVERSE \

        -d $EXT_ADDRESS -m state --state \

        ESTABLISHED,RELATED -j ACCEPT || status="1"

    # Enable DHCP server for the INTERNAL network

    $IPTABLES -A INPUT -i $INT_INTERFACE -p tcp \

        --sport 68 --dport 67 -j ACCEPT || status="1"

    $IPTABLES -A INPUT -i $INT_INTERFACE -p udp \

        --sport 68 --dport 67 -j ACCEPT || status="1"

    # Catch all rule, all other incoming is denied and logged. 

    $IPTABLES -A INPUT -s $UNIVERSE -d $UNIVERSE \

        -j drop-and-log-it || status="1"

    #######################################################################

    # OUTPUT: Outgoing traffic from various interfaces.  All rulesets are 

    #         already flushed and set to a default policy of DROP. 

    einfo "Setting OUTPUT chain"

    # loopback interface is valid.

    $IPTABLES -A OUTPUT -o lo -s $UNIVERSE -d $UNIVERSE \

        -j ACCEPT || status="1"

    # local interfaces, any source going to local net is valid

    $IPTABLES -A OUTPUT -o $INT_INTERFACE -s $EXT_ADDRESS \

        -d $INT_NETWORK -j ACCEPT || status="1"

    # local interface, any source going to local net is valid

    $IPTABLES -A OUTPUT -o $INT_INTERFACE -s $INT_ADDRESS \

        -d $INT_NETWORK -j ACCEPT || status="1"

    # outgoing to local net on remote interface, stuffed routing, deny

    $IPTABLES -A OUTPUT -o $EXT_INTERFACE -s $UNIVERSE \

        -d $INT_NETWORK -j drop-and-log-it || status="1"

    # anything else outgoing on remote interface is valid

    $IPTABLES -A OUTPUT -o $EXT_INTERFACE -s $EXT_ADDRESS \

        -d $UNIVERSE -j ACCEPT || status="1"

    # Enable DHCP server for the INTERNAL network

    $IPTABLES -A OUTPUT -o $INT_INTERFACE -p tcp -s $INT_ADDRESS \

        --sport 67 -d 255.255.255.255 --dport 68 -j ACCEPT || status="1"

    $IPTABLES -A OUTPUT -o $INT_INTERFACE -p udp -s $INT_ADDRESS \

        --sport 67 -d 255.255.255.255 --dport 68 -j ACCEPT || status="1"

    # Catch all rule, all other outgoing is denied and logged. 

    $IPTABLES -A OUTPUT -s $UNIVERSE -d $UNIVERSE \

        -j drop-and-log-it || status="1"

    ##################################################################

    # FORWARD: Enable Forwarding and thus IPMASQ

    einfo "Setting FORWARD chain"

    # Allow all connections OUT and only existing/related IN

    $IPTABLES -A FORWARD -i $EXT_INTERFACE -o $INT_INTERFACE \

        -m state --state ESTABLISHED,RELATED -j ACCEPT || status="1"

    $IPTABLES -A FORWARD -i $INT_INTERFACE -o $EXT_INTERFACE \

        -j ACCEPT || status="1"

    # Catch all rule, all other forwarding is denied and logged. 

    $IPTABLES -A FORWARD -j drop-and-log-it || status="1"

    ##################################################################

    # NAT: Enabling SNAT (MASQUERADE) functionality on $EXT_INTERFACE"

    $IPTABLES -t nat -A POSTROUTING -o $EXT_INTERFACE -j SNAT \

        --to $EXT_ADDRESS || status="1"

    eend $status

}

#---------- start firewall -------------------------------------------

start() {

    ebegin "Starting firewall"

    rules

    eend $status

}

#---------- stop firewall --------------------------------------------

stop() {

    ebegin "Shutdown firewall"

    $IPTABLES -t filter -F || status="1"

    $IPTABLES -t filter -X || status="1"

    $IPTABLES -t filter -P INPUT ACCEPT || status="1"

    $IPTABLES -t filter -P OUTPUT ACCEPT || status="1"

    $IPTABLES -t filter -P FORWARD ACCEPT || status="1"

    eend $status

}

#---------- show configuration ---------------------------------------

showstatus() {

    ebegin "Status"

    $IPTABLES -L -n -v --line-numbers

    einfo "NAT status"

    $IPTABLES -L -n -v --line-numbers -t nat

    eend $?

}

#---------- panic rules ----------------------------------------------

panic() {

    ebegin "Setting panic rules"

    $IPTABLES -F || status="1"

    $IPTABLES -X || status="1"

    $IPTABLES -t nat -F || status="1"

    $IPTABLES -P FORWARD DROP || status="1"

    $IPTABLES -P INPUT   DROP || status="1"

    $IPTABLES -P OUTPUT  DROP || status="1"

    $IPTABLES -A INPUT -i lo -j ACCEPT || status="1"

    $IPTABLES -A OUTPUT -o lo -j ACCEPT || status="1"

    eend $status

}

#---------- restart --------------------------------------------------

restart() {

    svc_stop

    svc_start

}

```

----------

## DaveArb

 *bertvv wrote:*   

> 
> 
> ```
> 
> # Gateway IP address on the local network
> ...

 

I haven't looked at the entire script. I do have comments on these two variables.

INT_ADDRESS looks like it should not be a CIDR range, but an IP?

Next is personal taste I guess, but since all IP addresses are not variable, I would not use UNIVERSE at all. I find it easier to read if as much as possible is inlined. A quick glance through looks like all the places you use UNIVERSE are defaults anyway, again my taste is to leave those out to make the script easier to read. IME, easier to read translates directly into fewer surprises.

Dave

----------

## bertvv

 *DaveArb wrote:*   

> 
> 
> INT_ADDRESS looks like it should not be a CIDR range, but an IP?
> 
> 

 

CIDR notation is not only used to specify an IP range, but also for a (classless) IP address. Here, it only means that the first 24 bits (192.168.0) are the network part of the address and the other 8 (.1) the host part.  This one's copied from the Masquerade HOWTO, by the way. The same goes for UNIVERSE, but I can agree with your comment on that one.

----------

## DaveArb

 *bertvv wrote:*   

> CIDR notation is not only used to specify an IP range, but also for a (classless) IP address.

 

Right, the C in CIDR means classless. To specify a single IP address, one uses a /32. However...

 *bertvv wrote:*   

> Here, it only means that the first 24 bits (192.168.0) are the network part of the address and the other 8 (.1) the host part.

 

How would iptables know that, exactly? Let's examine an example use:

```
# local interface, any source going to local net is valid

$IPTABLES -A OUTPUT -o $INT_INTERFACE -s $INT_ADDRESS -d $INT_NETWORK -j ACCEPT || status="1"
```

Expanded, this becomes:

```
# local interface, any source going to local net is valid

/sbin/iptables -A OUTPUT -o eth0 -s 192.168.0.1/24 -d 192.168.0.0/24 -j ACCEPT || status="1"
```

Now, I still haven't been though the whole script to see if this causes any problem anywhere, but you can easily see that at the very least the comment is misleading. In this case since the rule is for the OUTPUT table, it doesn't make any difference (assuming that eth0 is truly 192.168.0.1). What is going to happen when the user needs a bit of a modification, and assumes the variables actually mean what the comments say? If this were in the FORWARD table, it would have a very different meaning that one would be led to belive.

 *bertvv wrote:*   

> This one's copied from the Masquerade HOWTO, by the way. The same goes for UNIVERSE, but I can agree with your comment on that one.

 

That's OK. I'm arrogant enough to disagree with about anything...  :Wink:  Please don't think I'm just being ugly, this could be very valuable for a new user, and I'd like to help it be as good as possible.

Dave

----------

## Satori80

```
# NOTE: when dhcp lease is renewed and the IP address is changed, the

# ruleset should be rerun. If you want this behaviour, you should

# configure the dhcp client accordingly. 
```

This is a good point and something I really didn't consider. I use dhcp on my external interface and haven't configured the system to re-load the ruleset on a change. How would one go about that?[/code]

----------

## bertvv

 *Satori80 wrote:*   

> 
> 
> ```
> # NOTE: when dhcp lease is renewed and the IP address is changed, the
> 
> ...

 

One of the configuration files of the dhcp client is dhcpcd.exe. This is a script that is executed every time the client daemon brings down or configures the interface. In this script, restart the firewall by adding a line like.

```
/etc/init.d/firewall restart > /dev/null
```

Check out "man dhcpcd" for more information about dhcpcd.exe.

----------

## Satori80

I wanted to thank you for this script. I ended up using my original script but using your example to change it to work the Gentoo way.

One thing I thought you may (or may not) find interesting is that I thought it made sense to keep nat functionality with the firewall ruleset unloaded, so I added a section: 

```
#-------- NAT ruleset -------------------------------------------------

nat.rules() {

   ebegin "\nLoading NAT rules 2.4 $FWVER\n"

   #Clearing any previous configuration

   #

   #  Unless specified, the defaults for INPUT and OUTPUT is ACCEPT

   #    The default for FORWARD is DROP (REJECT is not a valid policy)

   #

   einfo "Clearing any existing rules and setting default policy.."

   $IPTABLES -P INPUT ACCEPT || status="1"

   $IPTABLES -F INPUT || status="1"

   $IPTABLES -P OUTPUT ACCEPT || status="1"

   $IPTABLES -F OUTPUT || status="1"

   $IPTABLES -P FORWARD DROP || status="1"

   $IPTABLES -F FORWARD || status="1"

   $IPTABLES -t nat -F || status="1"

   einfo "FWD: Allow all connections OUT and only existing and related ones IN"

   $IPTABLES -A FORWARD -i $EXTIF -o $INTIF \

        -m state --state ESTABLISHED,RELATED -j ACCEPT || status="1"

   $IPTABLES -A FORWARD -i $INTIF -o $EXTIF -j ACCEPT || status="1"

   $IPTABLES -A FORWARD -j LOG || status="1"

   einfo "Enabling SNAT (MASQUERADE) functionality on $EXTIF"

   $IPTABLES -t nat -A POSTROUTING -o $EXTIF -j MASQUERADE || status="1"

   einfo "\nNAT rules-2.4 $FWVER loaded\n"

   eend $status

}
```

The /etc/init.d/<script> stop command will run this 'nat.rules' section instead. To totally shut down the whole kit and caboodle I've made a kill section that does what your 'stop()' does. If not for your example I probably never would have gentooized this script.

PS. If anybody would want to add my nat.rules section to their own script, please keep in mind that mine is using different variable names thatn his! You will get errors if you simply cut and paste my example into his script.

----------

