diff --git a/conf/ban_ipv6.list.sample b/conf/ban_ipv6.list.sample index bbc3a73..5719910 100644 --- a/conf/ban_ipv6.list.sample +++ b/conf/ban_ipv6.list.sample @@ -10,8 +10,10 @@ # - # - # - Note: -# - - If no mask is given mask will be set to '64' +# - - If no mask is given mask will be set to 'ban_ipv6_mask' (main_ipv6.conf), +# - default is '128' # - - wrong addresses like '2g01::1' or '2a01::1/129' will be ignored +# - - entries covering an own (global) address of this host will be ignored # - # - Example: # - 240e:ec:4ab1:feba:e8b4:4fb1:7984:4c diff --git a/conf/default_settings.conf b/conf/default_settings.conf index b138d15..43f7656 100644 --- a/conf/default_settings.conf +++ b/conf/default_settings.conf @@ -90,6 +90,11 @@ default_max_concurrent_http_conn_per_ip=50 set_max_new_http_conn_per_second=true default_max_new_http_conn_per_second=200 +# ip6t-firewall-server: +# Prefix length for entries in 'ban_ipv6.list' given without mask. +# +default_ban_ipv6_mask=128 + # - Dovecot Service # - diff --git a/conf/include_functions.conf b/conf/include_functions.conf index ae7182c..8c15646 100644 --- a/conf/include_functions.conf +++ b/conf/include_functions.conf @@ -73,6 +73,54 @@ containsElement () { return 1 } +## - Expand a given IPv6 address (parameter 1) to 32 hex digits without colons. +## - Returns 1 if the address cannot be parsed. +## - +ipv6_expand() { + local _addr="${1,,}" + local _head _tail _group _n _out="" + local -a _h=() _t=() + + if [[ "$_addr" == *::* ]] ; then + _head="${_addr%%::*}" + _tail="${_addr#*::}" + [[ "$_tail" == *::* ]] && return 1 + [[ -n "$_head" ]] && IFS=':' read -ra _h <<< "$_head" + [[ -n "$_tail" ]] && IFS=':' read -ra _t <<< "$_tail" + _n=$(( 8 - ${#_h[@]} - ${#_t[@]} )) + [[ $_n -lt 1 ]] && return 1 + for (( ; _n > 0; _n-- )) ; do + _h+=("0") + done + _h+=("${_t[@]}") + else + IFS=':' read -ra _h <<< "$_addr" + fi + + [[ ${#_h[@]} -eq 8 ]] || return 1 + for _group in "${_h[@]}" ; do + [[ "$_group" =~ ^[0-9a-f]{1,4}$ ]] || return 1 + _out+="$(printf '%04x' "0x$_group")" + done + + echo "$_out" +} + +## - Check if a given IPv6 address (parameter 1) lies within the network +## - given by address (parameter 2) and prefix length (parameter 3). +## - +ipv6_in_net() { + local _a _n _full _rest + _a="$(ipv6_expand "$1")" || return 1 + _n="$(ipv6_expand "$2")" || return 1 + _full=$(( $3 / 4 )) + _rest=$(( $3 % 4 )) + + [[ "${_a:0:_full}" == "${_n:0:_full}" ]] || return 1 + [[ $_rest -eq 0 ]] && return 0 + (( (0x${_a:_full:1} >> (4 - _rest)) == (0x${_n:_full:1} >> (4 - _rest)) )) +} + is_number() { return $(test ! -z "${1##*[!0-9]*}" > /dev/null 2>&1); diff --git a/conf/main_ipv6.conf.sample b/conf/main_ipv6.conf.sample index 4906593..fa36602 100644 --- a/conf/main_ipv6.conf.sample +++ b/conf/main_ipv6.conf.sample @@ -753,6 +753,10 @@ portforward_udp="" blocked_ips="" +# - Prefix length for entries in 'ban_ipv6.list' given without mask +# - +ban_ipv6_mask=${default_ban_ipv6_mask} + # ------------- # --- Block Ports diff --git a/ip6t-firewall-server b/ip6t-firewall-server index b5b425e..1b9ea02 100755 --- a/ip6t-firewall-server +++ b/ip6t-firewall-server @@ -448,6 +448,29 @@ fi +# --- +# - ICMPv6 Neighbor Discovery and Packet Too Big +# --- +# Accept these before any rule blocking by source address (blocked_ips, +# ban_ipv6.list). Otherwise a blocked network containing a router of the +# own link breaks neighbor discovery - the router can no longer reach this host. +# +# 2 = packet-too-big +# 133= router-solicitation +# 134= router-advertisement +# 135= neighbor-solicitation +# 136= neighbor-advertisement +# --- +echononl "\tICMPv6 neighbor discovery / packet-too-big (before blocking rules).." +for t in 2 133 134 135 136 ; do + $ip6t -A INPUT -p ipv6-icmp --icmpv6-type $t -j ACCEPT +done +if $kernel_forward_between_interfaces ; then + $ip6t -A FORWARD -p ipv6-icmp --icmpv6-type 2 -j ACCEPT +fi +echo_done + + # ------------- # --- Block IPs / Networks / Interfaces # ------------- @@ -509,6 +532,19 @@ if [[ -f "$conf_ban_ipv6_list" ]] ; then declare -a ban_ipv6_arr=() declare -a no_valid_ipv6=() + declare -a own_ipv6_banned_arr=() + + if [[ -z "$ban_ipv6_mask" || ! "$ban_ipv6_mask" =~ ^[0-9]+$ ]] || [[ $ban_ipv6_mask -gt 128 ]] ; then + ban_ipv6_mask=${default_ban_ipv6_mask:-128} + fi + + # Own global IPv6 addresses (read at runtime, also covers SLAAC addresses). + # Entries covering one of them would block the own prefix including its routers. + # + declare -a own_ipv6_arr=() + for _own_ip in $(ip -6 -o addr show scope global | awk '{split($4, a, "/"); print a[1]}') ; do + own_ipv6_arr+=("$_own_ip") + done # Regex valid ipv6 address # @@ -568,7 +604,7 @@ if [[ -f "$conf_ban_ipv6_list" ]] ; then fi fi else - mask=64 + mask=$ban_ipv6_mask is_valid_mask=true fi @@ -586,6 +622,22 @@ if [[ -f "$conf_ban_ipv6_list" ]] ; then continue fi + # Never ban a network containing an own address + # + _covers_own_ip=false + for _own_ip in "${own_ipv6_arr[@]}" ; do + if ipv6_in_net "$_own_ip" "$ipv6" "$mask" ; then + _covers_own_ip=true + break + fi + done + if $_covers_own_ip ; then + if ! containsElement "$_ip" "${own_ipv6_banned_arr[@]}" ; then + own_ipv6_banned_arr+=("$_ip") + fi + continue + fi + for _dev in ${ext_if_arr[@]} ; do if $log_blocked_ip || $log_all ; then $ip6t -A INPUT -i $_dev -s $_ip -j $LOG_TARGET $tag_log_prefix "$log_prefix Blocked by ban_ipv6.list: " @@ -614,6 +666,9 @@ if [[ -f "$conf_ban_ipv6_list" ]] ; then if [[ ${#no_valid_ipv6_arr[@]} -gt 0 ]]; then warn "Ignored: ${no_valid_ipv6_arr[@]}" fi + if [[ ${#own_ipv6_banned_arr[@]} -gt 0 ]]; then + warn "Ignored (covers own address): ${own_ipv6_banned_arr[@]}" + fi else echo_skipped fi