Skip to content

IRC over TCP in the app shell - #222

Draft
rdrake wants to merge 1 commit into
evilnet:developfrom
rdrake:raw-tcp
Draft

rdrake wants to merge 1 commit into
evilnet:developfrom
rdrake:raw-tcp

Conversation

@rdrake

@rdrake rdrake commented Nov 8, 2026

Copy link
Copy Markdown
Collaborator

What

The app (Tauri shell) can now connect to IRC networks over plain TCP or TLS, not just WebSocket, so users can join networks that have no WebSocket listener. In the connect form and the network editor, a new Connection choice (WebSocket / TCP) appears only inside the app. In a browser or the PWA nothing changes. Browsers have no raw-socket API outside managed ChromeOS, so they would need a gateway, which is out of scope here.

How

  • client/js/irc/shellSocket.ts: ShellSocket dresses a shell-made TCP socket as the part of a WebSocket that WsTransport uses (the new LineSocket interface). Retries, the probe, PING, the line cap and registration are shared unchanged.
  • shells/tauri/src-tauri/src/socket.rs: a Socket plugin built on tokio and rustls (ring, Mozilla roots). It splits and decodes lines (UTF-8, Latin-1 fallback) and emits them in batches.
  • Saved networks and STS carry transport: "tcp". STS policies are keyed tcp:host so a WebSocket policy never rewrites a TCP dial. Default ports are 6667/6697. The disconnect hints have TCP-specific wording.

Limits on the raw socket

The page can be made to dial anything, so the shell restricts what it will do:

  • refuses the Fetch bad-ports list, minus the IRC ports;
  • refuses CR, LF, NUL and empty lines;
  • sends only registration commands until the server's first line looks like IRC (a prefix or tags, or PING/NOTICE/ERROR with a parameter), which stops HTTP, SMTP and memcached smuggling;
  • a 512-line send queue, a 10 s write timeout, a 2 s drain on close, at most 16 sockets and 30 opens a minute (no LAN scanning through the shell);
  • a per-load page token, so a reload can't adopt the old page's sockets;
  • a 1 s grace on reload so the pagehide QUIT still goes out.

A failure a retry can't mend (an untrusted certificate, a server that isn't IRC; a certificate not valid yet is retried, since the device clock may be wrong) closes with code 4001, and the page shows the reason once instead of retrying. build.rs restricts the app's commands to seance_call, which capabilities/default.json grants to the main window only.

Testing

  • pnpm lint, pnpm typecheck, pnpm i18n:check and mocha all pass (new test/irc/shellSocket.ts, plus TCP cases in the client, saved-networks, STS and disconnect tests). cargo test passes (11 tests) and clippy is clean.
  • In the macOS app:
    • plain TCP to the dev ircd: registers, joins and sends a message;
    • disconnect, connect and quit work;
    • a reload sends the QUIT and leaves no orphan;
    • TLS to a self-signed ircd fails with a clear reason;
    • TLS to irc.libera.chat registers;
    • switching a saved TCP network back to WebSocket dials ws:// and drops transport from the saved entry.

Known limits

  • A WebSocket STS policy doesn't upgrade a first plain-TCP dial.
  • Incoming data has no backpressure: each read is one event to the page.
  • Android and iOS are untested.

The app shell can dial IRC over plain TCP or TLS (shells/tauri/src-tauri/src/socket.rs, tokio + rustls on ring with Mozilla roots), dressed as a WebSocket for WsTransport (client/js/irc/shellSocket.ts). A Connection select in the connect form and the network editor, shown only in the app. The shell gates what the page can do with a raw socket: bad ports, line checks, registration-only until the server speaks IRC, queue/timeout/socket/open-rate caps, a page token per load. Untrusted certificates are final, not retried; seance_call is the only app command and only the main window may call it.

This branch was previously deployed

1 inactive deployment
github-pages — f696e176 Deployed Nov 8, 2026 by rdrake via deploy #765
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant