host_firewall: handshake meter carries the service limit, SYN meters become a flood cap at 2x rate and 2x burst

This commit is contained in:
2026-10-26 14:48:25 +00:00
parent ec64ace8f4
commit 46fd6a4c40
2 changed files with 19 additions and 14 deletions
+12 -8
View File
@@ -51,6 +51,8 @@ set {{ name }} {
'ports': ports | join(', '),
'rate': svc.value.rate,
'burst': burst,
'syn_rate': svc.value.rate * 2,
'syn_burst': burst * 2,
'per': per,
'f4': svc.value.range_factor_v4 | default(rl_factor.v4 | default(6), true),
'f6': svc.value.range_factor_v6 | default(rl_factor.v6 | default(3), true),
@@ -193,10 +195,12 @@ table inet host {
# range (IPv4 /24, IPv6 /48; the per-source rate times range_factor). Admin, cluster, VPN, guest and internal sources
# and the public addresses of all inventory hosts (rl_exempt_v4, rl_exempt_v6) are exempt: cluster nodes reach other hosts
# with their public address (ArgoCD git fetches, mail submission). The public set is checked only here, no other chain
# trusts it. The SYN meters drop the excess with the log prefixes fw-rl-syn-addr: (per address) and fw-rl-syn-range: (per range),
# which a spoofed source can trigger: evidence only. The handshake meters count the packet that completes a handshake
# (the second original-direction packet of an established connection), which a spoofed source cannot send: the excess is
# logged with fw-rl-hs: and reset. Only the address tier exists there; ranges are left to CrowdSec.
# trusts it. The handshake meters carry the service limit (rate and burst of firewall.ratelimit): they count the packet that
# completes a handshake (the second original-direction packet of an established connection), which a spoofed source cannot
# send, so a real client above the limit is logged with fw-rl-hs: and reset. Only the address tier exists there; ranges are
# left to CrowdSec. The SYN meters are a looser flood cap at twice the rate and burst: they drop the excess with the log
# prefixes fw-rl-syn-addr: (per address) and fw-rl-syn-range: (per range), which a spoofed source can trigger, so they are
# evidence only. Their cap sits above the service limit so that a real client reaches the handshake meter first.
{% macro rl_exempt() %}
iifname "wt0" return
{% for iface in cluster_ifaces + internal_ifaces + guest_ifaces %}
@@ -229,10 +233,10 @@ table inet host {
{% endfor %}
{% for svc in rl_services if svc.input or svc.forward %}
chain rl_{{ svc.name }} {
update @rl_{{ svc.name }}_v4 { ip saddr limit rate over {{ svc.rate }}/{{ svc.per }} burst {{ svc.burst }} packets } counter jump rl_drop_addr
update @rl_{{ svc.name }}_v6 { ip6 saddr & ffff:ffff:ffff:ffff:: limit rate over {{ svc.rate }}/{{ svc.per }} burst {{ svc.burst }} packets } counter jump rl_drop_addr
update @rl_{{ svc.name }}_v4r { ip saddr & 255.255.255.0 limit rate over {{ svc.rate * svc.f4 }}/{{ svc.per }} burst {{ svc.burst * svc.f4 }} packets } counter jump rl_drop_range
update @rl_{{ svc.name }}_v6r { ip6 saddr & ffff:ffff:ffff:: limit rate over {{ svc.rate * svc.f6 }}/{{ svc.per }} burst {{ svc.burst * svc.f6 }} packets } counter jump rl_drop_range
update @rl_{{ svc.name }}_v4 { ip saddr limit rate over {{ svc.syn_rate }}/{{ svc.per }} burst {{ svc.syn_burst }} packets } counter jump rl_drop_addr
update @rl_{{ svc.name }}_v6 { ip6 saddr & ffff:ffff:ffff:ffff:: limit rate over {{ svc.syn_rate }}/{{ svc.per }} burst {{ svc.syn_burst }} packets } counter jump rl_drop_addr
update @rl_{{ svc.name }}_v4r { ip saddr & 255.255.255.0 limit rate over {{ svc.syn_rate * svc.f4 }}/{{ svc.per }} burst {{ svc.syn_burst * svc.f4 }} packets } counter jump rl_drop_range
update @rl_{{ svc.name }}_v6r { ip6 saddr & ffff:ffff:ffff:: limit rate over {{ svc.syn_rate * svc.f6 }}/{{ svc.per }} burst {{ svc.syn_burst * svc.f6 }} packets } counter jump rl_drop_range
}
{% endfor %}