From 93d9af854b518f671297de7113f171a964b7044d Mon Sep 17 00:00:00 2001 From: Christoph Date: Mon, 2 Nov 2026 21:51:16 +0100 Subject: [PATCH] ip6t-firewall-server,ipt-firewall-server: HTTP limits only for local webserver; fix several bugs. - HTTP(S) connection limits are only created if 'http_server_ips' is set. The global SYN rate limit no longer ACCEPTs packets (which opened ports 80/443 on every host) - it now only drops packets above the limit (hashlimit without '--hashlimit-mode'). Ports are opened exclusively by the rules for 'http_server_ips'. - Use '$http_ports' instead of hard coded '80,443' for the limits. - ipt-firewall-server: use '--connlimit-mask 32' instead of 128. With the nf_tables backend mask 128 ended up as mask 0, so the limit applied to all clients together instead of per IP. Rename hashlimit 'http_v6' -> 'http_v4'. - Log rules use their own hashlimit tables ('*_log'), otherwise each packet is counted twice when logging is enabled. - SYN-Overflow log rule now logs packets above (not below) the limit. - ip6t-firewall-server: clamp TCP MSS for HTTPS on all external interfaces instead of hard coded 'eth0'. - Quote test for 'mm_tcp_ports_in' ('[ -n ${mm_tcp_ports_in} ]' was always true). Co-Authored-By: Claude Opus 5.5 --- ip6t-firewall-server | 73 +++++++++++++++++++++++++------------------- ipt-firewall-server | 67 +++++++++++++++++++++++----------------- 2 files changed, 81 insertions(+), 59 deletions(-) diff --git a/ip6t-firewall-server b/ip6t-firewall-server index 383f3c3..9ddd69d 100755 --- a/ip6t-firewall-server +++ b/ip6t-firewall-server @@ -736,10 +736,12 @@ if $throttle_tcp6_mss_for_https ; then tcp6_mss_for_https_val=$min_tcp6_mss_for_https_val fi - $ip6t -t mangle -A PREROUTING -i eth0 \ - -p tcp --dport 443 \ - --tcp-flags SYN,RST SYN \ - -j TCPMSS --set-mss $tcp6_mss_for_https_val + for _dev in ${ext_if_arr[@]} ; do + $ip6t -t mangle -A PREROUTING -i $_dev \ + -p tcp --dport 443 \ + --tcp-flags SYN,RST SYN \ + -j TCPMSS --set-mss $tcp6_mss_for_https_val + done echo_done @@ -2169,9 +2171,15 @@ echo_done # --- echo "" + +# Note: +# The following rules only DROP/REJECT excess connections - they never ACCEPT. +# Ports are opened exclusively by the rules for 'http_server_ips' below. +# Without local webserver (empty 'http_server_ips') no rules are created. +# echononl "\t\tA maximum number new connections per second per IP" -if $set_max_new_http_conn_per_ip_per_second ; then +if [[ ${#http_server_ip_arr[@]} -gt 0 ]] && $set_max_new_http_conn_per_ip_per_second ; then if [[ -z "$max_http_new_conn_per_ip_per_second" || ! "$max_http_new_conn_per_ip_per_second" =~ ^[0-9]+$ ]]; then @@ -2182,8 +2190,8 @@ if $set_max_new_http_conn_per_ip_per_second ; then burst_http_new_conn_per_ip_per_second=$((max_http_new_conn_per_ip_per_second * 2)) if $log_rejected || $log_all ; then - $ip6t -A INPUT -p tcp -m multiport --dports 80,443 -m conntrack --ctstate NEW \ - -m hashlimit --hashlimit-name http_v6 --hashlimit-mode srcip \ + $ip6t -A INPUT -p tcp -m multiport --dports $http_ports -m conntrack --ctstate NEW \ + -m hashlimit --hashlimit-name http_v6_log --hashlimit-mode srcip \ --hashlimit-above ${max_http_new_conn_per_ip_per_second}/second \ --hashlimit-burst $burst_http_new_conn_per_ip_per_second \ --hashlimit-htable-expire 600000 \ @@ -2192,9 +2200,9 @@ if $set_max_new_http_conn_per_ip_per_second ; then fi # HTTP/HTTPS: neue Verbindungen pro Quell-IP begrenzen - # Über 30 neue Verbindungen/s pro IP werden gedroppt + # Über 'max_http_new_conn_per_ip_per_second' neue Verbindungen/s pro IP werden gedroppt # Eintrag wird nach 600000 ms = 10 Minuten gelöscht (--hashlimit-htable-expire 600000). - $ip6t -A INPUT -p tcp -m multiport --dports 80,443 -m conntrack --ctstate NEW \ + $ip6t -A INPUT -p tcp -m multiport --dports $http_ports -m conntrack --ctstate NEW \ -m hashlimit --hashlimit-name http_v6 --hashlimit-mode srcip \ --hashlimit-above ${max_http_new_conn_per_ip_per_second}/second \ --hashlimit-burst $burst_http_new_conn_per_ip_per_second \ @@ -2211,8 +2219,7 @@ fi echononl "\t\tA maximum number of concurrent TCP connections per IP" - -if $set_max_concurrent_http_conn_per_ip ; then +if [[ ${#http_server_ip_arr[@]} -gt 0 ]] && $set_max_concurrent_http_conn_per_ip ; then if [[ -z "$max_concurrent_http_conn_per_ip" || ! "$max_concurrent_http_conn_per_ip" =~ ^[0-9]+$ ]]; then @@ -2221,17 +2228,17 @@ if $set_max_concurrent_http_conn_per_ip ; then fi if $log_rejected || $log_all ; then - $ip6t -A INPUT -p tcp -m multiport --dports 80,443 --syn \ + $ip6t -A INPUT -p tcp -m multiport --dports $http_ports --syn \ -m connlimit --connlimit-above $max_concurrent_http_conn_per_ip --connlimit-mask 128 \ -m limit --limit 10/second --limit-burst 20 \ -j $LOG_TARGET $tag_log_prefix "$log_prefix more then $max_concurrent_http_conn_per_ip concurrent connections per IP." fi # HTTP/HTTPS: gleichzeitige Verbindungen pro IP begrenzen - # Mehr als 50 parallele TCP-Verbindungen pro IP ablehnen + # Mehr als 'max_concurrent_http_conn_per_ip' parallele TCP-Verbindungen pro IP ablehnen # - # Limit gilt pro einzelner IPv4-Adresse (--connlimit-mask 128) (z.bsp. 2a01:599:108:40b0:e1a9:170b:ba18:fd58/128) - $ip6t -A INPUT -p tcp -m multiport --dports 80,443 --syn \ + # Limit gilt pro einzelner IPv6-Adresse (--connlimit-mask 128) (z.bsp. 2a01:599:108:40b0:e1a9:170b:ba18:fd58/128) + $ip6t -A INPUT -p tcp -m multiport --dports $http_ports --syn \ -m connlimit --connlimit-above $max_concurrent_http_conn_per_ip --connlimit-mask 128 \ -j REJECT --reject-with tcp-reset @@ -2244,10 +2251,9 @@ else fi - echononl "\t\tA maximum number of new TCP handshakes per second." -if $set_max_new_http_conn_per_second ; then +if [[ ${#http_server_ip_arr[@]} -gt 0 ]] && $set_max_new_http_conn_per_second ; then if [[ -z "$max_new_http_conn_per_second" || ! "$max_new_http_conn_per_second" =~ ^[0-9]+$ ]]; then @@ -2257,23 +2263,28 @@ if $set_max_new_http_conn_per_second ; then burst_max_new_http_conn_per_second=$((2 * max_new_http_conn_per_second)) - # SYN-Rate begrenzen + # SYN-Rate begrenzen (global, alle Quell-IPs zusammen) # - # Kurzfristig auch bis zu 400 syn pakete erlaubt. --limit-burst 400 - Danach greift das Limit." - $ip6t -A INPUT -p tcp -m multiport --dports 80,443 \ - --syn \ - -m limit --limit ${max_new_http_conn_per_second}/second --limit-burst $burst_max_new_http_conn_per_second \ - -j ACCEPT - + # hashlimit ohne '--hashlimit-mode' verwendet einen einzigen Bucket für alle + # Pakete. SYN-Pakete oberhalb des Limits werden gedroppt; alles darunter + # läuft weiter zu den Regeln für 'http_server_ips' (kein ACCEPT hier!). + # + # Kurzfristig auch bis zu 'burst_max_new_http_conn_per_second' SYN-Pakete erlaubt. if $log_rejected || $log_all ; then - $ip6t -A INPUT -p tcp -m multiport --dports 80,443 \ - --syn \ - -m limit --limit ${max_new_http_conn_per_second}/second --limit-burst $burst_max_new_http_conn_per_second \ - -j $LOG_TARGET $tag_log_prefix "$log_prefix SYN-Overflow." + $ip6t -A INPUT -p tcp -m multiport --dports $http_ports \ + --syn \ + -m hashlimit --hashlimit-name http_v6_syn_log \ + --hashlimit-above ${max_new_http_conn_per_second}/second \ + --hashlimit-burst $burst_max_new_http_conn_per_second \ + -m limit --limit 10/second --limit-burst 20 \ + -j $LOG_TARGET $tag_log_prefix "$log_prefix SYN-Overflow." fi - $ip6t -A INPUT -p tcp -m multiport --dports 80,443 \ + $ip6t -A INPUT -p tcp -m multiport --dports $http_ports \ --syn \ + -m hashlimit --hashlimit-name http_v6_syn \ + --hashlimit-above ${max_new_http_conn_per_second}/second \ + --hashlimit-burst $burst_max_new_http_conn_per_second \ -j DROP echo_done @@ -2319,7 +2330,7 @@ if [[ ${#mm_server_ip_arr[@]} -gt 0 ]] || [[ ${#forward_mm_server_ip_arr[@]} -gt if [[ ${#mm_server_ip_arr[@]} -gt 0 ]] ; then for _ip in ${mm_server_ip_arr[@]} ; do $ip6t -A INPUT -p udp -d $_ip -m multiport --dports $mm_udp_ports_in -m conntrack --ctstate NEW -j ACCEPT - if [ -n ${mm_tcp_ports_in} ] ; then + if [[ -n "$mm_tcp_ports_in" ]] ; then $ip6t -A INPUT -p tcp -d $_ip -m multiport --dports $mm_tcp_ports_in -m conntrack --ctstate NEW -j ACCEPT else $ip6t -A INPUT -p tcp -d $_ip -m multiport --dports $mm_udp_ports_in -m conntrack --ctstate NEW -j ACCEPT @@ -2330,7 +2341,7 @@ if [[ ${#mm_server_ip_arr[@]} -gt 0 ]] || [[ ${#forward_mm_server_ip_arr[@]} -gt if [[ ${#forward_mm_server_ip_arr[@]} -gt 0 ]] && $kernel_forward_between_interfaces ; then for _ip in ${forward_mm_server_ip_arr[@]} ; do $ip6t -A FORWARD -p udp -d $_ip -m multiport --dports $mm_udp_ports_in -m conntrack --ctstate NEW -j ACCEPT - if [ -n ${mm_tcp_ports_in} ] ; then + if [[ -n "$mm_tcp_ports_in" ]] ; then $ip6t -A FORWARD -p tcp -d $_ip -m multiport --dports $mm_tcp_ports_in -m conntrack --ctstate NEW -j ACCEPT else $ip6t -A FORWARD -p tcp -d $_ip -m multiport --dports $mm_udp_ports_in -m conntrack --ctstate NEW -j ACCEPT diff --git a/ipt-firewall-server b/ipt-firewall-server index dc33e5d..57b311e 100755 --- a/ipt-firewall-server +++ b/ipt-firewall-server @@ -2247,9 +2247,15 @@ echo_done # --- echo "" + +# Note: +# The following rules only DROP/REJECT excess connections - they never ACCEPT. +# Ports are opened exclusively by the rules for 'http_server_ips' below. +# Without local webserver (empty 'http_server_ips') no rules are created. +# echononl "\t\tA maximum number new connections per second per IP" -if $set_max_new_http_conn_per_ip_per_second ; then +if [[ ${#http_server_ip_arr[@]} -gt 0 ]] && $set_max_new_http_conn_per_ip_per_second ; then if [[ -z "$max_http_new_conn_per_ip_per_second" || ! "$max_http_new_conn_per_ip_per_second" =~ ^[0-9]+$ ]]; then @@ -2260,8 +2266,8 @@ if $set_max_new_http_conn_per_ip_per_second ; then burst_http_new_conn_per_ip_per_second=$((max_http_new_conn_per_ip_per_second * 2)) if $log_rejected || $log_all ; then - $ipt -A INPUT -p tcp -m multiport --dports 80,443 -m conntrack --ctstate NEW \ - -m hashlimit --hashlimit-name http_v6 --hashlimit-mode srcip \ + $ipt -A INPUT -p tcp -m multiport --dports $http_ports -m conntrack --ctstate NEW \ + -m hashlimit --hashlimit-name http_v4_log --hashlimit-mode srcip \ --hashlimit-above ${max_http_new_conn_per_ip_per_second}/second \ --hashlimit-burst $burst_http_new_conn_per_ip_per_second \ --hashlimit-htable-expire 600000 \ @@ -2270,10 +2276,10 @@ if $set_max_new_http_conn_per_ip_per_second ; then fi # HTTP/HTTPS: neue Verbindungen pro Quell-IP begrenzen - # Über 30 neue Verbindungen/s pro IP werden gedroppt + # Über 'max_http_new_conn_per_ip_per_second' neue Verbindungen/s pro IP werden gedroppt # Eintrag wird nach 600000 ms = 10 Minuten gelöscht (--hashlimit-htable-expire 600000). - $ipt -A INPUT -p tcp -m multiport --dports 80,443 -m conntrack --ctstate NEW \ - -m hashlimit --hashlimit-name http_v6 --hashlimit-mode srcip \ + $ipt -A INPUT -p tcp -m multiport --dports $http_ports -m conntrack --ctstate NEW \ + -m hashlimit --hashlimit-name http_v4 --hashlimit-mode srcip \ --hashlimit-above ${max_http_new_conn_per_ip_per_second}/second \ --hashlimit-burst $burst_http_new_conn_per_ip_per_second \ --hashlimit-htable-expire 600000 -j DROP @@ -2289,7 +2295,7 @@ fi echononl "\t\tA maximum number of concurrent TCP connections per IP" -if $set_max_concurrent_http_conn_per_ip ; then +if [[ ${#http_server_ip_arr[@]} -gt 0 ]] && $set_max_concurrent_http_conn_per_ip ; then if [[ -z "$max_concurrent_http_conn_per_ip" || ! "$max_concurrent_http_conn_per_ip" =~ ^[0-9]+$ ]]; then @@ -2298,18 +2304,18 @@ if $set_max_concurrent_http_conn_per_ip ; then fi if $log_rejected || $log_all ; then - $ipt -A INPUT -p tcp -m multiport --dports 80,443 --syn \ - -m connlimit --connlimit-above $max_concurrent_http_conn_per_ip --connlimit-mask 128 \ + $ipt -A INPUT -p tcp -m multiport --dports $http_ports --syn \ + -m connlimit --connlimit-above $max_concurrent_http_conn_per_ip --connlimit-mask 32 \ -m limit --limit 10/second --limit-burst 20 \ -j $LOG_TARGET $tag_log_prefix "$log_prefix more then $max_concurrent_http_conn_per_ip concurrent connections per IP." fi # HTTP/HTTPS: gleichzeitige Verbindungen pro IP begrenzen - # Mehr als 50 parallele TCP-Verbindungen pro IP ablehnen + # Mehr als 'max_concurrent_http_conn_per_ip' parallele TCP-Verbindungen pro IP ablehnen # - # Limit gilt pro einzelner IPv4-Adresse (--connlimit-mask 128) (z.bsp. 2a01:599:108:40b0:e1a9:170b:ba18:fd58/128) - $ipt -A INPUT -p tcp -m multiport --dports 80,443 --syn \ - -m connlimit --connlimit-above $max_concurrent_http_conn_per_ip --connlimit-mask 128 \ + # Limit gilt pro einzelner IPv4-Adresse (--connlimit-mask 32) + $ipt -A INPUT -p tcp -m multiport --dports $http_ports --syn \ + -m connlimit --connlimit-above $max_concurrent_http_conn_per_ip --connlimit-mask 32 \ -j REJECT --reject-with tcp-reset echo_done @@ -2323,7 +2329,7 @@ fi echononl "\t\tA maximum number of new TCP handshakes per second." -if $set_max_new_http_conn_per_second ; then +if [[ ${#http_server_ip_arr[@]} -gt 0 ]] && $set_max_new_http_conn_per_second ; then if [[ -z "$max_new_http_conn_per_second" || ! "$max_new_http_conn_per_second" =~ ^[0-9]+$ ]]; then @@ -2333,23 +2339,28 @@ if $set_max_new_http_conn_per_second ; then burst_max_new_http_conn_per_second=$((2 * max_new_http_conn_per_second)) - # SYN-Rate begrenzen + # SYN-Rate begrenzen (global, alle Quell-IPs zusammen) # - # Kurzfristig auch bis zu 400 syn pakete erlaubt. --limit-burst 400 - Danach greift das Limit." - $ipt -A INPUT -p tcp -m multiport --dports 80,443 \ - --syn \ - -m limit --limit ${max_new_http_conn_per_second}/second --limit-burst $burst_max_new_http_conn_per_second \ - -j ACCEPT - + # hashlimit ohne '--hashlimit-mode' verwendet einen einzigen Bucket für alle + # Pakete. SYN-Pakete oberhalb des Limits werden gedroppt; alles darunter + # läuft weiter zu den Regeln für 'http_server_ips' (kein ACCEPT hier!). + # + # Kurzfristig auch bis zu 'burst_max_new_http_conn_per_second' SYN-Pakete erlaubt. if $log_rejected || $log_all ; then - $ipt -A INPUT -p tcp -m multiport --dports 80,443 \ - --syn \ - -m limit --limit ${max_new_http_conn_per_second}/second --limit-burst $burst_max_new_http_conn_per_second \ - -j $LOG_TARGET $tag_log_prefix "$log_prefix SYN-Overflow." + $ipt -A INPUT -p tcp -m multiport --dports $http_ports \ + --syn \ + -m hashlimit --hashlimit-name http_v4_syn_log \ + --hashlimit-above ${max_new_http_conn_per_second}/second \ + --hashlimit-burst $burst_max_new_http_conn_per_second \ + -m limit --limit 10/second --limit-burst 20 \ + -j $LOG_TARGET $tag_log_prefix "$log_prefix SYN-Overflow." fi - $ipt -A INPUT -p tcp -m multiport --dports 80,443 \ + $ipt -A INPUT -p tcp -m multiport --dports $http_ports \ --syn \ + -m hashlimit --hashlimit-name http_v4_syn \ + --hashlimit-above ${max_new_http_conn_per_second}/second \ + --hashlimit-burst $burst_max_new_http_conn_per_second \ -j DROP echo_done @@ -2395,7 +2406,7 @@ if [[ ${#mm_server_ip_arr[@]} -gt 0 ]] || [[ ${#forward_mm_server_ip_arr[@]} -gt if [[ ${#mm_server_ip_arr[@]} -gt 0 ]] ; then for _ip in ${mm_server_ip_arr[@]} ; do $ipt -A INPUT -p udp -d $_ip -m multiport --dports $mm_udp_ports_in -m conntrack --ctstate NEW -j ACCEPT - if [ -n ${mm_tcp_ports_in} ] ; then + if [[ -n "$mm_tcp_ports_in" ]] ; then $ipt -A INPUT -p tcp -d $_ip -m multiport --dports $mm_tcp_ports_in -m conntrack --ctstate NEW -j ACCEPT else $ipt -A INPUT -p tcp -d $_ip -m multiport --dports $mm_udp_ports_in -m conntrack --ctstate NEW -j ACCEPT @@ -2406,7 +2417,7 @@ if [[ ${#mm_server_ip_arr[@]} -gt 0 ]] || [[ ${#forward_mm_server_ip_arr[@]} -gt if [[ ${#forward_mm_server_ip_arr[@]} -gt 0 ]] && $kernel_activate_forwarding ; then for _ip in ${forward_mm_server_ip_arr[@]} ; do $ipt -A FORWARD -p udp -d $_ip -m multiport --dports $mm_udp_ports_in -m conntrack --ctstate NEW -j ACCEPT - if [ -n ${mm_tcp_ports_in} ] ; then + if [[ -n "$mm_tcp_ports_in" ]] ; then $ipt -A FORWARD -p tcp -d $_ip -m multiport --dports $mm_tcp_ports_in -m conntrack --ctstate NEW -j ACCEPT else $ipt -A FORWARD -p tcp -d $_ip -m multiport --dports $mm_udp_ports_in -m conntrack --ctstate NEW -j ACCEPT