IP6tables firewall for Linux

As well as getting IPv6 working on my Zen internet connection at home with a Cisco 887VA and on my BTnet internet connection at work with a Cisco 3925E, I needed a new firewall set-up to support IPv6 on my Linux servers.

A standalone firewall script

Most of my Linux boxes run Ubuntu 14.04 LTS 64-bit “Trusty Tahr” release but I do not use the Ubuntu Firewall (UFW) on IPv4 for several reasons:

  • I actually have Ubuntu 10.04, 12.04 and 14.04 boxes in production systems
  • I have some ‘bleeding edge’ Ubuntu 15.10 servers
  • I have some legacy boxes still running RedHat 9
  • I have a solitary CentOS box
  • my firewall sub-system dates back to IPchains and was ported to IPtables

As such I have never adopted a firewall system from any of the Linux distributions, preferring to keep it simple and “all in one script” that is named, aptly, “rc.firewall”.

Location of firewall scripts

My IPv4 firewall script script lives in /etc/init.d and is manually symlinked in from /etc/rc2.d and starts immediately after the network stack:

S05firewall -> /etc/init.d/rc.firewall

I decided to follow the “dual stack” approach and create a new script for IPv6 firewall called “rc.firewall6” and place it in /etc/init.d and symlink it in immediately after the IPv4 script, so I now have:

S06firewall -> /etc/init.d/rc.firewall6

as well.

Requirements for the IPv6 firewall

I decided on the following requirements:

  • should be a single script that can be read top-to-bottom
  • suitable for a public server (node) with a single LAN interface
  • easily extended to have more interfaces
  • easily extended to be a router
  • reject bad address ranges
  • allow a safe subset of ICMPv6 messages
  • implement stateful packet inspection on TCP connections
  • allow TCP connections from one or more ranges of hosts and destination ports
  • allow UDP messages from one or more ranges of hosts and destination ports

Basic firewall for a IPv6 public Linux server

#!/bin/sh
#
# rc.firewall6 - A firewall script for IPv6 based Linux boxen
# Copyright (C) 2015-2016 by Mike Tubby, mike@tubby.org
#
#
# ABSTRACT
#
# A firewall script for Linux end-points (nodes) on the public internet
# employing IPv6. This script is based on my previous rc.firewall for
# IPv4 systems
#
# We employ he same techniques as we used on the previous IPv4 firewall
# plus the bits recommended by CERT:
#
# https://www.cert.org/downloads/IPv6/ip6tables_rules.txt
#
#


###
### Setup
###

#
# Public network segment (Internet) - my interface name/addresses
#
ZEN_IFACE="eth0"
ZEN_IPADDR="2a02:8010:7010::10/64"
SEC_IPADDR="2a02:8010:7010::1010/64"

#
# Localhost
#
LOCALHOST_IFACE="lo"
LOCALHOST="::1"

#
# other network definitions
#
TRUSTED_PREFIX1="2a02:8010:7010::/48"
TRUSTED_PREFIX2="2a00:2380:3003:1000::/56"
TRUSTED_PREFIX3="2a02:8010:7001::/48"
ANYWHERE="::/0"

#
# Path to iptables
#
IP6TABLES="/sbin/ip6tables"

###
### Initialisation
###

#
# delete our chains, flush system chains and zero the stats
#
$IP6TABLES -F INPUT
$IP6TABLES -F OUTPUT
$IP6TABLES -F FORWARD
$IP6TABLES -F
$IP6TABLES -Z
$IP6TABLES -X inet_icmp
$IP6TABLES -X inet_tcp
$IP6TABLES -X inet_udp
$IP6TABLES -X tcp_allow

#
# set default policies for the INPUT, FORWARD and OUTPUT chains
#
$IP6TABLES -P INPUT DROP
$IP6TABLES -P OUTPUT DROP
$IP6TABLES -P FORWARD DROP

#
# Create separate chains for ICMP, TCP and UDP packets to traverse
# when they come in from the public interface
#
$IP6TABLES -N inet_icmp
$IP6TABLES -N inet_tcp
$IP6TABLES -N inet_udp


###
### The "tcp_allow" chain for stateful packet inspection of TCP
###

#
# This is effectively a macro and is re-used for each TCP connection we
# want to allow...
#
# TCP packets from the internet interface travers this chain after their
# initial checks for source address/port number on the 'inet_tcp' chain.
#
# These checks are common to all allowed source address/port numbers -
# we allow new SYN packets and ESTABLISHED/RELATED packets for existing
# connections only. Non-matching (unrelated) TCP packets are quietly
# dropped on the floor. These could be logged but would probably create
# unnecessary noise.
#
$IP6TABLES -N tcp_allow
$IP6TABLES -A tcp_allow -p TCP --syn -j ACCEPT
$IP6TABLES -A tcp_allow -p TCP -m state --state ESTABLISHED,RELATED -j ACCEPT
$IP6TABLES -A tcp_allow -p TCP -j DROP


###
### ICMP rules (from public internet)
###

# Allow: Destination unreachable, packet too big, time exceeded, parameter problem
$IP6TABLES -A inet_icmp -p icmpv6 --icmpv6-type destination-unreachable -j ACCEPT
$IP6TABLES -A inet_icmp -p icmpv6 --icmpv6-type packet-too-big -j ACCEPT
$IP6TABLES -A inet_icmp -p icmpv6 --icmpv6-type time-exceeded -j ACCEPT
$IP6TABLES -A inet_icmp -p icmpv6 --icmpv6-type parameter-problem -j ACCEPT

# Allow: Echo Request (protect against flood) & echo reply
#$IP6TABLES -A inet_icmp -p icmpv6 --icmpv6-type echo-request -m limit --limit 5/sec --limit-burst 10 -j ACCEPT
#$IP6TABLES -A inet_icmp -p icmpv6 --icmpv6-type echo-reply -j ACCEPT
$IP6TABLES -A inet_icmp -p icmpv6 --icmpv6-type echo-request -j ACCEPT
$IP6TABLES -A inet_icmp -p icmpv6 --icmpv6-type echo-reply -j ACCEPT

# Allow: Other ICMPv6 types but only if the hop limit field is 255, ie. local/adjacent nodes
$IP6TABLES -A INPUT -p icmpv6 --icmpv6-type router-advertisement -m hl --hl-eq 255 -j ACCEPT
$IP6TABLES -A INPUT -p icmpv6 --icmpv6-type neighbor-solicitation -m hl --hl-eq 255 -j ACCEPT
$IP6TABLES -A INPUT -p icmpv6 --icmpv6-type neighbor-advertisement -m hl --hl-eq 255 -j ACCEPT
$IP6TABLES -A INPUT -p icmpv6 --icmpv6-type redirect -m hl --hl-eq 255 -j ACCEPT


###
### TCP rules (from public internet)
###

#
# SSH from trusted networks
#
$IP6TABLES -A inet_tcp -p TCP -s $TRUSTED_PREFIX1 --dport 22 -j tcp_allow
$IP6TABLES -A inet_tcp -p TCP -s $TRUSTED_PREFIX2 --dport 22 -j tcp_allow

#
# Web and secure web
#
$IP6TABLES -A inet_tcp -p TCP -s $ANYWHERE --dport 80 -j tcp_allow
$IP6TABLES -A inet_tcp -p TCP -s $ANYWHERE --dport 443 -j tcp_allow

#
# Email, SMTP, MSA, IMAP/IMAPS
#
$IP6TABLES -A inet_tcp -p TCP -s $ANYWHERE --dport 25 -j tcp_allow
$IP6TABLES -A inet_tcp -p TCP -s $ANYWHERE --dport 587 -j tcp_allow
$IP6TABLES -A inet_tcp -p TCP -s $ANYWHERE --dport 143 -j tcp_allow
$IP6TABLES -A inet_tcp -p TCP -s $ANYWHERE --dport 993 -j tcp_allow


###
### UDP rules (from public internet)
###

#
# dns/bind on port 53
#
$IP6TABLES -A inet_udp -p UDP -s $ANYWHERE --dport 53 -j ACCEPT

#
# ntp/timekeeping on port 123
#
$IP6TABLES -A inet_udp -p UDP -s $ANYWHERE --dport 123 -j ACCEPT

#
# Traceroute packets banging on our door?
#
$IP6TABLES -A inet_udp -p UDP -s $ANYWHERE --dport 33434:33523 -j ACCEPT



######
###
### INPUT table => traffic landing on this machine
###
######

#
# Drop impossible addresses
#
$IP6TABLES -A INPUT -d 0::/10 -j DROP

#
# Drop the old "site local" addresses which are deprecated
#
$IP6TABLES -A INPUT -d fec0::/10 -j DROP

#
# Drop the new "unique local" addresses which shouldn't be coming in from the internet
#
$IP6TABLES -A INPUT -d fc00::/7 -j DROP

#
# Drop all packets with RH0 headers - these are deprecated as they may allow DoS
#
$IP6TABLES -A INPUT -m rt --rt-type 0 -j DROP

#
# Allow multicast addresses (needed these days - think router advertisments)
#
$IP6TABLES -A INPUT -d ff00::/8 -j ACCEPT

#
# divert packets from the external interface (internet) onto individual
# chains for checking
#
$IP6TABLES -A INPUT -p icmpv6 -i $ZEN_IFACE -j inet_icmp
$IP6TABLES -A INPUT -p TCP -i $ZEN_IFACE -j inet_tcp
$IP6TABLES -A INPUT -p UDP -i $ZEN_IFACE -j inet_udp

#
# Allow to/from the loopback address (nb. single address, not interface)
#
$IP6TABLES -A INPUT -s $LOCALHOST -d $LOCALHOST -j ACCEPT

#
# Allow Link-Local addresses (this will be addressed to our link-local address)
#
$IP6TABLES -A INPUT -s fe80::/10 -j ACCEPT

#
# accept traffic from the external network that is state-mached/related
#
# Note: With IPv6 you may have to repeat this line for each/all of your IP addresses
#
$IP6TABLES -A INPUT -p ALL -d $ZEN_IPADDR -m state --state ESTABLISHED,RELATED -j ACCEPT

#
# log everthing else with a bandwidth throttle (in case of DoS)
#
$IP6TABLES -A INPUT -m limit --limit 5/minute --limit-burst 5 -j LOG --log-prefix "IPv6 INPUT: "

#
# explicit drop (paranoia)
#
$IP6TABLES -A INPUT -j DROP


######
###
### FORWARD chain => traffic being forwarded by this machine
###
######

#
# No rules here for an internet server that doesn't act as a router!
#

#
# explicit drop (paranoia)
#
$IP6TABLES -A FORWARD -j DROP


######
###
### OUTPUT chain => traffic originating from this machine
###
######

#
# Filter all packets that have RH0 headers, these a deprecated as they allow DoS
#
$IP6TABLES -A OUTPUT -m rt --rt-type 0 -j DROP

#
# Allow anything on the loopback address
#
$IP6TABLES -A OUTPUT -s $LOCALHOST -d $LOCALHOST -j ACCEPT

#
# Allow Link-Local addresses
#
$IP6TABLES -A OUTPUT -s fe80::/10 -j ACCEPT

#
# Allow multicast addresses (needed these days - think router advertisments)
#
$IP6TABLES -A OUTPUT -d ff00::/8 -j ACCEPT

#
# Drop the old "site local" addresses which are deprecated
#
$IP6TABLES -A OUTPUT -d fec0::/10 -j DROP

#
# Drop the new "unique local" addresses we shouldn't be using on the internet
#
$IP6TABLES -A OUTPUT -d fc00::/7 -j DROP

#
# Allow everything out for the loopback interface
#
$IP6TABLES -A OUTPUT -p ALL -s $LOCALHOST -j ACCEPT

#
# Allow everything out from our network address(es)
# Note: you will have to repeat this for each/every IPv6 address
#
$IP6TABLES -A OUTPUT -p ALL -s $ZEN_IPADDR -j ACCEPT

#
# log anything that falls off the end
#
$IP6TABLES -A OUTPUT -m limit --limit 5/minute --limit-burst 5 -j LOG --log-prefix "IPv6 OUTPUT: "

#
# explicit drop (paranoia)
#
$IP6TABLES -A OUTPUT -j DROP


######
###
### List what we've got configured
###
######

#
# you may not want this on at system boot!
#
$IP6TABLES -L -n


### end ###