readme: polished front-page layout — badges, about, grouped features, quick start, links
This commit is contained in:
parent
75de56f532
commit
02b2061561
1 changed files with 101 additions and 166 deletions
265
README.md
265
README.md
|
|
@ -1,189 +1,124 @@
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
# echoIRCd
|
# echoIRCd
|
||||||
|
|
||||||
A from-scratch IRC daemon written in Rust. Commands are objects, modes are
|
**A from-scratch, memory-safe IRCv3 server written in Rust.**
|
||||||
handler objects, and modules hook lifecycle events. Design goals:
|
|
||||||
`#![forbid(unsafe_code)]`, dependency-light (just two small crates — `openssl`
|
|
||||||
for TLS and `mio` for the epoll socket engine), and lock-free (a single core
|
|
||||||
thread owns all state).
|
|
||||||
|
|
||||||
> Status: capable and broad. It speaks a large slice of the IRC + IRCv3 protocol
|
[](Cargo.toml)
|
||||||
> — **100+ commands**, the full channel/user mode set, **28 IRCv3 capabilities**,
|
[](https://www.rust-lang.org)
|
||||||
> and **~55 pluggable modules** — with server-to-server linking, a services
|
[](src/lib.rs)
|
||||||
> interface, TLS, WebSocket, GeoIP and a JSON-RPC control plane. One reactor thread
|
[](https://ircv3.net)
|
||||||
> has served 5,000 concurrent connections in testing. Not battle-tested yet.
|
[](Cargo.toml)
|
||||||
|
|
||||||
## Run it
|
</div>
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
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**,
|
||||||
|
**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.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Full IRC core** — registration, channels (`JOIN`/`PART`/`KICK`/`INVITE`/
|
||||||
|
`KNOCK`/`CYCLE`/`REMOVE`/`TOPIC`), messaging (`PRIVMSG`/`NOTICE`/`TAGMSG`, CTCP),
|
||||||
|
and info (`WHO`/`WHOIS`/`WHOWAS`/`LIST`/`STATS`/`MAP`/`LUSERS`/`MOTD`).
|
||||||
|
- **Complete mode set** — prefixes `qaohv` (+ a network-staff `!` prefix), list
|
||||||
|
modes `beIgXw`, keyed/limit/flood/redirect/history/anticaps params, the full flag
|
||||||
|
set, all the standard user modes, and matching + acting **extbans**
|
||||||
|
(`g y r j s G b`, `m c n`).
|
||||||
|
- **IRCv3** — 28 capabilities including `message-tags`+`msgid`, `server-time`,
|
||||||
|
`labeled-response`, `batch`, `echo-message`, `account-tag`, **CHATHISTORY**,
|
||||||
|
**multiline**, **message-redaction**, **read-marker**, **relaymsg**, and
|
||||||
|
`WATCH`/`MONITOR`/`SILENCE`/callerid.
|
||||||
|
- **Operators** — `OPER`/`KILL`/`WALLOPS`/`GLOBOPS`, the `SA*`/`CHG*`/`SET*`
|
||||||
|
override toolbox, x-lines (`K`/`G`/`Z`/`E`/`SHUN`/`QLINE`/`CBAN`) persisted to
|
||||||
|
disk, staff prefix (`operprefix`/`OJOIN`), rank-gated `hidelist`/`hidemode`, and
|
||||||
|
a reload-safe `REHASH`.
|
||||||
|
- **Services & accounts** — SASL PLAIN/EXTERNAL relayed over S2S, the `SVS*` /
|
||||||
|
`ENCAP` / `METADATA` interface, account-gated modes, and optional ircd-side
|
||||||
|
account registration.
|
||||||
|
- **Server-to-server linking** — `UID`/`FJOIN` netburst, cross-server users and
|
||||||
|
channels, multi-hop routing, nick-collision handling and clean netsplit.
|
||||||
|
- **Security & anti-spam** — TLS with cert fingerprints, keyed host cloaking,
|
||||||
|
DNSBL, connection/message flood limits, mixed-script & gibberish detection,
|
||||||
|
CAPTCHA / PONG-cookie / arithmetic gates, and DCC filtering.
|
||||||
|
- **GeoIP** — a native MaxMind `.mmdb` reader with a `G:<cc>` geoban, `GEOIP`
|
||||||
|
command, and WHOIS country line.
|
||||||
|
- **Transports & control** — a native WebSocket layer (`ws://` / `wss://`), a
|
||||||
|
from-scratch forward-confirmed DNS resolver, and a token-authenticated JSON-RPC
|
||||||
|
control plane over HTTP.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cp echoircd.conf.example echoircd.conf # then edit: oper pass, cloak_key, TLS paths
|
git clone https://git.devtronic.pro/fedserv/echoIRCd
|
||||||
|
cd echoIRCd
|
||||||
|
cp echoircd.conf.example echoircd.conf # edit: oper pass, cloak_key, TLS paths
|
||||||
cargo run --release # reads ./echoircd.conf
|
cargo run --release # reads ./echoircd.conf
|
||||||
# point a client at it, e.g. /server 127.0.0.1 6667
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Config is plain `key = value` (see `echoircd.conf.example`). Your real
|
Then point a client at it: `/server 127.0.0.1 6667` (or `6697` for TLS once a
|
||||||
`echoircd.conf` is gitignored because it holds secrets (oper password, cloak
|
certificate is configured).
|
||||||
key, link password) — never commit it. For TLS, generate a cert/key into `tls/`
|
|
||||||
(the example config has the one-liner).
|
## Configuration
|
||||||
|
|
||||||
|
Configuration is a plain `key = value` file; see
|
||||||
|
[`echoircd.conf.example`](echoircd.conf.example) for the full, documented set of
|
||||||
|
keys. Your live `echoircd.conf` is gitignored — it holds secrets (oper password,
|
||||||
|
cloak key, link password), so never commit it. Generate a TLS certificate into
|
||||||
|
`tls/` with the one-liner in the example config.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
A single **core thread** owns every `User` and `Channel`, so command and module
|
A single **core thread** owns every `User` and `Channel`, so command and module
|
||||||
code is plain single-threaded logic over `&mut Server` — no `Arc<Mutex<…>>`
|
code is ordinary single-threaded logic over `&mut Server` — no `Arc<Mutex<…>>`
|
||||||
anywhere. The I/O edge feeds it events over mpsc channels:
|
anywhere. The I/O edge feeds it events over channels:
|
||||||
|
|
||||||
- **Client connections run on one `mio` epoll reactor thread.** The daemon drives
|
- **One `mio` epoll reactor** drives all client sockets — measured at 5,000
|
||||||
tens of thousands of sockets without a thread per connection — measured at 5,000
|
concurrent clients on 4 threads total, scaling toward ~50k with a release build
|
||||||
concurrent clients on **4 threads total**, and it scales toward ~50k (use a
|
and a high `LimitNOFILE`.
|
||||||
release build and a high `LimitNOFILE`). It's a bare epoll/kqueue readiness
|
- **TLS sessions and server links** run a thread each; both hand the core the same
|
||||||
reactor — no async runtime is pulled in, so the single-threaded core is
|
`OutSink`, so it never knows which transport a connection uses.
|
||||||
untouched.
|
|
||||||
- **TLS and server links** keep a thread per connection — there are few of them,
|
|
||||||
and a TLS session can't be split across reader/writer threads.
|
|
||||||
|
|
||||||
Both models hand the core the same `OutSink`, so it never knows or cares which one
|
**Why a raw reactor and not async?** IRC is one large shared mutable graph, and
|
||||||
a connection uses.
|
almost every command mutates it and then broadcasts. With one thread owning all of
|
||||||
|
it, handlers are plain synchronous code — no locks, no `.await`, no `Send + 'static`
|
||||||
|
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.
|
||||||
|
|
||||||
Memory-safety by design: `Uid` handles instead of raw pointers (no use-after-free,
|
Memory safety is structural: `Uid` handles instead of raw pointers, an `Extensible`
|
||||||
no cull list), an `Extensible` typemap instead of `void*` module data (freed
|
typemap instead of `void*` module data (freed on drop), and compiled-in trait
|
||||||
automatically on drop), `&str` slices, and compiled-in trait objects instead of a
|
objects instead of a fragile plugin ABI.
|
||||||
fragile `.so` ABI.
|
|
||||||
|
|
||||||
### Why a raw reactor, not async?
|
## Extending
|
||||||
|
|
||||||
IRC is one big shared mutable graph (users, channels, the nick index), and almost
|
Three small extension points, each one file + one table line:
|
||||||
every command mutates it and then broadcasts. With one thread owning all of it,
|
|
||||||
handlers are plain `&mut Server` code — no locks, no `.await`, no `Send + 'static`
|
|
||||||
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 locking cost for parallelism the workload can't use. `mio` is the same
|
|
||||||
readiness layer async runtimes are built on, so you keep the C50k scaling without
|
|
||||||
the runtime. CPU-heavy or blocking work (DNS, TLS, outbound HTTP) is pushed to its
|
|
||||||
own threads; the network scales out by **linking servers**, not by adding cores to
|
|
||||||
one process.
|
|
||||||
|
|
||||||
### The two extension points
|
- **Commands** (`src/command.rs`, `src/coremods/`) — a handler with `name`,
|
||||||
|
`min_params`, `before_reg`, `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.
|
||||||
|
|
||||||
- **Commands** (`src/command.rs`, `src/coremods/`) — a handler declares `name`,
|
## Links
|
||||||
`min_params`, `before_reg` and `handle(&mut Server, uid, params)`, registered
|
|
||||||
in `command_table()`. Adding a command is one struct + one table line.
|
|
||||||
- **Modes** (`src/mode.rs`) — channel/user modes are handler objects
|
|
||||||
(`ChanMode` / `UserMode`) in a table; adding a mode never touches the parser.
|
|
||||||
- **Modules** (`src/module.rs`, `src/modules/`) — lifecycle hooks. *Pre-hooks*
|
|
||||||
(`on_user_register`, `on_pre_command`, `on_pre_message`) return a `ModResult`
|
|
||||||
and can **Deny**; *notify-hooks* fire from a queue after the command.
|
|
||||||
|
|
||||||
## What works
|
- **Repository** — <https://git.devtronic.pro/fedserv/echoIRCd>
|
||||||
|
- **Issues** — <https://git.devtronic.pro/fedserv/echoIRCd/issues>
|
||||||
### Registration & session
|
- **Config reference** — [`echoircd.conf.example`](echoircd.conf.example)
|
||||||
- `CAP` negotiation, `NICK`/`USER`, `PING`/`PONG` with configurable idle +
|
|
||||||
registration timeouts, welcome burst (001–005) + ISUPPORT (optionally batched).
|
|
||||||
- `WEBIRC` (real client IP from a trusted web gateway), `conn_waitpong` (require a
|
|
||||||
PONG cookie before registering — filters bots), `autodrop` (silently drop
|
|
||||||
pre-registration HTTP scanners), configurable nick/channel length limits.
|
|
||||||
|
|
||||||
### Channels
|
|
||||||
- `JOIN`/`PART`/`NAMES`/`TOPIC`/`KICK`/`INVITE`, plus `KNOCK`, `CYCLE`, `REMOVE`,
|
|
||||||
`UNINVITE`.
|
|
||||||
- Bans / excepts / invex, ban **redirect** (`+b mask$#chan`), and matching +
|
|
||||||
acting **extbans** (below).
|
|
||||||
|
|
||||||
### Messaging
|
|
||||||
- `PRIVMSG`/`NOTICE`/`TAGMSG`, CTCP handling, `echo-message`, per-message `msgid`,
|
|
||||||
`server-time`, `account-tag`.
|
|
||||||
- **CHATHISTORY** (`draft/chathistory`: `LATEST`/`BEFORE`/`AFTER`/`AROUND`/`BETWEEN`
|
|
||||||
/`TARGETS`) with the `+H` join backlog, **REDACT** (`draft/message-redaction`),
|
|
||||||
**MARKREAD** (`draft/read-marker`), and **multiline** (`draft/multiline`).
|
|
||||||
- **RELAYMSG** (`draft/relaymsg`) — bridge messages under a spoofed relay nick.
|
|
||||||
|
|
||||||
### The full mode set
|
|
||||||
- **Prefixes** `+qaohv` (`~&@%+`), plus an optional network-staff prefix `+y` (`!`)
|
|
||||||
above owner (`operprefix` / `OJOIN`).
|
|
||||||
- **List modes** `+b` ban, `+e` except, `+I` invex, `+g` word filter, `+X`
|
|
||||||
exemptchanops, `+w` auto-status.
|
|
||||||
- **Parametered** `+k` key, `+l` limit, `+f` message-flood, `+j` join-flood, `+F`
|
|
||||||
nick-flood, `+L` redirect-when-full, `+H` history, `+B` anticaps, `+J`
|
|
||||||
kick-no-rejoin, `+d` delay-msg, `+K` no-repeat.
|
|
||||||
- **Flags** `+imnpstz`, `+O` oper-only, `+N` no-nick, `+C` no-CTCP, `+T` no-notice,
|
|
||||||
`+c` no-colour, `+S` strip-colour, `+R` reg-only, `+M` reg-moderated, `+G` censor,
|
|
||||||
`+u` auditorium, `+Q` no-kicks, `+A` allow-invite, `+P` permanent, `+U`
|
|
||||||
op-moderated, `+D` delay-join.
|
|
||||||
- **User modes** `+i w o x s g` plus `+B` bot, `+D` deaf, `+I` hide-chans, `+H`
|
|
||||||
hide-oper, `+r` logged-in, `+R` reg-only-PM, `+z` TLS-only-PM, `+W` show-whois,
|
|
||||||
`+h` helpop, `+c` common-chans-only.
|
|
||||||
- **Extbans** — matching `g:` security-group, `y:` reputation, `r:` realname,
|
|
||||||
`j:` in-channel, `s:` server, `G:` country, `b:` other-channel's ban list; acting
|
|
||||||
`m:` mute, `c:` no-colour, `n:` no-nick.
|
|
||||||
|
|
||||||
### Operators
|
|
||||||
- `OPER`/`KILL`/`WALLOPS`/`GLOBOPS`, snomasks (`+s`) with an optional `chanlog`.
|
|
||||||
- Overrides: `SAJOIN`/`SAPART`/`SANICK`/`SAMODE`/`SATOPIC`/`SAKICK`/`SAQUIT`,
|
|
||||||
`CHGHOST`/`CHGIDENT`/`CHGNAME`/`SETHOST`/`SETIDENT`/`SETIDLE`, `NICKLOCK`/
|
|
||||||
`NICKUNLOCK`, `SWHOIS`, `CHECK`, `CLEARCHAN`, `ALLTIME`, `OPERMOTD`, `VHOST`,
|
|
||||||
`TITLE`, oper-override-with-accountability, `operprefix`/`OJOIN`, `hidelist`/
|
|
||||||
`hidemode`.
|
|
||||||
- **X-lines** `KLINE`/`GLINE`/`ZLINE`/`ELINE`/`SHUN`/`QLINE`/`CBAN`, persisted to
|
|
||||||
disk and restored on boot; `STATS`; on-demand `CONNECT`; `DIE`/`RESTART`; and a
|
|
||||||
reload-safe **`REHASH`** (keeps the running config if the file can't be read).
|
|
||||||
|
|
||||||
### IRCv3 capabilities
|
|
||||||
`sasl`, `server-time`, `message-tags` (+ `msgid`), `multi-prefix`, `away-notify`,
|
|
||||||
`account-notify`, `extended-join`, `chghost`, `userhost-in-names`, `echo-message`,
|
|
||||||
`invite-notify`, `setname`, `extended-monitor`, `account-tag`, `standard-replies`,
|
|
||||||
`labeled-response`, `batch`, `cap-notify`, and drafts `chathistory`,
|
|
||||||
`message-redaction`, `pre-away`, `metadata-2`, `multiline`, `account-registration`,
|
|
||||||
`json-log`, `extended-isupport`, `relaymsg` — plus `WATCH`/`MONITOR`/`SILENCE` and
|
|
||||||
`ACCEPT` (+ `+g` callerid).
|
|
||||||
|
|
||||||
### Services interface & accounts
|
|
||||||
- **SASL** PLAIN + EXTERNAL (TLS client-cert), relayed to an external services
|
|
||||||
server over S2S.
|
|
||||||
- `SVSNICK`/`SVSJOIN`/`SVSPART`/`SVSMODE`/`SVSLOGIN`/`SVSLOGOUT`, `ENCAP`,
|
|
||||||
`METADATA`; account-gated modes (`+r`/`+R`/`+M`). The ircd is *ready* for an
|
|
||||||
external services package — it is not one itself.
|
|
||||||
- Optional ircd-side account **registration** (`REGISTER`/`VERIFY` over an HTTP
|
|
||||||
API), CAPTCHA gating, and `EXTJWT` / file-host tokens.
|
|
||||||
|
|
||||||
### Server-to-server linking
|
|
||||||
Handshake, `UID`/`FJOIN` netburst, cross-server users and channels, multi-hop
|
|
||||||
routing, nick-collision handling, and clean netsplit.
|
|
||||||
|
|
||||||
### Anti-abuse
|
|
||||||
`antimixedutf8` (look-alike script spam), `antirandom` (gibberish nicks), message
|
|
||||||
flood (`+f`) + global rate limits, `connflood`/`connectban` (connection floods),
|
|
||||||
`blockamsg`, `securelist`, `dnsbl`, CAPTCHA / challenge gating, `solvemsg`
|
|
||||||
(arithmetic gate), `dccallow` (DCC filtering), `conn_waitpong`, `autodrop`.
|
|
||||||
|
|
||||||
### GeoIP
|
|
||||||
A **native MaxMind `.mmdb` reader** (no crate): the `G:<cc>` geoban extban, a
|
|
||||||
`GEOIP` oper command, and a country line in WHOIS.
|
|
||||||
|
|
||||||
### TLS, cloaking, transports
|
|
||||||
- **TLS** (openssl) with `SSLINFO`, cert fingerprints, and `+z` secure-only.
|
|
||||||
- Keyed-SHA-256 host **cloaking** (`+x`).
|
|
||||||
- A **native WebSocket transport** (`ws://` and `wss://`) with real-IP / scheme
|
|
||||||
behind a trusted proxy.
|
|
||||||
- Reverse-DNS on connect via a from-scratch forward-confirmed PTR resolver over
|
|
||||||
UDP (no DNS crate), off the core thread, fail-safe to the IP.
|
|
||||||
|
|
||||||
### JSON-RPC control plane
|
|
||||||
A native inbound HTTP server + JSON-RPC interface (no `serde`, no `hyper`) with
|
|
||||||
token auth and ~30 methods across core/user/channel/server/stats/ban/message.
|
|
||||||
|
|
||||||
### Native building blocks
|
|
||||||
Everything is hand-rolled to stay dependency-light and `unsafe`-free: the DNS
|
|
||||||
resolver, JWT (HS256), JSON scan/build, the mmdb reader, an HTTP client, and
|
|
||||||
password hashing (`md5`/`sha1`/`sha256`/`sha512`/`pbkdf2`).
|
|
||||||
|
|
||||||
### Everything is configurable
|
|
||||||
Limits, thresholds, durations, timeouts and list sizes are all config keys — the
|
|
||||||
literal in code is only the default; nothing operational is hardcoded.
|
|
||||||
|
|
||||||
## Originality
|
|
||||||
|
|
||||||
echoIRCd is original Rust — no code is copied or translated from any other
|
|
||||||
project. `scripts/native-rust-guard.sh` enforces this (no `unsafe`, no C/FFI, and
|
|
||||||
dependencies limited to `openssl` + `mio`); it runs on every edit.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
See the repository for licensing.
|
echoIRCd is released under the [MIT License](Cargo.toml). It is original Rust —
|
||||||
|
no code is copied or translated from any other project, enforced on every edit by
|
||||||
|
`scripts/native-rust-guard.sh` (no `unsafe`, no C/FFI, dependencies limited to
|
||||||
|
`openssl` + `mio`).
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue