]> git.ipfire.org Git - people/stevee/network.git/blobdiff - functions.firewall
wireless: Add function to find DFS channels.
[people/stevee/network.git] / functions.firewall
index ce42e0f36c87e60e4ebfde6be8c1e80be8fde773..494e3bb367edc9be1286293cf7573d4763704a16 100644 (file)
 #                                                                             #
 ###############################################################################
 
+# This function initializes all kernel parameters that need to be adjusted
+# to run this firewall properly.
+function firewall_kernel_init() {
+       log INFO "Configuring kernel parameters..."
+       local option
+
+       # Enable conntrack accounting
+       conntrack_set_accounting "true"
+
+       # Adjust max. amount of simultaneous connections
+       conntrack_set_max_connections "${CONNTRACK_MAX_CONNECTIONS}"
+
+       # Increase UDP connection timeout (fixes DNS)
+       conntrack_set_udp_timeout "${CONNTRACK_UDP_TIMEOUT}"
+
+       # Disable sending redirects
+       log INFO "Disabling sending redirects"
+       sysctl_set_recursively "net.ipv6.conf" "send_redirects" 0
+       sysctl_set_recursively "net.ipv4.conf" "send_redirects" 0
+
+       # Enable source route protection
+       log INFO "Enabling source route protection"
+       sysctl_set_recursively "net.ipv6.conf" "accept_source_route" 0
+       sysctl_set_recursively "net.ipv4.conf" "accept_source_route" 0
+
+       # ICMP broadcast protection (smurf amplifier protection)
+       log INFO "Enabling ICMP broadcast protection (smurf amplifier protection)"
+       sysctl_set "net.ipv4.icmp_echo_ignore_broadcasts" 1
+
+       # ICMP Dead Error Message protection
+       log INFO "Enabling ICMP dead error message protection"
+       sysctl_set "net.ipv4.icmp_ignore_bogus_error_responses" 0
+
+       # Enable packet forwarding
+       log INFO "Enabling packet forwarding"
+       sysctl_set_recursively "net.ipv6.conf" "forwarding" 1
+       sysctl_set_recursively "net.ipv4.conf" "forwarding" 1
+
+       # Setting some kernel performance options
+       log INFO "Setting some kernel performance options"
+       for option in window_scaling timestamps sack dsack fack; do
+               sysctl_set "net.ipv4.tcp_${option}" 1
+       done
+       sysctl_set "net.ipv4.tcp_low_latency" 0
+
+       # Reduce DoS ability by reducing timeouts
+       log INFO "Reducing DoS ability"
+       sysctl_set "net.ipv4.tcp_fin_timeout" 30
+       sysctl_set "net.ipv4.tcp_keepalive_time" 1800
+
+       # Set number of times to retry SYN in a new connection
+       sysctl_set "net.ipv4.tcp_syn_retries" 3
+
+       # Set number of times to retry a SYN-ACK in a half-open new connection
+       sysctl_set "net.ipv4.tcp_synack_retries" 2
+
+       # Enable a fix for RFC1337 - time-wait assassination hazards in TCP
+       sysctl_set "net.ipv4.tcp_rfc1337" 1
+
+       # SYN-flood protection
+       if enabled FIREWALL_SYN_COOKIES; then
+               log INFO "Enabling SYN-flood protection via SYN-cookies"
+               sysctl_set_bool "net.ipv4.tcp_syncookies" 1
+       else
+               log INFO "Disabling SYN-flood protection via SYN-cookies"
+               sysctl_set_bool "net.ipv4.tcp_syncookies" 0
+       fi
+
+       # rp_filter
+       if enabled FIREWALL_RP_FILTER; then
+               log INFO "Enabling anti-spoof from non-routable IP addresses"
+               sysctl_set_recursively "net.ipv4.conf" "rp_filter" 1
+       else
+               log INFO "Disabling anti-spoof from non-routable IP addresses"
+               sysctl_set_recursively "net.ipv4.conf" "rp_filter" 0
+       fi
+
+       # Log martians
+       if enabled FIREWALL_LOG_MARTIANS; then
+               log INFO "Enabling the logging of martians"
+               sysctl_set_recursively "net.ipv4.conf" "log_martians" 1
+       else
+               log INFO "Disabling the logging of martians"
+               sysctl_set_recursively "net.ipv4.conf" "log_martians" 0
+       fi
+
+       # ICMP redirect messages
+       if enabled FIREWALL_ACCEPT_ICMP_REDIRECTS; then
+               log INFO "Enabling accepting ICMP-redirect messages"
+               sysctl_set_recursively "net.ipv6.conf" "accept_redirects" 1
+               sysctl_set_recursively "net.ipv4.conf" "accept_redirects" 1
+       else
+               log INFO "Disabling accepting ICMP-redirect messages"
+               sysctl_set_recursively "net.ipv6.conf" "accept_redirects" 0
+               sysctl_set_recursively "net.ipv4.conf" "accept_redirects" 0
+       fi
+
+       # Explicit Congestion Notification
+       if enabled FIREWALL_USE_ECN; then
+               log INFO "Enabling ECN (Explicit Congestion Notification)"
+               sysctl_set "net.ipv4.tcp_ecn" 1
+       else
+               log INFO "Disabling ECN (Explicit Congestion Notification)"
+               sysctl_set "net.ipv4.tcp_ecn" 2
+       fi
+
+       # Dynamic IP address hacking
+       log INFO "Enabling kernel support for dynamic IP addresses"
+       sysctl_set "net.ipv4.ip_dynaddr" 1
+
+       if enabled FIREWALL_PMTU_DISCOVERY; then
+               log INFO "Enabling PMTU discovery"
+               sysctl_set "net.ipv4.ip_no_pmtu_disc" 0
+       else
+               log INFO "Disabling PMTU discovery"
+               sysctl_set "net.ipv4.ip_no_pmtu_disc" 1
+       fi
+
+       # TTL
+       if ipv4_ttl_valid "${FIREWALL_DEFAULT_TTL}"; then
+               log INFO "Setting default TTL to ${FIREWALL_DEFAULT_TTL}"
+               sysctl_set "net.ipv4.ip_default_ttl" "${FIREWALL_DEFAULT_TTL}"
+       else
+               log ERROR "Invalid value for default TTL '${FIREWALL_DEFAULT_TTL}'"
+               log ERROR "  Must be between 10 and 255!"
+       fi
+
+       return ${EXIT_OK}
+}
+
 # High-level function which will create a ruleset for the current firewall
 # configuration and load it into the kernel.
 function firewall_start() {
+       local protocol="${1}"
+       assert isset protocol
+       shift
+
        # Test mode.
        local test="false"
 
@@ -30,6 +164,10 @@ function firewall_start() {
                        --test)
                                test="true"
                                ;;
+                       *)
+                               error "Unrecognized argument: ${1}"
+                               return ${EXIT_ERROR}
+                               ;;
                esac
                shift
        done
@@ -42,73 +180,89 @@ function firewall_start() {
        firewall_lock_acquire
 
        # Initialize an empty iptables ruleset.
-       iptables_init DROP
+       iptables_init "${protocol}" "DROP"
 
        # Add default chains.
-       firewall_tcp_state_flags
-       firewall_custom_chains
-       firewall_connection_tracking
-       firewall_tcp_clamp_mss
+       firewall_filter_rh0_headers "${protocol}"
+       firewall_filter_icmp "${protocol}"
+       firewall_filter_invalid_packets "${protocol}"
+       firewall_custom_chains "${protocol}"
+       firewall_connection_tracking "${protocol}"
+       firewall_tcp_clamp_mss "${protocol}"
 
        # Add policies for every zone.
-       firewall_localhost_create_chains
+       firewall_localhost_create_chains "${protocol}"
 
        local zone
        for zone in $(zones_get_all); do
                # Create all needed chains for the zone.
-               firewall_zone_create_chains ${zone}
+               firewall_zone_create_chains "${protocol}" "${zone}"
 
                # After the chains that are always available have been
                # created, we will add a custom policy to every single
                # zone.
 
-               policy_zone_add ${zone}
+               policy_zone_add "${protocol}" "${zone}"
        done
 
        # Load the new ruleset.
-       iptables_load ${test}
+       local args
+       if enabled testmode; then
+               list_append args "--test"
+       fi
+       iptables_commit "${protocol}" ${args}
 
        firewall_lock_release
 }
 
 function firewall_stop() {
+       local protocol="${1}"
+       assert isset protocol
+
        firewall_lock_acquire
 
        # Initialize an empty firewall ruleset
        # with default policy ACCEPT.
-       iptables_init ACCEPT
+       iptables_init "${protocol}" ACCEPT
 
        # Load it.
-       iptables_load
+       ipables_load "${protocol}"
 
        firewall_lock_release
 }
 
 function firewall_show() {
+       local protocol="${1}"
+       assert isset protocol
+
        # Shows the ruleset that is currently loaded.
-       iptables_status
+       iptables_status "${protocol}"
 
        return ${EXIT_OK}
 }
 
 function firewall_panic() {
+       local protocol="${1}"
+       assert isset protocol
+       shift
+
        local admin_hosts="$@"
 
-       firewall_lock_acquire
+       firewall_lock_acquire "${protocol}"
 
        # Drop all communications.
-       iptables_init DROP
+       iptables_init "${protocol}" DROP
 
        # If an admin host is provided, some administrative
        # things will be allowed from there.
        local admin_host
        for admin_host in ${admin_hosts}; do
-               iptables -A INPUT -s ${admin_host} -j ACCEPT
-               iptables -A OUTPUT -d ${admin_host} -j ACCEPT
+               iptables "${protocol}" -A INPUT  -s "${admin_host}" -j ACCEPT
+               iptables "${protocol}" -A OUTPUT -d "${admin_host}" -j ACCEPT
        done
 
        # Load it.
-       iptables_load
+       iptables_commit "${protocol}"
 
        firewall_lock_release
 }
@@ -141,82 +295,298 @@ function firewall_lock_release() {
 }
 
 function firewall_custom_chains() {
+       local protocol="${1}"
+       assert isset protocol
+
        log INFO "Creating CUSTOM* chains..."
 
        # These chains are intened to be filled with
        # rules by the user. They are processed at the very
        # beginning so it is possible to overwrite everything.
 
-       iptables_chain_create CUSTOMINPUT
-       iptables -A INPUT -j CUSTOMINPUT
+       iptables_chain_create "${protocol}" CUSTOMINPUT
+       iptables "${protocol}" -A INPUT -j CUSTOMINPUT
 
-       iptables_chain_create CUSTOMFORWARD
-       iptables -A FORWARD -j CUSTOMFORWARD
+       iptables_chain_create "${protocol}" CUSTOMFORWARD
+       iptables "${protocol}" -A FORWARD -j CUSTOMFORWARD
 
-       iptables_chain_create CUSTOMOUTPUT
-       iptables -A OUTPUT -j CUSTOMOUTPUT
+       iptables_chain_create "${protocol}" CUSTOMOUTPUT
+       iptables "${protocol}" -A OUTPUT -j CUSTOMOUTPUT
 
-       iptables_chain_create -4 -t nat CUSTOMPREROUTING
-       iptables -4 -t nat -A PREROUTING -j CUSTOMPREROUTING
+       iptables_chain_create "${protocol}" -t nat CUSTOMPREROUTING
+       iptables "${protocol}" -t nat -A PREROUTING -j CUSTOMPREROUTING
 
-       iptables_chain_create -4 -t nat CUSTOMPOSTROUTING
-       iptables -4 -t nat -A POSTROUTING -j CUSTOMPOSTROUTING
+       iptables_chain_create "${protocol}" -t nat CUSTOMPOSTROUTING
+       iptables "${protocol}" -t nat -A POSTROUTING -j CUSTOMPOSTROUTING
 
-       iptables_chain_create -4 -t nat CUSTOMOUTPUT
-       iptables -4 -t nat -A OUTPUT -j CUSTOMOUTPUT
+       iptables_chain_create "${protocol}" -t nat CUSTOMOUTPUT
+       iptables "${protocol}" -t nat -A OUTPUT -j CUSTOMOUTPUT
 }
 
-function firewall_tcp_state_flags() {
-       log INFO "Creating TCP State Flags chain..."
-       iptables_chain_create BADTCP_LOG
-       iptables -A BADTCP_LOG -p tcp -j $(iptables_LOG "Illegal TCP state: ")
-       iptables -A BADTCP_LOG -j DROP
-
-       iptables_chain_create BADTCP
-       iptables -A BADTCP -p tcp --tcp-flags ALL NONE -j BADTCP_LOG
-       iptables -A BADTCP -p tcp --tcp-flags SYN,FIN SYN,FIN -j BADTCP_LOG
-       iptables -A BADTCP -p tcp --tcp-flags SYN,RST SYN,RST -j BADTCP_LOG
-       iptables -A BADTCP -p tcp --tcp-flags FIN,RST FIN,RST -j BADTCP_LOG
-       iptables -A BADTCP -p tcp --tcp-flags ACK,FIN FIN     -j BADTCP_LOG
-       iptables -A BADTCP -p tcp --tcp-flags ACK,PSH PSH     -j BADTCP_LOG
-       iptables -A BADTCP -p tcp --tcp-flags ACK,URG URG     -j BADTCP_LOG
-
-       iptables -A INPUT   -p tcp -j BADTCP
-       iptables -A OUTPUT  -p tcp -j BADTCP
-       iptables -A FORWARD -p tcp -j BADTCP
+function firewall_filter_invalid_packets() {
+       local protocol="${1}"
+       assert isset protocol
+
+       local log_limit="-m limit --limit 5/m --limit-burst 10"
+
+       # Create a chain
+       iptables_chain_create "${protocol}" FILTER_INVALID
+       iptables "${protocol}" -A INPUT   -j FILTER_INVALID
+       iptables "${protocol}" -A OUTPUT  -j FILTER_INVALID
+       iptables "${protocol}" -A FORWARD -j FILTER_INVALID
+
+       # Create a chain where only TCP packets go
+       iptables_chain_create "${protocol}" FILTER_INVALID_TCP
+       iptables "${protocol}" -A FILTER_INVALID -p tcp -j FILTER_INVALID_TCP
+
+       # Create a chain where only UDP packets go
+       iptables_chain_create "${protocol}" FILTER_INVALID_UDP
+       iptables "${protocol}" -A FILTER_INVALID -p udp -j FILTER_INVALID_UDP
+
+       # Create a chain where only ICMP packets go
+       iptables_chain_create "${protocol}" FILTER_INVALID_ICMP
+       iptables "${protocol}" -A FILTER_INVALID -p icmp -j FILTER_INVALID_ICMP
+
+
+       # Optionally log all port scans
+
+       if enabled FIREWALL_LOG_STEALTH_SCANS; then
+               log INFO "Logging of stealth scans enabled"
+
+               # NMAP FIN/URG/PSH - XMAS scan
+               iptables "${protocol}" -A FILTER_INVALID_TCP -p tcp --tcp-flags ALL FIN,URG,PSH \
+                       "${log_limit}" -j "$(iptables_LOG "Stealth XMAS scan")"
+
+               # SYN/RST/ACK/FIN/URG
+               iptables "${protocol}" -A FILTER_INVALID_TCP -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG \
+                       "${log_limit}" -j "$(iptables_LOG "Stealth XMAS-PSH scan")"
+
+               # ALL/ALL
+               iptables "${protocol}" -A FILTER_INVALID_TCP -p tcp --tcp-flags ALL ALL \
+                       "${log_limit}" -j "$(iptables_LOG "Stealth XMAS-ALL scan")"
+
+               # NMAP FIN Stealth
+               iptables "${protocol}" -A FILTER_INVALID_TCP -p tcp --tcp-flags ALL FIN \
+                       "${log_limit}" -j "$(iptables_LOG "Stealth FIN scan")"
+
+               # SYN/RST
+               iptables "${protocol}" -A FILTER_INVALID_TCP -p tcp --tcp-flags SYN,RST SYN,RST \
+                       "${log_limit}" -j "$(iptables_LOG "Stealth SYN/RST scan")"
+
+               # SYN/FIN
+               iptables "${protocol}" -A FILTER_INVALID_TCP -p tcp --tcp-flags SYN,FIN SYN,FIN \
+                       "${log_limit}" -j "$(iptables_LOG "Stealth SYN/FIN scan")"
+
+               # Null scan
+               iptables "${protocol}" -A FILTER_INVALID_TCP -p tcp --tcp-flags ALL NONE \
+                       "${log_limit}" -j "$(iptables_LOG "Stealth NULL scan")"
+       else
+               log INFO "Logging of stealth scans disabled"
+       fi
+
+
+       # Drop scan packets
+
+       # NMAP FIN/URG/PSH
+       iptables "${protocol}" -A FILTER_INVALID_TCP -p tcp --tcp-flags ALL FIN,URG,PSH -j DROP
+
+       # SYN/RST/ACK/FIN/URG
+       iptables "${protocol}" -A FILTER_INVALID_TCP -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP
+
+       # ALL/ALL
+       iptables "${protocol}" -A FILTER_INVALID_TCP -p tcp --tcp-flags ALL ALL -j DROP
+
+       # NMAP FIN Stealth
+       iptables "${protocol}" -A FILTER_INVALID_TCP -p tcp --tcp-flags ALL FIN -j DROP
+
+       # SYN/RST
+       iptables "${protocol}" -A FILTER_INVALID_TCP -p tcp --tcp-flags SYN,RST SYN,RST -j DROP
+
+       # SYN/FIN
+       iptables "${protocol}" -A FILTER_INVALID_TCP -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
+
+       # Null scan
+       iptables "${protocol}" -A FILTER_INVALID_TCP -p tcp --tcp-flags ALL NONE -j DROP
+
+
+       # Log packets with bad flags
+
+       if enabled FIREWALL_LOG_BAD_TCP_FLAGS; then
+               log INFO "Logging of packets with bad TCP flags enabled"
+
+               # Option 64
+               iptables "${protocol}" -A FILTER_INVALID_TCP -p tcp --tcp-option 64 \
+                       "${log_limit}" -j "$(iptables_LOG "Bad TCP flag(64)")"
+
+               # Option 128
+               iptables "${protocol}" -A FILTER_INVALID_TCP -p tcp --tcp-option 128 \
+                       "${log_limit}" -j "$(iptables_LOG "Bad TCP flag(128)")"
+       else
+               log INFO "Logging of packets with bad TCP flags disabled"
+       fi
+
+       # Drop packets with bad flags
+
+       iptables "${protocol}" -A FILTER_INVALID_TCP -p tcp --tcp-option 64 -j DROP
+       iptables "${protocol}" -A FILTER_INVALID_TCP -p tcp --tcp-option 128 -j DROP
+
+
+       # Log invalid packets
+
+       if enabled FIREWALL_LOG_INVALID_TCP; then
+               log INFO "Logging of INVALID TCP packets enabled"
+
+               iptables "${protocol}" -A FILTER_INVALID_TCP -p tcp -m conntrack --ctstate INVALID \
+                       "${log_limit}" -j "$(iptables_LOG "INVALID TCP")"
+       else
+               log INFO "Logging of INVALID TCP packets disabled"
+       fi
+
+       if enabled FIREWALL_LOG_INVALID_UDP; then
+               log INFO "Logging of INVALID UDP packets enabled"
+
+               iptables "${protocol}" -A FILTER_INVALID_UDP -p udp -m conntrack --ctstate INVALID \
+                       "${log_limit}" -j "$(iptables_LOG "INVALID UDP")"
+       else
+               log INFO "Logging of INVALID UDP packets disabled"
+       fi
+
+       if enabled FIREWALL_LOG_INVALID_ICMP; then
+               log INFO "Logging of INVALID ICMP packets enabled"
+
+               iptables "${protocol}" -A FILTER_INVALID_ICMP -p icmp -m conntrack --ctstate INVALID \
+                       "${log_limit}" -j "$(iptables_LOG "INVALID ICMP")"
+       else
+               log INFO "Logging of INVALID ICMP packets disabled"
+       fi
+
+       # Drop all INVALID packets
+       iptables "${protocol}" -A FILTER_INVALID -m conntrack --ctstate INVALID -j DROP
 }
 
 function firewall_tcp_clamp_mss() {
        # Do nothing if this has been disabled.
        enabled FIREWALL_CLAMP_PATH_MTU || return ${EXIT_OK}
 
+       local protocol="${1}"
+       assert isset protocol
+
        log DEBUG "Adding rules to clamp MSS to path MTU..."
-       iptables -t mangle -A FORWARD \
+
+       iptables "${protocol}" -t mangle -A FORWARD \
                -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
 }
 
 function firewall_connection_tracking() {
+       local protocol="${1}"
+       assert isset protocol
+
        log INFO "Creating Connection Tracking chain..."
-       iptables_chain_create CONNTRACK
-       iptables -A CONNTRACK -m state --state ESTABLISHED,RELATED -j ACCEPT
-       iptables -A CONNTRACK -m state --state INVALID -j $(iptables_LOG "INVALID packet: ")
-       iptables -A CONNTRACK -m state --state INVALID -j DROP
-
-       iptables -A INPUT   -j CONNTRACK
-       iptables -A OUTPUT  -j CONNTRACK
-       iptables -A FORWARD -j CONNTRACK
+
+       iptables_chain_create "${protocol}" CONNTRACK
+       iptables "${protocol}" -A CONNTRACK -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
+       iptables "${protocol}" -A CONNTRACK -m conntrack --ctstate INVALID -j "$(iptables_LOG "INVALID packet: ")"
+       iptables "${protocol}" -A CONNTRACK -m conntrack --ctstate INVALID -j DROP
+
+       iptables "${protocol}" -A INPUT   -j CONNTRACK
+       iptables "${protocol}" -A OUTPUT  -j CONNTRACK
+       iptables "${protocol}" -A FORWARD -j CONNTRACK
 }
 
 function firewall_localhost_create_chains() {
+       local protocol="${1}"
+       assert isset protocol
+
        log DEBUG "Creating firewall chains for localhost..."
 
        # Accept everything on lo
-       iptables -A INPUT  -i lo -m state --state NEW -j ACCEPT
-       iptables -A OUTPUT -o lo -m state --state NEW -j ACCEPT
+       iptables "${protocol}" -A INPUT  -i lo -j ACCEPT
+       iptables "${protocol}" -A OUTPUT -o lo -j ACCEPT
+}
+
+function firewall_filter_rh0_headers() {
+       local protocol="${1}"
+       assert isset protocol
+
+       # Only IPv6.
+       [ "${protocol}" = "ipv6" ] || return ${EXIT_OK}
+
+       # Filter all packets that have RH0 headers
+       # http://www.ietf.org/rfc/rfc5095.txt
+       iptables_chain_create "${protocol}" FILTER_RH0
+       iptables "${protocol}" -A FILTER_RH0 -m rt --rt-type 0 -j DROP
+
+       iptables "${protocol}" -A INPUT   -j FILTER_RH0
+       iptables "${protocol}" -A FORWARD -j FILTER_RH0
+       iptables "${protocol}" -A OUTPUT  -j FILTER_RH0
+}
+
+function firewall_filter_icmp() {
+       local protocol="${1}"
+       assert isset protocol
+
+       # Only IPv6.
+       [ "${protocol}" = "ipv6" ] || return ${EXIT_OK}
+
+       local chain="FILTER_ICMPV6"
+
+       # Create an extra chain for handling ICMP packets.
+       iptables_chain_create "${protocol}" "${chain}_COMMON"
+
+       local suffix
+       for suffix in INC FWD OUT; do
+               iptables_chain_create "${protocol}" "${chain}_${suffix}"
+               iptables "${protocol}" -A "${chain}_${suffix}" -j "${chain}_COMMON"
+       done
+       iptables "${protocol}" -A INPUT   -p icmpv6 -j "${chain}_INC"
+       iptables "${protocol}" -A FORWARD -p icmpv6 -j "${chain}_FWD"
+       iptables "${protocol}" -A OUTPUT  -p icmpv6 -j "${chain}_OUT"
+
+       # Packets that must always pass the firewall.
+       # Type 4: Parameter Problem
+       local type
+       for type in ttl-zero-during-reassembly bad-header; do
+               iptables "${protocol}" -A "${chain}_COMMON" \
+                       -p icmpv6 --icmpv6-type "${type}" -j ACCEPT
+       done
+
+       # Packets that are accepted if they belong to an existing connection.
+       for type in echo-reply destination-unreachable packet-too-big \
+                       unknown-header-type unknown-option; do
+               iptables "${protocol}" -A "${chain}_COMMON" \
+                       -m conntrack --ctstate ESTABLISHED,RELATED \
+                       -p icmpv6 --icmpv6-type "${type}" -j ACCEPT
+       done
+
+       # Packets that are always discarded.
+       # Type 100, 101, 200, 201: Private Experimentation
+       for type in 100 101 200 201; do
+               iptables "${protocol}" -A "${chain}_COMMON" \
+                       -p icmpv6 --icmpv6-type "${type}" -j DROP
+       done
+
+       # Discard packets from local networks with hop limit smaller than $hoplimit.
+       # Type 148: Path solicitation
+       # Type 149: Path advertisement
+       local hoplimit=255
+       for type in {router,neighbour}-{advertisement,solicitation} 148 149; do
+               iptables "${protocol}" -A "${chain}_INC" \
+                       -p icmpv6 --icmpv6-type "${type}" \
+                       -m hl --hl-lt "${hoplimit}" -j DROP
+       done
+
+       # The firewall is always allowed to send ICMP echo requests.
+       iptables "${protocol}" -A "${chain}_OUT" \
+               -p icmpv6 --icmpv6-type echo-request -j ACCEPT
+
+       return ${EXIT_OK}
 }
 
 function firewall_zone_create_chains() {
-       local zone=${1}
+       local protocol="${1}"
+       assert isset protocol
+
+       local zone="${2}"
        assert isset zone
 
        log DEBUG "Creating firewall chains for zone '${zone}'."
@@ -224,17 +594,17 @@ function firewall_zone_create_chains() {
        local chain_prefix="ZONE_${zone^^}"
 
        # Create filter chains.
-       iptables_chain_create "${chain_prefix}_INPUT"
-       iptables -A INPUT   -i ${zone} -j "${chain_prefix}_INPUT"
+       iptables_chain_create "${protocol}" "${chain_prefix}_INPUT"
+       iptables "${protocol}" -A INPUT   -i ${zone} -j "${chain_prefix}_INPUT"
 
-       iptables_chain_create "${chain_prefix}_OUTPUT"
-       iptables -A OUTPUT  -o ${zone} -j "${chain_prefix}_OUTPUT"
+       iptables_chain_create "${protocol}" "${chain_prefix}_OUTPUT"
+       iptables "${protocol}" -A OUTPUT  -o ${zone} -j "${chain_prefix}_OUTPUT"
 
        # Custom rules.
-       iptables_chain_create "${chain_prefix}_CUSTOM"
+       iptables_chain_create "${protocol}" "${chain_prefix}_CUSTOM"
 
        # Intrusion Prevention System.
-       iptables_chain_create "${chain_prefix}_IPS"
+       iptables_chain_create "${protocol}" "${chain_prefix}_IPS"
 
        # Create a chain for each other zone.
        # This leaves us with n^2 chains. Duh.
@@ -242,52 +612,52 @@ function firewall_zone_create_chains() {
        local other_zone other_chain_prefix
        for other_zone in $(zones_get_all); do
                other_chain_prefix="${chain_prefix}_${other_zone^^}"
-               iptables_chain_create ${other_chain_prefix}
+               iptables_chain_create "${protocol}" "${other_chain_prefix}"
 
                # Connect the chain with the FORWARD chain.
-               iptables -A FORWARD -i ${zone} -o ${other_zone} \
+               iptables "${protocol}" -A FORWARD -i "${zone}" -o "${other_zone}" \
                        -j "${other_chain_prefix}"
 
                # Handle custom rules.
-               iptables -A ${other_chain_prefix} -j "${chain_prefix}_CUSTOM"
+               iptables "${protocol}" -A "${other_chain_prefix}" -j "${chain_prefix}_CUSTOM"
 
                # Link IPS.
-               iptables -A ${other_chain_prefix} -j "${chain_prefix}_IPS"
+               iptables "${protocol}" -A "${other_chain_prefix}" -j "${chain_prefix}_IPS"
 
                # Rules.
-               iptables_chain_create "${other_chain_prefix}_RULES"
-               iptables -A ${other_chain_prefix} -j "${other_chain_prefix}_RULES"
+               iptables_chain_create "${protocol}" "${other_chain_prefix}_RULES"
+               iptables "${protocol}" -A "${other_chain_prefix}" -j "${other_chain_prefix}_RULES"
 
                # Policy.
-               iptables_chain_create "${other_chain_prefix}_POLICY"
-               iptables -A ${other_chain_prefix} -j "${other_chain_prefix}_POLICY"
+               iptables_chain_create "${protocol}" "${other_chain_prefix}_POLICY"
+               iptables "${protocol}" -A "${other_chain_prefix}" -j "${other_chain_prefix}_POLICY"
        done
 
        ## Create mangle chain.
-       #iptables_chain_create -t mangle ${chain_prefix}
-       #iptables -t mangle -A PREROUTING  -i ${zone} -j ${chain_prefix}
-       #iptables -t mangle -A POSTROUTING -o ${zone} -j ${chain_prefix}
+       #iptables_chain_create "${protocol}" -t mangle "${chain_prefix}"
+       #iptables "${protocol}" -t mangle -A PREROUTING  -i "${zone}" -j "${chain_prefix}"
+       #iptables "${protocol}" -t mangle -A POSTROUTING -o "${zone}" -j "${chain_prefix}"
 
        ## Quality of Service
-       #iptables_chain_create -t mangle "${chain_prefix}_QOS_INC"
-       #iptables -t mangle -A ${chain_prefix} -i ${zone} -j "${chain_prefix}_QOS_INC"
-       #iptables_chain_create -t mangle "${chain_prefix}_QOS_OUT"
-       #iptables -t mangle -A ${chain_prefix} -o ${zone} -j "${chain_prefix}_QOS_OUT"
+       #iptables_chain_create "${protocol}" -t mangle "${chain_prefix}_QOS_INC"
+       #iptables "${protocol}" -t mangle -A "${chain_prefix}" -i "${zone}" -j "${chain_prefix}_QOS_INC"
+       #iptables_chain_create "${protocol}" -t mangle "${chain_prefix}_QOS_OUT"
+       #iptables "${protocol}" -t mangle -A "${chain_prefix}" -o "${zone}" -j "${chain_prefix}_QOS_OUT"
 
        # Create NAT chain.
-       iptables_chain_create -4 -t nat ${chain_prefix}
-       iptables -4 -t nat -A PREROUTING  -i ${zone} -j ${chain_prefix}
-       iptables -4 -t nat -A POSTROUTING -o ${zone} -j ${chain_prefix}
+       iptables_chain_create "${protocol}" -t nat "${chain_prefix}"
+       iptables "${protocol}" -t nat -A PREROUTING  -i "${zone}" -j "${chain_prefix}"
+       iptables "${protocol}" -t nat -A POSTROUTING -o "${zone}" -j "${chain_prefix}"
 
        # Network Address Translation
-       iptables_chain_create -4 -t nat "${chain_prefix}_DNAT"
-       iptables -4 -t nat -A PREROUTING  -i ${zone} -j "${chain_prefix}_DNAT"
-       iptables_chain_create -4 -t nat "${chain_prefix}_SNAT"
-       iptables -4 -t nat -A POSTROUTING -o ${zone} -j "${chain_prefix}_SNAT"
+       iptables_chain_create "${protocol}" -t nat "${chain_prefix}_DNAT"
+       iptables "${protocol}" -t nat -A PREROUTING  -i "${zone}" -j "${chain_prefix}_DNAT"
+       iptables_chain_create "${protocol}" -t nat "${chain_prefix}_SNAT"
+       iptables "${protocol}" -t nat -A POSTROUTING -o "${zone}" -j "${chain_prefix}_SNAT"
 
        # UPnP
-       iptables_chain_create -4 -t nat "${chain_prefix}_UPNP"
-       iptables -4 -t nat -A ${chain_prefix} -j "${chain_prefix}_UPNP"
+       iptables_chain_create "${protocol}" -t nat "${chain_prefix}_UPNP"
+       iptables "${protocol}" -t nat -A "${chain_prefix}" -j "${chain_prefix}_UPNP"
 
        return ${EXIT_OK}
 }