Files
3m-ui/backend/internal/protocol/compile_modules.go
T
kazeyukiro 05bf9a270e chore: align MetaCubeX audit — mihomo v1.19.31, TUIC modes, VMess schema
Per wiki.metacubex.one / KIMI audit:
- Bump bundled mihomo to v1.19.31 (amd64-v1 / arm64 digests verified)
- TUIC: panel keeps tuic-v4|tuic-v5; legacy "tuic" auto-detects token-only→v4 else v5
(YAML type always "tuic" per official inbound docs)
- UI: token field only on tuic-v4; tuic-v5 does not accept token in form
- VMess listener schema: drop allow-insecure (not in official VMess listener fields)
- Docs: version references → v1.19.31
chore: align MetaCubeX audit — mihomo v1.19.31, TUIC modes, VMess schema
Per wiki.metacubex.one / KIMI audit:
- Bump bundled mihomo to v1.19.31 (amd64-v1 / arm64 digests verified)
- TUIC: panel keeps tuic-v4|tuic-v5; legacy "tuic" auto-detects token-only→v4 else v5
(YAML type always "tuic" per official inbound docs)
- UI: token field only on tuic-v4; tuic-v5 does not accept token in form
- VMess listener schema: drop allow-insecure (not in official VMess listener fields)
- Docs: version references → v1.19.31
chore: align MetaCubeX audit — mihomo v1.19.31, TUIC modes, VMess schema
Per wiki.metacubex.one / KIMI audit:
- Bump bundled mihomo to v1.19.31 (amd64-v1 / arm64 digests verified)
- TUIC: panel keeps tuic-v4|tuic-v5; legacy "tuic" auto-detects token-only→v4 else v5
(YAML type always "tuic" per official inbound docs)
- UI: token field only on tuic-v4; tuic-v5 does not accept token in form
- VMess listener schema: drop allow-insecure (not in official VMess listener fields)
- Docs: version references → v1.19.31
chore: align MetaCubeX audit — mihomo v1.19.31, TUIC modes, VMess schema
Per wiki.metacubex.one / KIMI audit:
- Bump bundled mihomo to v1.19.31 (amd64-v1 / arm64 digests verified)
- TUIC: panel keeps tuic-v4|tuic-v5; legacy "tuic" auto-detects token-only→v4 else v5
(YAML type always "tuic" per official inbound docs)
- UI: token field only on tuic-v4; tuic-v5 does not accept token in form
- VMess listener schema: drop allow-insecure (not in official VMess listener fields)
- Docs: version references → v1.19.31
chore: align MetaCubeX audit — mihomo v1.19.31, TUIC modes, VMess schema
Per wiki.metacubex.one / KIMI audit:
- Bump bundled mihomo to v1.19.31 (amd64-v1 / arm64 digests verified)
- TUIC: panel keeps tuic-v4|tuic-v5; legacy "tuic" auto-detects token-only→v4 else v5
(YAML type always "tuic" per official inbound docs)
- UI: token field only on tuic-v4; tuic-v5 does not accept token in form
- VMess listener schema: drop allow-insecure (not in official VMess listener fields)
- Docs: version references → v1.19.31
chore: align MetaCubeX audit — mihomo v1.19.31, TUIC modes, VMess schema
Per wiki.metacubex.one / KIMI audit:
- Bump bundled mihomo to v1.19.31 (amd64-v1 / arm64 digests verified)
- TUIC: panel keeps tuic-v4|tuic-v5; legacy "tuic" auto-detects token-only→v4 else v5
(YAML type always "tuic" per official inbound docs)
- UI: token field only on tuic-v4; tuic-v5 does not accept token in form
- VMess listener schema: drop allow-insecure (not in official VMess listener fields)
- Docs: version references → v1.19.31
chore: align MetaCubeX audit — mihomo v1.19.31, TUIC modes, VMess schema
Per wiki.metacubex.one / KIMI audit:
- Bump bundled mihomo to v1.19.31 (amd64-v1 / arm64 digests verified)
- TUIC: panel keeps tuic-v4|tuic-v5; legacy "tuic" auto-detects token-only→v4 else v5
(YAML type always "tuic" per official inbound docs)
- UI: token field only on tuic-v4; tuic-v5 does not accept token in form
- VMess listener schema: drop allow-insecure (not in official VMess listener fields)
- Docs: version references → v1.19.31
chore: align MetaCubeX audit — mihomo v1.19.31, TUIC modes, VMess schema
Per wiki.metacubex.one / KIMI audit:
- Bump bundled mihomo to v1.19.31 (amd64-v1 / arm64 digests verified)
- TUIC: panel keeps tuic-v4|tuic-v5; legacy "tuic" auto-detects token-only→v4 else v5
(YAML type always "tuic" per official inbound docs)
- UI: token field only on tuic-v4; tuic-v5 does not accept token in form
- VMess listener schema: drop allow-insecure (not in official VMess listener fields)
- Docs: version references → v1.19.31
chore: align MetaCubeX audit — mihomo v1.19.31, TUIC modes, VMess schema
Per wiki.metacubex.one / KIMI audit:
- Bump bundled mihomo to v1.19.31 (amd64-v1 / arm64 digests verified)
- TUIC: panel keeps tuic-v4|tuic-v5; legacy "tuic" auto-detects token-only→v4 else v5
(YAML type always "tuic" per official inbound docs)
- UI: token field only on tuic-v4; tuic-v5 does not accept token in form
- VMess listener schema: drop allow-insecure (not in official VMess listener fields)
- Docs: version references → v1.19.31
chore: align MetaCubeX audit — mihomo v1.19.31, TUIC modes, VMess schema
Per wiki.metacubex.one / KIMI audit:
- Bump bundled mihomo to v1.19.31 (amd64-v1 / arm64 digests verified)
- TUIC: panel keeps tuic-v4|tuic-v5; legacy "tuic" auto-detects token-only→v4 else v5
(YAML type always "tuic" per official inbound docs)
- UI: token field only on tuic-v4; tuic-v5 does not accept token in form
- VMess listener schema: drop allow-insecure (not in official VMess listener fields)
- Docs: version references → v1.19.31
chore: align MetaCubeX audit — mihomo v1.19.31, TUIC modes, VMess schema
Per wiki.metacubex.one / KIMI audit:
- Bump bundled mihomo to v1.19.31 (amd64-v1 / arm64 digests verified)
- TUIC: panel keeps tuic-v4|tuic-v5; legacy "tuic" auto-detects token-only→v4 else v5
(YAML type always "tuic" per official inbound docs)
- UI: token field only on tuic-v4; tuic-v5 does not accept token in form
- VMess listener schema: drop allow-insecure (not in official VMess listener fields)
- Docs: version references → v1.19.31
chore: align MetaCubeX audit — mihomo v1.19.31, TUIC modes, VMess schema
Per wiki.metacubex.one / KIMI audit:
- Bump bundled mihomo to v1.19.31 (amd64-v1 / arm64 digests verified)
- TUIC: panel keeps tuic-v4|tuic-v5; legacy "tuic" auto-detects token-only→v4 else v5
(YAML type always "tuic" per official inbound docs)
- UI: token field only on tuic-v4; tuic-v5 does not accept token in form
- VMess listener schema: drop allow-insecure (not in official VMess listener fields)
- Docs: version references → v1.19.31
2026-09-16 23:50:00 +08:00

312 lines
9.4 KiB
Go

package protocol
import (
"crypto/rand"
"encoding/hex"
"fmt"
"strings"
)
// GenericCompiler passes config through with optional users injection.
type GenericCompiler struct{ kind string }
func (g GenericCompiler) Kind() string { return g.kind }
func (g GenericCompiler) Capability() ProtocolCapability {
return ProtocolCapability{Kind: g.kind, Label: g.kind}
}
func (g GenericCompiler) Compile(in CompileInput) (map[string]interface{}, error) {
m := baseMap(in)
copyConfigPassthrough(m, in.Config, managedKeys())
if in.UDP {
m["udp"] = true
}
if in.TLS {
m["tls"] = true
}
return m, nil
}
type VLESSCompiler struct{}
func (VLESSCompiler) Kind() string { return "vless" }
func (VLESSCompiler) Capability() ProtocolCapability { return vlessCapability() }
func (VLESSCompiler) Compile(in CompileInput) (map[string]interface{}, error) {
m := baseMap(in)
skip := managedKeys()
copyConfigPassthrough(m, in.Config, skip)
// Per official MetaCubeX wiki (inbound-vless), the listener YAML does NOT
// carry a top-level `udp` field — mihomo allows UDP by default when absent.
delete(m, "udp")
// Official VLESS listener has no top-level "tls" field; TLS is implied by
// certificate/private-key, reality-config, or wrappers — never emit tls: true.
delete(m, "tls")
users := asUsersArray(in.Config, in.Users, "uuid", in.HasCredentialState)
if len(users) > 0 {
m["users"] = users
}
return m, nil
}
type VMessCompiler struct{}
func (VMessCompiler) Kind() string { return "vmess" }
func (VMessCompiler) Capability() ProtocolCapability { return vmessCapability() }
func (VMessCompiler) Compile(in CompileInput) (map[string]interface{}, error) {
m := baseMap(in)
copyConfigPassthrough(m, in.Config, managedKeys())
// Per official MetaCubeX wiki (inbound-vmess), the listener YAML does NOT
// carry a top-level `udp` field — mihomo allows UDP by default when absent.
delete(m, "udp")
// Official VMess listener: no top-level tls boolean.
delete(m, "tls")
users := asUsersArray(in.Config, in.Users, "uuid", in.HasCredentialState)
if len(users) > 0 {
m["users"] = users
}
return m, nil
}
type TrojanCompiler struct{}
func (TrojanCompiler) Kind() string { return "trojan" }
func (TrojanCompiler) Capability() ProtocolCapability { return trojanCapability() }
func (TrojanCompiler) Compile(in CompileInput) (map[string]interface{}, error) {
m := baseMap(in)
copyConfigPassthrough(m, in.Config, managedKeys())
// Per official MetaCubeX wiki (inbound-trojan), the listener YAML does NOT
// carry a top-level `udp` field — mihomo allows UDP by default when absent.
delete(m, "udp")
// Official Trojan listener: no top-level tls boolean.
delete(m, "tls")
users := asUsersArray(in.Config, in.Users, "password", in.HasCredentialState)
if len(users) > 0 {
m["users"] = users
}
return m, nil
}
type ShadowsocksCompiler struct{}
func (ShadowsocksCompiler) Kind() string { return "shadowsocks" }
func (ShadowsocksCompiler) Capability() ProtocolCapability { return shadowsocksCapability() }
func (ShadowsocksCompiler) Compile(in CompileInput) (map[string]interface{}, error) {
m := baseMap(in)
copyConfigPassthrough(m, in.Config, managedKeys())
// Mihomo defaults the common listener udp field to true. Emit both values
// so disabling UDP in the panel is preserved instead of silently falling
// back to the core default after each config regeneration.
m["udp"] = in.UDP
if len(in.Users) > 1 {
return nil, fmt.Errorf("shadowsocks supports one password; %d credentials bound", len(in.Users))
}
if len(in.Users) == 1 && in.Users[0].Password != "" {
m["password"] = in.Users[0].Password
}
return m, nil
}
type Hysteria2Compiler struct{}
func (Hysteria2Compiler) Kind() string { return "hysteria2" }
func (Hysteria2Compiler) Capability() ProtocolCapability { return hysteria2Capability() }
func (Hysteria2Compiler) Compile(in CompileInput) (map[string]interface{}, error) {
m := baseMap(in)
copyConfigPassthrough(m, in.Config, managedKeys())
users := asUsersMap(in.Config, in.Users, in.HasCredentialState)
if len(users) > 0 {
m["users"] = users
}
return m, nil
}
type TUICCompiler struct{ kind string }
func (t TUICCompiler) Kind() string {
if t.kind != "" {
return t.kind
}
return "tuic"
}
func (TUICCompiler) Capability() ProtocolCapability { return tuicCapability() }
func (t TUICCompiler) Compile(in CompileInput) (map[string]interface{}, error) {
m := baseMap(in)
// Mihomo's listener type is always "tuic" regardless of v4/v5.
m["type"] = "tuic"
copyConfigPassthrough(m, in.Config, managedKeys())
// Mihomo expects token as a slice. Legacy configs may store a single string.
if tok, ok := m["token"].(string); ok {
m["token"] = []string{tok}
}
// Official Mihomo: always type "tuic". Auth mode is token (v4) vs users (v5).
// Panel protocol names: tuic-v4 / tuic-v5. Legacy "tuic" auto-detects.
kind := t.Kind()
toks := allStringTokens(m["token"])
hasUsers := false
if um, ok := m["users"].(map[string]interface{}); ok && len(um) > 0 {
hasUsers = true
}
isV4 := kind == "tuic-v4"
if kind == "tuic-v5" {
isV4 = false
} else if kind == "tuic" {
// Wiki: exactly one of token or users. Prefer explicit token-only as v4.
isV4 = len(toks) > 0 && !hasUsers
}
if isV4 {
if len(toks) == 0 {
toks = []string{randomToken()}
}
m["token"] = toks
delete(m, "users")
} else {
delete(m, "token")
users := asUsersMapUUID(in.Config, in.Users, in.HasCredentialState)
if len(users) > 0 {
m["users"] = users
}
}
// Sensible defaults when operator left advanced QUIC fields empty.
if _, ok := m["alpn"]; !ok {
m["alpn"] = []string{"h3"}
}
if _, ok := m["congestion-controller"]; !ok {
m["congestion-controller"] = "bbr"
}
return m, nil
}
type ShadowQUICCompiler struct{}
func (ShadowQUICCompiler) Kind() string { return "shadowquic" }
func (ShadowQUICCompiler) Capability() ProtocolCapability { return shadowquicCapability() }
func (ShadowQUICCompiler) Compile(in CompileInput) (map[string]interface{}, error) {
m := baseMap(in)
copyConfigPassthrough(m, in.Config, managedKeys())
users := asUsersArray(in.Config, in.Users, "password", in.HasCredentialState)
if len(users) > 0 {
m["users"] = users
}
if _, ok := m["congestion-controller"]; !ok {
m["congestion-controller"] = "cubic"
}
return m, nil
}
type TrustTunnelCompiler struct{}
func (TrustTunnelCompiler) Kind() string { return "trusttunnel" }
func (TrustTunnelCompiler) Capability() ProtocolCapability {
return ProtocolCapability{Kind: "trusttunnel", Label: "trusttunnel"}
}
func (TrustTunnelCompiler) Compile(in CompileInput) (map[string]interface{}, error) {
m := baseMap(in)
copyConfigPassthrough(m, in.Config, managedKeys())
// TrustTunnel uses users: [{username, password}] (array form, verified against Mihomo wiki).
users := asUsersArray(in.Config, in.Users, "password", in.HasCredentialState)
if len(users) > 0 {
m["users"] = users
}
// Mihomo expects network as a slice (e.g. ["tcp", "udp"]).
// Legacy configs may store it as a string; convert to slice.
if net, ok := m["network"].(string); ok {
m["network"] = []string{net}
}
return m, nil
}
type AnyTLSCompiler struct{}
func (AnyTLSCompiler) Kind() string { return "anytls" }
func (AnyTLSCompiler) Capability() ProtocolCapability {
return ProtocolCapability{Kind: "anytls", Label: "anytls"}
}
func (AnyTLSCompiler) Compile(in CompileInput) (map[string]interface{}, error) {
m := baseMap(in)
copyConfigPassthrough(m, in.Config, managedKeys())
if in.UDP {
m["udp"] = true
}
users := asUsersMap(in.Config, in.Users, in.HasCredentialState)
if len(users) > 0 {
m["users"] = users
}
return m, nil
}
type MieruCompiler struct{}
func (MieruCompiler) Kind() string { return "mieru" }
func (MieruCompiler) Capability() ProtocolCapability {
return ProtocolCapability{Kind: "mieru", Label: "mieru"}
}
func (MieruCompiler) Compile(in CompileInput) (map[string]interface{}, error) {
m := baseMap(in)
copyConfigPassthrough(m, in.Config, managedKeys())
users := asUsersMap(in.Config, in.Users, in.HasCredentialState)
if len(users) > 0 {
m["users"] = users
}
return m, nil
}
func randomToken() string {
b := make([]byte, 16)
_, _ = rand.Read(b)
return hex.EncodeToString(b)
}
func firstStringToken(tok interface{}) string {
switch v := tok.(type) {
case string:
return strings.TrimSpace(v)
case []string:
for _, s := range v {
if s = strings.TrimSpace(s); s != "" {
return s
}
}
case []interface{}:
for _, item := range v {
if s, ok := item.(string); ok {
if s = strings.TrimSpace(s); s != "" {
return s
}
}
}
}
return ""
}
// allStringTokens returns every non-empty trimmed string token from a
// listener Config `token` field. Mihomo's TUIC v4 spec accepts multiple
// tokens (`token: [T1, T2, …]`); any one authenticates the client.
// Used by TUIC v4 compiler to preserve the full array instead of dropping
// all but the first.
func allStringTokens(tok interface{}) []string {
switch v := tok.(type) {
case string:
if s := strings.TrimSpace(v); s != "" {
return []string{s}
}
case []string:
out := make([]string, 0, len(v))
for _, s := range v {
if s = strings.TrimSpace(s); s != "" {
out = append(out, s)
}
}
return out
case []interface{}:
out := make([]string, 0, len(v))
for _, item := range v {
if s, ok := item.(string); ok {
if s = strings.TrimSpace(s); s != "" {
out = append(out, s)
}
}
}
return out
}
return nil
}