> ## Documentation Index
> Fetch the complete documentation index at: https://crosslink.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Networking

> How Crosslink handles NAT, firewalls, and network topology

## Does Crosslink require port forwarding?

**No.** On the same network the phone dials the host's own address. From another
network, `networkMode: "remote"` asks the router for an inbound port over
NAT-PMP, PCP or UPnP — automatically, with nothing to configure. Where that is
impossible (carrier-grade NAT, a network you do not control), a relay carries
the connection over outbound sockets only.

## Does Crosslink require a central server?

**No.** With no services at all, a phone pairs and connects directly to the host
over the address in the QR:

```ts theme={null}
const server = createCrosslinkServer({
  application: { id: "com.example.app", name: "My App", version: "1.0.0" },
  lan: { enabled: true, bind: "all" }
});
```

There is no signaling service in that path, and none is required: the whole
claim / challenge / SAS / complete exchange runs on the host's own socket.

A signaling and relay pair is an *addition*, for phones that can reach neither
the host's LAN address nor a public one. See
[Connection modes](/guides/connection-modes) for the four host modes and
[Self-hosting](/guides/self-hosting) to run those services yourself.

## What happens when the host goes offline?

The client detects the disconnection and enters reconnect state:

```text theme={null}
connected -> offline -> reconnecting -> connected
                                 |
                                 v (after max retries)
                            offline (give up)
```

Offline call queueing: idempotent methods are queued and replayed after reconnection.

## Route priority

The pairing QR lists every route the host genuinely has, tagged by kind, and the
client tries them in this order:

```text theme={null}
1. lan     the host's address on this network — fastest, private, no third party
2. wan     a public address with a confirmed router mapping — direct, no relay
3. sig     a signaling service that brokers the connection
4. relay   a relay service that forwards encrypted frames
5. tunnel  a provider tunnel the developer opted into
```

A route appears in the QR only if it exists. A private address is only ever
tagged `lan`; it is never advertised as though it were reachable from the
internet.

Once connected, [WebRTC upgrade](/guides/node) can move traffic onto a direct
peer-to-peer DataChannel, which matters mainly for relayed sessions.

## Network topology support

### Same network (LAN)

```text theme={null}
Phone  <--ws://192.168.1.42:port-->  Desktop
```

* Host binds `ws://0.0.0.0:port` (with `bind: "all"`)
* Client discovers host via signaling presence or manual config
* Lowest latency, no external services

If the host machine is on more than one network at once (Wi-Fi + a wired
adapter, two Wi-Fi radios, a VPN, etc.), address auto-detection just grabs the
first non-internal interface it finds and may pick one your phone can't
reach. Pin the address explicitly instead:

```ts theme={null}
const server = createCrosslinkServer({
  lan: { enabled: true, bind: "all", host: "192.168.1.42" }
});
```

or set `CROSSLINK_LAN_HOST=192.168.1.42` in the environment -- no code
change needed. The reference PWA's dev server (`apps/demo-pwa`) reads the same
variable for its QR code.

### Cross-network (relay)

```text theme={null}
Phone  <--ws-->  Relay (internet)  <--ws-->  Desktop (behind NAT)
```

* Host allocates channel on relay
* Client joins via channel ID
* Works through CGNAT, firewalls, double-NAT
* Adds one network hop

### Mixed (LAN + relay)

```text theme={null}
Phone on same WiFi  <--ws://192.168.1.42-->  Desktop (LAN)
Phone on cellular    <--ws-->  Relay  <--ws-->  Desktop (relay)
```

Client automatically uses the best available transport.

## NAT traversal

Two independent mechanisms, used for different things:

**Getting an inbound port** (`networkMode: "remote"`) uses NAT-PMP (RFC 6886),
PCP (RFC 6887) and UPnP IGD, plus STUN (RFC 5389) to learn the public address.
The router's own report wins over STUN when they disagree, since a VPN changes
what STUN sees. Details and limits: [Remote access](/guides/remote-access).

**Getting through a NAT you cannot map** uses a relay: both sides make outbound
connections and the relay forwards encrypted frames between them. This works
through CGNAT, double NAT and restrictive firewalls, at the cost of one hop.
The relay only ever sees ciphertext.

## Firewall considerations

| Port      | Protocol         | Purpose                    | Required?              |
| --------- | ---------------- | -------------------------- | ---------------------- |
| 8081      | WebSocket + HTTP | Signaling                  | Only for cross-network |
| 8082      | WebSocket + HTTP | Relay                      | Only for cross-network |
| Ephemeral | WebSocket        | LAN host                   | Only for same-network  |
| 443       | HTTPS/WSS        | Production signaling/relay | In production          |

In production, signaling and relay run behind a reverse proxy on port 443 (WSS mandatory).

## Bandwidth

The relay adds overhead for encrypted traffic. Reduce relay usage by upgrading to WebRTC:

```ts theme={null}
import { tryUpgradeToWebrtc } from "@crosslink/webrtc-adapter";

await tryUpgradeToWebrtc(client.connection!, {
  createPeer: () => new RTCPeerConnection({ iceServers })
});
```

## Offline behavior

When the host is unreachable:

* **Idempotent RPC calls** are queued and replayed after reconnection
* **Non-idempotent calls** fail immediately with `not_connected`
* **Event subscriptions** are restored after reconnection
* **Transport hints** are persisted locally for faster reconnection
