RFC Errata
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.