Enhance IPv6 ban functionality: add dynamic mask handling and prevent banning own addresses

This commit is contained in:
2026-10-27 22:26:12 +01:00
parent fbe10b43d0
commit f6660709ce
5 changed files with 116 additions and 2 deletions
+3 -1
View File
@@ -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
+5
View File
@@ -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
# -
+48
View File
@@ -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);
+4
View File
@@ -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
+56 -1
View File
@@ -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