mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-28 00:24:19 +00:00
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:
@@ -595,6 +595,18 @@ export function genShadowsocksLink(input: GenShadowsocksLinkInput): string {
|
||||
applyExternalProxyTLSParams(externalProxy, params, security);
|
||||
}
|
||||
|
||||
// SIP002 clients (v2rayN) ignore type/headerType/host/path and only read
|
||||
// `plugin`. Re-encode a TCP http header as obfs-local so they build a
|
||||
// matching tcp/http outbound (v2rayN forces request path "/").
|
||||
if ((stream.network ?? 'tcp') === 'tcp' && params.get('headerType') === 'http') {
|
||||
const host = params.get('host') ?? '';
|
||||
params.delete('type');
|
||||
params.delete('headerType');
|
||||
params.delete('host');
|
||||
params.delete('path');
|
||||
params.set('plugin', `obfs-local;obfs=http;obfs-host=${host}`);
|
||||
}
|
||||
|
||||
const isSS2022 = settings.method.substring(0, 4) === '2022';
|
||||
const isSSMultiUser = settings.method !== '2022-blake3-chacha20-poly1305';
|
||||
const passwords: string[] = [];
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -704,6 +704,18 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||
applyShareTLSParams(stream, params)
|
||||
}
|
||||
|
||||
// SIP002 clients (v2rayN) ignore the xray-native type/headerType/host/path
|
||||
// params and only read `plugin`. Re-encode a TCP http header as obfs-local so
|
||||
// they build a matching tcp/http outbound (v2rayN forces request path "/").
|
||||
if streamNetwork == "tcp" && params["headerType"] == "http" {
|
||||
host := params["host"]
|
||||
delete(params, "type")
|
||||
delete(params, "headerType")
|
||||
delete(params, "host")
|
||||
delete(params, "path")
|
||||
params["plugin"] = "obfs-local;obfs=http;obfs-host=" + host
|
||||
}
|
||||
|
||||
encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password)
|
||||
if method[0] == '2' {
|
||||
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
||||
|
||||
Reference in New Issue
Block a user