# RustDesk Client vs OSS Server — Feature Gap Analysis This document compares the RustDesk **client** ([/Users/sn0/Desktop/rustdesk](../)) against the OSS **server** ([/Users/sn0/Desktop/rustdesk-server](../../rustdesk-server/)) and lists every feature the client implements or expects, but that the OSS server (`hbbs` + `hbbr` + `rustdesk-utils`) does **not** provide. The OSS server is, by upstream's own description in its README, deliberately minimal: > Self-host your own RustDesk server, it is free and open source. > If you want extra features, **RustDesk Server Pro** might suit you better. Almost every gap below is filled by RustDesk Server Pro (closed source). What follows is the concrete list. --- ## TL;DR | Area | OSS server | Client expects | |-----------------------------------------------------|-----------------|----------------------------------------------| | Rendezvous protocol (UDP/TCP/WS) | ✅ implemented | ✅ | | Relay protocol (`hbbr`) | ✅ implemented | ✅ | | `RegisterPk` over **TCP** | ❌ `NOT_SUPPORT`| Uses UDP — non-issue if UDP reachable | | `HttpProxyRequest` / `HttpProxyResponse` (HTTP-via-rendezvous tunnel) | ❌ no handler | Used when `USE_RAW_TCP_FOR_API=Y` | | **Entire `/api/*` HTTP surface** (35 endpoints in [CONSOLE_API.md](CONSOLE_API.md)) | ❌ no HTTP server | All login, AB, group, audit, sysinfo, etc. | | User / password / token authentication | ❌ | Bearer-token model | | Address book (legacy or shared) | ❌ | Personal AB, shared AB, tags, peers | | Device groups, users, accessible peers list | ❌ | "Group" tab in UI | | Audit logging (conn, file, alarm, note) | ❌ | Background fire-and-forget | | Session recording upload | ❌ | Chunked uploader | | Sysinfo / heartbeat-based device tracking | ❌ | Every 15 s (3 s when active) | | Strategy/policy push via heartbeat | ❌ | `config_options` / `disconnect` / `sysinfo` flag | | 2FA, email-code, SMS-code, OIDC/SSO | ❌ | Login challenge variants | | Plugin signing (`/lic/web/api/plugin-sign`) | ❌ | Optional, only if signed plugins shipped | | CLI bulk device assignment (`rustdesk --assign`) | ❌ | `POST /api/devices/cli` | | Per-tenant licensing / Pro status flag | ❌ | Inferred from sysinfo success | --- ## 1. Rendezvous-protocol gaps The OSS server *does* speak the rendezvous protocol — it accepts and responds to all of the variants the client sends in the normal connection path. There are exactly **two** variant-level gaps: ### 1.1 `RegisterPk` over TCP — explicitly rejected [src/rendezvous_server.rs:556-563](../../rustdesk-server/src/rendezvous_server.rs#L556) returns `RegisterPkResponse { result: NOT_SUPPORT }` for any TCP-arriving `RegisterPk`. UDP works fine. The client has UDP `RegisterPk` as the primary path ([src/rendezvous_mediator.rs:685-693](../src/rendezvous_mediator.rs#L685-L693)), so this is only an issue in fully UDP-blocked environments where the client falls back to TCP for everything. ### 1.2 `HttpProxyRequest` / `HttpProxyResponse` — no handler When `USE_RAW_TCP_FOR_API` is enabled (and WebSocket is off), the client tunnels its HTTP API calls **as protobuf messages** over the rendezvous server's TCP socket — see [src/common.rs:1188-1250](../src/common.rs#L1188-L1250) (`tcp_proxy_request`). - **Wire format:** an encrypted `KeyExchange` handshake, then a `RendezvousMessage::set_http_proxy_request(HttpProxyRequest { method, path, headers, body })`, expecting `HttpProxyResponse { status_code, headers, body }` back. - **OSS handling:** there is no match arm for `HttpProxyRequest` in [/Users/sn0/Desktop/rustdesk-server/src/rendezvous_server.rs](../../rustdesk-server/src/rendezvous_server.rs); it falls into the `_ => {}` catch-all and is silently dropped. - **Impact:** any client that has been pushed `OPTION_USE_RAW_TCP_FOR_API=Y` (typical for restricted networks) cannot reach the HTTP API at all on an OSS-only deployment, even if the API itself were implemented. ### 1.3 What the OSS server *does* handle (not gaps, for completeness) - UDP: `RegisterPeer`, `RegisterPk`, `PunchHoleRequest`, `PunchHoleSent`, `LocalAddr`, `ConfigureUpdate` (loopback only), `SoftwareUpdate`. - TCP: `PunchHoleRequest`, `RequestRelay`, `RelayResponse`, `PunchHoleSent`, `LocalAddr`, `TestNatRequest`. - TCP listener2 (`port - 1`): `TestNatRequest`, `OnlineRequest`, plus a loopback admin CLI. - WebSocket on `port + 2` (default 21118): same handler set as TCP. - The relay (`hbbr`) listens on `port` (default 21117) and `port + 2` (WebSocket, default 21119), and handles `RequestRelay` for tunnelling. So the **rendezvous + relay surface for plain peer-to-peer use is complete in the OSS server.** All the actual gaps are above the rendezvous layer. --- ## 2. HTTP API — none of it exists in OSS [/Users/sn0/Desktop/rustdesk-server/Cargo.toml](../../rustdesk-server/Cargo.toml) pulls in `axum` and `tower-http`, but they are unused — no `Router::new`, no route definitions, no HTTP listener bound. **All 35 endpoints documented in [CONSOLE_API.md](CONSOLE_API.md) are gaps.** Grouped by feature area: ### 2.1 Authentication & session (`/api/login`, `/api/login-options`, `/api/logout`, `/api/currentUser`) - No user table in the OSS schema (which is a single `peer` table — see §4 below). No password storage, hashing, salting. - No bearer-token issuance or validation. - No `email_check` / `tfa_check` challenge types (no email sender, no TOTP store). - No login-options registry — client always gets an empty `oidc/...` list. - No OIDC / SSO device flow (`/api/oidc/auth`, `/api/oidc/auth-query`). ### 2.2 Address book — both modes missing The client supports two AB modes ([CONSOLE_API.md §4](CONSOLE_API.md)) and the OSS server implements neither: - **Legacy single-blob mode** (`GET/POST /api/ab`) — needs a per-user blob store + gzip handling. - **Shared mode** (`/api/ab/settings`, `/api/ab/personal`, `/api/ab/shared/profiles`, `/api/ab/peers`, `/api/ab/tags/{guid}`, plus the per-peer and per-tag CRUD on `{guid}`) — needs a normalized AB / peer / tag / share-rule schema with `read | read/write | full control` ACLs. Without this, the client falls back to its local-only "Recents" / "Favorites" lists; nothing syncs across devices. ### 2.3 Device groups, users, accessible peers (`/api/device-group/accessible`, `/api/users`, `/api/peers`) The "Group" tab in the desktop UI populates from these three endpoints. With OSS the tab is empty; the client logs `get accessible device groups: ` and silently swallows it. ### 2.4 Heartbeat / sysinfo / strategy push (`/api/heartbeat`, `/api/sysinfo`, `/api/sysinfo_ver`) This is the agent-management heartbeat loop ([CONSOLE_API.md §6](CONSOLE_API.md)). OSS gaps: - No device-tracking table (last-seen, OS, hostname, version, IP, online state). - No sysinfo cache or version string. - No mechanism to **push back** in the heartbeat response: - `sysinfo: ` to force an immediate sysinfo re-upload. - `disconnect: [conn_id, ...]` to force a remote session to close. - `modified_at` / `strategy.config_options` to push policy. - Without strategy push, the operator cannot remotely set: - `whitelist`, `relay-server`, `rendezvous-servers`, `direct-access-port`, `stop-service`, `OPTION_DISABLE_UDP`, `OPTION_ENABLE_UDP_PUNCH`, `OPTION_ENABLE_IPV6_PUNCH`, `OPTION_USE_RAW_TCP_FOR_API`, `OPTION_DIRECT_SERVER`, etc. - Or any of the `OPTION_PRESET_*` keys that pre-fill the address book / username / device group / strategy on a freshly enrolled client. - `is_pro()` is set to `true` only when `/api/sysinfo` returns `SYSINFO_UPDATED` ([src/hbbs_http/sync.rs:219](../src/hbbs_http/sync.rs#L219)) — with OSS, `is_pro()` is permanently `false`, so any client behavior gated on it is disabled. ### 2.5 Audit (`/api/audit/conn`, `/api/audit/file`, `/api/audit/alarm`, `PUT /api/audit`) The client emits these fire-and-forget on every: - new remote session ([src/server/connection.rs:1248-1252](../src/server/connection.rs#L1248-L1252)), - file send/receive ([src/server/connection.rs:1297-1330](../src/server/connection.rs#L1297-L1330)), - security alarm — IP whitelist hit, brute-force thresholds ([src/server/connection.rs:1332-1349](../src/server/connection.rs#L1332-L1349)), and - the operator-typed end-of-session note (PUT, [flutter/lib/common/widgets/dialog.dart:1656-1687](../flutter/lib/common/widgets/dialog.dart#L1656-L1687)). Even though the client doesn't *block* on these, OSS silently swallows all of them, so: - No central session log. - No file-transfer log (incl. the top-10-by-size summary the client computes). - No alarm notifications for IP-whitelist hits or brute-force attempts. - No audit-row GUID exists, so the "leave a note when the session ends" dialog has nothing to attach to. ### 2.6 Session recording upload (`POST /api/record`) Chunked uploader ([src/hbbs_http/record_upload.rs](../src/hbbs_http/record_upload.rs)) with `?type=new|part|tail|remove`. OSS has no `/api/record` route and no on-disk recording store, so server-side recording is impossible. (Local-only recording on the controlling side still works — that's not a server feature.) ### 2.7 OIDC / SSO device flow (`/api/oidc/auth`, `/api/oidc/auth-query`) The polled device-code flow ([src/hbbs_http/account.rs](../src/hbbs_http/account.rs)) requires an OIDC client implementation, browser-flow URL generation, and a poll-for-token side. None of this is in OSS. ### 2.8 CLI bulk assign (`POST /api/devices/cli`) [src/core_main.rs:519-616](../src/core_main.rs#L519-L616). Used by `rustdesk --assign --token ...` for mass-deploy scripts to register a freshly-installed agent into a tenant, optionally setting `user_name`, `strategy_name`, `address_book_*`, `device_group_name`, `device_username`, `device_name`, `note`. Requires user/group/AB tables, none of which exist in OSS. ### 2.9 Plugin signature service (`POST /lic/web/api/plugin-sign`) [src/plugin/callback_msg.rs:282-296](../src/plugin/callback_msg.rs#L282-L296). Required only if the deployment ships signed plugins. OSS has no plugin infrastructure of any kind. ### 2.10 Generic file downloader `HEAD` then `GET` against an arbitrary URL with a required `Content-Length` ([src/hbbs_http/downloader.rs](../src/hbbs_http/downloader.rs)). Works against any static file server — OSS doesn't *need* to serve this, but a complete Pro-replacement backend usually exposes installer/plugin/recording downloads via this. --- ## 3. Schema gaps The OSS database is a single SQLite table called `peer`: ``` guid (PK), id (UNIQUE), uuid, pk, created_at, user (unused), status (unused), note (unused), info (JSON: { ip }) ``` [/Users/sn0/Desktop/rustdesk-server/src/database.rs:71-144](../../rustdesk-server/src/database.rs#L71-L144). To support the client's HTTP surface a backend needs at minimum: - `users` (id, name, display_name, avatar, email, note, password_hash, status, is_admin, totp_secret, oidc_subject, …) - `tokens` (token_hash, user_id, expires_at) - `oidc_sessions` (poll_code, state, created_at, access_token, …) - `address_books` (guid, owner_user_id, name, note, kind=personal|shared) - `address_book_shares` (ab_guid, user_or_group_id, rule={1,2,3}) - `address_book_peers` (ab_guid, peer_id, alias, tags[], note, password|hash, username, hostname, platform) - `address_book_tags` (ab_guid, name, color) - `device_groups` (id, name) - `device_group_members` (device_group_id, user_or_group_id) - `peers_extended` (peer_id, user_id, device_group_id, last_seen, version, sysinfo_blob, sysinfo_hash, sysinfo_ver, online, …) - `audit_conn` (guid, peer_id, conn_id, session_id, action, ip, started_at, ended_at, note) - `audit_file` (peer_id, peer_remote, type, path, is_file, info_json) - `audit_alarm` (peer_id, typ, info_json) - `recordings` (filename, peer_id, size, header_blob, started_at, finished_at) - `strategies` (id, name, modified_at, config_options_json, extra_json) - `peer_strategy_assignment` / `device_group_strategy_assignment` The OSS schema covers exactly **one row** of one of those tables (`peers_extended.peer_id` plus `pk`/`uuid`). Everything else is a gap. --- ## 4. Authentication / authorization gaps - **No user/password.** The OSS server identifies a peer entirely by `(id, uuid, pk)`. There is no concept of a logged-in *human user*, no password, no session, no role. - **No bearer tokens.** The client adds `Authorization: Bearer ` to every authenticated HTTP call ([flutter/lib/common.dart:2691-2695](../flutter/lib/common.dart#L2691-L2695)). With no HTTP API and no user store, OSS has nothing to validate against. - **No 2FA.** The client supports TOTP challenge (`type: tfa_check`, `tfa_type`, `secret`) and per-device 2FA-trust ([src/ui_session_interface.rs](../src/ui_session_interface.rs)). Not present in OSS. - **No email/SMS verification.** The `email_check` challenge type and `verificationCode` field have no sender on the OSS side. - **No SSO / OIDC.** No identity-provider integration. - **No admin/role concept.** The `UserPayload.is_admin` flag (used to gate the user-management UI) has no source. - **No per-AB ACL.** `AbProfile.rule` (read / read-write / full control) has no enforcement layer. - **No IP allowlisting / per-IP rate-limiting on HTTP endpoints.** OSS rate-limits `RegisterPk` per source IP ([src/rendezvous_server.rs:891-919](../../rustdesk-server/src/rendezvous_server.rs#L891-L919)) but that's at the rendezvous layer only. What the OSS server *does* offer in this space: - Optional symmetric server key (`-k`) checked against `licence_key` in `PunchHoleRequest` and `RequestRelay`. This is shared-secret deployment lockdown, not user auth. - Ed25519 signing of `RelayResponse` payloads using the server's private key. --- ## 5. Operations / fleet management gaps These are conveniences a Pro server offers via the strategy/heartbeat channel; OSS has no equivalent because heartbeat itself is not implemented. - **Force-disconnect a remote session** from the admin console (heartbeat returns `disconnect: [conn_id]` — [src/hbbs_http/sync.rs:251-254](../src/hbbs_http/sync.rs#L251-L254)). - **Force-refresh sysinfo** (`sysinfo` truthy in heartbeat). - **Push global config** to all enrolled agents (the `strategy.config_options` map). Without this, every option must be set per-machine. - **Pre-seed an agent** at install time with an address-book entry, alias, password, note, strategy, device group, custom hostname/username (`OPTION_PRESET_ADDRESS_BOOK_*`, `OPTION_PRESET_USERNAME`, `OPTION_PRESET_STRATEGY_NAME`, `OPTION_PRESET_DEVICE_GROUP_NAME`, …). Client emits these preset values on every sysinfo, but OSS discards them. - **Operator end-of-session notes** (PUT `/api/audit`). - **`rustdesk --assign --token …`** for mass deployment. - **Brute-force / IP-whitelist alarms** to a central log. What OSS *does* offer for ops: - A loopback-only TCP admin CLI on `port - 1` (default 21115) for hbbs and on `port` for hbbr ([src/rendezvous_server.rs:1102-1116](../../rustdesk-server/src/rendezvous_server.rs#L1102-L1116), [src/relay_server.rs:152-323](../../rustdesk-server/src/relay_server.rs#L152-L323)) — `relay-servers`, `ip-blocker`, `ip-changes`, `punch-requests`, `always-use-relay`, `test-geo`, `blacklist-add`, `blocklist-add`, `total-bandwidth`, `usage`, etc. - A `ConfigureUpdate` push *only from loopback* — the operator can update the rendezvous-server list pushed to clients, but only by `nc 127.0.0.1 21115` on the server box itself. --- ## 6. Client-side features that work fine against OSS For balance — these features in the client need no Pro-server support at all: - All in-session protocol (after the relay/direct connection is established): screen sharing, file transfer, terminal, RDP / VNC tunnel, port forward, voice call, view-only mode, whiteboard, printer, clipboard, multi-monitor, mouse/keyboard injection. These are negotiated on the session stream itself and never touch the management server. - LAN discovery (when both ends are reachable on the same LAN, no rendezvous server needed at all). - The client's local 2FA on the *controlled* side ("ask the operator for a one-time code"). That's a peer-to-peer protocol negotiation, not a server feature. - IP-whitelist enforcement on the controlled side ([src/server/connection.rs:1202-1228](../src/server/connection.rs#L1202-L1228)) — done locally against the `whitelist` config option. (But the *operator UX* of pushing that whitelist to a fleet is missing — see §5.) - Self-update — the client checks a hardcoded URL on the public update server, not the configured rendezvous/API server. - Custom-server bootstrap via filename (`rustdesk-host=…,key=…,api=…,relay=….exe`, [src/custom_server.rs](../src/custom_server.rs)) — works against OSS as long as the `api=` field is left empty / public. --- ## 7. What you'd need to build to fully replace Pro Given the analysis above, a full Pro-replacement backend on top of OSS would need: 1. **Add an HTTP server** (axum is already in the Cargo.toml of OSS, unused). Implement the 35 routes in [CONSOLE_API.md](CONSOLE_API.md). 2. **Add a `HttpProxyRequest` handler** in `rendezvous_server.rs` so that locked-down clients can reach the HTTP API through the rendezvous TCP port (decode the protobuf, replay the request internally, wrap the response). 3. **Extend the schema** along the lines of §3. 4. **Add user / token / OIDC / 2FA layers**, plus an email sender for `email_check`. 5. **Implement the strategy / push-config side of `/api/heartbeat`** and the sysinfo cache for `/api/sysinfo*`. 6. **Add audit + recording stores** with retention and access-control. 7. (Optional) **Plugin signing service** if you're shipping signed plugins. The rendezvous + relay protocol itself does not need to change — OSS is correct and complete there. --- ## Source-of-truth references - Client HTTP API the server must serve: [docs/CONSOLE_API.md](CONSOLE_API.md). - Client rendezvous receive loop: [src/rendezvous_mediator.rs](../src/rendezvous_mediator.rs). - Client HTTP-via-TCP fallback: [src/common.rs:1188-1250](../src/common.rs#L1188-L1250). - Client heartbeat loop: [src/hbbs_http/sync.rs](../src/hbbs_http/sync.rs). - OSS rendezvous handler: [/Users/sn0/Desktop/rustdesk-server/src/rendezvous_server.rs](../../rustdesk-server/src/rendezvous_server.rs). - OSS relay handler: [/Users/sn0/Desktop/rustdesk-server/src/relay_server.rs](../../rustdesk-server/src/relay_server.rs). - OSS schema: [/Users/sn0/Desktop/rustdesk-server/src/database.rs](../../rustdesk-server/src/database.rs).