IPv6 Firewall with Linux

September 17, 2009

More and more server hoster have configured IPv6 on their network. And most of their Linux based servers come with a basic IPv6 configuration. Even if IPv6 is not used, it is there and widely open as the netfilter/iptables default policy is ACCEPT.

If you don’t use IPv6 at all, disable it. On Debian, the interfaces configuration is located in /etc/network/interfaces. Remove all the inet6 configuration looking like this :

iface eth0 inet6 static
  address 2001:db8:4:fe34::3ea1
  netmask 64

If you plan to use IPv6, first you have to know ICMPv6 should be filtered carefully. In IPv6, ICMPv6 is widely used. It replaces IPv4′s ARP with neighbour-solicitation and neighbour-advertisement ICMPv6 messages. It is used for router discovery via router-solicitation and router-advertisement ICMPv6 messages. It replaces IPv4′s IGMP with group-membership-query, group-membership-report and group-membership-reduction ICMPv6 messages.

Here is a small script to activate IPv6 filtering with netfilter/ip6tables. The script has been test on Debian but it should work on other Linux flavor.

#!/bin/sh -e
#
# Simple example IPv6 Firewall configuration.
#
# Caveats:
# - This configuration applies to all network interfaces
# if you want to restrict this to only a given interface use
# '-i INTERFACE' in the ip6tables calls.
# - Remote access for TCP/UDP services is granted to any host,
# you probably will want to restrict this using '--source'.
#
# description: Activates/Deactivates the firewall at boot time
#
# You should test this script before applying with safe-restart option
#

IP6TABLES=/sbin/ip6tables
#IP6TABLES="/sbin/ip6tables -i eth0"

[ -x "$IP6TABLES" ] || exit 1

# Inbound TCP ports
TCP_INPUT_PORTS="21 22 53 443"

# Inbound UDP ports
UDP_INPUT_PORTS="53"

# Allowed ICMP messages
ALLOWED_ICMP="\
packet-too-big \
destination-unreachable \
time-exceeded parameter-problem \
echo-request \
echo-reply \
router-advertisement \
neighbour-solicitation \
neighbour-advertisement"

fw_start () {
# Allow related and established connection.
$IP6TABLES -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

# Allow ICMP as defined in ALLOWED_ICMP
if [ -n "$ALLOWED_ICMP" ] ; then
 for ICMP_TYPE in $ALLOWED_ICMP; do
  $IP6TABLES -A INPUT -p icmpv6 --icmpv6-type ${ICMP_TYPE} -j ACCEPT
 done
fi

# Open allowed TCP ports if any
if [ -n "$TCP_INPUT_PORTS" ] ; then
 for PORT in $TCP_INPUT_PORTS; do
  $IP6TABLES -A INPUT -m state --state NEW -p tcp --dport ${PORT} \
  -j ACCEPT
 done
fi

# Open allowed UDP ports if any
if [ -n "$UDP_INPUT_PORTS" ] ; then
 for PORT in $UDP_INPUT_PORTS; do
  $IP6TABLES -A INPUT -m state --state NEW -p udp --dport ${PORT} \
  -j ACCEPT
 done
fi

# Allow traffic to the loopback (needed by some applications)
$IP6TABLES -A INPUT -i lo -j ACCEPT

# Log and drop all other packets.
$IP6TABLES -A INPUT -j LOG
$IP6TABLES -P INPUT DROP

# Los and drop all packet to be forwarded, we're not a router...
$IP6TABLES -A FORWARD -j LOG
$IP6TABLES -P FORWARD DROP

# We're not going to filter outgoing packets
# but you can if you're paranoid like I am...
$IP6TABLES -P OUTPUT ACCEPT
}

# fw_stop disables completely the firewall and reset all chains to
# the default policy ACCEPT
fw_stop () {
  $IP6TABLES -P INPUT ACCEPT
  $IP6TABLES -P FORWARD ACCEPT
  $IP6TABLES -P OUTPUT ACCEPT
  $IP6TABLES -t mangle -P PREROUTING ACCEPT
  $IP6TABLES -t mangle -P POSTROUTING ACCEPT
  $IP6TABLES -t mangle -P INPUT ACCEPT
  $IP6TABLES -t mangle -P OUTPUT ACCEPT
  $IP6TABLES -t mangle -P FORWARD ACCEPT
  $IP6TABLES -t mangle -F
  $IP6TABLES -t mangle -X
  $IP6TABLES -F
  $IP6TABLES -X
}

# fw_clear remove the rule set from the firewall and keep the
# current default policy
fw_clear () {
  $IP6TABLES -t mangle -F
  $IP6TABLES -t mangle -X
  $IP6TABLES -F
  $IP6TABLES -X
}

case "$1" in
  start|restart)
    echo -n "Starting IPv6 firewall.."
    fw_clear
    fw_start
    echo "done."
    ;;
  stop)
    echo -n "Stopping IPv6 firewall.."
    fw_stop
    echo "done."
    ;;
  clear)
    echo -n "Clearing IPv6 firewall rules.."
    fw_clear
    echo "done."
    ;;
  test|safe-restart)
    echo -n "Safely restarting IPv6 firewall..."
    fw_clear
    fw_start
    test=""; read -t 10 -p "Is it still OK? " test ; \
    [ -z "$test" ] && fw_stop
    echo "done."
    ;;
  *)
    echo "Usage: $0 {start|stop|restart|safe-restart|clear}"
    exit 1
    ;;
esac

exit 0

Now let’s make the ip6firewall to start at boot time :
update-rc.d ip6firewall start 41 S . stop 34 0 6 .

Now the server is only accepting packets on ports we have decided.

IPv6 Certification Badge for krik
By the way, I’ve been certified ipv6 Guru by Hurricane Electric;-)

That’s all folks!

Thanks for leaving a comment if you found this useful