enroll(rustdesk-server): test suite + bridge poll (status-key overlay, ws + punch-hole-amplification tests)
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
"""Minimal RustDesk rendezvous protocol encoder/probe (stdlib only, zero deps).
|
||||
|
||||
hbbs speaks `RendezvousMessage` protobuf over UDP :21116 (one message per datagram, no
|
||||
framing). The wire format is hand-encoded here from the upstream proto
|
||||
(rustdesk/hbb_common protos/rendezvous.proto — field numbers are IDENTICAL between the
|
||||
1.1.12-vendored and the current shared-proto era, verified against both tags):
|
||||
|
||||
message RendezvousMessage {
|
||||
oneof union {
|
||||
RegisterPeer register_peer = 6;
|
||||
RegisterPeerResponse register_peer_response = 7;
|
||||
PunchHoleRequest punch_hole_request = 8;
|
||||
PunchHoleResponse punch_hole_response = 11;
|
||||
...
|
||||
}
|
||||
}
|
||||
message RegisterPeer { string id = 1; int32 serial = 2; }
|
||||
message PunchHoleRequest { string id = 1; ... }
|
||||
|
||||
This is the surface a real RustDesk client uses to register and request connections; it is
|
||||
the app's own interface (STYLE.md §1), exercised from the cc-ci host against the
|
||||
host-published :21116 (the stock compose publishes 21115-21119 mode:host).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import socket
|
||||
import time
|
||||
|
||||
RENDEZVOUS_PORT = 21116
|
||||
|
||||
|
||||
def varint(n: int) -> bytes:
|
||||
out = bytearray()
|
||||
while n > 0x7F:
|
||||
out.append((n & 0x7F) | 0x80)
|
||||
n >>= 7
|
||||
out.append(n)
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def field_str(field: int, s: str) -> bytes:
|
||||
b = s.encode()
|
||||
return varint((field << 3) | 2) + varint(len(b)) + b
|
||||
|
||||
|
||||
def field_varint(field: int, n: int) -> bytes:
|
||||
return varint((field << 3) | 0) + varint(n)
|
||||
|
||||
|
||||
def wrap(union_field: int, inner: bytes) -> bytes:
|
||||
"""Wrap an inner message as the given oneof field of RendezvousMessage."""
|
||||
return varint((union_field << 3) | 2) + varint(len(inner)) + inner
|
||||
|
||||
|
||||
def register_peer(peer_id: str) -> bytes:
|
||||
"""RendezvousMessage{ register_peer { id, serial=0 } } — a plain peer registration."""
|
||||
return wrap(6, field_str(1, peer_id) + field_varint(2, 0))
|
||||
|
||||
|
||||
def punch_hole_request(target_id: str) -> bytes:
|
||||
"""RendezvousMessage{ punch_hole_request { id } } — an UNAUTHENTICATED connection request
|
||||
for `target_id` (no licence_key/token — exactly the spoofable request shape from the
|
||||
CVE-2026-30784 / GH #670 amplification report)."""
|
||||
return wrap(8, field_str(1, target_id))
|
||||
|
||||
|
||||
def parse_union_field(data: bytes) -> int | None:
|
||||
"""Which RendezvousMessage oneof field the server answered with (None if unparseable)."""
|
||||
if not data:
|
||||
return None
|
||||
key = data[0]
|
||||
field, wire = key >> 3, key & 0x7
|
||||
return field if wire == 2 else field # field number regardless of wire type
|
||||
|
||||
|
||||
def udp_probe(
|
||||
msg: bytes, host: str = "127.0.0.1", port: int = RENDEZVOUS_PORT, wait: float = 2.0
|
||||
) -> bytes | None:
|
||||
"""Send one datagram, return the server's reply (or None after `wait` seconds of silence)."""
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.settimeout(wait)
|
||||
try:
|
||||
s.sendto(msg, (host, port))
|
||||
try:
|
||||
data, _ = s.recvfrom(65535)
|
||||
return data
|
||||
except TimeoutError:
|
||||
return None
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
||||
def retry_register_peer(peer_id: str, attempts: int = 6, interval: float = 5.0) -> bytes | None:
|
||||
"""Poll register_peer until the server answers (post-deploy settle), or give up.
|
||||
|
||||
Returns the raw register_peer_response datagram."""
|
||||
deadline = time.time() + attempts * interval
|
||||
last: bytes | None = None
|
||||
while time.time() < deadline:
|
||||
last = udp_probe(register_peer(peer_id))
|
||||
if last is not None:
|
||||
return last
|
||||
time.sleep(interval)
|
||||
return last
|
||||
Reference in New Issue
Block a user