Network and ports
Tealhand keeps listening sockets to a minimum. On a computer there is one: iroh’s QUIC socket, plus the MCP
endpoint on 127.0.0.1 only while you allow AI apps. The phone has that socket too, and a small playback server on 127.0.0.1 that runs only while a file plays.
What listens where
Section titled “What listens where”| Part | Protocol | Address | Who gets through |
|---|---|---|---|
| Phone and computer | QUIC over UDP (iroh) | All interfaces (0.0.0.0 and [::]). On a computer the port is chosen once, saved in the data folder and reused at every start, so a connection QR stays valid; --port <n> sets it, and a taken port is replaced by a free one that is saved. The phone takes a free port |
Only devices on the device list, plus a new phone while a QR code is showing |
| Phone and computer | mDNS (multicast DNS) on the local network | The local network | Announces the device’s key and local IP addresses, no name. Only with your own relay or no relay |
| Tealhand Host, only while AI apps are on | HTTP over TCP (MCP’s Streamable HTTP, POST /mcp) |
127.0.0.1 only, the port saved in mcp.json in the data folder (a free one when it is taken). Off deletes it |
A loopback peer with the bearer token from the OS credential store, a Host of 127.0.0.1:<port> or localhost:<port>, and no other Origin. At most 16 connections and 1 MiB per request |
| Tealhand Host | nothing else | No other TCP port, no local control port or pipe | |
| Phone | HTTP over TCP, for the media player | 127.0.0.1 only, a port the system picks, started with the first file played and stopped with the last. A random token in each address |
The player on the same phone: only the exact address with its token and a Host of 127.0.0.1 |
| Tealhand Relay | HTTPS over TCP | relay_bind (example 203.0.113.10:443; open TCP 443 in the firewall) |
Devices listed in a stored device list of an allowed owner |
| Tealhand Relay | HTTPS over TCP | roster_bind (example 203.0.113.10:8443; open TCP 8443 in the firewall) |
Anyone may try an upload; only valid lists of allowed owners are stored |
| Tealhand Relay | QUIC address discovery over UDP | quic_bind, optional (example 203.0.113.10:7842; port 7842 is what devices expect) |
Devices learning their public address |
| Tealhand Relay | HTTP over TCP | http_bind, optional; without it 127.0.0.1 on a port the system picks |
A captive-portal probe only |
| Tealhand Planner | HTTP over TCP | --bind, required (example 127.0.0.1:8787) |
Anyone who can reach it. No authentication yet: keep it private |
| Ollama (for the planner) | HTTP over TCP | 127.0.0.1:11434 (the planner’s default) |
The planner only. Keep it on loopback |
Relay and planner refuse unspecified bind addresses (0.0.0.0, ::), so they never listen on every interface by
accident. A relay on a cloud server behind address translation binds the server’s private IP and is reached on the
public one. Ports to open on a relay server: TCP 443, TCP 8443, and UDP 7842 only with quic_bind; TCP 80 only while
certbot’s standalone mode runs. On a computer, the one UDP port above, for the local network.
Outgoing connections
Section titled “Outgoing connections”| From | To | When |
|---|---|---|
| Phone, computer | Each other, over iroh QUIC | Always; directly when possible |
| Phone, computer | Your relay (https://), checked against the public roots, or against the one certificate whose fingerprint you pinned |
With your own relay |
| Phone, computer | n0’s public relays and address lookup | With public relays only |
| Phone, computer | The home router (UPnP, NAT-PMP, PCP) | With public relays only |
| Phone | Your relay’s upload address (its roster_bind address), POST /tealhand/v1/roster, with the same certificate rule |
On every app start, on a reconnect (at most every 10 minutes), after every device list change, and on a retry timer (1 to 10 minutes) after a failure |
| Phone | The planner, POST /v1/plan |
Only when you ask with a planner set up |
The phone’s HTTP client speaks https:// (with its own bundled root certificates). Only for the planner it also
accepts plain http://, to a loopback or private IP address. Both relay addresses, the relay and the upload
address, must be https://, with no exception. It follows no redirects, uses no proxy or cookies, and refuses
answers over 64 KiB. Addresses with a user name or password are refused.
Protocol names (ALPNs)
Section titled “Protocol names (ALPNs)”iroh picks the protocol once per connection, so there is one connection per pair of devices and protocol.
| ALPN | Use |
|---|---|
tealhand/pair/1 |
Connecting a new phone. Accepted only while a QR code is showing |
tealhand/ctl/1 |
Status, actions, rules, events and device list updates. Members only |
tealhand/files/1 |
Shared folders: browsing, range reads, uploads. Members only, at most 2 connections per phone |
tealhand/ssh/1 |
The terminal. Members only, and only while SSH access is on at the computer; each stream starts with a signed opener |
tealhand/blobs/1, tealhand/llm/1, tealhand/cam/1 |
Reserved for later features. No messages yet |
Limits
Section titled “Limits”Frames: every message is at most 64 KiB (65,536 bytes). A frame whose length prefix is larger is refused before its body is read, and the stream is closed. File data is not a frame: it travels as raw bytes in pieces of at most 64 KiB.
Mesh (phone and computer)
| Limit | Value |
|---|---|
| Connection attempts per source, before the handshake | 30 per 10 seconds (IPv6 per /64), in a table of 4096 sources |
| Handshakes admitted in total | 100 per second (members seen before, and members coming through a relay, bypass this budget) |
| Connection attempts from a new phone open at once | 4 |
| Age of such a connection | 150 seconds (the 2-minute connection plus slack), then closed |
| Check of live connections against the device list | Every 5 seconds |
Control stream (ctl/1, on the computer)
| Limit | Value |
|---|---|
| Time to open the stream and say Hello | 10 seconds |
| Silence from a phone that is not watching status | 120 seconds, then closed |
| Requests per connection | 30 per 10 seconds; more get Busy |
| Connections per phone | 4 |
| Status subscriptions per connection | 1, with a push every 5 seconds |
| A write that takes too long | 10 seconds, then closed |
Files stream (files/1, on the computer)
| Limit | Value |
|---|---|
| Time to open the stream and say Hello | 10 seconds |
| Connections per phone | 2 |
| Transfers at once on a connection | 8 |
| Session requests / signed requests per connection | 100 / 30 per 10 seconds; more get Busy |
| A session with no activity | 10 minutes, then closed |
| A transfer with no progress | 60 seconds, then reset |
| A listing | pages of at most 1000 entries (and 48 KiB); a folder of more than 100,000 entries is not listed |
| Largest upload; space that must stay free | 16 GiB; 1 GiB |
| Shared folders | 64 |
Downloads (on the computer): one at a time; largest 8 GiB; 1 GiB must stay free; link at most 2048 bytes, port 80, 443 or 1024 and up; at most 5 redirects; 15 seconds to connect and 60 seconds without data; up to 5 resumes.
Playback server (on the phone): at most 4 files open, 16 player connections, 10 seconds for a request, and a token ends after 30 minutes without use.
Phone side: the phone reconnects with a backoff of up to 30 seconds (it does not retry sooner when the
computer closed the connection with a refusal such as NotOnRoster or VersionTooOld), treats 25 seconds of
silence from a watched computer as a lost link, waits up to 20 seconds for an answer, gives each command a
60-second lifetime, keeps a card for 5 minutes, and waits up to 40 seconds for the key store to sign. A relay
upload times out after 15 seconds, and a planner request after 35.
Connecting a phone: QR valid 10 minutes, one use; the whole connection 2 minutes; 5 wrong attempts burn the QR.
Relay upload: 30 requests a minute per source, 6 open connections per source, 24 per IPv6 /48, 256 in total, 10 seconds per connection, 32,896 bytes per body. Relay connections: 4 per device (configurable), 8,000,000 bytes per second received per client connection (configurable).
Planner: 8 KiB per request, 500 characters per sentence, 1 to 16 devices, 1 to 3 steps, 30 seconds per request, 2 model calls at once, 10 requests in a burst and 30 a minute per client (IPv6 per /64).