Fix ForceRelay UUID mismatch; add GHCR notes

Change ForceRelay TCP path to return a PunchHoleResponse with NatType=SYMMETRIC instead of sending a server-generated RelayResponse, so clients will send RequestRelay with their own UUIDs and both sides use the same UUID (resolves relay pairing mismatch, Issue #66). Add diagnostic log.Printf calls in handleRequestRelay (UDP) and handleRequestRelayTCP to aid relay pairing debugging. Update docs and CI: add GHCR "pull access denied" troubleshooting and package visibility guidance to DOCKER_QUICKSTART.md, docker-compose.quick.yml, and the docker-publish workflow summary (addresses Issue #67). Also update changelog entry in .github/copilot-instructions.md and add related files to .gitignore.
This commit is contained in:
UNITRONIX
2026-03-19 22:41:24 +01:00
parent 511971b3ce
commit 98209249f3
6 changed files with 71 additions and 28 deletions
+6 -1
View File
@@ -536,6 +536,11 @@ sudo apt-get install -y build-essential libsqlite3-dev pkg-config libssl-dev git
153. [x] **PS1 `RandomNumberGenerator::Fill` crash (Issue #38)**: `[System.Security.Cryptography.RandomNumberGenerator]::Fill()` is a .NET 6+ static method unavailable in Windows PowerShell 5.1 (.NET Framework 4.x). Changed to `RNGCryptoServiceProvider.GetBytes()` instance method which works on both .NET Framework 4.x and .NET 6+. Fixes API key generation failure → 0 devices in panel on fresh Windows install.
154. [x] **Rust→Go upgrade detection (Issues #66, #38)**: `Do-Update` (PS1) and `do_update()` (bash) now detect `SERVER_TYPE=rust` (legacy hbbs/hbbr) and warn user that Rust→Go is a major architecture change requiring fresh installation. In auto mode, redirects to `Do-Install`/`do_install` automatically. In interactive mode, prompts user to confirm fresh install (recommended) or continue with partial update. Prevents broken upgrade path from v1.5.0 (Rust) to v2.3.0+ (Go).
#### Go Server — ForceRelay UUID Fix & Docker GHCR (Phase 27) ✅ COMPLETED 2026-03-19
155. [x] **ForceRelay TCP UUID mismatch (Issue #66)**: `handlePunchHoleRequestTCP` ForceRelay path returned `RelayResponse{uuid=SERVER_UUID}` directly to TCP initiator. Some RustDesk client versions ignore the UUID from `RelayResponse` received in response to `PunchHoleRequest`, generate their own UUID, and connect to relay with it — while the target connects with the server's UUID. Relay pairing always failed (different UUIDs). **Fix**: ForceRelay TCP now returns `PunchHoleResponse{nat_type=SYMMETRIC}` instead of `RelayResponse`. Client sees SYMMETRIC NAT → sends `RequestRelay{uuid=CLIENT_UUID}` on same TCP connection → `handleRequestRelayTCP` forwards CLIENT_UUID to target → both sides use same UUID → relay pairing succeeds.
156. [x] **Relay diagnostic logging**: Added `log.Printf` with UUID and relay server in `handleRequestRelayTCP` and `handleRequestRelay` (UDP) return paths for better relay pairing diagnostics.
157. [x] **Docker GHCR "denied" error (Issue #67)**: Pre-built images on `ghcr.io/unitronix/betterdesk-*:latest` not available — workflow never triggered or packages are private. Added troubleshooting section to `DOCKER_QUICKSTART.md` (3 solutions: build locally, trigger workflow, authenticate). Added fallback comment to `docker-compose.quick.yml`. Added package visibility reminder to CI workflow summary step.
---
## 🔄 System Statusu v3.0
@@ -805,4 +810,4 @@ All code changes MUST include a security review as part of the implementation pr
---
*Ostatnia aktualizacja: 2026-03-19 (ALL-IN-ONE Scripts — PS1 Compatibility & Upgrade Detection — Phase 26) przez GitHub Copilot*
*Ostatnia aktualizacja: 2026-03-19 (Go Server — ForceRelay UUID Fix & Docker GHCR — Phase 27) przez GitHub Copilot*
+5
View File
@@ -209,3 +209,8 @@ jobs:
echo "curl -fsSL https://raw.githubusercontent.com/${{ github.repository }}/main/docker-compose.quick.yml -o docker-compose.yml" >> $GITHUB_STEP_SUMMARY
echo "docker compose up -d" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### ⚠️ Package Visibility" >> $GITHUB_STEP_SUMMARY
echo "If packages are newly created, they default to **private**." >> $GITHUB_STEP_SUMMARY
echo "To allow unauthenticated pulls, change visibility to **Public**:" >> $GITHUB_STEP_SUMMARY
echo "GitHub repo → Packages → (each package) → Package settings → Change visibility → Public" >> $GITHUB_STEP_SUMMARY
+3
View File
@@ -174,3 +174,6 @@ package-lock.json
hbbs-patch/
hbbs-patch-v2/
tasks/todo.md
docs/SECURITY_AUDIT_FULL_2026-03-19.md
.github/copilot-instructions.md
.github/copilot-instructions.md
+27
View File
@@ -115,6 +115,33 @@ curl http://localhost:21114/api/health
## ❓ Troubleshooting
### "denied" or "pull access denied" when starting
This means the pre-built images are not yet published to GitHub Container Registry.
**Solution A — Build locally (recommended):**
```bash
# Use the full docker-compose.yml which builds images from source
git clone https://github.com/UNITRONIX/Rustdesk-FreeConsole.git
cd Rustdesk-FreeConsole
docker compose -f docker-compose.yml up -d --build
```
**Solution B — Wait for images to be published:**
The repository maintainer needs to trigger the Docker publish workflow:
1. Go to: GitHub repo → Actions → "Build & Publish Docker Images"
2. Click "Run workflow" → Branch: main → Click "Run workflow"
3. Wait ~10 minutes for images to build
4. Once images are published, retry `docker compose up -d`
**Solution C — Authenticate (if repo is private):**
```bash
# Create a GitHub Personal Access Token with 'read:packages' scope
docker login ghcr.io -u YOUR_GITHUB_USERNAME -p YOUR_GITHUB_TOKEN
docker compose up -d
```
### "Cannot connect to devices"
1. Check firewall allows ports 21116-21117
+25 -27
View File
@@ -574,13 +574,23 @@ func (s *Server) handlePunchHoleRequestTCP(msg *pb.PunchHoleRequest, raddr *net.
log.Printf("[signal] PunchHole (TCP): target %s found (addr=%s, status=%s), relay=%s",
targetID, target.UDPAddr, target.StatusTier, relayServer)
// ForceRelay or AlwaysUseRelay: send RelayResponse (NOT PunchHoleResponse)
// with a generated UUID, matching the UDP path's sendRelayResponse behavior.
// RelayResponse contains the uuid field required by hbbr for session pairing.
// ForceRelay or AlwaysUseRelay: return PunchHoleResponse with SYMMETRIC NAT
// type instead of RelayResponse. This tells the client that direct P2P is
// impossible and it should fall back to relay via RequestRelay.
//
// The client will then send RequestRelay (with its own UUID) on this same
// TCP connection. handleRequestRelayTCP will forward it to the target and
// return RelayResponse to the initiator. Both sides connect to relay with
// the SAME client-generated UUID, ensuring relay pairing succeeds.
//
// Previously, returning RelayResponse directly with a server-generated UUID
// caused UUID mismatch: some RustDesk client versions ignore the UUID from
// a RelayResponse received in response to PunchHoleRequest (they expect
// PunchHoleResponse), generate their own UUID, and connect to relay with it
// — while the target connects with the server's UUID. This broke relay
// pairing every time (Issue #66).
if msg.ForceRelay || s.cfg.AlwaysUseRelay {
log.Printf("[signal] PunchHole (TCP): force relay for %s", targetID)
relayUUID := uuid.New().String()
log.Printf("[signal] PunchHole (TCP): force relay for %s (returning SYMMETRIC to let client drive relay UUID)", targetID)
var signedPk []byte
if len(target.PK) > 0 {
@@ -592,32 +602,18 @@ func (s *Server) handlePunchHoleRequestTCP(msg *pb.PunchHoleRequest, raddr *net.
}
}
// Forward RequestRelay to target so it connects to hbbr with the same UUID.
reqRelay := &pb.RendezvousMessage{
Union: &pb.RendezvousMessage_RequestRelay{
RequestRelay: &pb.RequestRelay{
Id: msg.Id,
Uuid: relayUUID,
SocketAddr: crypto.EncodeAddr(raddr),
RelayServer: relayServer,
Secure: false,
ConnType: msg.ConnType,
},
},
}
var targetAddr []byte
if target.UDPAddr != nil {
// Store the UUID so we can recover it if target responds with empty UUID.
s.storePendingUUID(targetID, relayUUID)
s.sendUDP(reqRelay, target.UDPAddr)
log.Printf("[signal] PunchHole (TCP): forwarded RequestRelay to target %s (uuid=%s)", targetID, relayUUID[:8])
targetAddr = crypto.EncodeAddr(target.UDPAddr)
}
return &pb.RendezvousMessage{
Union: &pb.RendezvousMessage_RelayResponse{
RelayResponse: &pb.RelayResponse{
Uuid: relayUUID,
Union: &pb.RendezvousMessage_PunchHoleResponse{
PunchHoleResponse: &pb.PunchHoleResponse{
SocketAddr: targetAddr,
Pk: signedPk,
RelayServer: relayServer,
Union: &pb.RelayResponse_Pk{Pk: signedPk},
Union: &pb.PunchHoleResponse_NatType{NatType: pb.NatType_SYMMETRIC},
},
},
}
@@ -892,6 +888,7 @@ func (s *Server) handleRequestRelay(msg *pb.RequestRelay, raddr *net.UDPAddr) {
}
// Confirm to initiator with SIGNED public key
log.Printf("[signal] RequestRelay (UDP): returning RelayResponse to initiator %s (uuid=%s, relay=%s)", raddr, relayUUID[:8], relayServer)
resp := &pb.RendezvousMessage{
Union: &pb.RendezvousMessage_RelayResponse{
RelayResponse: &pb.RelayResponse{
@@ -997,6 +994,7 @@ func (s *Server) handleRequestRelayTCP(msg *pb.RequestRelay, raddr *net.UDPAddr)
}
// Immediate RelayResponse to TCP initiator — matching the UDP handler's behavior.
log.Printf("[signal] RequestRelay (TCP): returning RelayResponse to initiator %s (uuid=%s, relay=%s)", raddr, relayUUID[:8], relayServer)
return &pb.RendezvousMessage{
Union: &pb.RendezvousMessage_RelayResponse{
RelayResponse: &pb.RelayResponse{
+5
View File
@@ -10,6 +10,11 @@
# Web Console: http://localhost:5000
# Default credentials shown on first startup in logs:
# docker compose logs console | grep -i password
#
# TROUBLESHOOTING: If you get "denied" or "pull access denied" error,
# images may not be published yet. Build from source instead:
# git clone https://github.com/UNITRONIX/Rustdesk-FreeConsole.git
# cd Rustdesk-FreeConsole && docker compose -f docker-compose.yml up -d --build
# =============================================================================
services: