64 lines
2.9 KiB
Bash
Executable file
64 lines
2.9 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# echoIRCd kernel-layer flood mitigation — defense-in-depth on top of the ircd's own
|
|
# per-IP `accept_rate` limiter. Drops excess NEW connections to the IRC client ports
|
|
# (per source IP) in the kernel, before they ever reach the daemon.
|
|
#
|
|
# THIS BOX RUNS firewalld, so the rule is installed through firewalld's DIRECT interface
|
|
# (`firewall-cmd --direct`). firewalld then owns the rule — it won't be flushed on a
|
|
# `firewall-cmd --reload`, and `--permanent` persists it across reboot. (A raw
|
|
# `iptables -I INPUT ...` would be silently wiped the next time firewalld rebuilds its
|
|
# ruleset, which is why that approach is wrong here.)
|
|
#
|
|
# SAFE: it only DROPs the rate-limited *excess* to 6767/6770. It changes no zone,
|
|
# service, or default policy; ports 6767/6770 stay open as firewalld already has them.
|
|
# Loopback is exempt (the liveness probe + local tools). Idempotent and one-command
|
|
# removable.
|
|
#
|
|
# Apply: sudo deploy/firewalld-echoircd.sh add
|
|
# Remove: sudo deploy/firewalld-echoircd.sh del
|
|
# Show: sudo deploy/firewalld-echoircd.sh show
|
|
#
|
|
# Uses iptables `hashlimit` (per-source-IP). firewalld rich rules can't express this —
|
|
# their `limit` is a single global token bucket, not per-source — so a direct rule is
|
|
# the right tool. IPv4 only (the ircd binds 0.0.0.0); add an ipv6 rule if you ever bind ::.
|
|
set -u
|
|
|
|
PORTS="6767,6770" # IRC client ports (plaintext + TLS); S2S 7700 intentionally left alone
|
|
RATE="30/second" # sustained NEW connections/sec per source IP
|
|
BURST="60" # instantaneous burst allowed per source IP
|
|
ACTION="${1:-}"
|
|
|
|
# the raw rule (added into INPUT at priority 0 = top): drop NEW conns to the IRC ports
|
|
# from any single source IP that exceeds the rate. `! -i lo` exempts loopback.
|
|
RULE=(ipv4 filter INPUT 0
|
|
! -i lo -p tcp -m multiport --dports "$PORTS"
|
|
-m conntrack --ctstate NEW
|
|
-m hashlimit --hashlimit-name ircrate --hashlimit-mode srcip
|
|
--hashlimit-above "$RATE" --hashlimit-burst "$BURST"
|
|
-j DROP)
|
|
|
|
add_one() { firewall-cmd "$@" --direct --add-rule "${RULE[@]}"; }
|
|
del_one() { firewall-cmd "$@" --direct --remove-rule "${RULE[@]}" >/dev/null 2>&1 || true; }
|
|
|
|
case "$ACTION" in
|
|
add)
|
|
del_one # runtime: clear any prior copy (idempotent)
|
|
del_one --permanent
|
|
add_one # runtime (takes effect now)
|
|
add_one --permanent # persists across reboot / reload
|
|
echo "echoircd rate-limit added via firewalld direct rule: ports ${PORTS}, ${RATE} burst ${BURST} per source IP (runtime + permanent)"
|
|
;;
|
|
del)
|
|
del_one
|
|
del_one --permanent
|
|
echo "echoircd rate-limit removed (runtime + permanent)"
|
|
;;
|
|
show)
|
|
echo "== runtime direct rules =="; firewall-cmd --direct --get-all-rules
|
|
echo "== permanent direct rules =="; firewall-cmd --permanent --direct --get-all-rules
|
|
;;
|
|
*)
|
|
echo "usage: $0 {add|del|show}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|