Skip to content

Commit 39c04af

Browse files
committed
feat: agregar soporte para filtro de paquetes malformados y optimizar reglas de login en nftables
1 parent 25c11f9 commit 39c04af

File tree

6 files changed

+53
-16
lines changed

6 files changed

+53
-16
lines changed

docs/modules/10_l4d2_packet_validation.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ Bloquear tamaños UDP inválidos/malformados típicos de abuso sobre puertos de
1010
## Variables
1111
- `L4D2_GAMESERVER_PORTS`, `L4D2_TV_PORTS`
1212
- `LOG_PREFIX_INVALID_SIZE`, `LOG_PREFIX_MALFORMED`
13+
- `ENABLE_MALFORMED_FILTER` (`true|false`, default `false` en `nft`)
1314

1415
## Diferencias por backend
1516
- Ambos aplican validación de tamaños inválidos/malformados sobre puertos de juego.
17+
- En `nft`, `ENABLE_MALFORMED_FILTER=false` deja activos solo cortes por tamaño extremo (`0-28`, `2521+`) para reducir falsos positivos.
1618

1719
## Nota operativa
1820
- Mantener fuera de `MODULES_ONLY` en nodos sin tráfico L4D2 para minimizar reglas innecesarias.

docs/modules/11_l4d2_a2s_filters.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ El módulo inspecciona el prefijo Source `0xFFFFFFFF` y luego clasifica por byte
5353
- Login (`71`):
5454
- acepta a tasa controlada paquetes que contienen `connect`
5555
- acepta a tasa controlada paquetes que contienen `reserve`
56-
- `drop` para el resto de `71` en ventana corta (1..70 bytes)
56+
- aplica limitación de respaldo para el resto de `71` en ventana corta (evita `drop` ciego)
5757
- se aplica solo sobre `L4D2_GAMESERVER_PORTS` para evitar falsos positivos en SourceTV
5858
- El módulo mantiene logging con prefijos específicos para facilitar diagnóstico.
5959

@@ -69,6 +69,7 @@ Antes de aplicar reglas, el módulo valida:
6969
- Ambos aplican mitigaciones A2S/Steam y patrones de login equivalentes por área.
7070
- `iptables` usa `-m string --hex-string '|FFFFFFFF..|'`.
7171
- `nftables` usa payload match (`@th,64,40 0xFFFFFFFF..`) con meter/rate.
72+
- `nftables` usa ventana corta ampliada para login (`1..140`) y fallback rate-limit para `0x71` no clasificado.
7273

7374
## Nota operativa
7475
- Excluir `l4d2_a2s_filters` de `MODULES_ONLY` en nodos sin servicios de juego.

example.env

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ L4D2_LOGIN_BURST=16
158158
ENABLE_STEAM_GROUP_FILTER=true
159159
STEAM_GROUP_SIGNATURES="00"
160160

161+
# nft backend only: enable strict malformed fixed-size drops (30-32,46,60).
162+
# Keep false if you observe false positives in server browser visibility.
163+
ENABLE_MALFORMED_FILTER=false
164+
161165
# Trusted IPs (complete whitelist)
162166
# These IPs bypass ALL rate-limits and TCP blocks
163167
# Format: "1.2.3.4" (single IP), "1.2.3.4 5.6.7.8" (multiple space-separated)

modules/nf/nf_50_l4d2_udp_base.sh

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ ID=nf_l4d2_udp_base
99
ALIASES=l4d2_udp_base
1010
DESCRIPTION=Applies base UDP/state/ICMP rules in the nftables backend
1111
REQUIRED_VARS=TYPECHAIN L4D2_GAMESERVER_PORTS L4D2_TV_PORTS L4D2_CMD_LIMIT LOG_PREFIX_UDP_NEW_LIMIT LOG_PREFIX_UDP_EST_LIMIT LOG_PREFIX_ICMP_FLOOD
12-
OPTIONAL_VARS=FIREWALL_HOST_ALIAS
13-
DEFAULTS=TYPECHAIN=0 L4D2_GAMESERVER_PORTS=27015 L4D2_TV_PORTS=27020 L4D2_CMD_LIMIT=100 LOG_PREFIX_UDP_NEW_LIMIT=UDP_NEW_LIMIT: LOG_PREFIX_UDP_EST_LIMIT=UDP_EST_LIMIT: LOG_PREFIX_ICMP_FLOOD=ICMP_FLOOD: FIREWALL_HOST_ALIAS=
12+
OPTIONAL_VARS=FIREWALL_HOST_ALIAS STEAM_GROUP_SIGNATURES
13+
DEFAULTS=TYPECHAIN=0 L4D2_GAMESERVER_PORTS=27015 L4D2_TV_PORTS=27020 L4D2_CMD_LIMIT=100 LOG_PREFIX_UDP_NEW_LIMIT=UDP_NEW_LIMIT: LOG_PREFIX_UDP_EST_LIMIT=UDP_EST_LIMIT: LOG_PREFIX_ICMP_FLOOD=ICMP_FLOOD: FIREWALL_HOST_ALIAS= STEAM_GROUP_SIGNATURES=00
1414
EOF
1515
}
1616

@@ -35,11 +35,19 @@ nf_50_l4d2_udp_base_validate() {
3535

3636
nf_validate_ports_spec "$L4D2_GAMESERVER_PORTS" "nf_l4d2_udp_base: L4D2_GAMESERVER_PORTS" || return $?
3737
nf_validate_ports_spec "$L4D2_TV_PORTS" "nf_l4d2_udp_base: L4D2_TV_PORTS" || return $?
38+
39+
local steam_signatures="${STEAM_GROUP_SIGNATURES//[[:space:]]/}"
40+
if [ -n "$steam_signatures" ] && ! [[ "$steam_signatures" =~ ^[0-9A-Fa-f]{2}(,[0-9A-Fa-f]{2})*$ ]]; then
41+
echo "ERROR: nf_l4d2_udp_base: STEAM_GROUP_SIGNATURES must be comma-separated hex bytes (example: 00,69)"
42+
return 2
43+
fi
3844
}
3945

4046
nf_50_l4d2_udp_base_apply() {
4147
local cmd_limit_leeway cmd_limit_upper
4248
local game_ports_expr tv_ports_expr all_udp_ports_expr chain
49+
local steam_signatures_csv steam_sig
50+
local -a steam_signatures
4351
local log_udp_new log_udp_est log_icmp
4452

4553
cmd_limit_leeway=$((L4D2_CMD_LIMIT + 10))
@@ -60,7 +68,13 @@ nf_50_l4d2_udp_base_apply() {
6068
nf_add_rule udp_new_limit @th,64,40 0xFFFFFFFF54 return
6169
nf_add_rule udp_new_limit @th,64,40 0xFFFFFFFF55 return
6270
nf_add_rule udp_new_limit @th,64,40 0xFFFFFFFF56 return
63-
nf_add_rule udp_new_limit @th,64,40 0xFFFFFFFF00 return
71+
steam_signatures_csv="${STEAM_GROUP_SIGNATURES//[[:space:]]/}"
72+
IFS=',' read -r -a steam_signatures <<< "$steam_signatures_csv"
73+
for steam_sig in "${steam_signatures[@]}"; do
74+
[ -z "$steam_sig" ] && continue
75+
steam_sig="${steam_sig^^}"
76+
nf_add_rule udp_new_limit @th,64,40 "0xFFFFFFFF${steam_sig}" return
77+
done
6478
nf_add_rule udp_new_limit @th,64,40 0xFFFFFFFF71 return
6579

6680
nf_add_rule udp_new_limit meter udp_new_src_under '{ ip saddr . udp dport limit rate 1/second burst 3 packets }' jump udp_new_limit_global

modules/nf/nf_60_l4d2_packet_validation.sh

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ ID=nf_l4d2_packet_validation
99
ALIASES=l4d2_packet_validation
1010
DESCRIPTION=Validates invalid/malformed UDP packet sizes in the nftables backend
1111
REQUIRED_VARS=TYPECHAIN L4D2_GAMESERVER_PORTS L4D2_TV_PORTS LOG_PREFIX_INVALID_SIZE LOG_PREFIX_MALFORMED
12-
OPTIONAL_VARS=FIREWALL_HOST_ALIAS
13-
DEFAULTS=TYPECHAIN=0 L4D2_GAMESERVER_PORTS=27015 L4D2_TV_PORTS=27020 LOG_PREFIX_INVALID_SIZE=INVALID_SIZE: LOG_PREFIX_MALFORMED=MALFORMED: FIREWALL_HOST_ALIAS=
12+
OPTIONAL_VARS=FIREWALL_HOST_ALIAS ENABLE_MALFORMED_FILTER
13+
DEFAULTS=TYPECHAIN=0 L4D2_GAMESERVER_PORTS=27015 L4D2_TV_PORTS=27020 LOG_PREFIX_INVALID_SIZE=INVALID_SIZE: LOG_PREFIX_MALFORMED=MALFORMED: FIREWALL_HOST_ALIAS= ENABLE_MALFORMED_FILTER=false
1414
EOF
1515
}
1616

@@ -25,6 +25,14 @@ nf_60_l4d2_packet_validation_validate() {
2525

2626
nf_validate_ports_spec "$L4D2_GAMESERVER_PORTS" "nf_l4d2_packet_validation: L4D2_GAMESERVER_PORTS" || return $?
2727
nf_validate_ports_spec "$L4D2_TV_PORTS" "nf_l4d2_packet_validation: L4D2_TV_PORTS" || return $?
28+
29+
case "${ENABLE_MALFORMED_FILTER:-}" in
30+
true|false) ;;
31+
*)
32+
echo "ERROR: nf_l4d2_packet_validation: ENABLE_MALFORMED_FILTER must be true or false"
33+
return 2
34+
;;
35+
esac
2836
}
2937

3038
nf_60_l4d2_packet_validation_apply_chain() {
@@ -41,14 +49,16 @@ nf_60_l4d2_packet_validation_apply_chain() {
4149
nf_add_rule "$chain" udp dport "$ports_expr" meta length 2521-65535 limit rate over 60/minute log prefix "\"$log_invalid_size\""
4250
nf_add_rule "$chain" udp dport "$ports_expr" meta length 2521-65535 drop
4351

44-
nf_add_rule "$chain" udp dport "$ports_expr" meta length 30-32 limit rate over 60/minute log prefix "\"$log_malformed\""
45-
nf_add_rule "$chain" udp dport "$ports_expr" meta length 30-32 drop
52+
if [ "${ENABLE_MALFORMED_FILTER}" = "true" ]; then
53+
nf_add_rule "$chain" udp dport "$ports_expr" meta length 30-32 limit rate over 60/minute log prefix "\"$log_malformed\""
54+
nf_add_rule "$chain" udp dport "$ports_expr" meta length 30-32 drop
4655

47-
nf_add_rule "$chain" udp dport "$ports_expr" meta length 46 limit rate over 60/minute log prefix "\"$log_malformed\""
48-
nf_add_rule "$chain" udp dport "$ports_expr" meta length 46 drop
56+
nf_add_rule "$chain" udp dport "$ports_expr" meta length 46 limit rate over 60/minute log prefix "\"$log_malformed\""
57+
nf_add_rule "$chain" udp dport "$ports_expr" meta length 46 drop
4958

50-
nf_add_rule "$chain" udp dport "$ports_expr" meta length 60 limit rate over 60/minute log prefix "\"$log_malformed\""
51-
nf_add_rule "$chain" udp dport "$ports_expr" meta length 60 drop
59+
nf_add_rule "$chain" udp dport "$ports_expr" meta length 60 limit rate over 60/minute log prefix "\"$log_malformed\""
60+
nf_add_rule "$chain" udp dport "$ports_expr" meta length 60 drop
61+
fi
5262
}
5363

5464
nf_60_l4d2_packet_validation_apply() {

modules/nf/nf_70_l4d2_a2s_filters.sh

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ nf_70_l4d2_a2s_filters_apply() {
7676
local chain game_ports_expr all_query_ports_expr
7777
local steam_signatures_csv steam_sig
7878
local -a steam_signatures
79-
local log_a2s_info log_a2s_players log_a2s_rules log_steam_group log_connect log_reserve
79+
local log_a2s_info log_a2s_players log_a2s_rules log_steam_group log_connect log_reserve log_login_short
8080

8181
game_ports_expr="$(nf_ports_set_expr "$L4D2_GAMESERVER_PORTS")"
8282
all_query_ports_expr="{ $(nf_ports_normalize "$L4D2_GAMESERVER_PORTS"), $(nf_ports_normalize "$L4D2_TV_PORTS") }"
@@ -87,13 +87,15 @@ nf_70_l4d2_a2s_filters_apply() {
8787
nft add chain inet l4d2_filter steam_group_limit
8888
nft add chain inet l4d2_filter login_connect_limit
8989
nft add chain inet l4d2_filter login_reserve_limit
90+
nft add chain inet l4d2_filter login_short_limit
9091

9192
log_a2s_info="$(nf_build_log_prefix "$LOG_PREFIX_A2S_INFO" "A2S_INFO_FLOOD" "nf_70_l4d2_a2s_filters" "a2s_info_limit" "drop" "medium")"
9293
log_a2s_players="$(nf_build_log_prefix "$LOG_PREFIX_A2S_PLAYERS" "A2S_PLAYERS_FLOOD" "nf_70_l4d2_a2s_filters" "a2s_players_limit" "drop" "medium")"
9394
log_a2s_rules="$(nf_build_log_prefix "$LOG_PREFIX_A2S_RULES" "A2S_RULES_FLOOD" "nf_70_l4d2_a2s_filters" "a2s_rules_limit" "drop" "high")"
9495
log_steam_group="$(nf_build_log_prefix "$LOG_PREFIX_STEAM_GROUP" "STEAM_GROUP_FLOOD" "nf_70_l4d2_a2s_filters" "steam_group_limit" "drop" "medium")"
9596
log_connect="$(nf_build_log_prefix "$LOG_PREFIX_L4D2_CONNECT" "L4D2_CONNECT_FLOOD" "nf_70_l4d2_a2s_filters" "login_connect_limit" "drop" "high")"
9697
log_reserve="$(nf_build_log_prefix "$LOG_PREFIX_L4D2_RESERVE" "L4D2_RESERVE_FLOOD" "nf_70_l4d2_a2s_filters" "login_reserve_limit" "drop" "high")"
98+
log_login_short="$(nf_build_log_prefix "$LOG_PREFIX_L4D2_CONNECT" "L4D2_LOGIN_FLOOD" "nf_70_l4d2_a2s_filters" "login_short_limit" "drop" "high")"
9799

98100
nf_add_rule a2s_info_limit meter a2s_info_under "{ ip saddr . udp dport limit rate ${A2S_INFO_RATE}/second burst ${A2S_INFO_BURST} packets }" accept
99101
nf_add_rule a2s_info_limit meter a2s_info_over "{ ip saddr . udp dport limit rate over ${A2S_INFO_RATE}/second burst ${A2S_INFO_BURST} packets }" log prefix "\"$log_a2s_info\""
@@ -119,6 +121,10 @@ nf_70_l4d2_a2s_filters_apply() {
119121
nf_add_rule login_reserve_limit meter login_reserve_over "{ ip saddr . ip daddr . udp dport limit rate over ${L4D2_LOGIN_RATE}/second burst ${L4D2_LOGIN_BURST} packets }" log prefix "\"$log_reserve\""
120122
nf_add_rule login_reserve_limit meter login_reserve_over_drop "{ ip saddr . ip daddr . udp dport limit rate over ${L4D2_LOGIN_RATE}/second burst ${L4D2_LOGIN_BURST} packets }" drop
121123

124+
nf_add_rule login_short_limit meter login_short_under "{ ip saddr . ip daddr . udp dport limit rate ${L4D2_LOGIN_RATE}/second burst ${L4D2_LOGIN_BURST} packets }" accept
125+
nf_add_rule login_short_limit meter login_short_over "{ ip saddr . ip daddr . udp dport limit rate over ${L4D2_LOGIN_RATE}/second burst ${L4D2_LOGIN_BURST} packets }" log prefix "\"$log_login_short\""
126+
nf_add_rule login_short_limit meter login_short_over_drop "{ ip saddr . ip daddr . udp dport limit rate over ${L4D2_LOGIN_RATE}/second burst ${L4D2_LOGIN_BURST} packets }" drop
127+
122128
for chain in $(nf_get_target_chains); do
123129
nf_add_rule "$chain" udp dport "$all_query_ports_expr" @th,64,40 0xFFFFFFFF54 jump a2s_info_limit
124130
nf_add_rule "$chain" udp dport "$all_query_ports_expr" @th,64,40 0xFFFFFFFF55 jump a2s_players_limit
@@ -134,8 +140,8 @@ nf_70_l4d2_a2s_filters_apply() {
134140
fi
135141

136142
nf_add_rule "$chain" udp dport "$all_query_ports_expr" meta length 1-70 @th,64,48 0xFFFFFFFF0000 drop
137-
nf_add_rule "$chain" udp dport "$game_ports_expr" meta length 1-70 @th,64,40 0xFFFFFFFF71 @th,104,56 0x636f6e6e656374 jump login_connect_limit
138-
nf_add_rule "$chain" udp dport "$game_ports_expr" meta length 1-70 @th,64,40 0xFFFFFFFF71 @th,104,56 0x72657365727665 jump login_reserve_limit
139-
nf_add_rule "$chain" udp dport "$game_ports_expr" meta length 1-70 @th,64,40 0xFFFFFFFF71 drop
143+
nf_add_rule "$chain" udp dport "$game_ports_expr" meta length 1-140 @th,64,40 0xFFFFFFFF71 @th,104,56 0x636f6e6e656374 jump login_connect_limit
144+
nf_add_rule "$chain" udp dport "$game_ports_expr" meta length 1-140 @th,64,40 0xFFFFFFFF71 @th,104,56 0x72657365727665 jump login_reserve_limit
145+
nf_add_rule "$chain" udp dport "$game_ports_expr" meta length 1-140 @th,64,40 0xFFFFFFFF71 jump login_short_limit
140146
done
141147
}

0 commit comments

Comments
 (0)