When Two Devices Cannot Simply Connect
For RustDesk, the first technical problem is not drawing the remote screen. It is finding a viable path between two endpoints that may each be hidden behind a router, firewall, proxy, or private address. This chapter focuses on how RustDesk's rendezvous mediator turns an ID into connection choices, and why the system prefers a direct path when possible while retaining a relay when reachability demands one. The practical result is a way to tell coordination, transport, and failure apart before treating any connection as trusted.
Rendezvous does not carry the desktop
A peer ID is a stable way for a person or an account to identify a machine. It is not normally a public address that another machine can simply dial. A laptop may have a private address inside a home network; a workstation may be behind a corporate gateway; a mobile device may change networks; and the same endpoint may appear at a different public address after a reconnect. RustDesk therefore needs a rendezvous layer: a place where online peers register enough information for another peer to ask how a connection might be attempted.
The protocol makes the separation visible. A controller sends a request containing the target ID, the connection type, network information such as NAT classification, and the direct transports that are available in that attempt. It can also carry an optional token or server key and, when WebRTC is being used, an offer for the other endpoint to answer. These are coordination facts. They describe the request and the possible route; they are not the screen frames or keyboard events that make up the remote session.
The controlled endpoint remains an active participant. It registers with rendezvous, reports its observed or usable network state, and responds to the controller's request. If both devices are on the same intranet, the mediator can help them exchange a local address rather than forcing them through a public route. If the endpoints can open compatible mappings, they can attempt a direct path. If the network conditions make that unreliable, the controlled side can ask the relay path to be prepared. Rendezvous is thus a meeting coordinator, not a universal data tunnel.
Registration is also a continuing state, not a one-time directory entry. The mediator expects peers to keep the relationship alive and can update connection configuration or server choices. RustDesk's code tracks whether the rendezvous service has confirmed the endpoint's public identity key and can retry registration when a deployment-required response says that an administrator must first enroll the device. This is important for interpreting “online”: the label comes from a coordination relationship that can expire, be refreshed, or fail independently of the endpoint's ability to capture a screen.
That distinction matters for control. Moving the rendezvous service from a public host to a self-hosted endpoint changes who operates the coordinator and where its availability depends. It does not change the fact that the two RustDesk endpoints still have to establish their own route and later authenticate their session. A coordinator can tell one peer where to try; it does not turn an arbitrary route into permission to control the other machine.
From local address to relay fallback
The connection attempt starts with the least complicated path that the current network can support. A same-intranet connection may use a local address. This avoids a common problem in which two devices behind the same router cannot successfully “punch” through the router by connecting to their own public mappings. The local case is not a different product feature; it is a topology-specific answer to the question of where the peer can be reached.
When the endpoints are separated by address translation, RustDesk can try UDP or TCP hole punching. In simple terms, each endpoint first creates an outbound mapping that makes the gateway recognize traffic for the peer. The endpoints then attempt to use the mappings at the same time. The connection code retains the local address and, for TCP punching, uses socket reuse so the outgoing attempt and the listening side can cooperate. This is why the result depends on gateway behavior rather than on the application alone.
A punch is not one magic packet followed by an immediate verdict. A gateway may discard an unsolicited packet, reset a connection quickly, or allow a mapping to remain open long enough for the other endpoint's attempt to cross it. RustDesk therefore makes repeated attempts within a bounded connection window, while the controlled side can listen and punch in ways that let a simultaneous TCP opening succeed. The repeated schedule is an engineering response to uncertain timing: the two endpoints need to overlap their attempts without making a permanently stuck socket or an unbounded retry loop the price of failure.
NAT types change the odds. With a compatible or less restrictive mapping, an observed UDP port may be useful to the other endpoint. With a symmetric NAT, mappings can depend on the destination, so a port learned from the rendezvous service may not be reusable for the peer. RustDesk's code treats that case as a reason to favor relay behavior for the classic transports. This is not a statement that every symmetric-NAT connection must fail; it is a statement that the direct strategy has less reliable information and needs a practical fallback.
IPv6 can provide another direct candidate when a usable public address is available. It does not erase all policy or firewall constraints, but it can avoid some IPv4 NAT behavior. RustDesk also tests and retains the address family information needed to decide whether that candidate can be attempted. The important point is that “direct” is assembled from available conditions: an address, a socket, a route, and a peer willing to accept it. It is not a mode that can be assumed merely because both machines support IPv6.
WebRTC adds a different direct-connection mechanism. Its ICE process gathers candidates and checks possible pairs, while RustDesk uses rendezvous to carry the signaling information between the endpoints. A WebRTC offer can therefore travel through a rendezvous path even when the eventual data channel may be direct. If a TURN server is required by policy, the selected WebRTC pair may itself be relayed. RustDesk consequently examines whether the established WebRTC route is genuinely peer-to-peer before treating it as a direct result.
The signaling path has its own reliability problem. Candidates can arrive gradually as ICE discovers host, server-reflexive, or relay possibilities. RustDesk keeps the rendezvous route available for that exchange when a WebRTC offer is present, buffers candidates while the other endpoint applies the answer, and bounds the queues so a flood of candidates cannot grow without limit. Repeated candidate messages can be deduplicated, while a candidate that was refused because a bounded queue was full remains eligible for a later resend. These details serve one principle: signaling must be reliable enough to discover a data path, but it must not silently become an unlimited resource controlled by whoever can send messages to the route.
The interaction with WebSocket and proxies is especially instructive. When the classic connection must pass through a proxy or WebSocket transport, RustDesk forces the ordinary TCP/UDP-style path through a relay because the proxy cannot serve as an ordinary peer-to-peer punch. WebRTC can still be useful in a WebSocket-signaling deployment because its own ICE sockets may attempt a direct path. The signaling route and the data route are not identical. Treating “WebSocket” as synonymous with “all traffic is relayed” would lose that distinction; treating it as proof of directness would be just as wrong.
When the mediator asks a controlled endpoint to relay, the relay is coordinated rather than guessed. The controller requests a relay pairing, receives the relay server and a temporary association, and connects to that relay. The controlled endpoint performs the corresponding request. The association lets the relay join the two intended connection attempts instead of acting as a general-purpose port forward. The relay then supplies reachability; the later endpoint handshake gives the resulting stream its peer and session meaning.
RustDesk does not always choose the first stream that becomes ready. A relay TCP connection can complete quickly, while a direct WebRTC attempt needs time for candidate exchange, ICE checks, DTLS setup, and data-channel readiness. The client can hold a relayed result briefly while a direct WebRTC attempt is still alive. If a direct result arrives, it wins; if it does not, the held relay becomes the usable answer. That small preference window expresses a system trade-off: wait long enough to preserve the performance benefit of direct connectivity, but not so long that a blocked-UDP network feels broken when a relay could already work.
The other direct candidates are raced in parallel because waiting for them sequentially would make one failed network assumption delay every later option. A working UDP route, TCP punch, IPv6 path, or WebRTC pair can finish the attempt. If all direct options fail and a relay exists, the client coordinates a relay request and connects to the relay server. If the peer is offline, the ID is unknown, the server key or license does not match, or the rendezvous attempt times out, the result is a different failure and should not be described as “the remote desktop is slow.”
The sequence looks like this:
What each outcome proves—and what it does not
An online registration proves that a peer has reached the rendezvous service recently enough to be listed. It does not prove that the endpoint is ready to capture a display, that its user will approve a session, or that a password will succeed. A local-address response proves that RustDesk found an intranet-specific route to try. It does not prove that the operating system will permit the requested capability.
A successful punch proves that a particular transport path was established between the endpoints. It does not, by itself, prove that the far endpoint is the intended identity or that the controller has been authorized. The same is true of a relay result in the opposite direction: using a relay proves that a reachable bridge was found, not that RustDesk has abandoned its later peer and session checks. These distinctions are essential because the user sees only a connection attempt, while the system is making several independent decisions.
The code also remembers enough connection history to change timing. Registration intervals, connection deadlines, UDP testing, DNS or socket rebinding, and prior direct failures affect how long a later attempt waits and which paths are prepared. These adaptations are reliability mechanisms, not guarantees of performance. The repository can show how the decisions are made, but it cannot establish a universal success rate for every firewall, NAT, mobile carrier, or corporate proxy.
A useful diagnosis follows the same boundaries. If the ID is not found, the failure is at peer lookup. If the peer is listed but every candidate expires, the problem may be NAT, firewall, proxy, or relay availability. If a relay stream opens and the session then fails, the transport may have succeeded while a later identity or authorization step rejected it. If a WebRTC attempt loses to a relay, that may be an intentional reachability outcome rather than a defect. Reading all of these as one “connection error” hides the very information RustDesk's layered design is trying to preserve.
The transport problem therefore ends at a precise boundary. RustDesk has found a stream—or it has explained why it could not. The next question is more demanding: whether that stream is connected to the intended peer and protected with the right cryptographic meaning. Reachability gets the session to the door; it does not identify who is standing behind it.