host_firewall: rejected handshake flows are remembered for 30s and reset per packet, the first data segment no longer slips through; handshake match needs a bare ACK
This commit is contained in:
@@ -112,6 +112,16 @@ table inet host {
|
||||
type ipv6_addr; flags dynamic; timeout 10m; size 65535;
|
||||
}
|
||||
{% endfor %}
|
||||
{% if rl_input or rl_forward %}
|
||||
# Flows the handshake meter rejected: the client sends its first data segment right behind the ACK, which reaches the
|
||||
# host before the reset reaches the client and would complete the handshake there.
|
||||
set rl_hs_rejected_v4 {
|
||||
type ipv4_addr . inet_service . ipv4_addr . inet_service; flags dynamic,timeout; timeout 30s; size 65535;
|
||||
}
|
||||
set rl_hs_rejected_v6 {
|
||||
type ipv6_addr . inet_service . ipv6_addr . inet_service; flags dynamic,timeout; timeout 30s; size 65535;
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
chain input {
|
||||
type filter hook input priority filter; policy drop;
|
||||
@@ -129,9 +139,13 @@ table inet host {
|
||||
|
||||
{% endif %}
|
||||
{% if rl_input %}
|
||||
# Before the established rule: the packet that completes a handshake is the second original-direction packet
|
||||
# (needs net.netfilter.nf_conntrack_acct).
|
||||
meta l4proto tcp ct state established ct direction original ct original packets 2 jump ratelimit_hs_in
|
||||
# Before the established rule. Packets of rejected flows get a reset (not on RST, so that the half-open socket of the
|
||||
# client side reset is cleaned up; a reset per packet is cheap, the client sends only a few before it sees the first).
|
||||
# The packet that completes a handshake is a bare ACK and the second original-direction packet (needs
|
||||
# net.netfilter.nf_conntrack_acct); an RST or FIN as second packet is no handshake, and a blind spoofer can send it.
|
||||
meta l4proto tcp tcp flags & rst == 0 ip saddr . tcp sport . ip daddr . tcp dport @rl_hs_rejected_v4 counter reject with tcp reset
|
||||
meta l4proto tcp tcp flags & rst == 0 ip6 saddr . tcp sport . ip6 daddr . tcp dport @rl_hs_rejected_v6 counter reject with tcp reset
|
||||
meta l4proto tcp tcp flags & (fin | syn | rst | ack) == ack ct state established ct direction original ct original packets 2 jump ratelimit_hs_in
|
||||
{% endif %}
|
||||
ct state established,related accept
|
||||
{% if firewall.scanners %}
|
||||
@@ -258,8 +272,11 @@ table inet host {
|
||||
drop
|
||||
}
|
||||
|
||||
# The handshake is complete, so the connection has to be ended actively.
|
||||
# The handshake is complete, so the connection has to be ended actively; the flow is remembered so that its later
|
||||
# packets are reset without a log line (one fw-rl-hs: line per rejected connection).
|
||||
chain rl_hs_reject {
|
||||
meta nfproto ipv4 add @rl_hs_rejected_v4 { ip saddr . tcp sport . ip daddr . tcp dport }
|
||||
meta nfproto ipv6 add @rl_hs_rejected_v6 { ip6 saddr . tcp sport . ip6 daddr . tcp dport }
|
||||
limit rate {{ firewall.log.ratelimit_rate }} log prefix "fw-rl-hs: "
|
||||
reject with tcp reset
|
||||
}
|
||||
@@ -385,7 +402,9 @@ table inet host {
|
||||
|
||||
# Docker-published ports and port forwards arrive here after DNAT; pod and guest egress is not DNATed.
|
||||
ct status dnat ct state new jump ratelimit_fwd
|
||||
meta l4proto tcp ct status dnat ct state established ct direction original ct original packets 2 jump ratelimit_hs_fwd
|
||||
meta l4proto tcp ct status dnat tcp flags & rst == 0 ip saddr . tcp sport . ip daddr . tcp dport @rl_hs_rejected_v4 counter reject with tcp reset
|
||||
meta l4proto tcp ct status dnat tcp flags & rst == 0 ip6 saddr . tcp sport . ip6 daddr . tcp dport @rl_hs_rejected_v6 counter reject with tcp reset
|
||||
meta l4proto tcp tcp flags & (fin | syn | rst | ack) == ack ct status dnat ct state established ct direction original ct original packets 2 jump ratelimit_hs_fwd
|
||||
{% endif %}
|
||||
{% if firewall.spamhaus %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user