Proof of concept mechanical port of ircnet/ircd to Rust as part of a bit about C being insecure for network services
0

Configure Feed

Select the types of activity you want to include in your feed.

ircd.rs / docs / superpowers / plans / 2026-06-03-s2s-protocol-docs.md
7.9 kB

Plan: Server-to-Server Protocol Document (docs/s2s.md)#

Context#

This repository is a strangler-fig port of IRCnet ircd 2.11.3 (the ircu/IRCnet lineage) from C to Rust. There is already a docs/design.md describing the internal architecture of the C side, but nothing documents the wire protocol two servers speak to each other. Someone implementing a pseudo-server, a services package, or the Rust port's link layer currently has to reverse-engineer it from ircd/s_serv.c, ircd/s_user.c, and ircd/channel.c.

The user wants a detailed, accurate S2S protocol reference written to docs/s2s.md, plus runnable Python demonstrating how to implement a peer "from scratch". Per the clarifying answers: cover the full S2S lifecycle, use per-section Python snippets in the prose, and ship one runnable minimal pseudo-server at docs/pseudoserver/main.py.

The protocol facts below were verified directly against the C source (file:line cited), so the doc reflects what this server actually emits — not generic RFC 1459 / TS6 lore.

Verified protocol facts (the doc's source of truth)#

  • Lineage / version: IRCnet ircd 2.11.3. PATCHLEVEL = "0211030000", IRC_VERSION = "2.11.3" (common/patchlevel.h:20-22). This is not ircd-hybrid/ratbox TS6 — no SVINFO, no :UID colon prefixes in the TS6 sense.
  • Framing: [:prefix] COMMAND [params...] [:trailing], BUFSIZE = 512 incl. CRLF (common/struct_def.h:57), max 15 params MPAR (common/parse.c:32).
  • Identifiers: SIDLEN = 4 chars, but a SID string is 3 chars (e.g. 1AB); UIDLEN = 9 = SID(3) + 6 digits (e.g. 1AB000001) (common/struct_def.h:50,64). SV_UID/SV_2_11 = 0x0001 is the capability bit gating the modern (UID) protocol.
  • Message table (common/parse.c:37-121): per-command handler array indexed by [server, client, oper, service, unregistered]. Server-relevant commands include SERVER, SMASK, UNICK, NJOIN, EOB, EOBACK, SQUIT, KILL, SAVE, MODE, NICK, QUIT, KICK, PART, ENCAP, SERVICE.
  • Handshake (outbound), ircd/s_serv.c:1044-1062:
    • PASS <password> <pass_version> IRC|<serveropts> <P?><j?><Z?>
    • SERVER <myname> 1 <my_sid> :<my_info>
  • PASS handler m_pass carries version in parv[2], options in parv[3] (ircd/s_user.c:3034-3079); version is parsed by check_version into the SV_UID bit.
  • SERVER handler m_server (ircd/s_serv.c:557-564): parv[1]=name, parv[2]=hop, parv[3]=SID (2.11), parv[4]=version, parv[5]=info. Establishment in m_server_estab; SID validated via sid_valid/find_sid.
  • Burst order (ircd/s_serv.c:1246-1377), emitted to a freshly linked peer:
    1. send_server_burst → for each known server: :<uplink_sid> SERVER <name> <hop+1> <sid> <verstr> :<info>, or :<uplink_sid> SMASK <sid> <verstr> when masked (s_serv.c:441-449).
    2. For each user: :<sid> UNICK <nick> <uid> <user> <host> <ip> <umodes> :<realname> (s_serv.c:1261-1268; + sent when umodes empty). Services via :<sid> SERVICE ....
    3. For each channel with members: :<sid> NJOIN <chan> :[@@|@|+]<uid-or-nick>,... (channel.c:979-1009, prefixes @@=uniqop, @=op, +=voice) then :<sid> MODE <chan> <modes> <params> + ban/except/invite/reop lists via send_channel_modes (channel.c:935-972). Empty channels: :<sid> NJOIN <chan> . (lock marker, s_serv.c:1308).
    4. End of burst: :<sid> EOB alone, or :<sid> EOB :<sid1>,<sid2>,... listing already-finished servers (s_serv.c:1315-1377). Peer replies :<sid> EOBACK.
  • Ongoing propagation uses source-prefixed relays (:<uid|sid> COMMAND ...) through sendto_serv_butone / sendto_match_servs / sendto_serv_v(..., SV_UID, ...): NICK (rename), MODE, TOPIC, KICK, PART, QUIT, KILL (UID target), SQUIT <target_sid> :<reason>, SAVE (nick-collision TS resolution), ENCAP.

Deliverables#

1. docs/s2s.md — the protocol reference#

Match docs/design.md house style: subsystem-organized prose, path:symbol / path:line references back into the C so readers can verify, fenced wire examples, and focused Python snippets inline. Proposed sections:

  1. Overview & lineage — what protocol this is (IRCnet 2.11.3), the SV_UID capability era, where it sits vs. RFC 1459 and TS6, and the one-paragraph lifecycle map (connect → PASS/SERVER → burst → EOB/EOBACK → steady-state propagation → SQUIT).
  2. Message framing — the [:prefix] CMD params :trailing grammar, 512-byte/15-param limits, CRLF, prefix forms (SID for servers, UID for users, bare name on legacy links). Snippet: a parse_line() / format_line() pair.
  3. Identifiers (SID / UID) — formats, allocation, why UIDs survive nick collisions, SV_UID negotiation. Snippet: UID allocator.
  4. The handshake — exact PASS + SERVER exchange, both directions, serveropts capability flag string, password verification, version → SV_UID parsing, error/ERROR responses. Snippet: build and send the handshake; parse the peer's.
  5. The netburst — ordered walkthrough: SERVER/SMASK, UNICK, SERVICE, NJOIN
    • channel MODE/lists, empty-channel lock, EOB/EOBACK. Document member-prefix encoding and the EOB server-list batching. Snippets: emit UNICK; emit/parse NJOIN.
  6. Steady-state propagation — prefix-relay model and the routing helpers; per-command subsections with exact syntax for NICK(rename), MODE, TOPIC, KICK, PART, QUIT, KILL, SAVE, ENCAP, PRIVMSG/NOTICE, PING/PONG.
  7. Network splitsSQUIT syntax/semantics, what state to tear down, ghost/SAVE timestamp resolution at a high level.
  8. Full command reference table — command → syntax → direction → handler file:line, built from common/parse.c:37-121.
  9. Pointer to docs/pseudoserver/main.py — how to run it against this ircd.

2. docs/pseudoserver/main.py — runnable minimal pseudo-server#

A single dependency-free (stdlib socket/selectors) Python 3 program, heavily commented, that:

  • Connects (or listens) and performs the PASS/SERVER handshake with SV_UID advertised.
  • Parses the incoming burst (SERVER/SMASK/UNICK/NJOIN/MODE) into in-memory network state (servers, users, channels).
  • Sends its own minimal burst: itself (already in SERVER), one pseudo-user via UNICK, then EOB; replies EOBACK.
  • Runs a read loop dispatching prefixed commands (PINGPONG, QUIT, NICK, MODE, PRIVMSG, SQUIT), logging each to stdout.
  • A __main__ with argparse: --connect host:port, --name, --sid, --password.

Mirrors the same line codec and message shapes used in the doc snippets so the two stay consistent.

Critical files#

  • Create: docs/s2s.md, docs/pseudoserver/main.py.
  • Reference (read-only, for accuracy): common/patchlevel.h, common/parse.c, common/struct_def.h, ircd/s_serv.c, ircd/s_user.c, ircd/channel.c, and docs/design.md (style).

Verification#

  • python3 docs/pseudoserver/main.py --help runs and shows usage (no import/syntax errors).
  • python3 -c "import ast; ast.parse(open('docs/pseudoserver/main.py').read())" parses.
  • Extract each ```python block from docs/s2s.mdandpython3 -c "compile(...)" it to confirm every snippet is syntactically valid.
  • Manually re-check each documented wire format / file:line citation against the C source listed above (the facts section is the checklist).
  • Optional end-to-end: boot the C ircd with a matching N/C-line config and point main.py --connect at it; confirm the handshake completes and a burst is received. (Will flag if the local build/config isn't readily available rather than claim success.)