diff --git a/internal/kernel/singbox/config.go b/internal/kernel/singbox/config.go index 71b3a1c..13b06e9 100644 --- a/internal/kernel/singbox/config.go +++ b/internal/kernel/singbox/config.go @@ -152,28 +152,30 @@ func buildRoutes(panelRoutes []panel.RouteRule, custom []map[string]any) M { rules = append(rules, M(cr)) } - // Standard blocks for private/loopback - // rules = append(rules, M{ - // "outbound": "block", - // "ip_cidr": []string{ - // "10.0.0.0/8", - // "100.64.0.0/10", - // "127.0.0.0/8", - // "169.254.0.0/16", - // "172.16.0.0/12", - // "192.0.0.0/24", - // "192.168.0.0/16", - // "198.18.0.0/15", - // "fc00::/7", - // "fe80::/10", - // "::1/128", - // }, - // }, M{ - // "outbound": "block", - // "domain": []string{ - // "geoip:private", - // }, - // }) + // Standard blocks for private IPv4 and IPv6 ranges to prevent SSRF. + rules = append(rules, + M{ + "outbound": "block", + "ip_cidr": []string{ + "10.0.0.0/8", + "100.64.0.0/10", + "127.0.0.0/8", + "169.254.0.0/16", + "172.16.0.0/12", + "192.0.0.0/24", + "192.168.0.0/16", + "198.18.0.0/15", + }, + }, + M{ + "outbound": "block", + "ip_cidr": []string{ + "fc00::/7", + "fe80::/10", + "::1/128", + }, + }, + ) // Panel-defined routes (usually specific blocks/proxies) for _, pr := range panelRoutes { diff --git a/internal/kernel/singbox/config_test.go b/internal/kernel/singbox/config_test.go index 45aee99..99d5356 100644 --- a/internal/kernel/singbox/config_test.go +++ b/internal/kernel/singbox/config_test.go @@ -113,7 +113,7 @@ func TestBuildInbound_VMess_WithGRPC(t *testing.T) { ServerPort: 443, Network: "grpc", NetworkSettings: map[string]interface{}{ - "service_name": "mygrpc", + "serviceName": "mygrpc", }, } inbound := buildInbound(nc, testUsers, "", "") @@ -245,9 +245,13 @@ func TestBuildInbound_Trojan_NoTLS(t *testing.T) { TLS: 0, } inbound := buildInbound(nc, testUsers, "", "") - if _, exists := inbound["tls"]; exists { - t.Error("trojan with tls=0 should not have TLS config") + // Trojan requires TLS; buildTrojan should force-enable self-signed TLS + // 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) { diff --git a/internal/kernel/xray/config.go b/internal/kernel/xray/config.go index a64aa00..f32d799 100644 --- a/internal/kernel/xray/config.go +++ b/internal/kernel/xray/config.go @@ -311,42 +311,45 @@ var ss2022Methods = map[string]ss2022Config{ func buildShadowsocks(base M, nc *panel.NodeConfig, users []panel.User) M { ss2022, isSS2022 := ss2022Methods[nc.Cipher] - // Single-user mode for traditional ciphers - if !isSS2022 { - if len(users) > 0 { - base["settings"] = M{ - "method": nc.Cipher, - "password": users[0].UUID, - "email": userEmail(users[0].ID), - "network": "tcp,udp", + clients := make([]M, 0, len(users)) + + if isSS2022 { + // 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) + for i := range users { + u := &users[i] + for j := range rawBuf { + rawBuf[j] = 0 } + copy(rawBuf, u.UUID) + clients = append(clients, M{ + "password": base64.StdEncoding.EncodeToString(rawBuf), + "email": userEmail(u.ID), + }) } - return base - } - - // Multi-user mode for 2022-blake3 ciphers - clients := make([]M, len(users)) - rawBuf := make([]byte, ss2022.size) - - for i := range users { - u := &users[i] - // SS2022 User Key must be Base64 of fixed-length raw bytes - for j := range rawBuf { - rawBuf[j] = 0 + base["settings"] = M{ + "method": nc.Cipher, + "password": nc.ServerKey, + "clients": clients, + "network": "tcp,udp", } - copy(rawBuf, u.UUID) - - clients[i] = M{ - "password": base64.StdEncoding.EncodeToString(rawBuf), - "email": userEmail(u.ID), + } 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", } - } - - base["settings"] = M{ - "method": nc.Cipher, - "password": nc.ServerKey, - "clients": clients, - "network": "tcp,udp", } return base } @@ -426,7 +429,7 @@ func applyStreamSettings(base M, nc *panel.NodeConfig, certFile, keyFile string) case "grpc": grpcSettings := M{} if nc.NetworkSettings != nil { - if v, ok := nc.NetworkSettings["service_name"]; ok { + if v, ok := nc.NetworkSettings["serviceName"]; ok { grpcSettings["serviceName"] = v } } diff --git a/internal/kernel/xray/config_test.go b/internal/kernel/xray/config_test.go index 740f41f..398a2c3 100644 --- a/internal/kernel/xray/config_test.go +++ b/internal/kernel/xray/config_test.go @@ -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{ Protocol: "shadowsocks", ServerPort: 8388, @@ -377,12 +377,19 @@ func TestBuildConfig_Shadowsocks_SingleUser(t *testing.T) { ib := inbounds[0].(map[string]interface{}) settings := ib["settings"].(map[string]interface{}) - // Single-user mode: password directly, no clients array if settings["method"] != "aes-128-gcm" { t.Errorf("expected method aes-128-gcm, got %v", settings["method"]) } - if settings["password"] != testUsers[0].UUID { - t.Errorf("expected password %s, got %v", testUsers[0].UUID, settings["password"]) + clients := settings["clients"].([]interface{}) + 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"]) } } @@ -427,7 +434,7 @@ func TestBuildConfig_SocksStats(t *testing.T) { ib := inbounds[0].(map[string]interface{}) settings := ib["settings"].(map[string]interface{}) accounts := settings["accounts"].([]interface{}) - + if len(accounts) == 0 { t.Fatal("no accounts in socks config") }