Bypass DPI blocking of Discord voice chat.
Drover intercepts the 74-byte UDP STUN Binding Request that Discord sends to voice servers, injects two tiny 1-byte probe packets (0x00, 0x01) before it, waits 50ms, then releases the original packet. This corrupts the DPI's flow signature and lets voice traffic through.
Disclaimer: This project is experimental and for educational/research purposes only. It is a personal tool developed to study network filtering techniques. Use at your own risk. Not affiliated with Discord or any ISP.
git clone https://github.com/SimoHypers/discord-voice-patch.git
cd discord-voice-patch- Linux (kernel with NFQUEUE and nftables support)
- Go 1.25+
- Root privileges (or
CAP_NET_ADMIN+CAP_NET_RAW) for daemon mode gccfor LD_PRELOAD fallback mode
go build -o drover ./cmd/droverNo CGo required. The binary is pure Go.
To embed version/commit data:
go build -ldflags "-X main.version=$(git describe --tags --always --dirty) \
-X main.commit=$(git rev-parse --short HEAD) \
-X main.buildDate=$(date -u +%Y-%m-%d)" \
-o drover ./cmd/droversudo cp drover /usr/local/bin/Or run ./drover directly from the project directory.
sudo drover installInstalls the systemd service, starts the daemon immediately, and configures it to auto-start on boot. After this, Discord voice works β no terminal needed.
sudo drover daemonSets up nftables rules, opens an NFQUEUE, and processes packets. Press Ctrl+C to stop. Rules are automatically removed on exit unless disabled in config.
drover launchThis compiles an embedded C shim and launches Discord with LD_PRELOAD. No root required. Only works with native (RPM/deb/tar.gz) Discord installations.
drover daemon Start the NFQUEUE packet processing daemon
drover setup Install nftables rules (one-time)
drover teardown Remove nftables rules
drover status Check if nftables rules are active
drover launch Launch Discord with LD_PRELOAD shim
drover install Install systemd service (auto-starts on boot)
drover uninstall Remove systemd service
drover version Show version information
Drover reads a TOML config file searched in this order:
./drover.toml~/.config/drover/drover.toml/etc/drover/drover.toml
All values are optional β sensible defaults are used when absent.
[probe]
size = 74 # UDP payload size to match (STUN Binding Request)
bytes = [0, 1] # Probe packet payloads
delay_ms = 50 # Delay after probes in milliseconds
[filter]
ports = [
{ lo = 1024, hi = 65535 },
]
[daemon]
nfqueue_num = 1 # NFQUEUE number
auto_teardown = true # Remove nftables rules on exit
pid_file = "/run/drover.pid"
mark = 212 # 0xD4 β fwmark to bypass nftables
[log]
level = "info" # debug, info, warn, error
file = "" # Empty = stderr, or path to log fileEnvironment variables override TOML values:
DROVER_PROBE_SIZE=100
DROVER_PROBE_DELAY_MS=75
DROVER_PROBE_BYTES="0,1"
DROVER_DAEMON_NFQUEUE_NUM=2
DROVER_LOG_LEVEL=debugThe drover install command handles everything automatically. To remove:
sudo drover uninstallThe service uses AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW so it runs without full root, and logs to the systemd journal (journalctl -u drover).
Discord sends 74-byte UDP STUN packet
-> nftables rule matches (udp dport 1024-65535, mark != 0xD4)
-> packet sent to NFQUEUE
-> Go daemon receives packet
-> payload == 74 bytes AND destination not yet probed?
YES: NF_DROP, send probes via raw socket, wait, reinject original
NO: NF_ACCEPT immediately
Probe packets and reinjected originals carry SO_MARK=0xD4. The nftables rule excludes marked packets, preventing infinite re-queueing loops.
An adapted C shim (based on the original linux/drover.c) hooks socket(), sendto(), and sendmsg() inside the Discord process. When a 74-byte UDP send is detected on a socket that hasn't sent before, it injects probes via the real sendto(), sleeps, then lets the real call proceed.
| Client | Daemon Mode | LD_PRELOAD Mode |
|---|---|---|
| Native (RPM/deb/tar.gz) | Works | Works |
| Flatpak | Works | Fails (sandbox) |
| AppImage | Works | Fails (FUSE/re-exec) |
| Vencord / BetterDiscord | Works | Depends on host install |
- Daemon mode requires root or appropriate capabilities
- LD_PRELOAD mode does not work with Flatpak, AppImage, or sandboxed Discord
- The 74-byte probe size assumption matches current Discord WebRTC; if Discord updates their STUN packet size, the filter will need adjustment
- Designed for Linux only
# Unit tests (no root required)
go test ./...
# Integration tests (requires root)
go test -tags integration ./test/integration/MIT