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: Reported
Type: Technical
Publication Format(s) : TEXT

Reported By: William N.
Date Reported: 2024-05-03

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.

P.S. Ideally, this RFC would need an nftables version of this too.

Report New Errata



Advanced Search