fix(sub): emit Shadowsocks http-header links as SIP002 obfs-local plugin

v2rayN's SS parser only reads the SIP002 `plugin` query param; it ignores the
xray-native type/headerType/host/path, so an SS link with a TCP http header
imported as plain SS and failed to connect. Re-encode the http header as
`plugin=obfs-local;obfs=http;obfs-host=<host>`, which v2rayN maps to an
xray tcp/http-header outbound. Mirrored in the frontend link generator.

Note: v2rayN carries only the host and forces request path "/", so this matches
an inbound whose header path is "/" (the default); xray validates path, not host.
This commit is contained in:
MHSanaei
2026-06-17 14:11:25 +02:00
parent 5038fa1cec
commit 21e9b94bb4
3 changed files with 52 additions and 0 deletions
+28
View File
@@ -174,6 +174,34 @@ func TestChar_C3_ShadowsocksExternalProxy(t *testing.T) {
}
}
// A TCP http header on Shadowsocks must be emitted as a SIP002 obfs-local
// plugin (what v2rayN parses), not the xray-native type/headerType/host/path
// params (which SIP002 clients silently ignore).
func TestShadowsocksTcpHttpHeaderUsesObfsLocalPlugin(t *testing.T) {
stream := `{
"network":"tcp","security":"none",
"tcpSettings":{"header":{"type":"http","request":{"path":["/"],"headers":{"Host":["test"]}}}}
}`
in := &model.Inbound{
Listen: "203.0.113.1",
Port: 38143,
Protocol: model.Shadowsocks,
Remark: "ss",
Settings: `{"method":"2022-blake3-aes-256-gcm","password":"inboundpw","clients":[{"password":"clientpw","email":"user"}]}`,
StreamSettings: stream,
}
s := &SubService{}
got := s.genShadowsocksLink(in, "user")
if !strings.Contains(got, "plugin=obfs-local%3Bobfs%3Dhttp%3Bobfs-host%3Dtest") {
t.Fatalf("expected obfs-local plugin param, got: %q", got)
}
for _, leak := range []string{"headerType=", "type=tcp", "host=test", "path="} {
if strings.Contains(got, leak) {
t.Fatalf("xray-native param %q must not leak into SS link: %q", leak, got)
}
}
}
// C6 — Hysteria2, TLS, 1 externalProxy entry with a cert pin. Guards that the
// Hysteria generator stays on its own path (hex pinSHA256, not pcs) and is NOT
// folded into the unified builder. Pin hex is derived, so Contains is used.