Skip to content

Commit a1b8e8f

Browse files
committed
add built-in e2e test matrix
Signed-off-by: aleskxyz <39186039+aleskxyz@users.noreply.github.com>
1 parent 2a57818 commit a1b8e8f

7 files changed

Lines changed: 593 additions & 51 deletions

File tree

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Useful options:
6767
| Flag | Default | Meaning |
6868
| ---- | ------- | ------- |
6969
| `-config` | `./config.ini` if it exists | INI config file; CLI flags override file values |
70+
| `-test` | disabled | Run the built-in e2e test matrix for the selected `-connect`/`-fake-sni` pair, then exit |
7071
| `-fake-sni` | hostname from `-connect` | Decoy SNI used in the injected fake ClientHello |
7172
| `-fake-repeat` | `1` | Number of fake ClientHello injections |
7273
| `-fake-delay` | `2ms` | Delay after fake injection before forwarding real traffic |
@@ -93,6 +94,16 @@ sni-chunk = 3
9394

9495
The repository includes `config.example.ini`; copy it to `config.ini` to use the automatic default config loading.
9596

97+
Method test:
98+
99+
```bash
100+
./sni-spoofing-linux-amd64 -test -connect 104.19.229.21:443 -fake-sni hcaptcha.com
101+
```
102+
103+
`-test` first runs a preflight check for the selected upstream IP and fake SNI. The preflight confirms that the upstream path is reachable and compares the network-visible IPs used by the test; if the upstream path is unreachable or the known IPs differ, the method is not expected to work for that pair.
104+
105+
After preflight, it runs an e2e matrix through the local tunnel. The matrix tries the supported TLS fingerprints with one or two fake injections, both with and without real ClientHello fragmentation. `PASS` means the local tunnel completed a real HTTPS request; `FAIL` means that combination did not work in the current network conditions. If every case fails, try a different upstream IP or fake SNI. If only some cases pass, use one of the passing combinations for normal runs.
106+
96107
### Docker (prebuilt image)
97108

98109
Prebuilt images are published to GitHub Container Registry:

config/connect.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,24 @@ import (
1010
)
1111

1212
func ConnectFromCLI(listenAddr, connectAddr, fakeSNIOverride string) (*Config, error) {
13+
return connectFromCLI(listenAddr, connectAddr, fakeSNIOverride, false)
14+
}
15+
16+
func ConnectFromCLIAllowListenPortZero(listenAddr, connectAddr, fakeSNIOverride string) (*Config, error) {
17+
return connectFromCLI(listenAddr, connectAddr, fakeSNIOverride, true)
18+
}
19+
20+
func connectFromCLI(listenAddr, connectAddr, fakeSNIOverride string, allowListenPortZero bool) (*Config, error) {
1321
listenHost, listenPortStr, err := net.SplitHostPort(listenAddr)
1422
if err != nil {
1523
return nil, fmt.Errorf("listen address %q: %w", listenAddr, err)
1624
}
1725
listenPort, err := strconv.Atoi(listenPortStr)
18-
if err != nil || listenPort < 1 || listenPort > 65535 {
26+
minListenPort := 1
27+
if allowListenPortZero {
28+
minListenPort = 0
29+
}
30+
if err != nil || listenPort < minListenPort || listenPort > 65535 {
1931
return nil, fmt.Errorf("invalid listen port in %q", listenAddr)
2032
}
2133
connectHost, connectPortStr, err := net.SplitHostPort(connectAddr)

config/connect_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,16 @@ func TestConnectFromCLI_IPWithFakeSNI(t *testing.T) {
5353
t.Fatalf("ConnectIPv4s = %v", cfg.ConnectIPv4s)
5454
}
5555
}
56+
57+
func TestConnectFromCLIListenPortZeroOnlyWhenAllowed(t *testing.T) {
58+
if _, err := ConnectFromCLI("127.0.0.1:0", "198.51.100.2:443", "allowed.example.com"); err == nil {
59+
t.Fatal("expected listen port 0 to fail in normal mode")
60+
}
61+
cfg, err := ConnectFromCLIAllowListenPortZero("127.0.0.1:0", "198.51.100.2:443", "allowed.example.com")
62+
if err != nil {
63+
t.Fatal(err)
64+
}
65+
if cfg.ListenPort != 0 {
66+
t.Fatalf("ListenPort = %d, want 0", cfg.ListenPort)
67+
}
68+
}

injection/injector_linux.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,6 @@ func (f *FakeTcpInjector) cleanupIptables() {
250250
runCmd("iptables", "-D", "INPUT", "-p", "tcp",
251251
"-s", f.connectIP, "--sport", port,
252252
"-j", "NFQUEUE", "--queue-num", queueNum)
253-
254-
log.Print("iptables: cleaned")
255253
}
256254

257255
func runCmd(name string, args ...string) error {
@@ -270,7 +268,9 @@ func (f *FakeTcpInjector) Start() error {
270268
return 0
271269
},
272270
func(e error) int {
273-
log.Printf("nfqueue error: %v", e)
271+
if f.ctx.Err() == nil {
272+
log.Printf("nfqueue error: %v", e)
273+
}
274274
return 0
275275
},
276276
)

injection/injector_windows.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,13 @@ func (f *FakeTcpInjector) Close() {
112112
}
113113

114114
func (f *FakeTcpInjector) sendPacket(raw []byte, addr *godivert.Address) error {
115+
if f.shutdown.Load() {
116+
return nil
117+
}
115118
f.sendMu.Lock()
116119
_, err := f.wd.Send(raw, addr)
117120
f.sendMu.Unlock()
118-
if err != nil {
121+
if err != nil && !f.shutdown.Load() {
119122
log.Printf("WinDivert send error: %v", err)
120123
}
121124
return err

0 commit comments

Comments
 (0)