RFC Errata


Errata Search

 
Source of RFC  
Summary Table Full Records

RFC 4890, "Recommendations for Filtering ICMPv6 Messages in Firewalls", May 2007

Source of RFC: v6ops (ops)

Errata ID: 7922
Status: Held for Document Update
Type: Technical
Publication Format(s) : TEXT

Reported By: William N.
Date Reported: 2024-05-03
Held for Document Update by: Mohamed Boucadair
Date Held: 2025-06-02

Section Appendix B says:

#!/bin/bash
# Set of prefixes on the trusted ("inner") side of the firewall
export INNER_PREFIXES="2001:DB8:85::/60"
# Set of hosts providing services so that they can be made pingable
export PINGABLE_HOSTS="2001:DB8:85::/64"
# Configuration option: Change this to 1 if errors allowed only for
# existing sessions
export STATE_ENABLED=0
# Configuration option: Change this to 1 if messages to/from link
# local addresses should be filtered.
# Do not use this if the firewall is a bridge.
# Optional for firewalls that are routers.
export FILTER_LINK_LOCAL_ADDRS=0
# Configuration option: Change this to 0 if the site does not support
# Mobile IPv6 Home Agents - see Appendix A.14
export HOME_AGENTS_PRESENT=1
# Configuration option: Change this to 0 if the site does not support
# Mobile IPv6 mobile nodes being present on the site -
# see Appendix A.14
export MOBILE_NODES_PRESENT=1

ip6tables -N icmpv6-filter
ip6tables -A FORWARD -p icmpv6 -j icmpv6-filter

# Match scope of src and dest else deny
# This capability is not provided for in base ip6tables functionality
# An extension (agr) exists which may support it.
#@TODO@
# ECHO REQUESTS AND RESPONSES
# ===========================

# Allow outbound echo requests from prefixes which belong to the site
for inner_prefix in $INNER_PREFIXES
do
  ip6tables -A icmpv6-filter -p icmpv6 -s $inner_prefix \
        --icmpv6-type echo-request -j ACCEPT
done

# Allow inbound echo requests towards only predetermined hosts
for pingable_host in $PINGABLE_HOSTS
do
  ip6tables -A icmpv6-filter -p icmpv6 -d $pingable_host \
        --icmpv6-type echo-request -j ACCEPT
done

if [ "$STATE_ENABLED" -eq "1" ]
then
  # Allow incoming and outgoing echo reply messages
  # only for existing sessions
  ip6tables -A icmpv6-filter -m state -p icmpv6 \
        --state ESTABLISHED,RELATED --icmpv6-type \
      echo-reply -j ACCEPT
else
  # Allow both incoming and outgoing echo replies
  for pingable_host in $PINGABLE_HOSTS
  do
    # Outgoing echo replies from pingable hosts
    ip6tables -A icmpv6-filter -p icmpv6 -s $pingable_host \
        --icmpv6-type echo-reply -j ACCEPT
  done
  # Incoming echo replies to prefixes which belong to the site
  for inner_prefix in $INNER_PREFIXES
  do
    ip6tables -A icmpv6-filter -p icmpv6 -d $inner_prefix \
        --icmpv6-type echo-reply -j ACCEPT
  done
fi

# Deny icmps to/from link local addresses
# If the firewall is a router:
#    These rules should be redundant as routers should not forward
#    link local addresses but to be sure...
# DO NOT ENABLE these rules if the firewall is a bridge
if [ "$FILTER_LINK_LOCAL_ADDRS" -eq "1" ]
then
  ip6tables -A icmpv6-filter -p icmpv6 -d fe80::/10 -j DROP
  ip6tables -A icmpv6-filter -p icmpv6 -s fe80::/10 -j DROP
fi

# Drop echo replies which have a multicast address as a
# destination
ip6tables -A icmpv6-filter -p icmpv6 -d ff00::/8 \
        --icmpv6-type echo-reply -j DROP

# DESTINATION UNREACHABLE ERROR MESSAGES
# ======================================

if [ "$STATE_ENABLED" -eq "1" ]
then
  # Allow incoming destination unreachable messages
  # only for existing sessions
  for inner_prefix in $INNER_PREFIXES
  do
    ip6tables -A icmpv6-filter -m state -p icmpv6 \
         -d $inner_prefix \
         --state ESTABLISHED,RELATED --icmpv6-type \
         destination-unreachable -j ACCEPT
  done
else
  # Allow incoming destination unreachable messages
  for inner_prefix in $INNER_PREFIXES
  do
    ip6tables -A icmpv6-filter -p icmpv6 -d $inner_prefix \
         --icmpv6-type destination-unreachable -j ACCEPT
  done
fi

# Allow outgoing destination unreachable messages
for inner_prefix in $INNER_PREFIXES
do
  ip6tables -A icmpv6-filter -p icmpv6 -s $inner_prefix \
         --icmpv6-type destination-unreachable -j ACCEPT
done

# PACKET TOO BIG ERROR MESSAGES
# =============================

if [ "$STATE_ENABLED" -eq "1" ]
then
  # Allow incoming Packet Too Big messages
  # only for existing sessions
  for inner_prefix in $INNER_PREFIXES
  do
    ip6tables -A icmpv6-filter -m state -p icmpv6 \
         -d $inner_prefix \
         --state ESTABLISHED,RELATED \
         --icmpv6-type packet-too-big \
         -j ACCEPT
  done
else
  # Allow incoming Packet Too Big messages
  for inner_prefix in $INNER_PREFIXES
  do
    ip6tables -A icmpv6-filter -p icmpv6 -d $inner_prefix \
         --icmpv6-type packet-too-big -j ACCEPT
  done
fi

# Allow outgoing Packet Too Big messages
for inner_prefix in $INNER_PREFIXES
do
  ip6tables -A icmpv6-filter -p icmpv6 -s $inner_prefix \
         --icmpv6-type packet-too-big -j ACCEPT
done

# TIME EXCEEDED ERROR MESSAGES
# ============================

if [ "$STATE_ENABLED" -eq "1" ]
then
  # Allow incoming time exceeded code 0 messages
  # only for existing sessions
  for inner_prefix in $INNER_PREFIXES
  do
    ip6tables -A icmpv6-filter -m state -p icmpv6 \
         -d $inner_prefix \
         --state ESTABLISHED,RELATED --icmpv6-type packet-too-big \
         -j ACCEPT
  done
else
  # Allow incoming time exceeded code 0 messages
  for inner_prefix in $INNER_PREFIXES
  do
    ip6tables -A icmpv6-filter -p icmpv6 -d $inner_prefix \
         --icmpv6-type ttl-zero-during-transit -j ACCEPT
  done
fi

#@POLICY@
# Allow incoming time exceeded code 1 messages
for inner_prefix in $INNER_PREFIXES
do
ip6tables -A icmpv6-filter -p icmpv6 -d $inner_prefix \
         --icmpv6-type ttl-zero-during-reassembly -j ACCEPT
done

# Allow outgoing time exceeded code 0 messages
for inner_prefix in $INNER_PREFIXES
do
ip6tables -A icmpv6-filter -p icmpv6 -s $inner_prefix \
         --icmpv6-type ttl-zero-during-transit -j ACCEPT
done

#@POLICY@
# Allow outgoing time exceeded code 1 messages
for inner_prefix in $INNER_PREFIXES
do
ip6tables -A icmpv6-filter -p icmpv6 -s $inner_prefix \
         --icmpv6-type ttl-zero-during-reassembly -j ACCEPT
done

# PARAMETER PROBLEM ERROR MESSAGES
# ================================

if [ "$STATE_ENABLED" -eq "1" ]
then
  # Allow incoming parameter problem code 1 and 2 messages
  # for an existing session
  for inner_prefix in $INNER_PREFIXES
  do
    ip6tables -A icmpv6-filter -m state -p icmpv6 \
         -d $inner_prefix \
         --state ESTABLISHED,RELATED --icmpv6-type \
         unknown-header-type \
         -j ACCEPT
    ip6tables -A icmpv6-filter -m state -p icmpv6 \
         -d $inner_prefix \
         --state ESTABLISHED,RELATED \
         --icmpv6-type unknown-option \
         -j ACCEPT
  done
fi

# Allow outgoing parameter problem code 1 and code 2 messages
for inner_prefix in $INNER_PREFIXES
do
  ip6tables -A icmpv6-filter -p icmpv6 -s $inner_prefix \
         --icmpv6-type unknown-header-type -j ACCEPT
  ip6tables -A icmpv6-filter -p icmpv6 -s $inner_prefix \
         --icmpv6-type unknown-option -j ACCEPT
done

#@POLICY@
# Allow incoming and outgoing parameter
# problem code 0 messages
for inner_prefix in $INNER_PREFIXES
do
  ip6tables -A icmpv6-filter -p icmpv6 \
         --icmpv6-type bad-header \
         -j ACCEPT
done

# NEIGHBOR DISCOVERY MESSAGES
# ===========================

# Drop NS/NA messages both incoming and outgoing
ip6tables -A icmpv6-filter -p icmpv6 \
         --icmpv6-type neighbor-solicitation -j DROP
ip6tables -A icmpv6-filter -p icmpv6 \
         --icmpv6-type neighbor-advertisement -j DROP

# Drop RS/RA messages both incoming and outgoing
ip6tables -A icmpv6-filter -p icmpv6 \
         --icmpv6-type router-solicitation -j DROP
ip6tables -A icmpv6-filter -p icmpv6 \
         --icmpv6-type router-advertisement -j DROP

# Drop Redirect messages both incoming and outgoing
ip6tables -A icmpv6-filter -p icmpv6 --icmpv6-type redirect -j DROP

# MLD MESSAGES
# ============

# Drop incoming and outgoing
# Multicast Listener queries (MLDv1 and MLDv2)
ip6tables -A icmpv6-filter -p icmpv6 --icmpv6-type 130 -j DROP

# Drop incoming and outgoing Multicast Listener reports (MLDv1)
ip6tables -A icmpv6-filter -p icmpv6 --icmpv6-type 131 -j DROP

# Drop incoming and outgoing Multicast Listener Done messages (MLDv1)
ip6tables -A icmpv6-filter -p icmpv6 --icmpv6-type 132 -j DROP

# Drop incoming and outgoing Multicast Listener reports (MLDv2)
ip6tables -A icmpv6-filter -p icmpv6 --icmpv6-type 143 -j DROP

# ROUTER RENUMBERING MESSAGES
# ===========================

# Drop router renumbering messages
ip6tables -A icmpv6-filter -p icmpv6 --icmpv6-type 138 -j DROP

# NODE INFORMATION QUERIES
# ========================

# Drop node information queries (139) and replies (140)
ip6tables -A icmpv6-filter -p icmpv6 --icmpv6-type 139 -j DROP
ip6tables -A icmpv6-filter -p icmpv6 --icmpv6-type 140 -j DROP

# MOBILE IPv6 MESSAGES
# ====================

# If there are mobile ipv6 home agents present on the
# trusted side allow
if [ "$HOME_AGENTS_PRESENT" -eq "1" ]
then
  for inner_prefix in $INNER_PREFIXES
  do
    #incoming Home Agent address discovery request
    ip6tables -A icmpv6-filter -p icmpv6 -d $inner_prefix \
         --icmpv6-type 144 -j ACCEPT
    #outgoing Home Agent address discovery reply
    ip6tables -A icmpv6-filter -p icmpv6 -s $inner_prefix \
         --icmpv6-type 145 -j ACCEPT
    #incoming Mobile prefix solicitation
    ip6tables -A icmpv6-filter -p icmpv6 -d $inner_prefix \
         --icmpv6-type 146 -j ACCEPT
    #outgoing Mobile prefix advertisement
    ip6tables -A icmpv6-filter -p icmpv6 -s $inner_prefix \
         --icmpv6-type 147 -j ACCEPT
  done
fi

# If there are roaming mobile nodes present on the
# trusted side allow
if [ "$MOBILE_NODES_PRESENT" -eq "1" ]
then
  for inner_prefix in $INNER_PREFIXES
  do
    #outgoing Home Agent address discovery request
    ip6tables -A icmpv6-filter -p icmpv6 -s $inner_prefix \
         --icmpv6-type 144 -j ACCEPT
    #incoming Home Agent address discovery reply
    ip6tables -A icmpv6-filter -p icmpv6 -d $inner_prefix \
         --icmpv6-type 145 -j ACCEPT
    #outgoing Mobile prefix solicitation
    ip6tables -A icmpv6-filter -p icmpv6 -s $inner_prefix \
         --icmpv6-type 146 -j ACCEPT
    #incoming Mobile prefix advertisement
    ip6tables -A icmpv6-filter -p icmpv6 -d $inner_prefix \
         --icmpv6-type 147 -j ACCEPT
  done
fi

# DROP EVERYTHING ELSE
# ====================

ip6tables -A icmpv6-filter -p icmpv6 -j DROP

It should say:

#!/bin/bash

# Prefixes on the trusted ("inner") side of the firewall
readonly inner_prefixes=( "2001:db8:85::/60" )

# Hosts providing services so that they can be made pingable
readonly pingable_hosts=( "2001:db8:85::/64" )

# 1 - if errors are allowed only for existing sessions
readonly state_enabled=0

# 1 - if messages to/from link local addresses should be filtered
# Do not use this if the firewall is a bridge.
# Optional for firewalls that are routers.
readonly filter_linklocal_addrs=0

# 0 - if the site does not support Mobile IPv6 Home Agents
# 1 - if there are mobile ipv6 home agents present on the trusted side
# see Appendix A.14
readonly home_agents_present=1

# 0 - if the site does not support Mobile IPv6 mobile nodes
# 1 - if there are roaming mobile nodes present on the trusted side
# see Appendix A.14
readonly mobile_nodes_present=1

ip6tables -N icmpv6-filter
ip6tables -A FORWARD -p icmpv6 -j icmpv6-filter

state_args=(
	'--match' 'state'
	'--state' 'ESTABLISHED,RELATED'
)
(( state_enabled == 0 )) && state_args=()
readonly state_args

# Helper functions
filter() { ip6tables -A icmpv6-filter -p icmpv6	"${@}"; }
accept() { filter "${@}" -j ACCEPT; }
drop()   { filter "${@}" -j DROP; }

# Match scope of src and dest else deny
# This capability is not provided for in base ip6tables functionality
# An extension (agr) exists which may support it.
#@TODO@

# ECHO REQUESTS AND RESPONSES
# ===========================

# Outbound echo requests from prefixes belonging to the site
for inner_prefix in "${inner_prefixes[@]}"; do
	accept \
		-s "${inner_prefix}" \
		--icmpv6-type echo-request
done

# Inbound echo requests only towards predetermined hosts
for pingable_host in "${pingable_hosts[@]}"; do
	accept \
		-d "${pingable_host}" \
		--icmpv6-type echo-request
done

if (( state_enabled == 1 )); then
	# Incoming and outgoing messages
	# only for existing sessions
	accept \
		"${state_args[@]}" \
		--icmpv6-type echo-reply
else
	# Both incoming and outgoing echo replies
	for pingable_host in "${pingable_hosts[@]}"; do
		# Outgoing echo replies from pingable hosts
		accept \
			-s "${pingable_host}" \
			--icmpv6-type echo-reply
	done

	# Incoming echo replies to prefixes belonging to the site
	for inner_prefix in "${inner_prefixes[@]}"; do
		accept \
			-d "${inner_prefix}" \
			--icmpv6-type echo-reply
	done
fi

# Deny icmps to/from link local addresses
# If the firewall is a router:
#    These rules should be redundant as routers should not forward
#    link local addresses but to be sure...
# DO NOT ENABLE these rules if the firewall is a bridge
if (( filter_linklocal_addrs == 1 )); then
	drop -d fe80::/10
	drop -s fe80::/10
fi

# No echo replies for multicast destination addresses
drop \
	-d ff00::/8 \
	--icmpv6-type echo-reply

for inner_prefix in "${inner_prefixes[@]}"; do
	# DESTINATION UNREACHABLE ERROR MESSAGES
	# ======================================

	# incoming
	accept \
		-d "${inner_prefix}" \
		"${state_args[@]}" \
		--icmpv6-type destination-unreachable

	# outgoing
	accept \
		-s "${inner_prefix}" \
		--icmpv6-type destination-unreachable

	# PACKET TOO BIG ERROR MESSAGES
	# =============================

	# incoming
	accept \
		-d "${inner_prefix}" \
		"${state_args[@]}" \
		--icmpv6-type packet-too-big

	# outgoing
	accept \
		-s "${inner_prefix}" \
		--icmpv6-type packet-too-big

	# TIME EXCEEDED ERROR MESSAGES
	# ============================

	# incoming w/ code 0
	accept \
		-d "${inner_prefix}" \
		"${state_args[@]}" \
		--icmpv6-type 3/0

	# @POLICY@
	# incoming w/ code 1
	accept \
		-d "${inner_prefix}" \
		--icmpv6-type 3/1

	# outgoing w/ code 0
	accept \
		-s "${inner_prefix}" \
		--icmpv6-type 3/0

	# @POLICY@
	# outgoing w/ code 1
	accept \
		-s "${inner_prefix}" \
		--icmpv6-type 3/1

	# PARAMETER PROBLEM ERROR MESSAGES
	# ================================

	if (( state_enabled == 1 )); then
		# incoming
		accept \
			-d "${inner_prefix}" \
			"${state_args[@]}" \
			--icmpv6-type 4/1

		accept \
			-d "${inner_prefix}" \
			"${state_args[@]}" \
			--icmpv6-type 4/2
	fi

	# outgoing
	accept \
		-s "${inner_prefix}" \
		--icmpv6-type 4/1

	accept \
		-s "${inner_prefix}" \
		--icmpv6-type 4/2

	# @POLICY@
	# incoming and outgoing
	accept --icmpv6-type 4/0
done

# Drop all these, both incoming and outgoing
types=(
	# NEIGHBOR DISCOVERY MESSAGES
	# ===========================
	'135/0' # Neighbor solicitation
	'136/0' # Neighbor advertisement
	'133/0' # Router solicitation
	'134/0' # Router advertisement
	'137/0' # Rredirect'

	# Multicast Listener Discovery messages
	# =====================================
	130 # ML queries (MLDv1 and MLDv2)
	131 # ML reports (MLDv1)
	132 # ML Done messages (MLDv1)
	143 # ML reports (MLDv2)

	138 # Router renumbering messages

	# NODE INFORMATION QUERIES
	# ========================
	139 # Node information queries
	140 # Node information replies
)

for type in "${types[@]}"; do
	drop --icmpv6-type "${type}"
done

# MOBILE IPv6 MESSAGES
# ====================

for inner_prefix in "${inner_prefixes[@]}"; do
	if (( home_agents_present == 1 )); then
		# incoming Home Agent address discovery request
		accept \
			-d "${inner_prefix}" \
			--icmpv6-type 144

		# outgoing Home Agent address discovery reply
		accept \
			-s "${inner_prefix}" \
			--icmpv6-type 145

		# incoming Mobile prefix solicitation
		accept \
			-d "${inner_prefix}" \
			--icmpv6-type 146

		# outgoing Mobile prefix advertisement
		accept \
			-s "${inner_prefix}" \
			--icmpv6-type 147
	fi

	if (( mobile_nodes_present == 1 )); then
		# outgoing Home Agent address discovery request
		accept \
			-s "${inner_prefix}" \
			--icmpv6-type 144

		# incoming Home Agent address discovery reply
		accept \
			-d "${inner_prefix}" \
			--icmpv6-type 145

		# outgoing Mobile prefix solicitation
		accept \
			-s "${inner_prefix}" \
			--icmpv6-type 146

		# incoming Mobile prefix advertisement
		accept \
			-d "${inner_prefix}" \
			--icmpv6-type 147
	fi
done

# DROP EVERYTHING ELSE
# ====================

drop

Notes:

- Fix ShellCheck SC2086 warnings
- Code formatting: improve redability
- Remove unnecessary export statements
- Make uppercase variables lowercase constants
- Make pingable_hosts and inner_prefixes arrays, so that for loops make sense
- Use lowercase IPv6 addresses, as commonly accepted
- Remove useless newlines at the beginning of if-checks and for-loops
- Reduce code repetition by using state_args array
- Combine separate loops in sections
-- DESTINATION UNREACHABLE ERROR MESSAGES
-- PACKET TOO BIG ERROR MESSAGES
-- TIME EXCEEDED ERROR MESSAGES
-- PARAMETER PROBLEM ERROR MESSAGES
- Create filter, accept and drop functions to simplify code
- Reduce code repetition by using type array
- Simplify comments. Comments should explain why something is done, not what is done. If code needs explanation about what it does, it is not readable

NOTE: The original does not seem to address type codes as advised and claimed in the comments. Additionally, there is one pointless loop in the example code. In my errata, I use numerical codes and types to address all this.

Please test the code thoroughly.

=== Verifier note

Many of the changes in this erratum are not about errors, but more a refresh, better readability, simplification, etc. See https://mailarchive.ietf.org/arch/msg/v6ops/RnxLhcrAI4JmF8K7BRiW6xwTtkE/

Report New Errata



Advanced Search