fix: fix Shadowsocks support for Xray kernel

This commit is contained in:
xboard
2026-03-23 15:36:01 +08:00
parent e8e2abbc37
commit 4676caac96
4 changed files with 79 additions and 63 deletions
+24 -22
View File
@@ -152,28 +152,30 @@ func buildRoutes(panelRoutes []panel.RouteRule, custom []map[string]any) M {
rules = append(rules, M(cr)) rules = append(rules, M(cr))
} }
// Standard blocks for private/loopback // Standard blocks for private IPv4 and IPv6 ranges to prevent SSRF.
// rules = append(rules, M{ rules = append(rules,
// "outbound": "block", M{
// "ip_cidr": []string{ "outbound": "block",
// "10.0.0.0/8", "ip_cidr": []string{
// "100.64.0.0/10", "10.0.0.0/8",
// "127.0.0.0/8", "100.64.0.0/10",
// "169.254.0.0/16", "127.0.0.0/8",
// "172.16.0.0/12", "169.254.0.0/16",
// "192.0.0.0/24", "172.16.0.0/12",
// "192.168.0.0/16", "192.0.0.0/24",
// "198.18.0.0/15", "192.168.0.0/16",
// "fc00::/7", "198.18.0.0/15",
// "fe80::/10", },
// "::1/128", },
// }, M{
// }, M{ "outbound": "block",
// "outbound": "block", "ip_cidr": []string{
// "domain": []string{ "fc00::/7",
// "geoip:private", "fe80::/10",
// }, "::1/128",
// }) },
},
)
// Panel-defined routes (usually specific blocks/proxies) // Panel-defined routes (usually specific blocks/proxies)
for _, pr := range panelRoutes { for _, pr := range panelRoutes {
+7 -3
View File
@@ -113,7 +113,7 @@ func TestBuildInbound_VMess_WithGRPC(t *testing.T) {
ServerPort: 443, ServerPort: 443,
Network: "grpc", Network: "grpc",
NetworkSettings: map[string]interface{}{ NetworkSettings: map[string]interface{}{
"service_name": "mygrpc", "serviceName": "mygrpc",
}, },
} }
inbound := buildInbound(nc, testUsers, "", "") inbound := buildInbound(nc, testUsers, "", "")
@@ -245,9 +245,13 @@ func TestBuildInbound_Trojan_NoTLS(t *testing.T) {
TLS: 0, TLS: 0,
} }
inbound := buildInbound(nc, testUsers, "", "") inbound := buildInbound(nc, testUsers, "", "")
if _, exists := inbound["tls"]; exists { // Trojan requires TLS; buildTrojan should force-enable self-signed TLS
t.Error("trojan with tls=0 should not have TLS config") // even when the panel sends tls=0.
tls, exists := inbound["tls"].(M)
if !exists {
t.Fatal("trojan with tls=0 should still get TLS (force-enabled)")
} }
assertMapValue(t, tls, "enabled", true)
} }
func TestBuildInbound_Trojan_WithTLS(t *testing.T) { func TestBuildInbound_Trojan_WithTLS(t *testing.T) {
+24 -21
View File
@@ -311,43 +311,46 @@ var ss2022Methods = map[string]ss2022Config{
func buildShadowsocks(base M, nc *panel.NodeConfig, users []panel.User) M { func buildShadowsocks(base M, nc *panel.NodeConfig, users []panel.User) M {
ss2022, isSS2022 := ss2022Methods[nc.Cipher] ss2022, isSS2022 := ss2022Methods[nc.Cipher]
// Single-user mode for traditional ciphers clients := make([]M, 0, len(users))
if !isSS2022 {
if len(users) > 0 {
base["settings"] = M{
"method": nc.Cipher,
"password": users[0].UUID,
"email": userEmail(users[0].ID),
"network": "tcp,udp",
}
}
return base
}
// Multi-user mode for 2022-blake3 ciphers if isSS2022 {
clients := make([]M, len(users)) // SS2022: server key at top level, per-user key must be Base64 of fixed-length raw bytes.
// Only blake3-aes-* supports multi-user in Xray; chacha20 variant is single-user only.
rawBuf := make([]byte, ss2022.size) rawBuf := make([]byte, ss2022.size)
for i := range users { for i := range users {
u := &users[i] u := &users[i]
// SS2022 User Key must be Base64 of fixed-length raw bytes
for j := range rawBuf { for j := range rawBuf {
rawBuf[j] = 0 rawBuf[j] = 0
} }
copy(rawBuf, u.UUID) copy(rawBuf, u.UUID)
clients = append(clients, M{
clients[i] = M{
"password": base64.StdEncoding.EncodeToString(rawBuf), "password": base64.StdEncoding.EncodeToString(rawBuf),
"email": userEmail(u.ID), "email": userEmail(u.ID),
})
} }
}
base["settings"] = M{ base["settings"] = M{
"method": nc.Cipher, "method": nc.Cipher,
"password": nc.ServerKey, "password": nc.ServerKey,
"clients": clients, "clients": clients,
"network": "tcp,udp", "network": "tcp,udp",
} }
} else {
// Traditional ciphers: multi-user via clients array.
// Each entry must carry its own "method" field for Xray to parse correctly.
for i := range users {
u := &users[i]
clients = append(clients, M{
"method": nc.Cipher,
"password": u.UUID,
"email": userEmail(u.ID),
})
}
base["settings"] = M{
"method": nc.Cipher,
"clients": clients,
"network": "tcp,udp",
}
}
return base return base
} }
@@ -426,7 +429,7 @@ func applyStreamSettings(base M, nc *panel.NodeConfig, certFile, keyFile string)
case "grpc": case "grpc":
grpcSettings := M{} grpcSettings := M{}
if nc.NetworkSettings != nil { if nc.NetworkSettings != nil {
if v, ok := nc.NetworkSettings["service_name"]; ok { if v, ok := nc.NetworkSettings["serviceName"]; ok {
grpcSettings["serviceName"] = v grpcSettings["serviceName"] = v
} }
} }
+11 -4
View File
@@ -361,7 +361,7 @@ func TestBuildConfig_StatsEnabled(t *testing.T) {
} }
} }
func TestBuildConfig_Shadowsocks_SingleUser(t *testing.T) { func TestBuildConfig_Shadowsocks_MultiUserTraditional(t *testing.T) {
nc := panel.NodeConfig{ nc := panel.NodeConfig{
Protocol: "shadowsocks", Protocol: "shadowsocks",
ServerPort: 8388, ServerPort: 8388,
@@ -377,12 +377,19 @@ func TestBuildConfig_Shadowsocks_SingleUser(t *testing.T) {
ib := inbounds[0].(map[string]interface{}) ib := inbounds[0].(map[string]interface{})
settings := ib["settings"].(map[string]interface{}) settings := ib["settings"].(map[string]interface{})
// Single-user mode: password directly, no clients array
if settings["method"] != "aes-128-gcm" { if settings["method"] != "aes-128-gcm" {
t.Errorf("expected method aes-128-gcm, got %v", settings["method"]) t.Errorf("expected method aes-128-gcm, got %v", settings["method"])
} }
if settings["password"] != testUsers[0].UUID { clients := settings["clients"].([]interface{})
t.Errorf("expected password %s, got %v", testUsers[0].UUID, settings["password"]) if len(clients) != len(testUsers) {
t.Fatalf("expected %d clients, got %d", len(testUsers), len(clients))
}
c0 := clients[0].(map[string]interface{})
if c0["method"] != "aes-128-gcm" {
t.Errorf("expected per-user method aes-128-gcm, got %v", c0["method"])
}
if c0["password"] != testUsers[0].UUID {
t.Errorf("expected password %s, got %v", testUsers[0].UUID, c0["password"])
} }
} }