A Go implementation of the SNI-Spoofing DPI bypass technique.
This repository provides a local TCP proxy that injects a fake TLS ClientHello with a spoofed SNI during the TCP handshake. The real TLS connection is then relayed, allowing DPI devices to see a decoy SNI while the client continues to talk to the intended target.
Two interfaces share the same proxy core: the CLI (sni-spoofing*) and an experimental GUI (sni-spoofing-gui*) — see GUI (experimental).
At minimum, provide -listen and -connect. Use -fake-sni when -connect is an IP address.
./sni-spoofing -listen 127.0.0.1:40443 -connect 104.19.229.21:443 -fake-sni hcaptcha.com -utls firefoxPlatform-specific notes (CLI only):
- Linux/OpenWrt: run as
rootor withsudo. - macOS: run with
sudo; BPF requires root privileges. - Windows: run as Administrator.
-listensets the local proxy address, e.g.127.0.0.1:40443.-connectsets the upstream server IP and port, e.g.104.19.229.21:443.
If -connect is a hostname, the tool resolves it automatically. If it is an IP address, then -fake-sni must be provided.
-fake-snispecifies the decoy hostname that DPI should see.-utlsselects the ClientHello fingerprint preset.
Common presets:
firefoxchromeedgesafariiosqq360browsernone
Run -h for a full list of supported -utls names.
You can use CLI flags or a config file. If -config is not specified, the CLI loads ./config.ini automatically when present. CLI flags override config values. The GUI does not use config.ini; settings are edited in the app.
Example config.ini:
listen = 127.0.0.1:40443
connect = 104.19.229.21:443
fake-sni = hcaptcha.com
utls = firefox
fake-repeat = 1
fake-delay = 2ms
ack-timeout = 2s
injector = active
enable-fragment = false
fragment-delay = 500ms
sni-chunk = 3| Flag | Default | Description |
|---|---|---|
-config |
./config.ini if available |
Load INI config file |
-listen |
none | Local address to accept client connections |
-connect |
none | Upstream IP:port to connect through |
-fake-sni |
hostname from -connect |
Decoy SNI used in the fake ClientHello |
-injector |
active (passive on macOS) |
Injector backend |
-fake-repeat |
1 |
Number of fake ClientHello packets |
-fake-delay |
2ms |
Delay before forwarding real traffic |
-ack-timeout |
2s |
Max wait for server response after fake injection |
-utls |
firefox |
TLS fingerprint preset |
-enable-fragment |
false |
Split the real ClientHello into fragments |
-fragment-delay |
500ms |
Delay between fragmented writes |
-sni-chunk |
3 |
Bytes per write when fragmentation is enabled |
-test |
disabled | Run built-in method test matrix |
-injector controls the packet injection backend.
active: default on Linux/Windows.passive: use a passive observer/injector backend where available. In many cases, passive mode may also offer better performance.
Platform behavior:
- Linux:
activeuses nfqueue + raw socket,passiveuses AF_PACKET and link-layer writes. - Windows:
activeuses WinDivert reinjection,passiveuses WinDivert sniff/send. - macOS: only
passiveis supported by BPF tap and link-layer writes.
Run the proxy on Linux:
sudo ./sni-spoofing-linux-amd64 \
-listen 127.0.0.1:40443 \
-connect 104.19.229.21:443 \
-fake-sni hcaptcha.com \
-utls firefoxRun the proxy on Windows (amd64):
.\sni-spoofing-windows-amd64.exe -listen 127.0.0.1:40443 -connect 104.19.229.21:443 -fake-sni hcaptcha.com -utls firefoxOn Windows arm64, use sni-spoofing-windows-arm64.exe (same flags). Use the WinDivert DLL that matches your binary architecture.
Run with passive injector mode:
sudo ./sni-spoofing-linux-amd64 -listen 127.0.0.1:40443 -connect 104.19.229.21:443 -fake-sni hcaptcha.com -injector passiveRun the official Docker image with host networking and required capabilities:
docker run --rm -it \
--network host \
--cap-add NET_ADMIN --cap-add NET_RAW \
ghcr.io/aleskxyz/sni-spoofing-go:latest \
-listen 127.0.0.1:40443 \
-connect 104.19.229.21:443 \
-fake-sni hcaptcha.com \
-utls firefoxIf Docker is unavailable, Podman can be used alternatively.
Use -test to validate the selected upstream IP and fake SNI before normal operation.
./sni-spoofing-linux-amd64 -test -connect 104.19.229.21:443 -fake-sni hcaptcha.comThe test mode performs a preflight check and then runs a small matrix of endpoint combinations. If it reports failures, try a different upstream IP, a different fake SNI, or another -utls preset.
Example -test output:
Preflight
external IP: 198.51.100.1
internal IP: 198.51.100.1
result: IPs match; running e2e matrix
Matrix
UTLS Fake-Repeat Fragment Result
none 1 off PASS
none 1 on PASS
none 2 off PASS
none 2 on PASS
firefox 1 off PASS
firefox 1 on PASS
firefox 2 off PASS
firefox 2 on PASS
chrome 1 off PASS
chrome 1 on PASS
chrome 2 off PASS
chrome 2 on PASS
safari 1 off PASS
safari 1 on PASS
safari 2 off PASS
safari 2 on PASS
ios 1 off PASS
ios 1 on PASS
ios 2 off PASS
ios 2 on PASS
edge 1 off PASS
edge 1 on PASS
edge 2 off PASS
edge 2 on PASS
All 24 cases passed.
Press Enter to exit...
- This is a plain TCP proxy, not an HTTP or SOCKS proxy.
- Use
curl --resolveto test HTTPS through the local listener while preserving the client hostname. - The real target hostname comes from the client request, while
-fake-sniis the decoy seen by DPI. - If
-connectis an IP address, supply-fake-sniexplicitly.
Example test command:
curl -sSLf --resolve one.one.one.one:40443:127.0.0.1 https://one.one.one.one:40443/ | grep '^\.\.'Expected output:
............................................................
.........1............1............1............1...........
........11...........11...........11...........11...........
.......111..........111..........111..........111...........
......1111.........1111.........1111.........1111...........
........11...........11...........11...........11...........
........11...........11...........11...........11...........
........11...........11...........11...........11...........
........11....ooo....11....ooo....11....ooo....11...........
......111111..ooo..111111..ooo..111111..ooo..111111.........
............................................................
| Platform | CLI | GUI |
|---|---|---|
| Linux/OpenWrt | Requires root. Uses nfqueue + raw socket by default. | Normal user at launch; sudo/pkexec when starting proxy or tests. |
| macOS | Requires sudo. Uses BPF tap and passive injection. | Normal user at launch; elevation when starting proxy or tests. |
| Windows | Requires Administrator. Uses WinDivert. Binaries: sni-spoofing-windows-amd64.exe, sni-spoofing-windows-arm64.exe. |
Normal user at launch; UAC when starting proxy or tests. Binaries: sni-spoofing-gui-windows-amd64.exe, sni-spoofing-gui-windows-arm64.exe. |
On OpenWrt, install the required nfqueue packages before running in active injector mode:
apk update
apk install iptables-mod-nfqueue kmod-nfnetlink-queueDesktop app (Wails + Svelte) in gui/ — English/Persian UI, same proxy core as the CLI. Separate gui/go.mod keeps Wails out of the CLI build.
The GUI runs without admin at launch. When you Start the proxy or Run test matrix, it spawns an elevated helper process (same binary, -helper mode) and talks to it over authenticated localhost TCP. You approve UAC / sudo / pkexec at that point — typically once per app session, not on every start/stop.
make deps-linux # Linux: GTK + WebKit (once)
make gui # this machine → dist/
make gui-dist # all GUI targets for this hostGo 1.25+, Node 20.19+. make gui* installs Wails to $(go env GOPATH)/bin/wails. Linux release builds need -tags webkit2_41 (Ubuntu 24.04+, Debian 13). gui-linux-arm64 needs an arm64 host, not x86 cross-compile.
Settings are edited in the UI (no config.ini). Logs and test results appear in the app; helper diagnostics are appended to %LOCALAPPDATA%\sni-spoofing-gui\helper.log on Windows (or the platform user-cache equivalent).
From gui/ after make install-wails (or any make gui*):
cd gui
$(go env GOPATH)/bin/wails devWails runs the Vite dev server and reloads the UI on frontend changes. Add $(go env GOPATH)/bin to PATH if you want to type wails directly.
Linux: install deps with make deps-linux first. If wails dev fails on WebKitGTK 4.1-only distros, use wails dev -tags webkit2_41.
Windows: the GUI manifest uses asInvoker — run wails dev from a normal terminal. Elevation is requested when you start the proxy or run the test matrix in the UI, not at app launch.
macOS: install Xcode Command Line Tools (Wails docs).
| Location | Contents |
|---|---|
dist/ |
Release binaries |
.build/ |
Local dev CLI; npm/Wails scratch |
make build # .build/sni-spoofing
make dist # CLI → dist/
make gui-dist # GUI → dist/ (host-dependent set)
make dist-checksums # after dist + gui-dist if you want GUI in SHA256SUMS too
make cleanCLI (make dist): sni-spoofing-windows-amd64.exe, sni-spoofing-windows-arm64.exe, sni-spoofing-linux-*, sni-spoofing-darwin-* — via make windows-amd64, windows-arm64, linux-amd64, linux-arm64, linux-armv7, linux-mipsle, linux-mips, darwin-amd64, darwin-arm64
GUI (dist/): sni-spoofing-gui-linux-amd64, sni-spoofing-gui-linux-arm64, sni-spoofing-gui-windows-amd64.exe, sni-spoofing-gui-windows-arm64.exe, sni-spoofing-gui-darwin-universal.zip — via make gui-linux-amd64, gui-linux-arm64, gui-windows-amd64, gui-windows-arm64, gui-darwin-universal.
Put config.ini next to the CLI binary you run, not in dist/. The GUI does not read config.ini. Run make help for all targets.
This project is licensed under the GNU General Public License v3.0.
See LICENSE for details.
Based on https://github.com/patterniha/SNI-Spoofing by @patterniha.