2026-08-12 00:29:54 +00:00
|
|
|
|
# Robovoice DHCP Tunnel Protocol
|
2026-08-11 09:03:50 +00:00
|
|
|
|
|
|
|
|
|
|
## Overview
|
|
|
|
|
|
|
2026-08-12 00:29:54 +00:00
|
|
|
|
Robovoice communicates with a remote STT server by tunneling through the
|
|
|
|
|
|
DHCP UDP ports (68→67). This exploits a common killswitch exception: VPN
|
|
|
|
|
|
software (e.g. WireGuard) blocks all traffic except DHCP, which is allowed
|
|
|
|
|
|
for network connectivity maintenance.
|
2026-08-11 09:03:50 +00:00
|
|
|
|
|
|
|
|
|
|
```
|
2026-08-12 00:29:54 +00:00
|
|
|
|
[Robovoice client] --broadcast UDP :68→:67--> [STT server]
|
|
|
|
|
|
[Robovoice client] <--unicast UDP :67→:68-- [STT server]
|
2026-08-11 09:03:50 +00:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-12 00:29:54 +00:00
|
|
|
|
The client broadcasts NOP heartbeats while PTT is held. The server starts
|
|
|
|
|
|
recording on the first NOP and stops when it receives OFF or when 150ms
|
|
|
|
|
|
pass with no NOPs.
|
|
|
|
|
|
|
2026-08-11 09:03:50 +00:00
|
|
|
|
## Transport
|
|
|
|
|
|
|
2026-08-12 00:29:54 +00:00
|
|
|
|
- **Protocol:** UDP (connectionless, unreliable)
|
|
|
|
|
|
- **Client → Server:** broadcast, source port 68, dest port 67
|
|
|
|
|
|
- **Server → Client:** unicast, source port 67, dest port 68
|
|
|
|
|
|
- **Client binds:** to a specific LAN interface IP on port 68 (with
|
|
|
|
|
|
`SO_REUSEADDR` to coexist with the Windows DHCP service)
|
|
|
|
|
|
- **No connection state** — purely fire-and-forget datagrams
|
2026-08-11 09:03:50 +00:00
|
|
|
|
|
2026-08-12 00:29:54 +00:00
|
|
|
|
## Wire format
|
2026-08-11 09:03:50 +00:00
|
|
|
|
|
2026-08-12 00:29:54 +00:00
|
|
|
|
All messages are plain text, newline-terminated (`\n`). Every message starts
|
|
|
|
|
|
with the 6-byte magic `HKMSTR` to distinguish our traffic from real DHCP.
|
2026-08-11 09:03:50 +00:00
|
|
|
|
|
2026-08-12 00:29:54 +00:00
|
|
|
|
### Client → Server
|
2026-08-11 09:03:50 +00:00
|
|
|
|
|
2026-08-12 00:29:54 +00:00
|
|
|
|
**NOP (heartbeat while PTT held):**
|
2026-08-11 09:03:50 +00:00
|
|
|
|
```
|
2026-08-12 00:29:54 +00:00
|
|
|
|
HKMSTR <nonce>\n
|
|
|
|
|
|
```
|
|
|
|
|
|
Sent every 50ms while PTT is held. The nonce is an incrementing unsigned
|
|
|
|
|
|
integer that makes each datagram unique. The server discards it — it's
|
|
|
|
|
|
purely for packet uniqueness, not for any protocol logic.
|
2026-08-11 09:03:50 +00:00
|
|
|
|
|
2026-08-12 00:29:54 +00:00
|
|
|
|
**OFF (PTT released):**
|
|
|
|
|
|
```
|
|
|
|
|
|
HKMSTR:OFF <nonce>\n
|
|
|
|
|
|
```
|
|
|
|
|
|
Sent once when PTT is released. This is the fast-stop signal. If lost, the
|
|
|
|
|
|
150ms timeout acts as a backstop.
|
2026-08-11 09:03:50 +00:00
|
|
|
|
|
2026-08-12 00:29:54 +00:00
|
|
|
|
### Server → Client
|
2026-08-11 09:03:50 +00:00
|
|
|
|
|
2026-08-12 00:29:54 +00:00
|
|
|
|
**Partial transcript:**
|
2026-08-11 09:03:50 +00:00
|
|
|
|
```
|
2026-08-12 00:29:54 +00:00
|
|
|
|
HKMSTR:P <text>\n
|
2026-08-11 09:03:50 +00:00
|
|
|
|
```
|
2026-08-12 00:29:54 +00:00
|
|
|
|
Intermediate recognition result. Fire-and-forget. Client logs it but does
|
|
|
|
|
|
not act on it.
|
2026-08-11 09:03:50 +00:00
|
|
|
|
|
2026-08-12 00:29:54 +00:00
|
|
|
|
**Final transcript:**
|
|
|
|
|
|
```
|
|
|
|
|
|
HKMSTR:F <text>\n
|
|
|
|
|
|
```
|
|
|
|
|
|
Complete utterance. Client feeds this to the TTS engine.
|
2026-08-11 09:03:50 +00:00
|
|
|
|
|
2026-08-12 00:29:54 +00:00
|
|
|
|
## Server state machine
|
2026-08-11 09:03:50 +00:00
|
|
|
|
|
2026-08-12 00:29:54 +00:00
|
|
|
|
```
|
|
|
|
|
|
┌──────────────────────────────────────────┐
|
|
|
|
|
|
│ │
|
|
|
|
|
|
▼ │
|
|
|
|
|
|
┌──────────┐ first NOP ┌──────────────┐ │
|
|
|
|
|
|
│ IDLE │ ──────────► │ RECORDING │ │
|
|
|
|
|
|
└──────────┘ └──────────────┘ │
|
|
|
|
|
|
│ │ │
|
|
|
|
|
|
OFF │ │ 150ms │
|
|
|
|
|
|
recv'd │ │ silence │
|
|
|
|
|
|
▼ ▼ │
|
|
|
|
|
|
┌─────────────┐ │
|
|
|
|
|
|
│ PROCESSING │ │
|
|
|
|
|
|
└─────────────┘ │
|
|
|
|
|
|
│ │
|
|
|
|
|
|
STT │ │
|
|
|
|
|
|
done │ │
|
|
|
|
|
|
▼ │
|
|
|
|
|
|
send HKMSTR:F ─────────┘
|
|
|
|
|
|
```
|
2026-08-11 09:03:50 +00:00
|
|
|
|
|
2026-08-12 00:29:54 +00:00
|
|
|
|
- **IDLE → RECORDING:** first NOP received, start mic capture
|
|
|
|
|
|
- **RECORDING → PROCESSING:** OFF received, OR 150ms since last NOP
|
|
|
|
|
|
- **PROCESSING → IDLE:** STT done, send `HKMSTR:F <text>`
|
|
|
|
|
|
|
|
|
|
|
|
## Timing
|
|
|
|
|
|
|
|
|
|
|
|
| Parameter | Value | Purpose |
|
|
|
|
|
|
|-----------|-------|---------|
|
|
|
|
|
|
| NOP interval | 50ms | Heartbeat frequency while PTT held |
|
|
|
|
|
|
| Silence timeout | 150ms | Stop recording if no NOPs (3 missed = lost OFF) |
|
|
|
|
|
|
| NOP bandwidth | ~20 msg/s × ~20 bytes | ~400 bytes/s — negligible |
|
|
|
|
|
|
|
|
|
|
|
|
## Why this works
|
|
|
|
|
|
|
|
|
|
|
|
1. **Outbound broadcast `:68→:67` to `255.255.255.255`** passes the
|
|
|
|
|
|
WireGuard WFP killswitch (DHCP exception matches this exact pattern)
|
|
|
|
|
|
2. **Inbound `:67→:68`** has no address restriction in the WFP rule, so
|
|
|
|
|
|
unicast replies pass through
|
|
|
|
|
|
3. **Binding to a specific interface IP** (not `0.0.0.0`) wins unicast
|
|
|
|
|
|
delivery over the Windows DHCP client service
|
|
|
|
|
|
4. **NOP spam** ensures the ON message gets through even at 5% packet loss
|
|
|
|
|
|
(3 consecutive NOPs = ~0.01% drop probability)
|
|
|
|
|
|
5. **150ms timeout** is the backstop for lost OFF — at 50ms intervals, 3
|
|
|
|
|
|
consecutive NOPs must all be lost to false-stop
|