Install
openclaw skills install @ctz168/rdptunnelExpose local RDP (Remote Desktop) servers to the public internet via aitun TCP tunnel. Uses the AITUN/1 plain-TCP handshake (v4.9.22+) so multiple subdomains share a single public port 3389 without TLS. Perfect for AI agents that need to provide remote desktop access to Windows machines, GUI servers, or VDI instances behind NAT/firewall.
openclaw skills install @ctz168/rdptunnelUse this skill when:
Do NOT use this skill when:
The AITUN/1 plain-TCP handshake used for RDP routing was introduced in v4.9.22. Make sure both the tunnel-host side and the connecting-client side run v4.9.22+ (v4.9.23 recommended — it prints smart per-port connection hints).
pip install aitun
Or install via one-line script (Linux/macOS):
curl -fsSL https://aitun.cc/install.sh | bash
Windows (PowerShell):
irm https://aitun.cc/install.ps1 | iex
Verify version:
aitun --help | head -1
# Expected: Tunnel Client v4.9.23 (Relay + HTTP-Port + TCP + Subdomain)
Verify the local RDP service is running and accessible:
On Windows:
# Check if Remote Desktop is enabled (0 = enabled, 1 = disabled)
Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name fDenyTSConnections
# Enable Remote Desktop if needed
Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name fDenyTSConnections -Value 0
# Ensure the RDP service is running
Get-Service -Name TermService | Start-Service
# Verify it is listening on port 3389
Get-NetTCPConnection -State Listen | Where-Object LocalPort -eq 3389
On Linux (xrdp):
# Install xrdp
sudo apt install xrdp -y # Debian/Ubuntu
sudo yum install xrdp -y # CentOS/RHEL
# Start xrdp service
sudo systemctl start xrdp
sudo systemctl enable xrdp
# Verify it is listening on port 3389
ss -tlnp | grep :3389
RDP uses TCP port 3389 by default. On the machine running the RDP server, start aitun with --tcp-ports 3389. TCP forwarding requires an auth token (register at https://aitun.cc):
# On the RDP server machine (e.g. Windows PC you want to access remotely)
C:\aitun-client-windows-amd64.exe -k YOUR_TOKEN --tcp-ports 3389
Or with SSH alongside RDP:
C:\aitun-client-windows-amd64.exe -k YOUR_TOKEN --tcp-ports 22,3389
The output (v4.9.23+) includes smart per-port connection hints:
Tunnel Client v4.9.23 (Relay + HTTP-Port + TCP + Subdomain)
...
[TCP] tcp-3389 -> localhost:3389 (subdomain: yourname.aitun.cc:3389)
LAN connect: mstsc /v:<local-ip>:3389 (or just mstsc /v:<hostname>)
Public connect: aitun tcp-proxy -l 13389 yourname.aitun.cc 3389
mstsc /v:127.0.0.1:13389 (in another terminal)
If port 3389 is occupied on the server (another subdomain already claimed it), aitun automatically assigns a port from the 7000-7999 range and prints a [Port conflict, fell back to range port] notice. Use whichever public port aitun reports.
There are two ways to connect, depending on where you are relative to the RDP server.
If your client machine is on the same LAN as the RDP server, you don't need aitun at all — connect directly:
Windows:
Win + R, type mstsc, press Entershan or 192.168.1.50)Linux (FreeRDP):
xfreerdp /v:192.168.1.50:3389 /u:username /cert:ignore
RDP clients (mstsc.exe, FreeRDP, Microsoft Remote Desktop for macOS) speak plain TCP and have no ProxyCommand-style hook. The aitun shared port 3389 uses the AITUN/1 plain-TCP handshake to identify which subdomain the connection targets, so the RDP client cannot connect to it directly — it needs a local bridge.
Use aitun tcp-proxy -l to start a local bridge, then point mstsc at the local port:
On the client machine (the one connecting TO the RDP server):
# Terminal 1: start the local bridge (keep this open)
aitun tcp-proxy -l 13389 yourname.aitun.cc 3389
# Output: aitun tcp-proxy: listening on 127.0.0.1:13389 -> yourname.aitun.cc:3389
# Terminal 2: connect with mstsc
mstsc /v:127.0.0.1:13389
Linux (FreeRDP):
# Terminal 1
aitun tcp-proxy -l 13389 yourname.aitun.cc 3389
# Terminal 2
xfreerdp /v:127.0.0.1:13389 /u:username /cert:ignore
macOS (Microsoft Remote Desktop):
# Terminal 1
aitun tcp-proxy -l 13389 yourname.aitun.cc 3389
# Microsoft Remote Desktop app: add a new PC with address 127.0.0.1:13389
The local port 13389 is just a suggestion (computed as 10000 + public_port). Any free local port works — use aitun tcp-proxy -l <any-free-port> yourname.aitun.cc 3389 and point your RDP client at 127.0.0.1:<that-port>.
When done, stop the tunnel on the RDP server machine:
# Press Ctrl+C in the aitun terminal, or if running in background:
taskkill /IM aitun-client-windows-amd64.exe /F
And on the client machine, Ctrl+C the aitun tcp-proxy process.
Starting in v4.9.22, aitun uses the AITUN/1 plain-TCP handshake so that RDP and other plain-TCP protocols can share a single public port across multiple subdomains — without requiring TLS on the application traffic.
mstsc.exe → 127.0.0.1:13389 (local bridge)
↓
aitun tcp-proxy (adds AITUN/1 handshake header)
↓
yourname.aitun.cc:3389 (shared public port)
↓
aitun server reads AITUN/1 line, routes to "yourname" tunnel
↓
aitun client on RDP server machine
↓
localhost:3389 → RDP service
The handshake is a single ASCII line sent before any RDP data:
AITUN/1 <subdomain> <port>\r\n
e.g. AITUN/1 yourname 3389\r\n. The server replies OK\r\n and then the connection becomes a transparent byte stream between the client and the tunnel client's localhost:3389. The RDP protocol flows through unchanged.
Why a handshake instead of TLS+SNI? SSH works with TLS+SNI because the SSH client supports ProxyCommand — aitun ssh-proxy wraps SSH in TLS and the server uses SNI to route. RDP clients (mstsc.exe etc.) have no equivalent hook, so they cannot speak TLS on port 3389. The AITUN/1 handshake is a ~30-byte ASCII prefix that any plain-TCP client can send via a local bridge, and the magic prefix AITUN/1 is chosen so that no common application protocol is mis-detected (TLS=0x16, SSH=SSH-, HTTP=method verbs, RDP/TPKT=0x03, MySQL=length-encoded packet).
First-byte dispatch on the shared port:
| First byte | Protocol | Routing |
|---|---|---|
0x16 | TLS ClientHello | TLS termination + SNI (existing, used by SSH) |
'A' (0x41) | AITUN/1 handshake | Plain-TCP routing (new, used by RDP/MySQL/etc.) |
| anything else | unknown | rejected |
Backward compatible — existing TLS+SNI clients (SSH-over-TLS, HTTPS) continue to work unchanged.
# On the RDP server machine — expose both SSH and RDP
C:\aitun-client-windows-amd64.exe -k YOUR_TOKEN --tcp-ports 22,3389
The v4.9.23+ output shows hints for both ports:
[TCP] ssh -> localhost:22 (subdomain: yourname.aitun.cc:22)
Public connect: ssh user@yourname.aitun.cc
(add to ~/.ssh/config: ProxyCommand aitun ssh-proxy %h %p)
LAN connect: ssh user@<local-ip> (no aitun needed)
[TCP] tcp-3389 -> localhost:3389 (subdomain: yourname.aitun.cc:3389)
LAN connect: mstsc /v:<local-ip>:3389 (or just mstsc /v:<hostname>)
Public connect: aitun tcp-proxy -l 13389 yourname.aitun.cc 3389
mstsc /v:127.0.0.1:13389 (in another terminal)
If RDP is running on a non-standard port (e.g., 13389 locally):
C:\aitun-client-windows-amd64.exe -k YOUR_TOKEN --tcp-ports 13389
The smart hint shows a different suggested bridge port (10000 + 13389 = 23389, which exceeds 65535, so aitun falls back to 13389 + 1000 = 14389):
[TCP] tcp-13389 -> localhost:13389 (subdomain: yourname.aitun.cc:13389)
Public connect: aitun tcp-proxy -l 14389 yourname.aitun.cc 13389
then connect your client to 127.0.0.1:14389
LAN connect: use <local-ip>:13389 directly
# Container running xrdp on port 3389, mapped to host port 13389
# On the Docker host:
aitun -k YOUR_TOKEN --tcp-ports 13389
For long-lived access, run the local bridge in the background:
# Windows: start in a new window, minimized
start /min cmd /c "aitun tcp-proxy -l 13389 yourname.aitun.cc 3389"
# Or use PowerShell Start-Process
Start-Process -WindowStyle Hidden -FilePath "aitun" -ArgumentList "tcp-proxy","-l","13389","yourname.aitun.cc","3389"
# Linux/macOS: nohup
nohup aitun tcp-proxy -l 13389 yourname.aitun.cc 3389 >/tmp/rdp-bridge.log 2>&1 &
The aitun command (installed via pip install aitun, or alternatively curl -fsSL https://aitun.cc/install.sh | bash / irm https://aitun.cc/install.ps1 | iex on Windows) accepts these flags:
| Flag | Description |
|---|---|
-p PORT | Local HTTP service port (optional; omit for TCP-only mode with --tcp-ports) |
-k TOKEN | Auth token for registered subdomain (required for TCP forwarding) |
--host HOST | Local service address (default: localhost) |
--tcp-ports PORTS | TCP forwarding ports, comma-separated (e.g., 3389,22; requires -k). Use without -p for TCP-only mode |
--p2p | Enable P2P direct connection (default: enabled) |
--no-p2p | Disable P2P, force server relay mode |
--daemon | Run as background daemon |
--stop | Stop running daemon |
Subcommands:
| Command | Description |
|---|---|
aitun ssh-proxy <host> [port] | SSH ProxyCommand — wraps SSH in TLS for SNI routing |
aitun tcp-proxy <host> <port> | Plain-TCP ProxyCommand — sends AITUN/1 handshake, pipes stdin/stdout |
aitun tcp-proxy -l <local_port> <host> <port> | Plain-TCP local bridge — listens on local_port, bridges each connection through AITUN/1 to host:port. Use this for mstsc.exe / FreeRDP / Microsoft Remote Desktop. |
-k token — free tunnels do not support TCPtcp_register output, showing exactly how to connect (LAN vs public, with the correct tcp-proxy command for the latter)--no-p2p only if P2P connection fails--daemon for persistent background operation on the server sideaitun tcp-proxy -l bridge process on the client side stays active until you Ctrl+C it — close it when done to free the local port