Process Model#
xmppd uses a Postfix-inspired multi-process architecture. The master supervisor spawns and monitors isolated child processes, each handling a specific responsibility.
xmppd (master, runs as root)
├── xmppd-core (C2S, runs as jabber) ← N worker threads
├── xmppd-auth (authentication, runs as jabber)
└── xmppd-s2s (federation, runs as jabber)
Privilege separation: The master binds privileged ports (5222, 5269) while
running as root, then passes the file descriptors to child processes via
--listen-fd. Children drop privileges to the configured user immediately after
initialization.
Fault isolation: If xmppd-auth crashes, the master restarts it with
exponential backoff. Client connections in xmppd-core are unaffected during
the brief restart window (auth requests queue in the IPC buffer).
Thread-Per-Core#
xmppd-core uses a thread-per-core model for horizontal scaling within a
single process.
xmppd-core process
├── Worker 0 — kqueue + listener fd 5 + sessions 0..1023
├── Worker 1 — kqueue + listener fd 6 + sessions 0..1023
├── Worker 2 — kqueue + listener fd 7 + sessions 0..1023
└── Worker 3 — kqueue + listener fd 8 + sessions 0..1023
Each worker thread has:
- Its own kqueue event loop (no shared epoll/kqueue)
- Its own SO_REUSEPORT_LB listener (kernel distributes connections)
- Its own session slots (no cross-thread session access)
- Access to a shared SessionMap (JID-keyed, RwLock-protected)
Cross-thread message delivery uses MPSC ring buffers with pipe-based wakeup. When Worker 0 needs to deliver a message to a session on Worker 2, it enqueues the stanza into Worker 2's MPSC buffer and writes a byte to Worker 2's wake pipe.
IPC Protocol#
Communication between processes uses a binary IPC protocol over Unix domain sockets. Messages are length-prefixed (4 bytes LE + 1 byte tag + fields).
| Tag | Direction | Purpose |
|---|---|---|
| 0x01 | core → auth | Authentication request (username, mechanism, client IP) |
| 0x02 | auth → core | SASL challenge |
| 0x03 | auth → core | Authentication success |
| 0x04 | auth → core | Authentication failure |
| 0x05 | core → auth | SASL response |
| 0x0C | auth → core | Mechanism list (sent on IPC connect) |
| 0x10 | core → s2s | Outbound stanza delivery |
| 0x11 | s2s → core | Inbound stanza delivery |
Storage#
xmppd uses a comptime-generic storage subsystem. The StorageBackend trait
defines the interface, and multiple backends are available at build time:
| Backend | Use Case | Build Flag |
|---|---|---|
| LMDB | Operational data (default) | -Dop-storage=lmdb |
| RocksDB | Message archive (MAM) | (always used for archive) |
| SQLite | Lightweight alternative | -Dop-storage=sqlite |
| Memory | Testing only | (unit tests) |
Operational stores (LMDB by default):
UserStore— SCRAM credentials (salt, stored key, server key, iterations)RosterStore— Contact list with subscription state machineVCardStore— Raw vCard XML blobsRoomStore— MUC room configuration and affiliationsOfflineStore— Offline message queue with per-user capLockStore— Permanent account locksInviteStore— Registration invitation codes
Archive store (RocksDB):
ArchiveStore— XEP-0313 MAM with paginated query and retention policy
MUC Architecture#
Multi-User Chat is handled inline in xmppd-core (not a separate process).
Rooms are sharded across worker threads using hash(room_jid) % N_workers.
Each worker maintains:
- Owned rooms — full room state (occupants, config, mailbox)
- Shadow rooms — lightweight mirrors of rooms owned by other workers
Groupchat fan-out uses worker-level multicast: instead of N MPSC enqueues (one per remote occupant), xmppd sends one multicast message per remote worker. This makes fan-out O(workers) instead of O(occupants).
Connection Lifecycle#
1. TCP accept (SO_REUSEPORT_LB assigns to a worker)
2. XML stream open
3. STARTTLS negotiation (OpenSSL)
4. SASL authentication (via IPC to xmppd-auth)
5. Resource binding
6. Session established — stanza routing begins
7. Stream Management enabled (XEP-0198)
8. ...normal XMPP operation...
9. Stream close (or abnormal disconnect → SM resume window)