fix(vless): pair encryption/decryption correctly for TCP Vision
- Stop copying server decryption into client encryption - Store both fields; strip encryption (client-only) from server Compile - Form + capability + schema support encryption for subscription export - Tests for export and compile stripping fix(vless): pair encryption/decryption correctly for TCP Vision - Stop copying server decryption into client encryption - Store both fields; strip encryption (client-only) from server Compile - Form + capability + schema support encryption for subscription export - Tests for export and compile stripping fix(vless): pair encryption/decryption correctly for TCP Vision - Stop copying server decryption into client encryption - Store both fields; strip encryption (client-only) from server Compile - Form + capability + schema support encryption for subscription export - Tests for export and compile stripping fix(vless): pair encryption/decryption correctly for TCP Vision - Stop copying server decryption into client encryption - Store both fields; strip encryption (client-only) from server Compile - Form + capability + schema support encryption for subscription export - Tests for export and compile stripping fix(vless): pair encryption/decryption correctly for TCP Vision - Stop copying server decryption into client encryption - Store both fields; strip encryption (client-only) from server Compile - Form + capability + schema support encryption for subscription export - Tests for export and compile stripping fix(vless): pair encryption/decryption correctly for TCP Vision - Stop copying server decryption into client encryption - Store both fields; strip encryption (client-only) from server Compile - Form + capability + schema support encryption for subscription export - Tests for export and compile stripping fix(vless): pair encryption/decryption correctly for TCP Vision - Stop copying server decryption into client encryption - Store both fields; strip encryption (client-only) from server Compile - Form + capability + schema support encryption for subscription export - Tests for export and compile stripping fix(vless): pair encryption/decryption correctly for TCP Vision - Stop copying server decryption into client encryption - Store both fields; strip encryption (client-only) from server Compile - Form + capability + schema support encryption for subscription export - Tests for export and compile stripping fix(vless): pair encryption/decryption correctly for TCP Vision - Stop copying server decryption into client encryption - Store both fields; strip encryption (client-only) from server Compile - Form + capability + schema support encryption for subscription export - Tests for export and compile stripping fix(vless): pair encryption/decryption correctly for TCP Vision - Stop copying server decryption into client encryption - Store both fields; strip encryption (client-only) from server Compile - Form + capability + schema support encryption for subscription export - Tests for export and compile stripping fix(vless): pair encryption/decryption correctly for TCP Vision - Stop copying server decryption into client encryption - Store both fields; strip encryption (client-only) from server Compile - Form + capability + schema support encryption for subscription export - Tests for export and compile stripping fix(vless): pair encryption/decryption correctly for TCP Vision - Stop copying server decryption into client encryption - Store both fields; strip encryption (client-only) from server Compile - Form + capability + schema support encryption for subscription export - Tests for export and compile stripping
This commit is contained in:
1 parent
ee7807e8f2
commit
fc00113ea5
10 files changed
+104
-20
No files matched your search
@@ -12,9 +12,9 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/kazeyukiro/3m-ui/backend/internal/config"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"github.com/kazeyukiro/3m-ui/backend/internal/database/models"
|
||||
"github.com/kazeyukiro/3m-ui/backend/internal/user"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"gopkg.in/yaml.v3"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -209,12 +209,9 @@ func listenerToProxies(l models.Listener, server string, credentials []user.Cred
|
||||
copyOption(p, opts, "packet-encoding")
|
||||
copyOption(p, opts, "global-padding")
|
||||
copyOption(p, opts, "authenticated-length")
|
||||
// encryption (client) and decryption (server) are a generated pair from
|
||||
// `mihomo generate vless-x25519` / `vless-mlkem768` — never copy decryption→encryption.
|
||||
copyOption(p, opts, "encryption")
|
||||
if p["encryption"] == nil {
|
||||
if dec, ok := opts["decryption"].(string); ok && strings.TrimSpace(dec) != "" {
|
||||
p["encryption"] = dec
|
||||
}
|
||||
}
|
||||
if flow := userFieldFromOpts(opts, cred.UUID, "flow"); flow != nil {
|
||||
p["flow"] = flow
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package converter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kazeyukiro/3m-ui/backend/internal/database/models"
|
||||
"github.com/kazeyukiro/3m-ui/backend/internal/user"
|
||||
)
|
||||
|
||||
func TestVLESSClientUsesEncryptionNotDecryption(t *testing.T) {
|
||||
l := models.Listener{
|
||||
Name: "vless-enc", Protocol: "vless", Port: "443", Enabled: true,
|
||||
Config: `{"flow":"xtls-rprx-vision","decryption":"server-decryption-only","encryption":"client-encryption-pair"}`,
|
||||
}
|
||||
creds := []user.Credential{{Username: "u1", UUID: "9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68"}}
|
||||
proxies, err := listenerToProxies(l, "1.2.3.4", creds)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p := proxies[0]
|
||||
if p["encryption"] != "client-encryption-pair" {
|
||||
t.Fatalf("expected client encryption pair, got %#v", p["encryption"])
|
||||
}
|
||||
if p["flow"] != "xtls-rprx-vision" {
|
||||
t.Fatalf("flow: %#v", p["flow"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestVLESSClientDoesNotCopyDecryption(t *testing.T) {
|
||||
l := models.Listener{
|
||||
Name: "vless-enc", Protocol: "vless", Port: "443", Enabled: true,
|
||||
Config: `{"flow":"xtls-rprx-vision","decryption":"server-decryption-only"}`,
|
||||
}
|
||||
creds := []user.Credential{{Username: "u1", UUID: "9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68"}}
|
||||
proxies, err := listenerToProxies(l, "1.2.3.4", creds)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if proxies[0]["encryption"] != nil {
|
||||
t.Fatalf("must not copy decryption→encryption, got %#v", proxies[0]["encryption"])
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,7 @@ var MihomoListenerSchemas = map[string]ListenerSchema{
|
||||
},
|
||||
"vless": {
|
||||
Protocol: "vless",
|
||||
Fields: listenerFields("users", "flow", "ws-path", "grpc-service-name", "xhttp-config", "decryption", "reality-config", "certificate", "private-key", "client-auth-type", "client-auth-cert", "ech-key", "allow-insecure", "jls-config", "shadow-tls", "res-tls", "mux-option"),
|
||||
Fields: listenerFields("users", "flow", "ws-path", "grpc-service-name", "xhttp-config", "decryption", "encryption", "reality-config", "certificate", "private-key", "client-auth-type", "client-auth-cert", "ech-key", "allow-insecure", "jls-config", "shadow-tls", "res-tls", "mux-option"),
|
||||
NestedFields: listenerNested(
|
||||
"xhttp-config.path", "xhttp-config.host", "xhttp-config.mode", "xhttp-config.no-sse-header", "xhttp-config.x-padding-bytes", "xhttp-config.x-padding-obfs-mode", "xhttp-config.x-padding-key", "xhttp-config.x-padding-header", "xhttp-config.x-padding-placement", "xhttp-config.x-padding-method", "xhttp-config.uplink-http-method", "xhttp-config.session-placement", "xhttp-config.session-key", "xhttp-config.session-table", "xhttp-config.session-length", "xhttp-config.seq-placement", "xhttp-config.seq-key", "xhttp-config.uplink-data-placement", "xhttp-config.uplink-data-key", "xhttp-config.uplink-chunk-size", "xhttp-config.sc-max-buffered-posts", "xhttp-config.sc-stream-up-server-secs", "xhttp-config.sc-max-each-post-bytes",
|
||||
"reality-config.dest", "reality-config.private-key", "reality-config.short-id", "reality-config.server-names", "reality-config.limit-fallback-upload.after-bytes", "reality-config.limit-fallback-upload.bytes-per-sec", "reality-config.limit-fallback-upload.burst-bytes-per-sec", "reality-config.limit-fallback-download.after-bytes", "reality-config.limit-fallback-download.bytes-per-sec", "reality-config.limit-fallback-download.burst-bytes-per-sec",
|
||||
|
||||
@@ -190,8 +190,9 @@ func vlessURIs(name, host, port string, cfg map[string]interface{}) ([]string, e
|
||||
if flow, _ := row["flow"].(string); flow != "" {
|
||||
params["flow"] = flow
|
||||
}
|
||||
if decryption, _ := cfg["decryption"].(string); decryption != "" {
|
||||
params["encryption"] = decryption
|
||||
// Client URI uses encryption (not server-side decryption).
|
||||
if encryption, _ := cfg["encryption"].(string); encryption != "" {
|
||||
params["encryption"] = encryption
|
||||
}
|
||||
if reality, ok := cfg["reality-config"].(map[string]interface{}); ok {
|
||||
params["security"] = "reality"
|
||||
|
||||
@@ -165,11 +165,12 @@ func vlessCapability() ProtocolCapability {
|
||||
comps := append(transportComponents(), securityComponents(true)...)
|
||||
return ProtocolCapability{
|
||||
Kind: "vless", Label: "VLESS",
|
||||
Layers: transportSecurityLayers("raw", "reality"),
|
||||
Layers: transportSecurityLayers("raw", "reality"),
|
||||
Components: comps,
|
||||
Fields: []FieldCapability{
|
||||
{Path: "flow", Label: "Flow", Type: FieldString, Options: []string{"xtls-rprx-vision"}},
|
||||
{Path: "decryption", Label: "Decryption", Type: FieldText, Advanced: true},
|
||||
{Path: "decryption", Label: "Decryption", Type: FieldText, Advanced: true, Description: "Server-side VLESS decryption (mihomo generate vless-x25519 / vless-mlkem768)"},
|
||||
{Path: "encryption", Label: "Encryption", Type: FieldText, Advanced: true, Description: "Client-side VLESS encryption pair (do not reuse decryption value)"},
|
||||
},
|
||||
UserFields: []FieldCapability{
|
||||
{Path: "uuid", Label: "UUID", Type: FieldString, Required: true},
|
||||
@@ -183,7 +184,7 @@ func vmessCapability() ProtocolCapability {
|
||||
comps := append(transportComponents(), securityComponents(true)...)
|
||||
return ProtocolCapability{
|
||||
Kind: "vmess", Label: "VMess",
|
||||
Layers: transportSecurityLayers("raw", "none"),
|
||||
Layers: transportSecurityLayers("raw", "none"),
|
||||
Components: comps,
|
||||
Fields: []FieldCapability{
|
||||
{Path: "alterId", Label: "Alter ID", Type: FieldInteger},
|
||||
@@ -199,7 +200,7 @@ func trojanCapability() ProtocolCapability {
|
||||
comps := append(transportComponents(), securityComponents(true)...)
|
||||
return ProtocolCapability{
|
||||
Kind: "trojan", Label: "Trojan",
|
||||
Layers: transportSecurityLayers("raw", "tls"),
|
||||
Layers: transportSecurityLayers("raw", "tls"),
|
||||
Components: comps,
|
||||
UserFields: []FieldCapability{
|
||||
{Path: "password", Label: "Password", Type: FieldSecret, Required: true},
|
||||
@@ -211,7 +212,7 @@ func trojanCapability() ProtocolCapability {
|
||||
func shadowsocksCapability() ProtocolCapability {
|
||||
return ProtocolCapability{
|
||||
Kind: "shadowsocks", Label: "Shadowsocks",
|
||||
Layers: []LayerCapability{},
|
||||
Layers: []LayerCapability{},
|
||||
Components: []ComponentCapability{},
|
||||
Fields: []FieldCapability{
|
||||
{Path: "cipher", Label: "Cipher", Type: FieldString, Required: true, Options: []string{
|
||||
@@ -277,7 +278,7 @@ func tuicCapability() ProtocolCapability {
|
||||
func shadowquicCapability() ProtocolCapability {
|
||||
return ProtocolCapability{
|
||||
Kind: "shadowquic", Label: "ShadowQUIC",
|
||||
Layers: []LayerCapability{},
|
||||
Layers: []LayerCapability{},
|
||||
Components: []ComponentCapability{},
|
||||
Fields: []FieldCapability{
|
||||
{Path: "alpn", Label: "ALPN", Type: FieldStringList},
|
||||
|
||||
@@ -124,8 +124,9 @@ func copyConfigPassthrough(dst, cfg map[string]interface{}, skip map[string]stru
|
||||
if _, ok := skip[k]; ok {
|
||||
continue
|
||||
}
|
||||
// strip panel-only keys
|
||||
if strings.HasPrefix(k, "_") || k == "access_profile" {
|
||||
// strip panel-only / client-export-only keys
|
||||
if strings.HasPrefix(k, "_") || k == "access_profile" || k == "encryption" ||
|
||||
k == "transport_layer" || k == "security_layer" {
|
||||
continue
|
||||
}
|
||||
dst[k] = v
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package protocol
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestVLESSCompileStripsClientEncryption(t *testing.T) {
|
||||
reg := DefaultCompileRegistry()
|
||||
in := CompileInput{
|
||||
Name: "vless-enc",
|
||||
Protocol: "vless",
|
||||
Listen: "0.0.0.0",
|
||||
Port: 443,
|
||||
Config: map[string]interface{}{
|
||||
"flow": "xtls-rprx-vision",
|
||||
"decryption": "server-decryption",
|
||||
"encryption": "client-only-must-strip",
|
||||
"transport_layer": "raw",
|
||||
"security_layer": "none",
|
||||
},
|
||||
Users: []UserCred{{UUID: "9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68"}},
|
||||
}
|
||||
m, err := reg.Compile(in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if m["decryption"] != "server-decryption" {
|
||||
t.Fatalf("decryption missing: %#v", m["decryption"])
|
||||
}
|
||||
if _, ok := m["encryption"]; ok {
|
||||
t.Fatalf("encryption must not appear on server listener: %#v", m["encryption"])
|
||||
}
|
||||
if _, ok := m["transport_layer"]; ok {
|
||||
t.Fatalf("transport_layer panel key leaked")
|
||||
}
|
||||
}
|
||||
@@ -298,7 +298,7 @@ export function configToFormValues(raw: string | undefined | null): Record<strin
|
||||
|
||||
/** Keys the visual form fully owns. */
|
||||
const FORM_OWNED_KEYS = new Set([
|
||||
'cipher', 'password', 'psk', 'version', 'alterId', 'flow', 'decryption',
|
||||
'cipher', 'password', 'psk', 'version', 'alterId', 'flow', 'decryption', 'encryption',
|
||||
'ws-path', 'grpc-service-name', 'ss-option',
|
||||
'up', 'down', 'ignore-client-bandwidth', 'obfs', 'obfs-password', 'obfs-min-packet-size', 'obfs-max-packet-size',
|
||||
'masquerade', 'alpn', 'max-idle-time', 'handshake-timeout', 'token', 'congestion-controller',
|
||||
@@ -386,6 +386,7 @@ export function formValuesToConfig(
|
||||
set('ws-path', values['ws-path']);
|
||||
set('grpc-service-name', values['grpc-service-name']);
|
||||
set('decryption', values.decryption);
|
||||
set('encryption', values.encryption);
|
||||
break;
|
||||
case 'trojan':
|
||||
set('ws-path', values['ws-path']);
|
||||
@@ -778,6 +779,9 @@ const ListenerConfigFields: React.FC<Props> = ({ protocol }) => {
|
||||
<Form.Item name="decryption" label={t('listeners.decryption')} tooltip={t('listeners.decryptionHint')}>
|
||||
<Input.TextArea rows={2} placeholder="mlkem768x25519plus...." />
|
||||
</Form.Item>
|
||||
<Form.Item name="encryption" label={t('listeners.encryption')} tooltip={t('listeners.encryptionHint')}>
|
||||
<Input.TextArea rows={2} placeholder="mlkem768x25519plus...." />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -13,7 +13,9 @@ export default {
|
||||
listeners: { title: 'Nodes', create: 'Create Node', edit: 'Edit Node', name: 'Name', protocol: 'Protocol', port: 'Port', bindAddress: 'Bind Address', status: 'Status', actions: 'Actions', config: 'Protocol Config', udp: 'UDP', tls: 'TLS', deleteConfirm: 'Delete this node?', created: 'Created', updated: 'Updated', deleted: 'Deleted', reloaded: 'Reloaded', copyURI: 'Copy subscription URI', urisTitle: 'Node subscription links', invalidJSON: 'Invalid JSON', selectProtocolFirst: 'Select a protocol first', usersHint: 'Create credentials under Users and bind them to this node. Only official Listener fields are configured here.', portHint: 'Single port (e.g. 443) or official ports syntax (e.g. 200,204,401-429)', sectionProtocol: 'Protocol options', transportLayer: 'Transport',
|
||||
securityLayer: 'Security',
|
||||
sectionSecurity: 'Security',
|
||||
sectionTransport: 'Transport', sectionTLS: 'TLS certificate', sectionReality: 'Reality', sectionSSOption: 'Trojan SS option', cipher: 'Cipher', password: 'Password', passwordOptional: 'Password (optional)', uuid: 'UUID', privateKey: 'Private key', certificate: 'Certificate', alpn: 'ALPN', sni: 'SNI', clientFingerprint: 'Client fingerprint', allowInsecure: 'Allow insecure', muxOption: 'Mux', realityPrivateKey: 'Reality private key', realityShortID: 'Short ID', realityServerNames: 'Server names', realityDest: 'Dest', transport: 'Transport', path: 'Path', host: 'Host', serviceName: 'Service name', up: 'Up', down: 'Down', ignoreClientBandwidth: 'Ignore client bandwidth', masquerade: 'Masquerade', congestionControl: 'Congestion control', clone: 'Clone', saveTemplate: 'Save as template', versions: 'Versions', cloned: 'Cloned', templateCreated: 'Template created', instantiated: 'Listener created from template', batchDone: 'Batch operation completed', templates: 'Listener Templates', templateName: 'Template name', createdAt: 'Created at', instantiate: 'Instantiate', deleteTemplateConfirm: 'Delete this template?', templateDeleted: 'Template deleted', enableSelected: 'Enable selected', disableSelected: 'Disable selected', newPort: 'New port', version: 'Version', reason: 'Reason', diff: 'Diff', rollback: 'Rollback', rollbackConfirm: 'Rollback to this version?', rollbackDone: 'Rollback completed' , addUser: 'Add user', aeadMethod: 'AEAD method', allowInsecureHint: 'Allow insecure (debug only)', alterId: 'Alter ID', alterIdHint: 'VMess alterId, prefer 0', authenticationTimeout: 'Auth timeout', bandwidthHint: 'Bandwidth limit, e.g. 100 mbps', bbrProfile: 'BBR profile', clientAuthCert: 'Client cert', clientAuthType: 'Client auth type', congestionController: 'Congestion controller', createWizard: 'Create listener wizard', cwnd: 'CWND', decryption: 'Decryption', decryptionHint: 'VLESS decryption, usually none', echKey: 'ECH Key', enablePureDownlink: 'Pure downlink', flow: 'Flow', flowHint: 'e.g. xtls-rprx-vision', grpcHint: 'gRPC transport', grpcServiceName: 'gRPC service name', handshakeTimeout: 'Handshake timeout', jlsDest: 'JLS dest', jlsHint: 'JLS settings', jlsProxy: 'JLS proxy', jlsSni: 'JLS SNI', jlsSniHint: 'JLS SNI hostname', jlsUpstreamAddr: 'JLS upstream', jlsUsers: 'JLS users', kcpTunConn: 'KCP connections', kcpTunCrypt: 'KCP crypt', kcpTunKey: 'KCP key', kcpTunMode: 'KCP mode', kcpTunNocomp: 'KCP no compress', kcpTunRcvwnd: 'KCP recv window', kcpTunSndwnd: 'KCP send window', maxIdleTime: 'Max idle time', maxUdpRelayPacketSize: 'Max UDP relay packet', mekyaHint: 'Mekya settings', mekyaKcpSection: 'Mekya KCP', mekyaMaxSimultaneous: 'Max simultaneous', mekyaMaxSimultaneousHint: 'Max concurrent connections', mekyaMaxWriteDuration: 'Max write duration', mekyaMaxWriteDurationHint: 'Write timeout', mekyaMaxWriteSize: 'Max write size', mekyaMaxWriteSizeHint: 'Single write limit', mekyaPacketBuffer: 'Packet buffer', mekyaPacketBufferHint: 'Packet buffer size', mkcpCongestion: 'mKCP congestion', mkcpDownlink: 'mKCP downlink', mkcpHeader: 'mKCP header', mkcpHint: 'mKCP transport', mkcpReadBuffer: 'mKCP read buffer', mkcpSeed: 'mKCP seed', mkcpUplink: 'mKCP uplink', mkcpWriteBuffer: 'mKCP write buffer', muxBrutal: 'Mux brutal', muxBrutalDown: 'Brutal down', muxBrutalUp: 'Brutal up', muxMaxConnections: 'Mux max connections', muxMaxStreams: 'Mux max streams', muxMinStreams: 'Mux min streams', muxOnlyTcp: 'Mux TCP only', muxPadding: 'Mux padding', muxProtocol: 'Mux protocol', muxStatistic: 'Mux statistics', network: 'Network', obfs: 'Obfuscation', obfsMaxPacketSize: 'Obfs max packet', obfsMinPacketSize: 'Obfs min packet', obfsOptsHost: 'Obfs host', obfsOptsMode: 'Obfs mode', obfsPassword: 'Obfs password', paddingMax: 'Padding max', paddingMin: 'Padding min', paddingScheme: 'Padding scheme', passwordOptionalHint: 'Optional; empty uses bound user password', passwordOptionalPlaceholder: 'Optional password', psk: 'PSK', quicVersions: 'QUIC versions', rateLimit: 'Rate limit', rateLimitHint: 'e.g. 10m', realityExclusiveHint: 'Reality and TLS cert are mutually exclusive', realityKeyHint: 'Reality private key', realityShortId: 'Reality short ID', realmId: 'Realm ID', realmProxy: 'Realm proxy', realmServerUrl: 'Realm server URL', realmSkipCert: 'Skip cert verify', realmStun: 'STUN', realmToken: 'Realm token', resTlsDest: 'ResTLS dest', resTlsHint: 'ResTLS settings', resTlsMinRecordLen: 'Min record length', resTlsPassword: 'ResTLS password', resTlsProxy: 'ResTLS proxy', resTlsScript: 'ResTLS script', sectionJLS: 'JLS', sectionJlsUpstream: 'JLS upstream', sectionKcpTun: 'KCP Tun', sectionMekya: 'Mekya', sectionMkcp: 'mKCP', sectionMux: 'Mux', sectionRealm: 'Realm', sectionResTLS: 'ResTLS', sectionShadowTLS: 'ShadowTLS', sectionSimpleObfs: 'Simple Obfs', sectionXHTTP: 'XHTTP', shadowTlsHandshakeDest: 'Handshake dest', shadowTlsHandshakeProxy: 'Handshake proxy', shadowTlsHint: 'ShadowTLS settings', shadowTlsPassword: 'ShadowTLS passwLine truncated
|
||||
sectionTransport: 'Transport', sectionTLS: 'TLS certificate', sectionReality: 'Reality', sectionSSOption: 'Trojan SS option', cipher: 'Cipher', password: 'Password', passwordOptional: 'Password (optional)', uuid: 'UUID', privateKey: 'Private key', certificate: 'Certificate', alpn: 'ALPN', sni: 'SNI', clientFingerprint: 'Client fingerprint', allowInsecure: 'Allow insecure', muxOption: 'Mux', realityPrivateKey: 'Reality private key', realityShortID: 'Short ID', realityServerNames: 'Server names', realityDest: 'Dest', transport: 'Transport', path: 'Path', host: 'Host', serviceName: 'Service name', up: 'Up', down: 'Down', ignoreClientBandwidth: 'Ignore client bandwidth', masquerade: 'Masquerade', congestionControl: 'Congestion control', clone: 'Clone', saveTemplate: 'Save as template', versions: 'Versions', cloned: 'Cloned', templateCreated: 'Template created', instantiated: 'Listener created from template', batchDone: 'Batch operation completed', templates: 'Listener Templates', templateName: 'Template name', createdAt: 'Created at', instantiate: 'Instantiate', deleteTemplateConfirm: 'Delete this template?', templateDeleted: 'Template deleted', enableSelected: 'Enable selected', disableSelected: 'Disable selected', newPort: 'New port', version: 'Version', reason: 'Reason', diff: 'Diff', rollback: 'Rollback', rollbackConfirm: 'Rollback to this version?', rollbackDone: 'Rollback completed' , addUser: 'Add user', aeadMethod: 'AEAD method', allowInsecureHint: 'Allow insecure (debug only)', alterId: 'Alter ID', alterIdHint: 'VMess alterId, prefer 0', authenticationTimeout: 'Auth timeout', bandwidthHint: 'Bandwidth limit, e.g. 100 mbps', bbrProfile: 'BBR profile', clientAuthCert: 'Client cert', clientAuthType: 'Client auth type', congestionController: 'Congestion controller', createWizard: 'Create listener wizard', cwnd: 'CWND', decryption: 'Decryption',
|
||||
encryption: 'Encryption',
|
||||
encryptionHint: 'Client-side VLESS encryption pair from generate output; do not reuse the decryption value', decryptionHint: 'Server-side VLESS decryption from mihomo generate vless-x25519 / vless-mlkem768', echKey: 'ECH Key', enablePureDownlink: 'Pure downlink', flow: 'Flow', flowHint: 'e.g. xtls-rprx-vision', grpcHint: 'gRPC transport', grpcServiceName: 'gRPC service name', handshakeTimeout: 'Handshake timeout', jlsDest: 'JLS dest', jlsHint: 'JLS settings', jlsProxy: 'JLS proxy', jlsSni: 'JLS SNI', jlsSniHint: 'JLS SNI hostname', jlsUpstreamAddr: 'JLS upstream', jlsUsers: 'JLS users', kcpTunConn: 'KCP connections', kcpTunCrypt: 'KCP crypt', kcpTunKey: 'KCP key', kcpTunMode: 'KCP mode', kcpTunNocomp: 'KCP no compress', kcpTunRcvwnd: 'KCP recv window', kcpTunSndwnd: 'KCP send window', maxIdleTime: 'Max idle time', maxUdpRelayPacketSize: 'Max UDP relay packet', mekyaHint: 'Mekya settings', mekyaKcpSection: 'Mekya KCP', mekyaMaxSimultaneous: 'Max simultaneous', mekyaMaxSimultaneousHint: 'Max concurrent connections', mekyaMaxWriteDuration: 'Max write duration', mekyaMaxWriteDurationHint: 'Write timeout', mekyaMaxWriteSize: 'Max write size', mekyaMaxWriteSizeHint: 'Single write limit', mekyaPacketBuffer: 'Packet buffer', mekyaPacketBufferHint: 'Packet buffer size', mkcpCongestion: 'mKCP congestion', mkcpDownlink: 'mKCP downlink', mkcpHeader: 'mKCP header', mkcpHint: 'mKCP transport', mkcpReadBuffer: 'mKCP read buffer', mkcpSeed: 'mKCP seed', mkcpUplink: 'mKCP uplink', mkcpWriteBuffer: 'mKCP write buffer', muxBrutal: 'Mux brutal', muxBrutalDown: 'Brutal down', muxBrutalUp: 'Brutal up', muxMaxConnections: 'Mux max connections', muxMaxStreams: 'Mux max streams', muxMinStreams: 'Mux min streams', muxOnlyTcp: 'Mux TCP only', muxPadding: 'Mux padding', muxProtocol: 'Mux protocol', muxStatistic: 'Mux statistics', network: 'Network', obfs: 'Obfuscation', obfsMaxPacketSize: 'Obfs max packet', obfsMinPacketSize: 'Obfs min packet', obfsOptsHost: 'Obfs host', obfsOptsMode: 'Obfs mode', obfsPassword: 'Obfs password', paddingMax: 'Padding max', paddingMin: 'Padding min', paddingScheme: 'Padding scheme', passwordOptionalHint: 'Optional; empty uses bound user password', passwordOptionalPlaceholder: 'Optional password', psk: 'PSK', quicVersions: 'QUIC versions', rateLimit: 'Rate limit', rateLimitHint: 'e.g. 10m', realityExclusiveHint: 'Reality and TLS cert are mutually exclusive', realityKeyHint: 'Reality private key', realityShortId: 'Reality short ID', realmId: 'Realm ID', realmProxy: 'Realm proxy', realmServerUrl: 'Realm server URL', realmSkipCert: 'Skip cert verify', realmStun: 'STUN', realmToken: 'Realm token', resTlsDest: 'ResTLS dest', resTlsHint: 'ResTLS settings', resTlsMinRecordLen: 'Min record length', resTlsPassword: 'ResTLS password', resTlsProxy: 'ResTLS proxy', resTlsScript: 'ResTLS script', sectionJLS: 'JLS', sectionJlsUpstream: 'JLS upstream', sectionKcpTun: 'KCP Tun', sectionMekya: 'Mekya', sectionMkcp: 'mKCP', sectionMux: 'Mux', sectionRealm: 'Realm', sectionResTLS: 'ResTLS', sectionShadowTLS: 'ShadowTLS', sectionSimpleObfs: 'Simple Obfs', sectionXHTTP: 'XHTTP', shadowTlsHandshakeDest: 'Handshake dest', shadowTlsHandshakeProxy: 'Handshake proxy', shadowTlsHint: 'ShadowTLS settings', shadowTlsPassword: 'ShadowTLS password', shadowTlsPasswordHint: 'v2/v3 password', shadowTlsUsers: 'ShadowTLS users', shadowTlsVersion: 'ShadowTLS version', simpleObfsMode: 'Simple Obfs mode', snellVersion: 'Snell version', ssOptionMethod: 'SS method', ssOptionPassword: 'SS password', sudokuKey: 'Sudoku key', tableType: 'Table type', tlsPairHint: 'Certificate and private key must both be set', token: 'Token', tokenHint: 'Auth token', tokenPlaceholder: 'Enter token', trafficPattern: 'Traffic pattern', userHintMandatory: 'At least one user required', wizardBasic: 'Basics', wizardConfig: 'Protocol', wizardConfigHint: 'Fill protocol-specific fields (TLS / Reality / users, etc.)', wizardReview: 'Review', wsPath: 'WebSocket path', wsPathHint: 'e.g. /ws', xhttpHint: 'XHTTP transport', xhttpHost: 'XHTTP host', xhttpMode: 'XHTTP mode', xhttpPath: 'XHTTP path', zeroRtt: '0-RTT'},
|
||||
core: { title: 'Core', status: 'Status', running: 'Running', stoppedStatus: 'Stopped', version: 'Version', pid: 'PID', uptime: 'Uptime', start: 'Start', stop: 'Stop', restart: 'Restart', started: 'Started', stopped: 'Stopped', restarted: 'Restarted' , operationFailed: 'Operation failed', unavailable: 'Core unavailable'},
|
||||
logs: { title: 'Logs', empty: 'No logs', level: 'Level', timestamp: 'Time', payload: 'Payload', autoRefresh: 'Auto refresh', clear: 'Clear' },
|
||||
config: { title: 'Config Engine', subtitle: 'Visual Mihomo configuration', visual: 'Visual Config', yaml: 'YAML Config', proxies: 'Proxy Configuration', yamlPreview: 'YAML Preview', addProxy: 'Add Proxy', editProxy: 'Edit Proxy', deleteProxy: 'Delete Proxy', deleteConfirm: 'Delete this proxy?', generateSuccess: 'Configuration generated successfully', validateSuccess: 'Configuration is valid', validateFailed: 'Configuration validation failed', proxyName: 'Proxy Name', proxyType: 'Proxy Type', proxyServer: 'Proxy Server', proxyPort: 'Proxy Port', proxyPassword: 'Proxy Password', proxyUUID: 'Proxy UUID' , generate: 'Generate config', validate: 'Validate config'},
|
||||
|
||||
@@ -13,7 +13,9 @@ export default {
|
||||
listeners: { title: '节点管理', create: '创建节点', edit: '编辑节点', name: '名称', protocol: '协议', port: '端口', bindAddress: '绑定地址', status: '状态', actions: '操作', config: '协议配置', udp: 'UDP', tls: 'TLS', deleteConfirm: '确定要删除此节点吗?', created: '创建成功', updated: '更新成功', deleted: '删除成功', reloaded: '重载成功', copyURI: '复制订阅链接', urisTitle: '节点订阅链接', invalidJSON: '无效的 JSON', selectProtocolFirst: '请先选择协议', usersHint: '用户凭证请在「用户管理」中创建并绑定到本节点;此处仅配置官方 Listener 协议字段。', portHint: '支持单端口(如 443)或官方 ports 语法(如 200,204,401-429)', sectionProtocol: '协议选项', transportLayer: '传输层',
|
||||
securityLayer: '安全层',
|
||||
sectionSecurity: '安全层',
|
||||
sectionTransport: '传输层', sectionTLS: 'TLS 证书', sectionReality: 'Reality', sectionSSOption: 'Trojan SS 混淆', cipher: '加密方法', password: '密码', passwordOptional: '密码(可选)', uuid: 'UUID', privateKey: '私钥', certificate: '证书', alpn: 'ALPN', sni: 'SNI', clientFingerprint: '客户端指纹', allowInsecure: '允许不安全', muxOption: '多路复用', realityPrivateKey: 'Reality 私钥', realityShortID: 'Short ID', realityServerNames: 'Server Names', realityDest: 'Dest', transport: '传输', path: '路径', host: 'Host', serviceName: 'Service Name', up: '上行', down: '下行', ignoreClientBandwidth: '忽略客户端带宽', masquerade: 'Masquerade', congestionControl: '拥塞控制', clone: '克隆', saveTemplate: '保存为模板', versions: '配置版本', cloned: '克隆成功', templateCreated: '模板创建成功', instantiated: '已从模板创建节点', batchDone: '批量操作完成', templates: '节点模板', templateName: '模板名称', createdAt: '创建时间', instantiate: '实例化', deleteTemplateConfirm: '确定删除此模板?', templateDeleted: '模板已删除', enableSelected: '启用选中', disableSelected: '禁用选中', newPort: '新端口', version: '版本', reason: '原因', diff: '配置差异', rollback: '回滚', rollbackConfirm: '确定回滚到此版本?', rollbackDone: '回滚完成' , addUser: '添加用户', aeadMethod: 'AEAD 加密方式', allowInsecureHint: '允许不安全连接(仅调试)', alterId: 'Alter ID', alterIdHint: 'VMess 额外 ID,建议 0', authenticationTimeout: '认证超时', bandwidthHint: '带宽限制,如 100 mbps', bbrProfile: 'BBR 配置', clientAuthCert: '客户端证书', clientAuthType: '客户端认证类型', congestionController: '拥塞控制', createWizard: '向导创建节点', cwnd: 'CWND', decryption: '解密', decryptionHint: 'VLESS 解密方式,通常 none', echKey: 'ECH Key', enablePureDownlink: '纯下行', flow: '流控', flowHint: '如 xtls-rprx-vision', grpcHint: 'gRPC 传输', grpcServiceName: 'gRPC Service Name', handshakeTimeout: '握手超时', jlsDest: 'JLS 目标', jlsHint: 'Joliz 相关配置', jlsProxy: 'JLS 代理', jlsSni: 'JLS SNI', jlsSniHint: 'JLS SNI 主机名', jlsUpstreamAddr: 'JLS 上游地址', jlsUsers: 'JLS 用户', kcpTunConn: 'KCP 连接数', kcpTunCrypt: 'KCP 加密', kcpTunKey: 'KCP 密钥', kcpTunMode: 'KCP 模式', kcpTunNocomp: 'KCP 禁用压缩', kcpTunRcvwnd: 'KCP 接收窗口', kcpTunSndwnd: 'KCP 发送窗口', maxIdleTime: '最大空闲时间', maxUdpRelayPacketSize: '最大 UDP 中继包', mekyaHint: 'Mekya 相关配置', mekyaKcpSection: 'Mekya KCP', mekyaMaxSimultaneous: '最大并发', mekyaMaxSimultaneousHint: '最大同时连接数', mekyaMaxWriteDuration: '最大写入时长', mekyaMaxWriteDurationHint: '写入超时', mekyaMaxWriteSize: '最大写入大小', mekyaMaxWriteSizeHint: '单次写入上限', mekyaPacketBuffer: '包缓冲', mekyaPacketBufferHint: '数据包缓冲大小', mkcpCongestion: 'mKCP 拥塞控制', mkcpDownlink: 'mKCP 下行', mkcpHeader: 'mKCP 头类型', mkcpHint: 'mKCP 传输', mkcpReadBuffer: 'mKCP 读缓冲', mkcpSeed: 'mKCP Seed', mkcpUplink: 'mKCP 上行', mkcpWriteBuffer: 'mKCP 写缓冲', muxBrutal: 'Mux Brutal', muxBrutalDown: 'Brutal 下行', muxBrutalUp: 'Brutal 上行', muxMaxConnections: 'Mux 最大连接', muxMaxStreams: 'Mux 最大流', muxMinStreams: 'Mux 最小流', muxOnlyTcp: '仅 TCP Mux', muxPadding: 'Mux 填充', muxProtocol: 'Mux 协议', muxStatistic: 'Mux 统计', network: '传输网络', obfs: '混淆', obfsMaxPacketSize: '混淆最大包', obfsMinPacketSize: '混淆最小包', obfsOptsHost: '混淆 Host', obfsOptsMode: '混淆模式', obfsPassword: '混淆密码', paddingMax: '填充最大', paddingMin: '填充最小', paddingScheme: '填充方案', passwordOptionalHint: '可选,留空使用绑定用户密码', passwordOptionalPlaceholder: '可选密码', psk: 'PSK', quicVersions: 'QUIC 版本', rateLimit: '速率限制', rateLimitHint: '如 10m', realityExclusiveHint: 'Reality 与 TLS 证书互斥', realityKeyHint: 'Reality 私钥', realityShortId: 'Reality Short ID', realmId: 'Realm ID', realmProxy: 'Realm 代理', realmServerUrl: 'Realm 服务地址', realmSkipCert: '跳过证书校验', realmStun: 'STUN', realmToken: 'Realm Token', resTlsDest: 'ResTLS 目标', resTlsHint: 'ResTLS 配置', resTlsMinRecordLen: '最小记录长度', resTlsPassword: 'ResTLS 密码', resTlsProxy: 'ResTLS 代理', resTlsScript: 'ResTLS 脚本', sectionJLS: 'JLS', sectionJlsUpstream: 'JLS 上游', sectionKcpTun: 'KCP Tun', sectionMekya: 'Mekya', sectionMkcp: 'mKCP', sectionMux: 'Mux', sectionRealm: 'Realm', sectionResTLS: 'ResTLS', sectionShadowTLS: 'ShadowTLS', sectionSimpleObfs: 'Simple Obfs', sectionXHTTP: 'XHTTP', shadowTlsHandshakeDest: '握手目标', shadowTlsHandshakeProxy: '握手代理', shadowTlsHint: 'ShadowTLS 配置', shadowTlsPassword: 'ShadowTLS 密码', shadowTlsPasswordHint: '版本 2/3
Line truncated
|
||||
sectionTransport: '传输层', sectionTLS: 'TLS 证书', sectionReality: 'Reality', sectionSSOption: 'Trojan SS 混淆', cipher: '加密方法', password: '密码', passwordOptional: '密码(可选)', uuid: 'UUID', privateKey: '私钥', certificate: '证书', alpn: 'ALPN', sni: 'SNI', clientFingerprint: '客户端指纹', allowInsecure: '允许不安全', muxOption: '多路复用', realityPrivateKey: 'Reality 私钥', realityShortID: 'Short ID', realityServerNames: 'Server Names', realityDest: 'Dest', transport: '传输', path: '路径', host: 'Host', serviceName: 'Service Name', up: '上行', down: '下行', ignoreClientBandwidth: '忽略客户端带宽', masquerade: 'Masquerade', congestionControl: '拥塞控制', clone: '克隆', saveTemplate: '保存为模板', versions: '配置版本', cloned: '克隆成功', templateCreated: '模板创建成功', instantiated: '已从模板创建节点', batchDone: '批量操作完成', templates: '节点模板', templateName: '模板名称', createdAt: '创建时间', instantiate: '实例化', deleteTemplateConfirm: '确定删除此模板?', templateDeleted: '模板已删除', enableSelected: '启用选中', disableSelected: '禁用选中', newPort: '新端口', version: '版本', reason: '原因', diff: '配置差异', rollback: '回滚', rollbackConfirm: '确定回滚到此版本?', rollbackDone: '回滚完成' , addUser: '添加用户', aeadMethod: 'AEAD 加密方式', allowInsecureHint: '允许不安全连接(仅调试)', alterId: 'Alter ID', alterIdHint: 'VMess 额外 ID,建议 0', authenticationTimeout: '认证超时', bandwidthHint: '带宽限制,如 100 mbps', bbrProfile: 'BBR 配置', clientAuthCert: '客户端证书', clientAuthType: '客户端认证类型', congestionController: '拥塞控制', createWizard: '向导创建节点', cwnd: 'CWND', decryption: '解密',
|
||||
encryption: 'Encryption',
|
||||
encryptionHint: '客户端 VLESS Encryption:与 decryption 成对,取 generate 输出的 encryption 一侧,勿直接复制 decryption', decryptionHint: '服务端 VLESS Encryption:由 mihomo generate vless-x25519 / vless-mlkem768 生成,填 decryption 一侧', echKey: 'ECH Key', enablePureDownlink: '纯下行', flow: '流控', flowHint: '如 xtls-rprx-vision', grpcHint: 'gRPC 传输', grpcServiceName: 'gRPC Service Name', handshakeTimeout: '握手超时', jlsDest: 'JLS 目标', jlsHint: 'Joliz 相关配置', jlsProxy: 'JLS 代理', jlsSni: 'JLS SNI', jlsSniHint: 'JLS SNI 主机名', jlsUpstreamAddr: 'JLS 上游地址', jlsUsers: 'JLS 用户', kcpTunConn: 'KCP 连接数', kcpTunCrypt: 'KCP 加密', kcpTunKey: 'KCP 密钥', kcpTunMode: 'KCP 模式', kcpTunNocomp: 'KCP 禁用压缩', kcpTunRcvwnd: 'KCP 接收窗口', kcpTunSndwnd: 'KCP 发送窗口', maxIdleTime: '最大空闲时间', maxUdpRelayPacketSize: '最大 UDP 中继包', mekyaHint: 'Mekya 相关配置', mekyaKcpSection: 'Mekya KCP', mekyaMaxSimultaneous: '最大并发', mekyaMaxSimultaneousHint: '最大同时连接数', mekyaMaxWriteDuration: '最大写入时长', mekyaMaxWriteDurationHint: '写入超时', mekyaMaxWriteSize: '最大写入大小', mekyaMaxWriteSizeHint: '单次写入上限', mekyaPacketBuffer: '包缓冲', mekyaPacketBufferHint: '数据包缓冲大小', mkcpCongestion: 'mKCP 拥塞控制', mkcpDownlink: 'mKCP 下行', mkcpHeader: 'mKCP 头类型', mkcpHint: 'mKCP 传输', mkcpReadBuffer: 'mKCP 读缓冲', mkcpSeed: 'mKCP Seed', mkcpUplink: 'mKCP 上行', mkcpWriteBuffer: 'mKCP 写缓冲', muxBrutal: 'Mux Brutal', muxBrutalDown: 'Brutal 下行', muxBrutalUp: 'Brutal 上行', muxMaxConnections: 'Mux 最大连接', muxMaxStreams: 'Mux 最大流', muxMinStreams: 'Mux 最小流', muxOnlyTcp: '仅 TCP Mux', muxPadding: 'Mux 填充', muxProtocol: 'Mux 协议', muxStatistic: 'Mux 统计', network: '传输网络', obfs: '混淆', obfsMaxPacketSize: '混淆最大包', obfsMinPacketSize: '混淆最小包', obfsOptsHost: '混淆 Host', obfsOptsMode: '混淆模式', obfsPassword: '混淆密码', paddingMax: '填充最大', paddingMin: '填充最小', paddingScheme: '填充方案', passwordOptionalHint: '可选,留空使用绑定用户密码', passwordOptionalPlaceholder: '可选密码', psk: 'PSK', quicVersions: 'QUIC 版本', rateLimit: '速率限制', rateLimitHint: '如 10m', realityExclusiveHint: 'Reality 与 TLS 证书互斥', realityKeyHint: 'Reality 私钥', realityShortId: 'Reality Short ID', realmId: 'Realm ID', realmProxy: 'Realm 代理', realmServerUrl: 'Realm 服务地址', realmSkipCert: '跳过证书校验', realmStun: 'STUN', realmToken: 'Realm Token', resTlsDest: 'ResTLS 目标', resTlsHint: 'ResTLS 配置', resTlsMinRecordLen: '最小记录长度', resTlsPassword: 'ResTLS 密码', resTlsProxy: 'ResTLS 代理', resTlsScript: 'ResTLS 脚本', sectionJLS: 'JLS', sectionJlsUpstream: 'JLS 上游', sectionKcpTun: 'KCP Tun', sectionMekya: 'Mekya', sectionMkcp: 'mKCP', sectionMux: 'Mux', sectionRealm: 'Realm', sectionResTLS: 'ResTLS', sectionShadowTLS: 'ShadowTLS', sectionSimpleObfs: 'Simple Obfs', sectionXHTTP: 'XHTTP', shadowTlsHandshakeDest: '握手目标', shadowTlsHandshakeProxy: '握手代理', shadowTlsHint: 'ShadowTLS 配置', shadowTlsPassword: 'ShadowTLS 密码', shadowTlsPasswordHint: '版本 2/3 密码', shadowTlsUsers: 'ShadowTLS 用户', shadowTlsVersion: 'ShadowTLS 版本', simpleObfsMode: 'Simple Obfs 模式', snellVersion: 'Snell 版本', ssOptionMethod: 'SS 加密方式', ssOptionPassword: 'SS 密码', sudokuKey: 'Sudoku Key', tableType: '表类型', tlsPairHint: '证书与私钥需成对填写', token: 'Token', tokenHint: '认证 Token', tokenPlaceholder: '输入 Token', trafficPattern: '流量模式', userHintMandatory: '至少一个用户', wizardBasic: '基础信息', wizardConfig: '协议配置', wizardConfigHint: '填写协议相关字段(TLS / Reality / 用户等)', wizardReview: '确认创建', wsPath: 'WebSocket 路径', wsPathHint: '如 /ws', xhttpHint: 'XHTTP 传输', xhttpHost: 'XHTTP Host', xhttpMode: 'XHTTP 模式', xhttpPath: 'XHTTP 路径', zeroRtt: '0-RTT'},
|
||||
core: { title: '核心管理', status: '核心状态', running: '运行中', stoppedStatus: '已停止', version: '版本', pid: '进程 ID', uptime: '运行时间', start: '启动', stop: '停止', restart: '重启', started: '已启动', stopped: '已停止', restarted: '已重启' , operationFailed: '操作失败', unavailable: '核心不可用'},
|
||||
logs: { title: '运行日志', empty: '暂无日志', level: '级别', timestamp: '时间', payload: '内容', autoRefresh: '自动刷新', clear: '清空' },
|
||||
config: { title: '配置引擎', subtitle: '可视化管理 Mihomo 配置', visual: '可视化配置', yaml: 'YAML 配置', proxies: '代理配置', yamlPreview: 'YAML 预览', addProxy: '添加代理', editProxy: '编辑代理', deleteProxy: '删除代理', deleteConfirm: '确定要删除此代理吗?', generateSuccess: '配置生成成功', validateSuccess: '配置验证通过', validateFailed: '配置验证失败', proxyName: '代理名称', proxyType: '代理类型', proxyServer: '代理服务器', proxyPort: '代理端口', proxyPassword: '代理密码', proxyUUID: '代理 UUID' , generate: '生成配置', validate: '校验配置'},
|
||||
|
||||
Reference in new issue
Block a user