fix: convert Clash network to sing-box transport for vless/vmess

vless outbound was passing Clash's network field (ws/grpc/h2) directly
to sing-box's network field (which only accepts tcp/udp). Extract
clashToSingBoxTransport() to convert ws/grpc/h2/quic/httpupgrade into
sing-box transport objects. Apply to both vless and vmess outbounds.
This commit is contained in:
2026-07-29 07:58:56 +08:00
parent fbe09f13de
commit 89802cbaf8
+81 -22
View File
@@ -93,6 +93,85 @@ func RenderSingBoxJson(proxies []model.ProxyNode) string {
return string(data)
}
// clashToSingBoxTransport converts a Clash proxy node's network/transport
// fields into a sing-box transport object. Returns nil for tcp (no transport).
func clashToSingBoxTransport(proxy model.ProxyNode) any {
network := getString(proxy, "network")
switch network {
case "ws":
wsOpts, _ := proxy["ws-opts"].(map[string]any)
path := "/"
if wsOpts != nil {
if p, ok := wsOpts["path"].(string); ok && p != "" {
path = p
}
}
transportMap := map[string]any{
"type": "ws",
"path": path,
}
if wsOpts != nil {
if h, ok := wsOpts["headers"]; ok && h != nil {
transportMap["headers"] = h
}
}
return transportMap
case "grpc":
grpcOpts, _ := proxy["grpc-opts"].(map[string]any)
serviceName := ""
if grpcOpts != nil {
if s, ok := grpcOpts["grpc-service-name"].(string); ok {
serviceName = s
}
}
return map[string]any{
"type": "grpc",
"service_name": serviceName,
}
case "h2", "http":
h2Opts, _ := proxy["h2-opts"].(map[string]any)
transportMap := map[string]any{
"type": "http",
}
if h2Opts != nil {
if host, ok := h2Opts["host"].([]any); ok && len(host) > 0 {
hosts := make([]string, 0, len(host))
for _, h := range host {
if s, ok := h.(string); ok && s != "" {
hosts = append(hosts, s)
}
}
transportMap["host"] = hosts
}
if p, ok := h2Opts["path"].(string); ok && p != "" {
transportMap["path"] = p
}
}
return transportMap
case "quic":
return map[string]any{
"type": "quic",
}
case "httpupgrade":
wsOpts, _ := proxy["ws-opts"].(map[string]any)
path := "/"
if wsOpts != nil {
if p, ok := wsOpts["path"].(string); ok && p != "" {
path = p
}
}
return map[string]any{
"type": "httpupgrade",
"path": path,
}
}
return nil
}
// ToSingBoxOutbound converts a proxy node to a sing-box outbound map.
// Returns nil for unsupported proxy types.
func ToSingBoxOutbound(proxy model.ProxyNode) map[string]any {
@@ -127,9 +206,9 @@ func ToSingBoxOutbound(proxy model.ProxyNode) map[string]any {
"server_port": port,
"uuid": proxy["uuid"],
"flow": proxy["flow"],
"network": strOr(proxy, "network", "tcp"),
"packet_encoding": "xudp",
"tls": tls,
"transport": clashToSingBoxTransport(proxy),
})
case "hysteria2":
@@ -261,26 +340,6 @@ func ToSingBoxOutbound(proxy model.ProxyNode) map[string]any {
if getBool(proxy, "tls") {
tls = map[string]any{"enabled": true, "server_name": proxy["servername"]}
}
var transport any
if getString(proxy, "network") == "ws" {
wsOpts, _ := proxy["ws-opts"].(map[string]any)
path := "/"
if wsOpts != nil {
if p, ok := wsOpts["path"].(string); ok && p != "" {
path = p
}
}
transportMap := map[string]any{
"type": "ws",
"path": path,
}
if wsOpts != nil {
if h, ok := wsOpts["headers"]; ok && h != nil {
transportMap["headers"] = h
}
}
transport = transportMap
}
return util.StripUndefined(map[string]any{
"type": "vmess",
"tag": name,
@@ -290,7 +349,7 @@ func ToSingBoxOutbound(proxy model.ProxyNode) map[string]any {
"security": strOr(proxy, "cipher", "auto"),
"alter_id": proxy["alterId"],
"tls": tls,
"transport": transport,
"transport": clashToSingBoxTransport(proxy),
})
}