5.7 KiB
Anti-abuse & flood protection
Abuse is stopped at whichever layer can stop it most cheaply. Being honest about which layer owns which threat matters, because the daemon cannot defend against some attacks no matter how it's written.
The three layers
┌─ upstream / provider ─┐ volumetric floods (large SYN / UDP), amplification
│ (scrubbing, e.g. VAC) │ → only the network above you can absorb these
├─ host kernel / firewall┤ SYN cookies, per-IP connection-rate, conntrack caps
│ │ → cheap kernel drops before the daemon is involved
├─ echoIRCd (application)┤ connection & message floods, clones, spam content
└────────────────────────┘ → everything that survives to a real IRC session
What the daemon can't do: a SYN flood is spoofed TCP handshake packets
that exhaust the kernel's backlog before accept() ever returns — kernel
territory (enable net.ipv4.tcp_syncookies). A UDP flood is pure bandwidth
exhaustion, and since IRC is TCP-only the packets never reach the application at
all. Both are volumetric and are handled by the kernel and the upstream
provider, not by Rust. Everything below is what the application can do:
stop abusive sessions and content.
Application layer (echoIRCd)
At the accept edge
Rejected before any per-connection state is allocated — the cheapest point:
accept_rate/accept_burst— a per-source-IP token bucket. A source opening connections faster than the rate has its socket dropped immediately. Off by default; trusted proxies and server links are exempt. Complements the concurrent clone caps below with a rate cap.connflood = <max> <secs>and connectban — refuse, and optionally z-line, an IP that opens too many connections too fast.
Per connection
- Clone caps — connection classes cap concurrent connections per IP with
localmax(this server) andglobalmax(network-wide). registration_timeout— a connection that never sends NICK+USER is dropped.tls_handshake_timeout— a TLS connection that opens the port but never negotiates is reaped (it holds no session, so nothing else would).conn_waitpong— hold registration until the client answers a server PING with the exact cookie. Real clients auto-reply; dumb bots never do.- recvq / hardsendq / softsendq — per-connection queue caps bound memory; a
client past
softsendqhas its reads paused (backpressure), and pasthardsendqis dropped.
Per message (flood control)
- Fakelag —
flood_messagesperflood_seconds(opers exempt) throttles a client sending too fast; per class,fakelag=nodisconnects instead of throttling. Channel modes+f/+j/+Fadd per-channel message / join / nick-change flood limits.
Reputation & network bans
- Reputation — every address accrues a score over time; the
y:<score>extban bans by it (+b y:<100). - DNSBL — check connecting IPs against DNS blocklists (
mark/kill/kline/gline/zline). - X-lines — persistent
K/G/Z/Q/CBAN/RLINEbans (see operators).
Spam content
Modules that inspect what users do:
| Module / setting | Catches |
|---|---|
antirandom |
Random-looking (drone) nick/ident/realname. |
antimixedutf8 |
Words mixing look-alike Unicode scripts. |
filter / badword (+G) |
Configured spam phrases / words. |
solvemsg |
Un-vouched users must answer an arithmetic question before PMs deliver. |
recaptcha / cloudflare_challenge |
Human-verification gate at registration. |
autodrop |
Pre-registration clients that blurt HTTP verbs (scanners). |
blockamsg |
Mass /amsg / /ame spam. |
restrictmsg / restrictcommands / restrictchans / denychans / channames |
Constrain who may PM, run commands, or create/name channels. |
dccallow |
Unwanted DCC transfers. |
| Security groups | Named user sets (g:<name> extban) for policy by trust level. |
Kernel / firewall layer
The repo ships deploy/firewalld-echoircd.sh,
a per-source-IP connection-rate limit installed through firewalld's direct-rule
interface (so firewalld owns it and won't flush it on reload):
sudo deploy/firewalld-echoircd.sh add # 30/s burst 60 per IP on the IRC ports
sudo deploy/firewalld-echoircd.sh del # remove
sudo deploy/firewalld-echoircd.sh show
It's safe by design: a policy-ACCEPT rule that only drops the rate-limited
excess to the client ports, loopback-exempt, changing no other policy. This
drops connection-churn floods in the kernel before they cost the daemon anything —
the same job as accept_rate, one layer lower. On a host without firewalld, the
equivalent is an iptables/nftables hashlimit rule, or an upstream WAF.
A recommended baseline
- Kernel:
net.ipv4.tcp_syncookies = 1(usually already on). - Firewall: the firewalld script above (or an equivalent per-IP rate limit).
- Daemon: set
accept_rate/accept_burst; keepregistration_timeoutandtls_handshake_timeoutat sane values; enableconn_waitpongif bots are a problem; add DNSBL zones; use connection classes withlocalmax/globalmaxto cap clones. - Upstream: rely on your provider's DDoS scrubbing for volumetric attacks — nothing on the host can absorb a saturating flood.
Keep the firewall rate and accept_rate roughly aligned so the two layers agree,
and pick values generous enough that a real shared-NAT never trips them (a single
client never opens dozens of connections per second).