docs: add a full docs/ manual (architecture, configuration, modes, operators, linking, ircv3, anti-abuse, deployment, building) and refresh the README for the reactor pool + TLS-in-reactor + resilience
This commit is contained in:
parent
d8a963d0f9
commit
bd054f7721
11 changed files with 1182 additions and 11 deletions
39
README.md
39
README.md
|
|
@ -16,9 +16,10 @@
|
|||
|
||||
echoIRCd is a full IRC + IRCv3 server built from the ground up in safe Rust
|
||||
(`#![forbid(unsafe_code)]`) with just two dependencies — `openssl` for TLS and
|
||||
`mio` for the socket engine. A single lock-free core thread owns all state while
|
||||
one epoll reactor drives tens of thousands of connections without an async
|
||||
runtime. It ships **100+ commands**, the **complete channel & user mode set**,
|
||||
`mio` for the socket engine. A single lock-free core thread owns all state; a
|
||||
**pool of epoll reactor threads** (one per core) drives the connections around it
|
||||
— TLS crypto and all — without an async runtime. It ships **100+ commands**, the
|
||||
**complete channel & user mode set**,
|
||||
**28 IRCv3 capabilities**, server-to-server linking, a services interface, TLS,
|
||||
WebSocket, GeoIP, layered anti-spam, and a JSON-RPC control plane — with every
|
||||
operational limit configurable and nothing hardcoded.
|
||||
|
|
@ -66,6 +67,14 @@ cargo run --release # reads ./echoircd.conf
|
|||
Then point a client at it: `/server 127.0.0.1 6667` (or `6697` for TLS once a
|
||||
certificate is configured).
|
||||
|
||||
## Documentation
|
||||
|
||||
The full manual lives in [`docs/`](docs/):
|
||||
|
||||
- [Building & running](docs/building.md) · [Configuration](docs/configuration.md) · [Architecture](docs/architecture.md)
|
||||
- [Channel & user modes](docs/modes.md) · [Operators](docs/operators.md) · [Server linking & services](docs/linking.md)
|
||||
- [IRCv3](docs/ircv3.md) · [Anti-abuse & flood protection](docs/anti-abuse.md) · [Deployment](docs/deployment.md)
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration is a plain `key = value` file; see
|
||||
|
|
@ -80,11 +89,16 @@ A single **core thread** owns every `User` and `Channel`, so command and module
|
|||
code is ordinary single-threaded logic over `&mut Server` — no `Arc<Mutex<…>>`
|
||||
anywhere. The I/O edge feeds it events over channels:
|
||||
|
||||
- **One `mio` epoll reactor** drives all client sockets — measured at 5,000
|
||||
concurrent clients on 4 threads total, scaling toward ~50k with a release build
|
||||
and a high `LimitNOFILE`.
|
||||
- **TLS sessions and server links** run a thread each; both hand the core the same
|
||||
`OutSink`, so it never knows which transport a connection uses.
|
||||
- **A pool of `mio` epoll reactors** drives client sockets — an acceptor
|
||||
round-robins each connection onto a worker (one per core by default), and each
|
||||
worker frames lines and runs **TLS handshakes and record crypto non-blocking**
|
||||
in-thread. So the socket work and the crypto spread across cores while the state
|
||||
core stays single-threaded and lock-free. (Proxied TLS and server links keep a
|
||||
thread each; there are few of them.)
|
||||
- **Resilience is built in.** Slow work (KDF hashing, DNS, disk snapshots) runs
|
||||
off the core so a flood can't freeze it; each event and each connection's I/O is
|
||||
panic-isolated so one bad client can't crash the server; a watchdog flags a
|
||||
stuck core; and half-open/stalled connections are reaped on a timer.
|
||||
|
||||
**Why a raw reactor and not async?** IRC is one large shared mutable graph, and
|
||||
almost every command mutates it and then broadcasts. With one thread owning all of
|
||||
|
|
@ -92,12 +106,14 @@ it, handlers are plain synchronous code — no locks, no `.await`, no `Send + 's
|
|||
bounds. A multi-threaded async runtime would force that shared state behind mutexes
|
||||
or an actor mailbox, and a channel broadcast is serialized anyway, so you'd pay for
|
||||
parallelism the workload can't use. `mio` is the same readiness layer async runtimes
|
||||
build on, so you keep the scaling without the runtime; blocking work (DNS, TLS,
|
||||
HTTP) is offloaded to its own threads.
|
||||
build on, so you keep the scaling without the runtime. What *does* parallelize —
|
||||
the socket syscalls and TLS crypto — runs in the reactor pool; scaling past one
|
||||
machine is done by linking servers, not threading one harder.
|
||||
|
||||
Memory safety is structural: `Uid` handles instead of raw pointers, an `Extensible`
|
||||
typemap instead of `void*` module data (freed on drop), and compiled-in trait
|
||||
objects instead of a fragile plugin ABI.
|
||||
objects instead of a fragile plugin ABI. **Full design notes:**
|
||||
[`docs/architecture.md`](docs/architecture.md).
|
||||
|
||||
## Extending
|
||||
|
||||
|
|
@ -114,6 +130,7 @@ Three small extension points, each one file + one table line:
|
|||
|
||||
- **Repository** — <https://git.devtronic.pro/fedserv/echoIRCd>
|
||||
- **Issues** — <https://git.devtronic.pro/fedserv/echoIRCd/issues>
|
||||
- **Documentation** — [`docs/`](docs/)
|
||||
- **Config reference** — [`echoircd.conf.example`](echoircd.conf.example)
|
||||
|
||||
## License
|
||||
|
|
|
|||
49
docs/README.md
Normal file
49
docs/README.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# echoIRCd documentation
|
||||
|
||||
echoIRCd is a from-scratch IRC + IRCv3 server written in safe Rust
|
||||
(`#![forbid(unsafe_code)]`) with two dependencies — `openssl` for TLS and `mio`
|
||||
for the socket engine. A single lock-free core thread owns all state; a pool of
|
||||
reactor threads drives the connections around it.
|
||||
|
||||
This folder is the reference manual. Start with whichever fits what you're doing:
|
||||
|
||||
| Doc | What's in it |
|
||||
|-----|--------------|
|
||||
| [Building & running](building.md) | Prerequisites, debug/release builds, tests, the originality guard, project layout. |
|
||||
| [Configuration](configuration.md) | The config file format and a grouped reference of every setting. |
|
||||
| [Architecture](architecture.md) | The single-threaded core + reactor-pool design, the I/O models, resilience, and memory safety. Read this to understand *why* it's built the way it is. |
|
||||
| [Channel & user modes](modes.md) | Every prefix, list, parameter, and flag mode, plus extbans. |
|
||||
| [Operators](operators.md) | The oper system, oper levels, snomasks, the override toolbox, and X-lines. |
|
||||
| [Server linking & services](linking.md) | Server-to-server links, the netburst, routing, and the services / SASL interface. |
|
||||
| [IRCv3](ircv3.md) | The advertised capabilities and the notable extensions. |
|
||||
| [Anti-abuse & flood protection](anti-abuse.md) | The layered defenses, from the accept edge up to the application, and how they fit with kernel/upstream filtering. |
|
||||
| [Deployment](deployment.md) | Running in production: release builds, a supervised service, log shipping, reverse proxies, and firewall hardening. |
|
||||
|
||||
## At a glance
|
||||
|
||||
- **Full IRC core** — registration, channels, messaging, and the informational
|
||||
command set (~130 commands total).
|
||||
- **Complete mode set** — the standard prefixes plus a staff prefix, list modes,
|
||||
keyed/limit/flood/redirect/history/anticaps parameters, the full flag set, and
|
||||
matching + acting extbans. See [modes](modes.md).
|
||||
- **IRCv3** — a broad capability set including message-tags, server-time,
|
||||
labeled-response, batch, echo-message, account-tag, CHATHISTORY, multiline,
|
||||
message-redaction, read-marker, and relaymsg. See [IRCv3](ircv3.md).
|
||||
- **Operators & services** — a rich oper toolbox with oper levels, X-lines
|
||||
persisted to disk, and a services interface (SASL over links, the `SVS*` /
|
||||
`ENCAP` / `METADATA` set, account-gated modes).
|
||||
- **Transports** — plaintext, TLS (with client-cert fingerprints), a native
|
||||
WebSocket layer, and the PROXY protocol behind a load balancer.
|
||||
- **Security** — keyed host cloaking, DNSBL, GeoIP, layered connection/message
|
||||
flood limits, and script/gibberish spam detection.
|
||||
|
||||
## Design in one paragraph
|
||||
|
||||
One **core thread** owns every user and channel, so command and module code is
|
||||
ordinary single-threaded logic over `&mut Server` — no locks anywhere. The I/O
|
||||
edge is a **pool of reactor workers** (one per core by default): they accept
|
||||
connections, frame lines, and run TLS crypto, then hand the core plain events
|
||||
over a channel. The core can't be frozen by slow work (KDF hashing, DNS, disk
|
||||
writes all run off-thread), can't be killed by one bad connection (panics are
|
||||
isolated per event and per connection), and is watched by a liveness thread. See
|
||||
[architecture](architecture.md) for the full story.
|
||||
116
docs/anti-abuse.md
Normal file
116
docs/anti-abuse.md
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
# 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
|
||||
|
||||
```text
|
||||
┌─ 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) and `globalmax` (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 `softsendq` has its reads paused (backpressure), and past
|
||||
`hardsendq` is dropped.
|
||||
|
||||
### Per message (flood control)
|
||||
|
||||
- **Fakelag** — `flood_messages` per `flood_seconds` (opers exempt) throttles a
|
||||
client sending too fast; per class, `fakelag=no` disconnects instead of
|
||||
throttling. Channel modes `+f` / `+j` / `+F` add 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` / `RLINE` bans (see
|
||||
[operators](operators.md)).
|
||||
|
||||
### 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`](../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):
|
||||
|
||||
```sh
|
||||
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
|
||||
|
||||
1. **Kernel:** `net.ipv4.tcp_syncookies = 1` (usually already on).
|
||||
2. **Firewall:** the firewalld script above (or an equivalent per-IP rate limit).
|
||||
3. **Daemon:** set `accept_rate` / `accept_burst`; keep `registration_timeout`
|
||||
and `tls_handshake_timeout` at sane values; enable `conn_waitpong` if bots are
|
||||
a problem; add DNSBL zones; use connection classes with `localmax` / `globalmax`
|
||||
to cap clones.
|
||||
4. **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).
|
||||
163
docs/architecture.md
Normal file
163
docs/architecture.md
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
# Architecture
|
||||
|
||||
echoIRCd is built around one idea: **all shared state lives on a single thread,
|
||||
and everything that can be parallelized without touching that state is pushed off
|
||||
of it.** This page explains what that means, why it was chosen, and how the pieces
|
||||
fit.
|
||||
|
||||
## The core thread
|
||||
|
||||
A single **core thread** owns every `User` and `Channel` in a `Server` struct.
|
||||
Command handlers, mode handlers, and module hooks are all ordinary synchronous
|
||||
functions that take `&mut Server` and mutate it directly. There is no
|
||||
`Arc<Mutex<…>>`, no `RwLock`, no actor mailbox, and no `.await` anywhere in the
|
||||
command path.
|
||||
|
||||
The core runs one loop:
|
||||
|
||||
```text
|
||||
for ev in rx { // rx is an mpsc channel fed by the I/O edge
|
||||
handle_event(ev) // Connect / Line / Disconnect / Tick / async results
|
||||
}
|
||||
```
|
||||
|
||||
Every state change funnels through this loop, so there is exactly one writer to
|
||||
the state graph and no data races are possible by construction.
|
||||
|
||||
### Why not an async runtime?
|
||||
|
||||
IRC is one large shared mutable graph. Almost every command reads part of it,
|
||||
mutates part of it, and then broadcasts to many connections — a channel message
|
||||
touches the send buffers of everyone in the channel, a nick change updates a
|
||||
global map and notifies every common-channel peer, and so on. This workload has
|
||||
two properties that decide the design:
|
||||
|
||||
1. **The write set is global and interconnected.** You cannot cleanly shard the
|
||||
state by connection, because messages cross shards constantly.
|
||||
2. **The per-message CPU cost is tiny.** Parse a line, look up a channel, append
|
||||
bytes to some send buffers. It is almost entirely I/O-bound.
|
||||
|
||||
Put those together and a multi-threaded async runtime buys you nothing here:
|
||||
you'd have to wrap the shared graph in a global lock (serializing everything you
|
||||
just spread across threads) or an actor that processes messages one at a time
|
||||
(a single-threaded loop with extra steps). Meanwhile you'd pay for `Send +
|
||||
'static` bounds on every future and a scheduler you don't need. So the core stays
|
||||
single-threaded and lock-free, and `mio` provides the same readiness layer an
|
||||
async runtime would build on — without the runtime.
|
||||
|
||||
The thing that single-threaded state *can't* do is use more than one core. That's
|
||||
fine, because the two things that actually benefit from multiple cores — the
|
||||
socket syscalls and the TLS crypto — don't touch shared state at all. They run in
|
||||
the reactor pool.
|
||||
|
||||
## The reactor pool
|
||||
|
||||
Client connections are served by a **pool of reactor threads**, sized by
|
||||
`io_threads` (default: one per CPU, capped). The shape is:
|
||||
|
||||
```text
|
||||
┌──────────── acceptor ────────────┐
|
||||
listener ───▶ │ accept(), pick a worker (round- │
|
||||
│ robin), hand off the socket │
|
||||
└───────┬──────────┬──────────┬─────┘
|
||||
▼ ▼ ▼
|
||||
reactor 0 reactor 1 reactor N (each: its own mio poll,
|
||||
its own conns map, its own token space)
|
||||
│ │ │
|
||||
└──────────┴──────────┘
|
||||
▼
|
||||
Event channel ──▶ core thread (single, lock-free)
|
||||
```
|
||||
|
||||
- One **acceptor** owns the listener, accepts connections, and round-robins each
|
||||
new socket onto a worker.
|
||||
- Each **worker** runs its own `mio` poll loop over its own shard of connections.
|
||||
It reads bytes, frames complete lines, and sends the core `Line` / `Connect` /
|
||||
`Disconnect` events. Tokens are worker-local; the shared atomic counter only
|
||||
mints globally-unique `Uid`s.
|
||||
- The **core** processes those events serially. Output flows back the other way:
|
||||
when the core writes to a connection, the write is routed to the owning worker,
|
||||
which drains it to the socket.
|
||||
|
||||
Because workers only frame bytes and feed the core, and the core owns all state,
|
||||
the parallel part needs **no shared locking** — the only cross-thread contact is
|
||||
the lock-free event channel.
|
||||
|
||||
## The I/O models
|
||||
|
||||
Three transports coexist behind one `OutSink` handle, so the core never knows or
|
||||
cares which one a connection uses:
|
||||
|
||||
| Transport | Model | Notes |
|
||||
|-----------|-------|-------|
|
||||
| **Plaintext clients** | reactor pool | The common case; one worker frames many sockets. |
|
||||
| **Direct TLS clients** | reactor pool | The handshake and record crypto run **non-blocking inside the worker** (OpenSSL driven off a `mio` socket). TLS work spreads across cores like everything else. |
|
||||
| **Proxied TLS** (a PROXY header before the handshake) | thread per connection | Reading the pre-handshake header wants the simpler blocking path; there are few of these. |
|
||||
| **Server links** | thread per connection | A handful of long-lived peers; not worth multiplexing. |
|
||||
|
||||
Scaling out to more machines is done by **linking servers** (see
|
||||
[linking](linking.md)), not by threading one server harder — the single core is
|
||||
the correct unit, and the network grows by adding nodes.
|
||||
|
||||
## Resilience
|
||||
|
||||
A single-threaded core has an obvious risk: one slow or crashing thing could
|
||||
freeze or kill everyone. Each of those failure modes is closed off:
|
||||
|
||||
- **Nothing slow runs inline.** Deliberately-slow work is offloaded to bounded
|
||||
worker threads and delivered back as an event:
|
||||
- **KDF password hashing** (bcrypt / pbkdf2) for `OPER`, `PASS` connect-class
|
||||
checks, `TITLE`, and `MKPASSWD` — a flood of auth attempts can't freeze the
|
||||
core.
|
||||
- **DNS, ident, and HTTP** lookups.
|
||||
- **Disk snapshot writes** (reputation, channel metadata, X-lines) go through a
|
||||
coalescing background writer, so a slow or full disk never stalls the event
|
||||
loop. Writes are **atomic** (temp file + rename), so a crash mid-write can't
|
||||
leave a truncated file.
|
||||
- **One panic can't take down the server.** Each event is handled inside
|
||||
`catch_unwind`, and in the reactor each connection's reads/writes are isolated —
|
||||
a panic parsing one client's bytes drops *that* client and logs it, never the
|
||||
worker that serves everyone else.
|
||||
- **A stuck core is visible.** A watchdog thread reads a shared "busy since"
|
||||
marker and logs if the core stays on one event past `watchdog_ms`; slow events
|
||||
also raise a server notice (`slow_command_ms`). In production a liveness probe
|
||||
restarts the service if a register round-trip stops answering.
|
||||
- **Half-open connections are reaped.** A connection that never registers is
|
||||
dropped after `registration_timeout`; a TLS connection that opens the port but
|
||||
never negotiates is dropped after `tls_handshake_timeout`.
|
||||
|
||||
See [anti-abuse](anti-abuse.md) for how these combine with flood limits and
|
||||
kernel-level filtering.
|
||||
|
||||
## Memory safety
|
||||
|
||||
Safety is structural, not just `#![forbid(unsafe_code)]`:
|
||||
|
||||
- **Handles, not pointers.** Users and channels are referenced by `Uid` /
|
||||
channel-key handles looked up in maps, so there are no dangling references and
|
||||
no use-after-free.
|
||||
- **A typemap, not `void*`.** Modules attach per-user / per-channel / per-server
|
||||
state through an `Extensible` typemap keyed by Rust type; it's dropped
|
||||
automatically with its owner, so module state can't leak or be freed twice.
|
||||
- **Trait objects, not a plugin ABI.** Commands, modes, and modules are
|
||||
compiled-in trait objects. There is no dynamic-loading FFI boundary to get
|
||||
wrong.
|
||||
|
||||
The two dependencies (`openssl`, `mio`) keep their own `unsafe` internal, so the
|
||||
daemon itself never writes any. The `scripts/native-rust-guard.sh` check enforces
|
||||
all of this on every edit: no `unsafe`, no C/FFI, dependencies limited to those
|
||||
two, and no code copied or translated from another project.
|
||||
|
||||
## Tuning knobs
|
||||
|
||||
| Setting | Effect |
|
||||
|---------|--------|
|
||||
| `io_threads` | Reactor workers; `0` = auto (one per core, capped). Raise for very high connection/packet rates. |
|
||||
| `max_line` / `max_sendq` | Per-connection receive/send-queue caps (per-class overrides exist). |
|
||||
| `slow_command_ms` / `watchdog_ms` | Core-health visibility. |
|
||||
| `tls_handshake_timeout` | Reap stalled TLS handshakes. |
|
||||
| `LimitNOFILE` (OS) | File-descriptor ceiling; must be high to reach tens of thousands of connections. |
|
||||
|
||||
For a serious deployment, always run a **release build** — a debug build is
|
||||
unoptimized and dramatically slower on the CPU-bound paths (TLS, hashing,
|
||||
cloaking, parsing). See [deployment](deployment.md).
|
||||
100
docs/building.md
Normal file
100
docs/building.md
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# Building & running
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A stable **Rust** toolchain (`cargo`, `rustc`).
|
||||
- **OpenSSL** development headers (the `openssl` crate links against the system
|
||||
library) — e.g. `libssl-dev` on Debian/Ubuntu.
|
||||
|
||||
That's it. There are exactly two dependencies: `openssl` and `mio`.
|
||||
|
||||
## Build
|
||||
|
||||
```sh
|
||||
git clone https://git.devtronic.pro/fedserv/echoIRCd
|
||||
cd echoIRCd
|
||||
|
||||
# development build (fast to compile, slow to run)
|
||||
cargo build
|
||||
|
||||
# release build — ALWAYS use this in production; the debug build is unoptimized
|
||||
# and far slower on the CPU-bound paths (TLS, hashing, cloaking, line parsing)
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
The binaries land in `target/debug/echoircd` and `target/release/echoircd`.
|
||||
|
||||
## Run
|
||||
|
||||
echoircd takes one argument, the path to a config file (default `./echoircd.conf`):
|
||||
|
||||
```sh
|
||||
cp echoircd.conf.example echoircd.conf # then edit: oper pass, cloak_key, TLS paths
|
||||
cargo run --release # reads ./echoircd.conf
|
||||
# or run the binary directly:
|
||||
./target/release/echoircd /path/to/echoircd.conf
|
||||
```
|
||||
|
||||
Point a client at it: `/server 127.0.0.1 6667`, or `6697` for TLS once a
|
||||
certificate is configured. See [configuration](configuration.md) for every
|
||||
setting and [deployment](deployment.md) for running it as a supervised service.
|
||||
|
||||
> Your live `echoircd.conf` is gitignored on purpose — it holds secrets (oper
|
||||
> password, cloak key, link password). Never commit it.
|
||||
|
||||
## Tests
|
||||
|
||||
```sh
|
||||
cargo test # unit + integration tests
|
||||
cargo test --test integration # just the end-to-end suite
|
||||
```
|
||||
|
||||
The integration suite spawns the real binary on ephemeral ports and drives it as
|
||||
a client — covering reactor-pool cross-worker delivery, TLS-in-reactor handshakes,
|
||||
the stalled-handshake reap, the accept-rate limiter, and nick collisions. It
|
||||
tracks and kills its child processes by PID, never by name.
|
||||
|
||||
## The originality guard
|
||||
|
||||
Every source edit is checked by `scripts/native-rust-guard.sh`, which enforces the
|
||||
project's invariants:
|
||||
|
||||
- no `unsafe` (the crate is `#![forbid(unsafe_code)]`),
|
||||
- no C / FFI,
|
||||
- dependencies limited to `openssl` + `mio`,
|
||||
- and no code copied or translated from any other project — everything is
|
||||
original Rust.
|
||||
|
||||
Run it on a file directly with `bash scripts/native-rust-guard.sh <file>`.
|
||||
|
||||
## Project layout
|
||||
|
||||
```text
|
||||
src/
|
||||
main.rs startup: config, listeners, the reactor pool, the core thread
|
||||
ircd.rs the core event loop and event types
|
||||
server.rs the Server: all state, plus helpers (send, snotice, off-core work)
|
||||
socketengine.rs the I/O edge: reactor pool, acceptor, TLS-in-reactor, links
|
||||
tls.rs the TLS backend (OpenSSL) — blocking + non-blocking sessions
|
||||
channels.rs Channel, membership, and the channel mode struct
|
||||
users.rs User and session state
|
||||
mode.rs ChanMode / UserMode traits and the mode registry
|
||||
command.rs the Command trait
|
||||
module.rs the module lifecycle-hook trait
|
||||
coremods/ built-in commands (registration, channels, messaging, oper, …)
|
||||
modules/ optional, pluggable modules (~70 of them)
|
||||
tests/ end-to-end integration tests
|
||||
deploy/ production systemd units + firewall script
|
||||
docs/ this manual
|
||||
```
|
||||
|
||||
## Extending
|
||||
|
||||
Three extension points, each one file plus one table entry:
|
||||
|
||||
- **Commands** (`src/command.rs`, `src/coremods/`) — a handler with `name`,
|
||||
`min_params`, `before_reg`, and `handle(&mut Server, uid, params)`.
|
||||
- **Modes** (`src/mode.rs`) — channel/user modes as `ChanMode` / `UserMode`
|
||||
handler objects; adding one never touches the parser.
|
||||
- **Modules** (`src/module.rs`, `src/modules/`) — lifecycle hooks; pre-hooks can
|
||||
**Deny** a register/command/message, notify-hooks fire after.
|
||||
172
docs/configuration.md
Normal file
172
docs/configuration.md
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
# Configuration
|
||||
|
||||
The config is a plain `key = value` text file (default `./echoircd.conf`). Some
|
||||
keys repeat to build a list (`motd`, `oper`, `link`, `connectclass`, `dnsbl`,
|
||||
`securitygroup`, …). Comments start with `#`.
|
||||
|
||||
The shipped [`echoircd.conf.example`](../echoircd.conf.example) is the fully
|
||||
annotated master reference — every key with its default. This page organizes those
|
||||
keys by topic. **Every operational limit is a config key**; nothing is hardcoded.
|
||||
Most settings apply on `REHASH` without a restart.
|
||||
|
||||
> `echoircd.conf` is gitignored because it holds secrets (oper password, cloak
|
||||
> key, link password). Never commit your live config.
|
||||
|
||||
## Server identity
|
||||
|
||||
| Key | Meaning |
|
||||
|-----|---------|
|
||||
| `servername` | This server's name (e.g. `irc.example.net`). |
|
||||
| `network` | Network name (advertised in ISUPPORT). |
|
||||
| `sid` | Server ID for linking — 3 chars, first a digit (e.g. `0AA`). |
|
||||
| `serverdesc` | Human-readable server description. |
|
||||
| `motd` | Message of the day; repeat the key for extra lines. |
|
||||
|
||||
## Listeners & TLS
|
||||
|
||||
| Key | Meaning |
|
||||
|-----|---------|
|
||||
| `bind` | Plaintext client listener, `ip:port` (e.g. `0.0.0.0:6667`). |
|
||||
| `bind_tls` | TLS client listener (e.g. `0.0.0.0:6697`). |
|
||||
| `tls_cert` / `tls_key` | PEM certificate + private key for TLS (and `wss://`). |
|
||||
| `bind_server` | Server-to-server link listener (see [linking](linking.md)). |
|
||||
|
||||
Generate a self-signed cert to start:
|
||||
|
||||
```sh
|
||||
openssl req -x509 -newkey rsa:2048 -keyout tls/key.pem -out tls/cert.pem \
|
||||
-days 3650 -nodes -subj "/CN=irc.example.net"
|
||||
```
|
||||
|
||||
## I/O & performance
|
||||
|
||||
| Key | Default | Meaning |
|
||||
|-----|---------|---------|
|
||||
| `io_threads` | `0` (auto) | Reactor workers; `0` = one per core, capped. |
|
||||
| `max_line` | `16384` | Max bytes in one line / receive queue. |
|
||||
| `max_sendq` | `1048576` | Max queued output before a slow client is dropped. |
|
||||
| `tls_handshake_timeout` | `15` | Drop a TLS conn that stalls mid-handshake (secs; `0` = off). |
|
||||
| `slow_command_ms` | `200` | Server-notice when one event takes at least this long (`0` = off). |
|
||||
| `watchdog_ms` | `5000` | Log if the core is stuck on one event this long (`0` = off). |
|
||||
|
||||
## Connection classes
|
||||
|
||||
`connectclass = <name> key=value …` — per-class connection policy. Each line
|
||||
matches connecting clients by IP/host mask (glob **or** CIDR) and optional
|
||||
TLS/port; the first match wins, else the global limits apply. Masks are tested at
|
||||
connect (against the IP) and re-tested at registration (against the resolved
|
||||
host).
|
||||
|
||||
Key options: `allow=<mask[,mask]>`, `deny=yes`, `parent=<name>` (inherit),
|
||||
`requiressl=yes|trusted`, `password=<pw>` + `hash=<algo>`, `port=<p[,p]>`,
|
||||
`localmax=<n>` (per-IP, this server), `globalmax=<n>` (per-IP, network-wide),
|
||||
`limit=<n>` (total users in class), `maxchans=<n>`, `pingfreq=<secs>`,
|
||||
`timeout=<secs>` (registration), `modes=<+modes>`, `recvq` / `hardsendq` /
|
||||
`softsendq` (queue caps), `fakelag=no` (disconnect flooders instead of throttling),
|
||||
`penaltythreshold` / `commandrate` (flood window), `useident=yes`,
|
||||
`requireident=yes`, `resolvehostnames=no`, `maxconnwarn=yes`.
|
||||
|
||||
```text
|
||||
connectclass = trusted allow=10.0.0.0/8 maxchans=200 pingfreq=120 fakelag=no
|
||||
connectclass = secure allow=* requiressl=yes password=sha256:<hex> hash=sha256
|
||||
connectclass = vpn allow=* parent=trusted localmax=2 maxchans=20 modes=+ix
|
||||
connectclass = banned allow=1.2.3.0/24 deny=yes
|
||||
connectclass_required = yes # refuse clients matching no allow class (default no)
|
||||
```
|
||||
|
||||
## Connection policy & timeouts
|
||||
|
||||
| Key | Default | Meaning |
|
||||
|-----|---------|---------|
|
||||
| `registration_timeout` | `60` | Drop clients that never send NICK+USER in time. |
|
||||
| `ping_frequency` | `90` | Send a PING after this much idle. |
|
||||
| `ping_timeout` | `60` | Then drop if no PONG within this much longer. |
|
||||
| `resolve_hosts` | `on` | Do reverse DNS on connect. |
|
||||
| `use_resolved_host` | `on` | Use the resolved host in the mask (else keep the IP). |
|
||||
| `useident` / `requireident` / `ident_timeout` | off / off / `5` | RFC 1413 ident lookups. |
|
||||
| `conn_waitpong` | off | Hold registration until the client PONGs a cookie (bot filter). |
|
||||
| `abbreviation` | off | Let a unique command prefix resolve (`WHOI` → `WHOIS`). |
|
||||
|
||||
## Operators
|
||||
|
||||
| Key | Meaning |
|
||||
|-----|---------|
|
||||
| `oper = <name> <password> [level]` | An oper account; the password may be hashed (see `MKPASSWD`). Optional numeric [oper level](operators.md). |
|
||||
| `opermotd` | A line shown to opers via `/OPERMOTD` (repeatable). |
|
||||
| `operprefix` | Give every oper a `!` prefix in their channels. |
|
||||
| `ojoin` / `ojoin_op` | Enable `/OJOIN` (join as staff, with op unless `ojoin_op = no`). |
|
||||
|
||||
## Linking & services
|
||||
|
||||
| Key | Meaning |
|
||||
|-----|---------|
|
||||
| `link = <name> <ip> <port> <password> [autoconnect]` | A peer server; password is a shared secret. |
|
||||
| `sasl_server` | The linked server that handles SASL (relayed AUTHENTICATE). Unset disables SASL. |
|
||||
| `webirc = <password> [gateway] [ip-mask]` | Trust a web gateway's `WEBIRC` (real client host/IP). |
|
||||
|
||||
See [linking](linking.md) for the full picture.
|
||||
|
||||
## Anti-abuse
|
||||
|
||||
| Key | Meaning |
|
||||
|-----|---------|
|
||||
| `accept_rate` / `accept_burst` | Per-IP new-connection rate limit at the accept edge (`0` = off). |
|
||||
| `flood_messages` / `flood_seconds` | Per-user message-rate limit (opers exempt). |
|
||||
| `connflood = <max> <secs>` | Refuse an IP opening more than `max` connections per `secs`. |
|
||||
| `dnsbl` / `dnsbl_action` / `dnsbl_reason` / `dnsbl_duration` | DNS blocklist checks (`mark` / `kill` / `kline` / `gline` / `zline`). |
|
||||
| `antimixedutf8` + `amu_*` | Block spam mixing look-alike scripts. |
|
||||
| `badword = <find> [replacement]` | `+G` censor list. |
|
||||
| `autodrop_commands` | Silently drop pre-registration clients that send these (HTTP scanners). |
|
||||
| `solvemsg` | Make un-vouched users answer an arithmetic question before their PMs deliver. |
|
||||
| `dccallow_*` | Filter unwanted DCC transfers. |
|
||||
|
||||
See [anti-abuse](anti-abuse.md) for how these layer together.
|
||||
|
||||
## Identity & privacy
|
||||
|
||||
| Key | Meaning |
|
||||
|-----|---------|
|
||||
| `cloak_key` | Secret for host cloaking (`+x`). A long random hex string; changing it re-cloaks everyone. |
|
||||
| `vhost = <user> <pass> <host>` | A self-service vhost claimable with `/VHOST`. |
|
||||
| `customprefix` | Reconfigure prefix tiers or define brand-new prefixes. |
|
||||
| `hidewhois` + `hidewhois_*` | Hide sensitive WHOIS lines from ordinary users. |
|
||||
| `hidemode = <mode> <rank>` / `hidelist = <mode> <rank>` | Restrict who sees mode changes / list-mode entries. |
|
||||
|
||||
## GeoIP, reputation & security groups
|
||||
|
||||
| Key | Meaning |
|
||||
|-----|---------|
|
||||
| `geoip_database` | Path to a MaxMind `GeoLite2-Country.mmdb`; enables the `G:<cc>` extban, `GEOIP` command, WHOIS country. |
|
||||
| `reputation_*` / `reputationexpire` | Per-address reputation scoring and the `y:<score>` extban. |
|
||||
| `securitygroup = <name> [criteria…]` | A named user set usable as the `g:<name>` extban. |
|
||||
|
||||
## Logging
|
||||
|
||||
| Key | Meaning |
|
||||
|-----|---------|
|
||||
| `syslog` + `syslog_target` / `syslog_facility` / `syslog_tag` | Mirror the notice/log stream to the system logger (`/dev/log` or `host:port`). |
|
||||
| `log_json` | Append the notice/log stream to a file as JSON lines. |
|
||||
| `chanlog` | Mirror the oper server-notice stream into a channel. |
|
||||
|
||||
## Transports
|
||||
|
||||
| Key | Meaning |
|
||||
|-----|---------|
|
||||
| `proxy = <glob\|CIDR>` | Trust the PROXY protocol header from these sources (real client IP). Repeatable. |
|
||||
| `bind_ws` / `bind_wss` | WebSocket listeners (`ws://` / `wss://`). |
|
||||
| `ws_origin`, `ws_proxyranges`, `ws_trust_proxy`, `ws_timeout`, … | WebSocket policy — see the example config. |
|
||||
|
||||
## Advertised limits
|
||||
|
||||
Each is a config key with a built-in default; set a line only to override.
|
||||
|
||||
| Key | Default | | Key | Default |
|
||||
|-----|---------|-|-----|---------|
|
||||
| `maxnick` | `30` | | `maxwatch` | `128` |
|
||||
| `maxchannel` | `50` | | `maxmonitor` | `128` |
|
||||
| `whowas_maxentries` | `256` | | `maxsilence` | `32` |
|
||||
| `chathistory_limit` | `256` | | `maxaccept` | `64` |
|
||||
| `multiline_maxbytes` | `4096` | | `multiline_maxlines` | `24` |
|
||||
|
||||
For the complete, per-key annotated list including every module's options, see
|
||||
[`echoircd.conf.example`](../echoircd.conf.example).
|
||||
125
docs/deployment.md
Normal file
125
docs/deployment.md
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
# Deployment
|
||||
|
||||
Running echoIRCd in production. The repo ships ready-to-use units and scripts in
|
||||
[`deploy/`](../deploy).
|
||||
|
||||
## 1. Build a release binary
|
||||
|
||||
Always run the **release** build in production — a debug build is unoptimized and
|
||||
far slower on the CPU-bound paths (TLS crypto, password hashing, cloaking, line
|
||||
parsing):
|
||||
|
||||
```sh
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
## 2. Pin the binary
|
||||
|
||||
Copy the release binary to a **stable path** and run *that*, not `target/`. This
|
||||
decouples "what's running" from "what you're compiling" — a stray `cargo build`
|
||||
can never change what a restart would launch:
|
||||
|
||||
```sh
|
||||
mkdir -p bin
|
||||
cp target/release/echoircd bin/echoircd
|
||||
```
|
||||
|
||||
`bin/` is gitignored. **Deploying an update** is then: build release → copy over
|
||||
`bin/echoircd` → restart the service.
|
||||
|
||||
## 3. Supervise it with systemd
|
||||
|
||||
[`deploy/echoircd-dev.service`](../deploy/echoircd-dev.service) runs the pinned
|
||||
binary, restarts on failure, raises the file-descriptor limit, and starts on boot:
|
||||
|
||||
```ini
|
||||
[Service]
|
||||
Type=simple
|
||||
User=youruser
|
||||
WorkingDirectory=/path/to/echoIRCd
|
||||
LimitNOFILE=200000
|
||||
ExecStart=/path/to/echoIRCd/bin/echoircd /path/to/echoIRCd/echoircd.conf
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Install it (adjust the paths / `User` first):
|
||||
|
||||
```sh
|
||||
sudo cp deploy/echoircd-dev.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now echoircd-dev.service
|
||||
```
|
||||
|
||||
`LimitNOFILE` is what lets the reactor pool reach tens of thousands of
|
||||
connections — each connection is a file descriptor. `Restart=on-failure` means a
|
||||
crash self-heals instead of leaving the network down.
|
||||
|
||||
## 4. Add a liveness probe
|
||||
|
||||
`Restart=on-failure` catches a *crash*, but not a *hang* (a process that's alive
|
||||
but stopped answering). [`deploy/echoircd-liveness.timer`](../deploy/echoircd-liveness.timer)
|
||||
runs [`scripts/liveness.sh`](../scripts/liveness.sh) every couple of minutes: it
|
||||
does a real NICK/USER register round-trip on the plaintext port and restarts the
|
||||
service if it doesn't get a welcome.
|
||||
|
||||
```sh
|
||||
sudo cp deploy/echoircd-liveness.service deploy/echoircd-liveness.timer /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now echoircd-liveness.timer
|
||||
```
|
||||
|
||||
## 5. Harden the firewall
|
||||
|
||||
Add the kernel-layer connection-rate limit (see [anti-abuse](anti-abuse.md)):
|
||||
|
||||
```sh
|
||||
sudo deploy/firewalld-echoircd.sh add
|
||||
```
|
||||
|
||||
For volumetric (SYN/UDP) floods, rely on your upstream provider's scrubbing —
|
||||
nothing on the host can absorb a saturating flood.
|
||||
|
||||
## Behind a reverse proxy / load balancer
|
||||
|
||||
If a TCP proxy (HAProxy, nginx stream) sits in front, enable the **PROXY protocol**
|
||||
so the real client IP is used instead of the proxy's:
|
||||
|
||||
```text
|
||||
proxy = 10.0.0.0/8 # trust the PROXY header from these sources (repeatable)
|
||||
```
|
||||
|
||||
A connection from a trusted proxy must lead with a PROXY (v1 or v2) header. This
|
||||
applies to the plaintext and TLS client listeners; for browser clients over
|
||||
WebSocket, use `ws_proxyranges` with `X-Forwarded-For` instead.
|
||||
|
||||
## Log shipping
|
||||
|
||||
| Setting | Sends the notice/log stream to |
|
||||
|---------|--------------------------------|
|
||||
| `syslog = yes` (+ `syslog_target`) | the system logger (`/dev/log` or `host:port`) |
|
||||
| `log_json = <path>` | a file, as JSON lines (easy to ingest) |
|
||||
| `chanlog = #snotices` | an in-network channel staff can watch |
|
||||
|
||||
The core also surfaces its own health: `slow_command_ms` raises a notice when an
|
||||
event runs long, and `watchdog_ms` logs if the core is stuck.
|
||||
|
||||
## TLS certificates
|
||||
|
||||
Point `tls_cert` / `tls_key` at your PEM files (the same pair serves `bind_tls`
|
||||
and `wss://`). After renewing a certificate, `REHASH` reloads it without dropping
|
||||
the server. Client-certificate fingerprints are read automatically for SASL
|
||||
EXTERNAL / CertFP.
|
||||
|
||||
## Updating checklist
|
||||
|
||||
1. `git pull` && `cargo build --release`
|
||||
2. `cargo test` (the integration suite spawns the binary and exercises the real
|
||||
paths)
|
||||
3. `cp target/release/echoircd bin/echoircd`
|
||||
4. `sudo systemctl restart echoircd-dev.service`
|
||||
5. Confirm: `systemctl is-active echoircd-dev.service`, then check a client
|
||||
connects on the TLS port.
|
||||
71
docs/ircv3.md
Normal file
71
docs/ircv3.md
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# IRCv3
|
||||
|
||||
echoIRCd implements a broad set of [IRCv3](https://ircv3.net) capabilities. A
|
||||
client negotiates them with `CAP LS` / `CAP REQ`; `cap-notify` keeps the client
|
||||
informed as they change. This page groups what's supported.
|
||||
|
||||
## Message tags & framing
|
||||
|
||||
| Capability | What it adds |
|
||||
|------------|--------------|
|
||||
| `message-tags` (+ `msgid`) | Arbitrary message tags, and a unique `msgid` on messages. |
|
||||
| `server-time` | A `time` tag with the server's timestamp on each message. |
|
||||
| `account-tag` | The sender's logged-in account as a tag. |
|
||||
| `echo-message` | The server echoes your own `PRIVMSG`/`NOTICE` back to you. |
|
||||
| `labeled-response` | Correlate a response batch to the command that caused it. |
|
||||
| `batch` | Group related messages (history playback, netjoins, …). |
|
||||
| `standard-replies` | Structured `FAIL` / `WARN` / `NOTE` replies. |
|
||||
| `TAGMSG` | A tag-only message (e.g. typing / reactions) with no text body. |
|
||||
|
||||
## Membership & identity
|
||||
|
||||
| Capability | What it adds |
|
||||
|------------|--------------|
|
||||
| `extended-join` | Account and real name included in `JOIN`. |
|
||||
| `account-notify` | Notified when a user logs in / out of an account. |
|
||||
| `away-notify` | Notified when a user's away state changes. |
|
||||
| `chghost` | Notified when a user's host/ident changes (instead of a rejoin). |
|
||||
| `setname` | Change your real name in-session (`SETNAME`). |
|
||||
| `multi-prefix` | See all of a member's status prefixes at once. |
|
||||
| `userhost-in-names` | Full `nick!user@host` in `NAMES`. |
|
||||
| `invite-notify` | Channel ops see invites to their channel. |
|
||||
|
||||
## Authentication
|
||||
|
||||
| Capability | What it adds |
|
||||
|------------|--------------|
|
||||
| `sasl` | `AUTHENTICATE` with **PLAIN** or **EXTERNAL** (client-cert / CertFP), relayed to the services server. See [linking](linking.md). |
|
||||
| `draft/account-registration` | Create and confirm an account in-band with `REGISTER` / `VERIFY`. |
|
||||
|
||||
## History & messaging
|
||||
|
||||
| Capability | What it adds |
|
||||
|------------|--------------|
|
||||
| `draft/chathistory` | `CHATHISTORY` — fetch recent messages for a conversation. |
|
||||
| `draft/message-redaction` | `REDACT` — delete/redact a prior message. |
|
||||
| `draft/multiline` | Send one logical message spanning multiple lines. |
|
||||
| `draft/read-marker` | `MARKREAD` — set/query the last-read point of a conversation. |
|
||||
| `draft/relaymsg` | `RELAYMSG` — speak under a spoofed relay nick (for bridges). |
|
||||
|
||||
## Monitoring & notifications
|
||||
|
||||
`MONITOR` (+ `extended-monitor`), the legacy `WATCH` list, `SILENCE`, and
|
||||
caller-id (`ACCEPT` + user mode `+g`) let clients track other users' presence and
|
||||
control who may message them.
|
||||
|
||||
## Metadata & misc
|
||||
|
||||
| Capability / feature | What it adds |
|
||||
|----------------------|--------------|
|
||||
| `draft/metadata-2` | `METADATA` key/value data on users and channels. |
|
||||
| `draft/extended-isupport` | Re-request the current ISUPPORT tokens on demand. |
|
||||
| `draft/json-log` | Stream the server log to an oper as JSON. |
|
||||
| `EXTJWT` | A short-lived, server-signed HS256 token a client can present elsewhere. |
|
||||
| network icon / profile link | Advertise a network icon and per-account profile URLs. |
|
||||
|
||||
## ISUPPORT
|
||||
|
||||
On registration the server advertises its limits and features via `RPL_ISUPPORT`
|
||||
(005), including `PREFIX`, `CHANMODES`, `EXTBAN` (see [modes](modes.md)), `WHOX`,
|
||||
`CHATHISTORY`, `MONITOR` / `WATCH` / `SILENCE` sizes, `NICKLEN` / `CHANNELLEN`,
|
||||
`CASEMAPPING=ascii`, `UTF8ONLY`, and `NETWORK`.
|
||||
111
docs/linking.md
Normal file
111
docs/linking.md
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
# Server linking & services
|
||||
|
||||
Multiple echoIRCd servers form one network by **linking**. Users and channels are
|
||||
shared across all servers, and a services package (accounts, nick/channel
|
||||
registration) attaches as just another linked server.
|
||||
|
||||
## Setting up a link
|
||||
|
||||
Each server needs a unique **SID** (3 characters, first a digit) and a link
|
||||
listener:
|
||||
|
||||
```text
|
||||
sid = 0AA
|
||||
bind_server = 0.0.0.0:7000
|
||||
```
|
||||
|
||||
Then declare each peer, with a shared-secret password on both sides:
|
||||
|
||||
```text
|
||||
# link = <name> <ip> <port> <password> [autoconnect]
|
||||
link = peer.example.net 203.0.113.5 7000 SHARED_LINK_SECRET autoconnect
|
||||
```
|
||||
|
||||
`autoconnect` dials the peer on startup; otherwise an oper runs
|
||||
`/CONNECT peer.example.net`. Link connections use the thread-per-connection I/O
|
||||
model (there are only a handful of them) — see [architecture](architecture.md).
|
||||
|
||||
## The netburst
|
||||
|
||||
When two servers link, they exchange a **burst** that synchronizes state:
|
||||
|
||||
- **Servers** — every server the peer knows, so the topology is complete.
|
||||
- **Users** — each as a `UID` introduction carrying nick, ident, host, real name,
|
||||
modes, account, and cloak.
|
||||
- **Channels** — each as an `FJOIN` carrying the member list with their status
|
||||
prefixes, plus the channel's modes, topic, and ban lists.
|
||||
|
||||
After the burst both sides have identical state and stay in sync by propagating
|
||||
every subsequent change.
|
||||
|
||||
## Routing, collisions & netsplits
|
||||
|
||||
- **Multi-hop routing** — a message for a remote user is forwarded hop-by-hop
|
||||
toward the server that owns them; the ircd tracks which direction each SID lies.
|
||||
- **Nick collisions** — if the same nick appears on both sides of a new link, the
|
||||
collision is resolved deterministically (by sign-on time / UID) so the network
|
||||
converges to one owner.
|
||||
- **Netsplits** — when a link drops, every user and channel behind it is cleanly
|
||||
removed locally and re-introduced on reconnect via a fresh burst.
|
||||
|
||||
## Services & accounts
|
||||
|
||||
echoIRCd stays a **pure ircd**: it does not implement NickServ/ChanServ itself.
|
||||
Instead it carries the *interface* a services package uses, and services run as a
|
||||
linked server. That interface is:
|
||||
|
||||
### SASL
|
||||
|
||||
Client authentication is relayed to the configured services server:
|
||||
|
||||
```text
|
||||
sasl_server = services.example.net
|
||||
```
|
||||
|
||||
- **PLAIN** — the client's credentials are forwarded to services over the link.
|
||||
- **EXTERNAL** — the client must be on TLS with a client certificate; its
|
||||
fingerprint (CertFP) is forwarded, so services can match it to an account with
|
||||
no password.
|
||||
|
||||
With `sasl_server` unset, SASL is disabled.
|
||||
|
||||
### Account state & gated modes
|
||||
|
||||
A logged-in user carries an **account** name (advertised via `account-tag` /
|
||||
`extended-join` / WHOIS, and user mode `+r`). Modes and channel policies can be
|
||||
gated on being logged in — e.g. channel `+R` (registered users only may join),
|
||||
`+M` (only registered may speak), and the `g:<group>` / account-based extbans.
|
||||
|
||||
### The services command set
|
||||
|
||||
Services drive the network through the `SVS*` family and generic transports:
|
||||
|
||||
- `SVSNICK`, `SVSJOIN`, `SVSPART`, `SVSMODE` — act on a user's nick/membership/modes.
|
||||
- `SVSLOGIN` / `SVSLOGOUT` — set or clear a user's account.
|
||||
- `SVSHOLD`, `SVSTOPIC`, `SVSOPER`, `SVSCMODE` — hold a nick, set a topic, grant
|
||||
oper, set channel modes with service authority.
|
||||
- `ENCAP` — an encapsulated command routed to a specific server.
|
||||
- `METADATA` — attach arbitrary key/value data to users and channels.
|
||||
|
||||
### Optional ircd-side registration
|
||||
|
||||
Independently of a services package, the server can offer IRCv3
|
||||
`draft/account-registration` directly: a client uses `REGISTER` / `VERIFY` to
|
||||
create and confirm an account.
|
||||
|
||||
## Web gateways (WEBIRC)
|
||||
|
||||
A trusted web-based client gateway (the browser-to-IRC kind) can declare the real
|
||||
client's host and IP so users don't all appear to come from the gateway:
|
||||
|
||||
```text
|
||||
# webirc = <password> [gateway-name] [ip-mask]
|
||||
webirc = WEBIRC_SECRET mygateway 203.0.113.9
|
||||
```
|
||||
|
||||
The `ip-mask` restricts which source IP may use the password — always set it. The
|
||||
gateway sends a `WEBIRC` line at connect with the password and the real client
|
||||
details.
|
||||
|
||||
> For browser clients connecting *directly* (no gateway), use the native
|
||||
> [WebSocket transport](configuration.md#transports) instead.
|
||||
142
docs/modes.md
Normal file
142
docs/modes.md
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
# Channel & user modes
|
||||
|
||||
This is the full mode set echoIRCd advertises. The authoritative ISUPPORT string
|
||||
is:
|
||||
|
||||
```text
|
||||
PREFIX=(qaohv)~&@%+ CHANMODES=beIgXw,k,lfjFLHBJdK,ACDGMNOPQRSTUcimnpstuz
|
||||
EXTBAN=,Gbcgjmnrsy
|
||||
```
|
||||
|
||||
`CHANMODES` groups modes into four types: **A** = list modes (always take a mask),
|
||||
**B** = always take a parameter, **C** = take a parameter only when set, **D** =
|
||||
flags with no parameter.
|
||||
|
||||
## Channel status (prefix) modes
|
||||
|
||||
Granted to members; each has a name prefix shown before the nick.
|
||||
|
||||
| Mode | Prefix | Rank | Meaning |
|
||||
|------|--------|------|---------|
|
||||
| `+q` | `~` | owner | Channel owner — full control. |
|
||||
| `+a` | `&` | admin | Protected/admin — like op, can't be kicked/deopped by ops. |
|
||||
| `+o` | `@` | op | Channel operator. |
|
||||
| `+h` | `%` | halfop | Half-operator — a reduced op. |
|
||||
| `+v` | `+` | voice | May speak when the channel is `+m`. |
|
||||
|
||||
Network staff (IRC operators) can additionally carry a `!` prefix (mode `y`) via
|
||||
[operprefix](operators.md), which ranks above owner.
|
||||
|
||||
## List modes (type A)
|
||||
|
||||
Take a mask and maintain a list; `MODE #chan +b` with no argument lists entries.
|
||||
|
||||
| Mode | Name | Meaning |
|
||||
|------|------|---------|
|
||||
| `+b` | ban | Ban a `nick!user@host` mask (accepts [extbans](#extbans)). |
|
||||
| `+e` | ban exempt | A mask exempt from `+b`. |
|
||||
| `+I` | invite exempt | A mask that may join a `+i` channel without an invite. |
|
||||
| `+g` | filter | Block matching users from speaking (a channel-scoped mute list). |
|
||||
| `+X` | exempt-chanops | Masks whose members bypass selected restrictions. |
|
||||
| `+w` | auto-status | `+w <prefix>:<hostmask>` grants a status prefix on join, e.g. `+w o:*!*@trusted.host` (auto-op), `+w v:*!*@*.friend.net` (auto-voice). |
|
||||
|
||||
## Parameter modes
|
||||
|
||||
**Type B — always a parameter:**
|
||||
|
||||
| Mode | Meaning |
|
||||
|------|---------|
|
||||
| `+k <key>` | Channel key; must be supplied to join. |
|
||||
|
||||
**Type C — a parameter only when setting:**
|
||||
|
||||
| Mode | Meaning |
|
||||
|------|---------|
|
||||
| `+l <n>` | Member limit. |
|
||||
| `+f [*]<lines>:<secs>` | Message flood: kick past `lines` messages in `secs`; a leading `*` also bans. |
|
||||
| `+j <count>:<secs>` | Join flood throttle. |
|
||||
| `+F <count>:<secs>` | Nick-change flood throttle. |
|
||||
| `+L <#target>` | Redirect joiners here when the channel is full/keyed/invite-only. |
|
||||
| `+H <lines>:<secs>` | Replay recent messages to joiners (in-channel history). |
|
||||
| `+B <percent>` | Anti-caps: block messages that are more than `percent` uppercase. |
|
||||
| `+J <secs>` | Block rejoin for `secs` after a kick. |
|
||||
| `+d <secs>` | New joiners can't speak for `secs`. |
|
||||
| `+K <n>` | Block a line repeated within a member's last `n` messages. |
|
||||
|
||||
## Flag modes (type D)
|
||||
|
||||
No parameter.
|
||||
|
||||
| Mode | Meaning | | Mode | Meaning |
|
||||
|------|---------|-|------|---------|
|
||||
| `+i` | invite-only | | `+S` | strip formatting/colour |
|
||||
| `+m` | moderated (only `+ov` speak) | | `+R` | registered users only may join |
|
||||
| `+n` | no external messages | | `+M` | only registered users may speak |
|
||||
| `+p` | private (hidden from WHOIS) | | `+G` | censor configured bad words |
|
||||
| `+s` | secret | | `+u` | auditorium (hide non-ops) |
|
||||
| `+t` | only ops set the topic | | `+Q` | KICK disabled |
|
||||
| `+z` | TLS-only join | | `+A` | any member may INVITE |
|
||||
| `+O` | opers only may join | | `+P` | permanent (survives 0 members) |
|
||||
| `+N` | no nick changes while joined | | `+U` | op-moderated (unprivileged msgs go to ops) |
|
||||
| `+C` | block CTCP | | `+D` | delay-join (hide JOIN until they speak) |
|
||||
| `+T` | block NOTICEs | | `+c` | reject formatting/colour |
|
||||
|
||||
Channel modes can also be set or queried by long name with the `PROP` command
|
||||
(e.g. `PROP #chan moderated=on`).
|
||||
|
||||
## User modes
|
||||
|
||||
| Mode | Meaning |
|
||||
|------|---------|
|
||||
| `+i` | invisible (hidden from WHO / global `WHOIS` channel list) |
|
||||
| `+w` | receive `WALLOPS` |
|
||||
| `+o` | IRC operator (set only via `OPER`) |
|
||||
| `+x` | cloaked host (keyed masking; usually auto-set on connect) |
|
||||
| `+r` | logged into an account (server-set; can't be self-applied) |
|
||||
| `+z` | only accept private messages from TLS-connected users |
|
||||
| `+B` | flagged as a bot |
|
||||
| `+D` | deaf — ignore channel messages |
|
||||
| `+I` | hide your channel list in WHOIS |
|
||||
| `+H` | hide your oper status |
|
||||
| `+R` | block private messages from users not logged into an account |
|
||||
| `+g` | caller-id — only people you `ACCEPT` may message you |
|
||||
| `+W` | be notified when someone WHOISes you |
|
||||
| `+c` | block private messages from users with no common channel |
|
||||
| `+h` | available for help (helpop; oper-settable) |
|
||||
| `+s` | server-notice mask — see [snomasks](operators.md#snomasks) |
|
||||
|
||||
## Extbans
|
||||
|
||||
Extended bans extend any ban-style list (`+b`, `+e`, `+I`, `+g`, …) beyond plain
|
||||
host masks. `EXTBAN=,Gbcgjmnrsy` — there is no prefix character; a ban simply
|
||||
starts with the extban letter and a colon.
|
||||
|
||||
**Matching extbans** — match a user by something other than host, usable with any
|
||||
list mode:
|
||||
|
||||
| Extban | Matches |
|
||||
|--------|---------|
|
||||
| `g:<name>` | members of a named [security group](configuration.md). |
|
||||
| `y:<score>` | [reputation](configuration.md) score, e.g. `y:<100` (below 100), `y:>500`. |
|
||||
| `r:<mask>` | real name (GECOS). |
|
||||
| `j:<#chan>` | users who are also in `#chan`. |
|
||||
| `s:<mask>` | the server a user is on. |
|
||||
| `G:<cc>` | GeoIP country code, e.g. `G:CN,RU`. |
|
||||
| `b:<#chan>` | anyone banned in `#chan` (shares a ban list between channels). |
|
||||
|
||||
**Acting extbans** — change *what* matched users can do rather than blocking them
|
||||
outright (used with `+b`):
|
||||
|
||||
| Extban | Effect on matched users |
|
||||
|--------|-------------------------|
|
||||
| `m:<mask>` | muted — can't speak (but stay joined). |
|
||||
| `c:<mask>` | can't send formatting/colour. |
|
||||
| `n:<mask>` | can't change nick. |
|
||||
|
||||
Example — quarantine everyone from a bad network to read-only, and bounce a
|
||||
spammer to another channel:
|
||||
|
||||
```text
|
||||
/MODE #main +b m:*!*@*.spammer.net
|
||||
/MODE #main +b *!*@*.badhost$#quarantine
|
||||
```
|
||||
105
docs/operators.md
Normal file
105
docs/operators.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# Operators
|
||||
|
||||
IRC operators ("opers") are staff with elevated privileges. This page covers
|
||||
becoming an oper, the command toolbox, snomasks, and the ban ("X-line") system.
|
||||
|
||||
## Becoming an operator
|
||||
|
||||
Define oper accounts in the config:
|
||||
|
||||
```text
|
||||
oper = admin CHANGE_THIS_PASSWORD # plaintext (fine behind a private config)
|
||||
oper = helper sha256:<hex> # or a hashed password
|
||||
oper = root $2b$12$… # bcrypt is supported too
|
||||
```
|
||||
|
||||
A client authenticates with `/OPER <name> <password>`, gains user mode `+o`, and
|
||||
(optionally) a staff prefix — see [operprefix](configuration.md). Generate a
|
||||
hashed password with the oper-only `/MKPASSWD <algo> <password>` command
|
||||
(`md5`, `sha1`, `sha256`, `sha512`, `pbkdf2`, `bcrypt`). KDF hashes are verified
|
||||
off the core thread, so an `OPER` flood can't freeze the server.
|
||||
|
||||
### Oper levels
|
||||
|
||||
An `oper` block may carry a trailing numeric **level** (`oper = <name> <pass>
|
||||
<level>`). Levels gate sensitive actions — for example, a higher-level oper can't
|
||||
be `KILL`ed by a lower-level one. Levels are advisory policy layered on top of the
|
||||
`+o` flag.
|
||||
|
||||
## Snomasks
|
||||
|
||||
Server-notice masks (`+s`) subscribe an oper to categories of the server's live
|
||||
event stream — connects, floods, link events, and so on. Set them as a
|
||||
mode parameter, e.g. `/MODE yournick +s +ck`. The stream can also be mirrored to
|
||||
a channel (`chanlog`), a file (`log_json`), or the system logger (`syslog`).
|
||||
|
||||
## User & network management
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `KILL <nick> :<reason>` | Disconnect a user from the network. |
|
||||
| `WALLOPS :<msg>` | Message all `+w` users. |
|
||||
| `GLOBOPS :<msg>` | Message all opers. |
|
||||
| `SHUN <mask> [dur] :<reason>` | Silence a user (they stay connected but can't act). |
|
||||
| `CHECK <nick\|#chan\|mask>` | Deep inspection of a user, channel, or mask. |
|
||||
| `GEOIP <nick\|ip>` | Country lookup (needs `geoip_database`). |
|
||||
| `TLINE <mask>` | How many connected users a proposed ban mask would hit. |
|
||||
|
||||
## X-lines (bans)
|
||||
|
||||
Bans are persisted to disk and survive restarts. Expired entries are purged
|
||||
automatically.
|
||||
|
||||
| Command | Bans by | Scope |
|
||||
|---------|---------|-------|
|
||||
| `KLINE <mask> [dur] :<reason>` | user@host | this server |
|
||||
| `GLINE <mask> [dur] :<reason>` | user@host | whole network |
|
||||
| `ZLINE <ip> [dur] :<reason>` | IP / CIDR | whole network (cheapest — pre-DNS) |
|
||||
| `ELINE <mask> [dur] :<reason>` | user@host | exemption from other X-lines |
|
||||
| `QLINE <mask> [dur] :<reason>` | nick mask | reserve/forbid nicknames |
|
||||
| `CBAN <#mask> [dur] :<reason>` | channel name | forbid joining/creating |
|
||||
| `RLINE <regex> [dur] :<reason>` | `nick!user@host realname` regex | native regex engine |
|
||||
| `TBAN <#chan> <dur> <mask>` | a timed `+b` on one channel | auto-lifts |
|
||||
|
||||
Durations accept human forms (`1d`, `2h`, `30m`); `0` or omitted means permanent.
|
||||
|
||||
## Override toolbox
|
||||
|
||||
Force actions an ordinary user couldn't take. These change a target's identity or
|
||||
state directly.
|
||||
|
||||
| Command | Effect |
|
||||
|---------|--------|
|
||||
| `SANICK <nick> <new>` | Force a nick change. |
|
||||
| `SAJOIN <nick> <#chan>` / `SAPART` | Force join / part. |
|
||||
| `SAKICK <#chan> <nick>` | Force a kick. |
|
||||
| `SAMODE <target> <modes>` | Set modes with server authority. |
|
||||
| `SATOPIC <#chan> :<topic>` | Force a topic. |
|
||||
| `SAQUIT <nick> :<reason>` | Force a quit. |
|
||||
| `CHGHOST` / `CHGIDENT` / `CHGNAME` | Change a user's displayed host / ident / real name. |
|
||||
| `SETHOST` / `SETIDENT` / `SETNAME` | Change your *own* host / ident / real name. |
|
||||
| `SWHOIS <nick> :<line>` | Add a custom WHOIS line to a user. |
|
||||
| `NICKLOCK` / `NICKUNLOCK` | Freeze / release a user's nick. |
|
||||
| `CLEARCHAN <#chan>` | Clear a channel (kick everyone / reset it). |
|
||||
| `SETIDLE <secs>` | Adjust your reported idle time. |
|
||||
|
||||
## Services-side commands
|
||||
|
||||
These are the interface a linked services package drives (see
|
||||
[linking](linking.md)): `SVSNICK`, `SVSJOIN`, `SVSPART`, `SVSMODE`, `SVSLOGIN`,
|
||||
`SVSLOGOUT`, plus `SVSHOLD` / `SVSTOPIC` / `SVSOPER` / `SVSCMODE` and generic
|
||||
`ENCAP` / `METADATA`.
|
||||
|
||||
## Server management
|
||||
|
||||
| Command | Effect |
|
||||
|---------|--------|
|
||||
| `REHASH` | Re-read the config and apply every setting that can change at runtime. |
|
||||
| `CONNECT <server>` | Dial a configured uplink. |
|
||||
| `DIE` / `RESTART` | Shut down / restart the daemon. |
|
||||
| `MAP` / `LINKS` | Show the network topology (hideable from non-opers). |
|
||||
|
||||
## Diagnostics
|
||||
|
||||
`STATS <char>`, `SSLINFO <nick>` (TLS/cert details), `REPUTATION <nick\|ip>`,
|
||||
`SECURITYGROUPS`, and `FILTER` (manage spam/word filters at runtime).
|
||||
Loading…
Add table
Add a link
Reference in a new issue