mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-12 17:05:01 +03:00
feat(xray): add the MASQUE protocol and transport
xray-core v26.10.10 serves MASQUE: CONNECT-IP over HTTP/3 or HTTP/2, always behind TLS, as an inbound and an outbound protocol riding a dedicated "masque" transport. The panel could not create either. Inbound: MASQUE is a multi-user protocol whose users log in with their email and password over Basic auth. The panel keeps the client's "password" like trojan and GenXrayInboundConfig hands it to the core as "pass", so the full-config path, the live AddInbound path and node pushes all emit what the core reads; one user with an empty pass makes the core refuse the whole inbound. Live user adds build a masque.Account from either key, client-only changes diff per user so a new client does not drop every connected tunnel, and client creation, copy, validation and edits key on the password. The address pool and MTU get form fields, TLS is mandatory, and the port check follows the core's listener choice: HTTP/2 on TCP when the ALPN offers h2, HTTP/3 on UDP when it offers h3 or not h2. Nodes may host MASQUE from v3.9.1, the first panel release that knows to rename the password. MASQUE has no share-link format, so links and Clash skip it; the JSON subscription builds the client's masque outbound with its email and password, and the subscription allowlist now lets a subId see MASQUE inbounds at all. The IP-limit job, client pages and traffic tracking list the protocol. Outbound: the form edits the server, remote DNS, and the transport's host, path, Basic credentials, headers and WARP block (enrolled key, endpoint public key, tunnel addresses), which xray-core refuses next to user/pass. The outbound test probes it through the core, since its default HTTP/3 dial is UDP.
This commit is contained in:
@@ -694,7 +694,7 @@ func (j *CheckClientIpJob) disconnectClientTemporarily(inbound *model.Inbound, c
|
||||
// wireguard stays out: keepAlive marshals as a number, AddUser wants a string.
|
||||
protocol := string(inbound.Protocol)
|
||||
switch protocol {
|
||||
case "vmess", "vless", "trojan", "shadowsocks", "hysteria":
|
||||
case "vmess", "vless", "trojan", "shadowsocks", "hysteria", "masque":
|
||||
// supported protocols, continue
|
||||
default:
|
||||
logger.Warningf("[LIMIT_IP] Temporary disconnect is not supported for protocol %s on inbound %s", protocol, inbound.Tag)
|
||||
|
||||
@@ -411,7 +411,7 @@ func (s *ClientService) fillProtocolDefaults(c *model.Client, ib *model.Inbound)
|
||||
if c.ID == "" {
|
||||
c.ID = uuid.NewString()
|
||||
}
|
||||
case model.Trojan:
|
||||
case model.Trojan, model.MASQUE:
|
||||
if c.Password == "" {
|
||||
c.Password = strings.ReplaceAll(uuid.NewString(), "-", "")
|
||||
}
|
||||
|
||||
@@ -443,7 +443,7 @@ func (s *ClientService) AddInboundClient(inboundSvc *InboundService, data *model
|
||||
return false, common.NewError("client email is required")
|
||||
}
|
||||
switch oldInbound.Protocol {
|
||||
case "trojan":
|
||||
case "trojan", "masque":
|
||||
if client.Password == "" {
|
||||
return false, common.NewError("empty client ID")
|
||||
}
|
||||
@@ -688,7 +688,7 @@ func (s *ClientService) UpdateInboundClient(inboundSvc *InboundService, data *mo
|
||||
|
||||
newClientId := ""
|
||||
switch oldInbound.Protocol {
|
||||
case "trojan":
|
||||
case "trojan", "masque":
|
||||
newClientId = clients[0].Password
|
||||
case "shadowsocks":
|
||||
newClientId = clients[0].Email
|
||||
|
||||
@@ -656,6 +656,7 @@ func (s *InboundService) normalizeStreamSettings(inbound *model.Inbound) {
|
||||
model.Hysteria: true,
|
||||
model.WireGuard: true,
|
||||
model.Tunnel: true,
|
||||
model.MASQUE: true,
|
||||
}
|
||||
|
||||
if !protocolsWithStream[inbound.Protocol] {
|
||||
@@ -1241,7 +1242,7 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
|
||||
// Secure client ID
|
||||
for _, client := range clients {
|
||||
switch inbound.Protocol {
|
||||
case "trojan":
|
||||
case "trojan", "masque":
|
||||
if client.Password == "" {
|
||||
return inbound, false, common.NewError("empty client ID")
|
||||
}
|
||||
@@ -1767,6 +1768,10 @@ func (s *InboundService) validateUpdatedInboundClients(inbound *model.Inbound) e
|
||||
if client.Auth == "" {
|
||||
return common.NewError("empty client ID")
|
||||
}
|
||||
case model.MASQUE:
|
||||
if client.Password == "" {
|
||||
return common.NewError("empty client ID")
|
||||
}
|
||||
case model.TUIC:
|
||||
if client.ID == "" {
|
||||
return common.NewError("empty client ID")
|
||||
|
||||
@@ -230,7 +230,7 @@ func (s *InboundService) buildTargetClientFromSource(source model.Client, target
|
||||
inboundCanEnableTlsFlow(string(targetProtocol), targetInbound.StreamSettings, targetInbound.Settings) {
|
||||
target.Flow = flow
|
||||
}
|
||||
case model.Trojan, model.Shadowsocks:
|
||||
case model.Trojan, model.Shadowsocks, model.MASQUE:
|
||||
target.Password = s.generateRandomCredential(targetProtocol)
|
||||
case model.Hysteria:
|
||||
target.Auth = s.generateRandomCredential(targetProtocol)
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
)
|
||||
|
||||
const masqueTestStream = `{"network":"masque","security":"tls","masqueSettings":{"path":"/.well-known/masque/ip/*/*/"},
|
||||
"tlsSettings":{"alpn":["h3"],"certificates":[{"certificateFile":"/etc/ssl/certs/m.crt","keyFile":"/etc/ssl/private/m.key"}]}}`
|
||||
|
||||
// GetXrayConfig rebuilds every inbound's users from the clients table; a MASQUE user
|
||||
// emitted without its password makes xray-core refuse the whole config at startup.
|
||||
func TestGetXrayConfig_EmitsMasqueUserPasswords(t *testing.T) {
|
||||
setupConflictDB(t)
|
||||
in := &model.Inbound{
|
||||
Tag: "in-8443-masque", Enable: true, Listen: "127.0.0.1", Port: 8443, Protocol: model.MASQUE,
|
||||
Settings: `{"clients":[{"email":"ivy@example.test","password":"masque-pass-ivy","enable":true}],"address":["10.14.0.1/24"]}`,
|
||||
StreamSettings: masqueTestStream,
|
||||
}
|
||||
if _, _, err := (&InboundService{}).AddInbound(in); err != nil {
|
||||
t.Fatalf("AddInbound: %v", err)
|
||||
}
|
||||
|
||||
cfg, err := (&XrayService{}).GetXrayConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("GetXrayConfig: %v", err)
|
||||
}
|
||||
for i := range cfg.InboundConfigs {
|
||||
if cfg.InboundConfigs[i].Tag != "in-8443-masque" {
|
||||
continue
|
||||
}
|
||||
var settings struct {
|
||||
Clients []map[string]any `json:"clients"`
|
||||
}
|
||||
if err := json.Unmarshal(cfg.InboundConfigs[i].Settings, &settings); err != nil {
|
||||
t.Fatalf("decode emitted settings: %v", err)
|
||||
}
|
||||
if len(settings.Clients) != 1 || settings.Clients[0]["pass"] != "masque-pass-ivy" {
|
||||
t.Fatalf("emitted clients = %v, want one with pass masque-pass-ivy", settings.Clients)
|
||||
}
|
||||
raw, err := json.Marshal(cfg.InboundConfigs[i])
|
||||
if err != nil {
|
||||
t.Fatalf("marshal emitted inbound: %v", err)
|
||||
}
|
||||
var emitted map[string]any
|
||||
if err := json.Unmarshal(raw, &emitted); err != nil {
|
||||
t.Fatalf("decode emitted inbound: %v", err)
|
||||
}
|
||||
if stream, _ := emitted["streamSettings"].(map[string]any); stream["network"] != "masque" {
|
||||
t.Fatalf("emitted streamSettings = %v, want the stored masque transport", emitted["streamSettings"])
|
||||
}
|
||||
assertXrayAccepts(t, "the emitted MASQUE inbound", buildGoldenInbound(t, emitted))
|
||||
return
|
||||
}
|
||||
t.Fatal("inbound in-8443-masque not found in the generated config")
|
||||
}
|
||||
|
||||
// A client added to a MASQUE inbound without a password could never authenticate,
|
||||
// and xray-core refuses the whole inbound over one empty pass.
|
||||
func TestFillProtocolDefaults_MintsMasquePassword(t *testing.T) {
|
||||
client := model.Client{Email: "ivy@example.test"}
|
||||
if err := (&ClientService{}).fillProtocolDefaults(&client, &model.Inbound{Protocol: model.MASQUE}); err != nil {
|
||||
t.Fatalf("fillProtocolDefaults: %v", err)
|
||||
}
|
||||
if len(client.Password) != 32 {
|
||||
t.Fatalf("Password = %q, want a minted 32-character password", client.Password)
|
||||
}
|
||||
kept := model.Client{Email: "ivy@example.test", Password: "chosen"}
|
||||
if err := (&ClientService{}).fillProtocolDefaults(&kept, &model.Inbound{Protocol: model.MASQUE}); err != nil {
|
||||
t.Fatalf("fillProtocolDefaults: %v", err)
|
||||
}
|
||||
if kept.Password != "chosen" {
|
||||
t.Fatalf("Password = %q, want the chosen password kept", kept.Password)
|
||||
}
|
||||
}
|
||||
|
||||
// Editing a MASQUE client must key on its password like trojan; falling back to the
|
||||
// empty UUID refused every MASQUE client edit with "empty client ID".
|
||||
func TestUpdateInboundClient_MasquePasswordChange(t *testing.T) {
|
||||
setupBulkDB(t)
|
||||
svc := &ClientService{}
|
||||
source := []model.Client{{Email: "ivy@x", Password: "pw-old", SubID: "sub-ivy", Enable: true}}
|
||||
ib := mkInbound(t, 22101, model.MASQUE, clientsSettings(t, source))
|
||||
if err := svc.SyncInbound(nil, ib.Id, source); err != nil {
|
||||
t.Fatalf("seed linkage: %v", err)
|
||||
}
|
||||
|
||||
updated := []model.Client{{Email: "ivy@x", Password: "pw-new", SubID: "sub-ivy", Enable: true}}
|
||||
if _, err := svc.UpdateInboundClient(&InboundService{}, &model.Inbound{
|
||||
Id: ib.Id,
|
||||
Settings: clientsSettings(t, updated),
|
||||
}, "ivy@x"); err != nil {
|
||||
t.Fatalf("UpdateInboundClient: %v", err)
|
||||
}
|
||||
|
||||
if rec := lookupClientRecord(t, "ivy@x"); rec.Password != "pw-new" {
|
||||
t.Fatalf("stored password = %q, want pw-new", rec.Password)
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,7 @@ var nodeEligibleProtocols = map[model.Protocol]bool{
|
||||
model.MTProto: true,
|
||||
model.AmneziaWG: true,
|
||||
model.TUIC: true,
|
||||
model.MASQUE: true,
|
||||
}
|
||||
|
||||
// nodeProtocolFirstRelease is the panel release that introduced each protocol
|
||||
@@ -78,6 +79,7 @@ var nodeProtocolFirstRelease = map[model.Protocol]string{
|
||||
model.MTProto: "v3.5.0",
|
||||
model.AmneziaWG: "v3.7.0",
|
||||
model.TUIC: "v3.8.0",
|
||||
model.MASQUE: "v3.9.1",
|
||||
}
|
||||
|
||||
// checkNodeCanHostProtocol refuses assigning protocol to nodeID unless the
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
)
|
||||
|
||||
// MASQUE is native Xray, so a node runs it, but a node panel older than the release
|
||||
// that knows it would hand the clients' "password" to a core that reads "pass".
|
||||
func TestCheckNodeCanHostProtocol_Masque(t *testing.T) {
|
||||
setupConflictDB(t)
|
||||
tests := []struct {
|
||||
version string
|
||||
wantErr string
|
||||
}{
|
||||
{"v3.9.0", "need v3.9.1 or newer"},
|
||||
{"v3.9.1", ""},
|
||||
{"dev+1d1128cf", ""},
|
||||
}
|
||||
for i, tc := range tests {
|
||||
t.Run(tc.version, func(t *testing.T) {
|
||||
node := &model.Node{Name: "masque-node-" + tc.version, Address: "127.0.0.1", Port: 2096 + i, PanelVersion: tc.version}
|
||||
if err := database.GetDB().Create(node).Error; err != nil {
|
||||
t.Fatalf("create node: %v", err)
|
||||
}
|
||||
err := checkNodeCanHostProtocol(database.GetDB(), node.Id, model.MASQUE)
|
||||
if tc.wantErr == "" {
|
||||
if err != nil {
|
||||
t.Fatalf("node on %s refused MASQUE: %v", tc.version, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err == nil || !strings.Contains(err.Error(), tc.wantErr) {
|
||||
t.Fatalf("node on %s: err = %v, want one containing %q", tc.version, err, tc.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -225,6 +226,9 @@ func outboundTransportIsUDP(ob map[string]any) bool {
|
||||
if protocol, _ := ob["protocol"].(string); equalsAnyFold(protocol, "hysteria", "wireguard", "amneziawg") {
|
||||
return true
|
||||
}
|
||||
if protocol, _ := ob["protocol"].(string); equalsAnyFold(protocol, "masque") {
|
||||
return masqueDialsHTTP3(ob)
|
||||
}
|
||||
if stream, ok := ob["streamSettings"].(map[string]any); ok {
|
||||
// The core resolves "kcp" and "mkcp" to the same mKCP transport.
|
||||
if n, _ := stream["network"].(string); equalsAnyFold(n, "hysteria", "kcp", "mkcp", "quic") {
|
||||
@@ -234,6 +238,15 @@ func outboundTransportIsUDP(ob map[string]any) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// masqueDialsHTTP3 mirrors the core's MASQUE client, which falls back to HTTP/2 over
|
||||
// TCP only when the TLS ALPN offers h2 and not h3.
|
||||
func masqueDialsHTTP3(ob map[string]any) bool {
|
||||
stream, _ := ob["streamSettings"].(map[string]any)
|
||||
tlsSettings, _ := stream["tlsSettings"].(map[string]any)
|
||||
alpn, _ := tlsSettings["alpn"].([]any)
|
||||
return !slices.Contains(alpn, any("h2")) || slices.Contains(alpn, any("h3"))
|
||||
}
|
||||
|
||||
// equalsAnyFold mirrors the core, which lowercases a protocol id and a
|
||||
// transport name before it resolves either of them.
|
||||
func equalsAnyFold(value string, want ...string) bool {
|
||||
@@ -281,7 +294,7 @@ func extractOutboundEndpoints(ob map[string]any) []string {
|
||||
if len(out) == 0 {
|
||||
addServer(settings["address"], settings["port"])
|
||||
}
|
||||
case "hysteria":
|
||||
case "hysteria", "masque":
|
||||
addServer(settings["address"], settings["port"])
|
||||
case "trojan", "shadowsocks", "http", "socks":
|
||||
if servers, ok := settings["servers"].([]any); ok {
|
||||
|
||||
@@ -52,3 +52,15 @@ func TestExtractOutboundEndpointsVLESS(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A MASQUE outbound names its server flat in settings, like hysteria; without it the
|
||||
// outbound test has no endpoint to resolve and reports the outbound unreachable.
|
||||
func TestExtractOutboundEndpointsMasque(t *testing.T) {
|
||||
got := extractOutboundEndpoints(map[string]any{
|
||||
"protocol": "masque",
|
||||
"settings": map[string]any{"address": "masque.example.com", "port": float64(8443)},
|
||||
})
|
||||
if want := []string{"masque.example.com:8443"}; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("extractOutboundEndpoints() = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,9 @@ func TestOutboundTransportIsUDPMatchesTheCore(t *testing.T) {
|
||||
{"capitalised wireguard", map[string]any{"protocol": "WireGuard"}, true},
|
||||
{"upper hysteria", map[string]any{"protocol": "HYSTERIA"}, true},
|
||||
{"amneziawg", map[string]any{"protocol": "amneziawg"}, true},
|
||||
// The core's MASQUE client dials HTTP/3 over QUIC unless the ALPN offers h2 without h3.
|
||||
{"masque over http/3", map[string]any{"protocol": "masque", "streamSettings": map[string]any{"network": "masque"}}, true},
|
||||
{"masque pinned to http/2", map[string]any{"protocol": "masque", "streamSettings": map[string]any{"network": "masque", "tlsSettings": map[string]any{"alpn": []any{"h2"}}}}, false},
|
||||
{"kcp transport", map[string]any{"streamSettings": map[string]any{"network": "kcp"}}, true},
|
||||
{"kcp transport capitalised", map[string]any{"streamSettings": map[string]any{"network": "KCP"}}, true},
|
||||
{"mkcp alias", map[string]any{"streamSettings": map[string]any{"network": "mkcp"}}, true},
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/amneziawg"
|
||||
@@ -30,6 +31,8 @@ func inboundTransports(protocol model.Protocol, streamSettings, settings string)
|
||||
return transportUDP
|
||||
case model.MTProto:
|
||||
return transportTCP
|
||||
case model.MASQUE:
|
||||
return masqueTransports(streamSettings)
|
||||
}
|
||||
|
||||
var bits transportBits
|
||||
@@ -93,6 +96,26 @@ func inboundTransports(protocol model.Protocol, streamSettings, settings string)
|
||||
return bits
|
||||
}
|
||||
|
||||
// masqueTransports mirrors xray-core's MASQUE listener: HTTP/2 on TCP when the TLS
|
||||
// ALPN offers h2, HTTP/3 on UDP when it offers h3 or does not offer h2.
|
||||
func masqueTransports(streamSettings string) transportBits {
|
||||
var stream struct {
|
||||
TLSSettings struct {
|
||||
ALPN []string `json:"alpn"`
|
||||
} `json:"tlsSettings"`
|
||||
}
|
||||
_ = json.Unmarshal([]byte(streamSettings), &stream)
|
||||
h2 := slices.Contains(stream.TLSSettings.ALPN, "h2")
|
||||
var bits transportBits
|
||||
if h2 {
|
||||
bits |= transportTCP
|
||||
}
|
||||
if !h2 || slices.Contains(stream.TLSSettings.ALPN, "h3") {
|
||||
bits |= transportUDP
|
||||
}
|
||||
return bits
|
||||
}
|
||||
|
||||
// bindAddr is a listen address plus sockopt.v6only. xray listens on "tcp"/"udp",
|
||||
// so Go opens every wildcard, 0.0.0.0 included, dual-stack unless v6only is set.
|
||||
type bindAddr struct {
|
||||
|
||||
@@ -75,6 +75,13 @@ func TestInboundTransports(t *testing.T) {
|
||||
{"hysteria forced udp", model.Hysteria, `{"network":"tcp"}`, ``, transportUDP},
|
||||
{"wireguard forced udp", model.WireGuard, ``, ``, transportUDP},
|
||||
|
||||
// MASQUE serves HTTP/3 on UDP unless the TLS ALPN offers only h2, and adds an
|
||||
// HTTP/2 listener on TCP whenever h2 is offered.
|
||||
{"masque without alpn is udp", model.MASQUE, `{"network":"masque","tlsSettings":{}}`, ``, transportUDP},
|
||||
{"masque h3 is udp", model.MASQUE, `{"network":"masque","tlsSettings":{"alpn":["h3"]}}`, ``, transportUDP},
|
||||
{"masque h2 is tcp", model.MASQUE, `{"network":"masque","tlsSettings":{"alpn":["h2"]}}`, ``, transportTCP},
|
||||
{"masque h3+h2 is both", model.MASQUE, `{"network":"masque","tlsSettings":{"alpn":["h3","h2"]}}`, ``, transportTCP | transportUDP},
|
||||
|
||||
{"shadowsocks tcp,udp", model.Shadowsocks, ``, `{"network":"tcp,udp"}`, transportTCP | transportUDP},
|
||||
{"shadowsocks udp only", model.Shadowsocks, ``, `{"network":"udp"}`, transportUDP},
|
||||
{"shadowsocks tcp only", model.Shadowsocks, ``, `{"network":"tcp"}`, transportTCP},
|
||||
|
||||
@@ -282,6 +282,10 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
if c.Auth != "" {
|
||||
entry["auth"] = c.Auth
|
||||
}
|
||||
case model.MASQUE:
|
||||
if c.Password != "" {
|
||||
entry["password"] = c.Password
|
||||
}
|
||||
case model.WireGuard:
|
||||
if inboundClient, ok := wireguardClientsByEmail[strings.ToLower(strings.TrimSpace(c.Email))]; ok {
|
||||
c.AllowedIPs = inboundClient.AllowedIPs
|
||||
|
||||
@@ -718,6 +718,10 @@
|
||||
"holeTimeout": "مهلة المقطع المفقود (ms)",
|
||||
"sessionTtl": "مدة صلاحية الجلسة (s)",
|
||||
"concurrency": "التزامن"
|
||||
},
|
||||
"masque": {
|
||||
"addressPool": "مجمّع العناوين",
|
||||
"addressPoolDesc": "تُخصَّص عناوين النفق من هذه البادئات — بحد أقصى IPv4 واحد و IPv6 واحد."
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
@@ -813,7 +817,7 @@
|
||||
"limitIpFail2banWindows": "Fail2ban غير متوفّر على نظام Windows، لذا لا يمكن تطبيق حد عناوين IP.",
|
||||
"limitIpDisabled": "ميزة حد عناوين IP معطّلة على هذا الخادم.",
|
||||
"password": "كلمة المرور",
|
||||
"passwordDesc": "تُستخدم فقط من قبل عملاء Trojan و Shadowsocks؛ ويتم تجاهلها لـ VLESS و VMess و Hysteria و WireGuard.",
|
||||
"passwordDesc": "يستخدمها عملاء Trojan و Shadowsocks و TUIC و MASQUE؛ ويتم تجاهلها لـ VLESS و VMess و Hysteria و WireGuard.",
|
||||
"subId": "معرّف الاشتراك",
|
||||
"subIdDesc": "يُستخدم أيضًا كرمز دعوة لهذا العميل في بوت تيليجرام: من يرسله إلى البوت يُربط بهذا العميل. اجعله طويلًا وعشوائيًا — فالمعرّف القصير أو السهل التخمين قد يستولي عليه شخص آخر.",
|
||||
"online": "متصل",
|
||||
@@ -1961,7 +1965,10 @@
|
||||
"interface": "الواجهة",
|
||||
"proxyProtocol": "Proxy protocol",
|
||||
"tcpUserTimeoutMs": "TCP user timeout (ms)",
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (ثانية)"
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (ثانية)",
|
||||
"masqueWarpDesc": "الاتصال بـ Cloudflare WARP عبر MASQUE بمفتاح ECDSA P-256 مسجَّل؛ يكون host و path افتراضيًا نقطة نهاية Cloudflare.",
|
||||
"masqueWarpPublicKey": "المفتاح العام لنقطة النهاية",
|
||||
"masqueWarpAddress": "عناوين النفق"
|
||||
},
|
||||
"outbound": {
|
||||
"tag": "الوسم",
|
||||
|
||||
@@ -719,6 +719,10 @@
|
||||
"holeTimeout": "Missing segment timeout (ms)",
|
||||
"sessionTtl": "Session TTL (s)",
|
||||
"concurrency": "Concurrency"
|
||||
},
|
||||
"masque": {
|
||||
"addressPool": "Address pool",
|
||||
"addressPoolDesc": "Tunnel addresses are leased from these prefixes — at most one IPv4 and one IPv6."
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
@@ -813,7 +817,7 @@
|
||||
"limitIpFail2banWindows": "Fail2ban is not available on Windows, so the IP limit cannot be enforced.",
|
||||
"limitIpDisabled": "The IP limit feature is disabled on this server.",
|
||||
"password": "Password",
|
||||
"passwordDesc": "Used by Trojan, Shadowsocks, and TUIC clients; ignored for VLESS, VMess, Hysteria, and WireGuard.",
|
||||
"passwordDesc": "Used by Trojan, Shadowsocks, TUIC, and MASQUE clients; ignored for VLESS, VMess, Hysteria, and WireGuard.",
|
||||
"subId": "Subscription ID",
|
||||
"subIdDesc": "Also works as this client's Telegram bot invite code: whoever sends it to the bot is linked to this client. Keep it long and random — a short or guessable ID can be claimed by someone else.",
|
||||
"online": "Online",
|
||||
@@ -2079,7 +2083,10 @@
|
||||
"interface": "Interface",
|
||||
"proxyProtocol": "Proxy protocol",
|
||||
"tcpUserTimeoutMs": "TCP user timeout (ms)",
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (s)"
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (s)",
|
||||
"masqueWarpDesc": "Connect to Cloudflare WARP over MASQUE with an enrolled ECDSA P-256 key; host and path default to Cloudflare's endpoint.",
|
||||
"masqueWarpPublicKey": "Endpoint public key",
|
||||
"masqueWarpAddress": "Tunnel addresses"
|
||||
},
|
||||
"outbound": {
|
||||
"tag": "Tag",
|
||||
|
||||
@@ -718,6 +718,10 @@
|
||||
"holeTimeout": "Tiempo de espera de segmento perdido (ms)",
|
||||
"sessionTtl": "TTL de sesión (s)",
|
||||
"concurrency": "Concurrencia"
|
||||
},
|
||||
"masque": {
|
||||
"addressPool": "Pool de direcciones",
|
||||
"addressPoolDesc": "Las direcciones del túnel se asignan de estos prefijos: como máximo uno IPv4 y uno IPv6."
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
@@ -813,7 +817,7 @@
|
||||
"limitIpFail2banWindows": "Fail2ban no está disponible en Windows, por lo que no se puede aplicar el límite de IP.",
|
||||
"limitIpDisabled": "La función de límite de IP está deshabilitada en este servidor.",
|
||||
"password": "Contraseña",
|
||||
"passwordDesc": "Solo la usan los clientes Trojan y Shadowsocks; se ignora para VLESS, VMess, Hysteria y WireGuard.",
|
||||
"passwordDesc": "La usan los clientes Trojan, Shadowsocks, TUIC y MASQUE; se ignora para VLESS, VMess, Hysteria y WireGuard.",
|
||||
"subId": "ID de suscripción",
|
||||
"subIdDesc": "También sirve como código de invitación de este cliente en el bot de Telegram: quien lo envíe al bot queda vinculado a este cliente. Mantenlo largo y aleatorio: un ID corto o fácil de adivinar puede ser reclamado por otra persona.",
|
||||
"online": "En línea",
|
||||
@@ -1961,7 +1965,10 @@
|
||||
"interface": "Interfaz",
|
||||
"proxyProtocol": "Proxy protocol",
|
||||
"tcpUserTimeoutMs": "TCP user timeout (ms)",
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (s)"
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (s)",
|
||||
"masqueWarpDesc": "Conecta a Cloudflare WARP mediante MASQUE con una clave ECDSA P-256 registrada; host y path usan por defecto el endpoint de Cloudflare.",
|
||||
"masqueWarpPublicKey": "Clave pública del endpoint",
|
||||
"masqueWarpAddress": "Direcciones del túnel"
|
||||
},
|
||||
"outbound": {
|
||||
"tag": "Etiqueta",
|
||||
|
||||
@@ -718,6 +718,10 @@
|
||||
"holeTimeout": "مهلت قطعهٔ گمشده (میلیثانیه)",
|
||||
"sessionTtl": "TTL نشست (ثانیه)",
|
||||
"concurrency": "همزمانی"
|
||||
},
|
||||
"masque": {
|
||||
"addressPool": "مخزن آدرس",
|
||||
"addressPoolDesc": "آدرسهای تونل از این پیشوندها تخصیص داده میشوند — حداکثر یک IPv4 و یک IPv6."
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
@@ -813,7 +817,7 @@
|
||||
"limitIpFail2banWindows": "Fail2ban روی ویندوز در دسترس نیست، بنابراین محدودیت IP قابل اعمال نیست.",
|
||||
"limitIpDisabled": "قابلیت محدودیت IP روی این سرور غیرفعال است.",
|
||||
"password": "رمز عبور",
|
||||
"passwordDesc": "فقط توسط کلاینتهای Trojan و Shadowsocks استفاده میشود؛ برای VLESS، VMess، Hysteria و WireGuard نادیده گرفته میشود.",
|
||||
"passwordDesc": "توسط کلاینتهای Trojan، Shadowsocks، TUIC و MASQUE استفاده میشود؛ برای VLESS، VMess، Hysteria و WireGuard نادیده گرفته میشود.",
|
||||
"subId": "شناسه اشتراک",
|
||||
"subIdDesc": "این شناسه، کد دعوت این کاربر در ربات تلگرام هم هست: هر کسی آن را به ربات بفرستد به این کاربر متصل میشود. آن را طولانی و تصادفی نگه دارید؛ شناسهٔ کوتاه یا قابل حدس ممکن است توسط شخص دیگری تصاحب شود.",
|
||||
"online": "آنلاین",
|
||||
@@ -1961,7 +1965,10 @@
|
||||
"interface": "رابط",
|
||||
"proxyProtocol": "Proxy Protocol",
|
||||
"tcpUserTimeoutMs": "TCP user timeout (ms)",
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (s)"
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (s)",
|
||||
"masqueWarpDesc": "اتصال به Cloudflare WARP از طریق MASQUE با یک کلید ECDSA P-256 ثبتشده؛ host و path بهطور پیشفرض endpoint کلودفلر هستند.",
|
||||
"masqueWarpPublicKey": "کلید عمومی endpoint",
|
||||
"masqueWarpAddress": "آدرسهای تونل"
|
||||
},
|
||||
"outbound": {
|
||||
"tag": "تگ",
|
||||
|
||||
@@ -718,6 +718,10 @@
|
||||
"holeTimeout": "Batas waktu segmen hilang (ms)",
|
||||
"sessionTtl": "TTL sesi (s)",
|
||||
"concurrency": "Konkurensi"
|
||||
},
|
||||
"masque": {
|
||||
"addressPool": "Kumpulan alamat",
|
||||
"addressPoolDesc": "Alamat terowongan diberikan dari prefiks ini — maksimal satu IPv4 dan satu IPv6."
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
@@ -813,7 +817,7 @@
|
||||
"limitIpFail2banWindows": "Fail2ban tidak tersedia di Windows, sehingga batas IP tidak dapat diterapkan.",
|
||||
"limitIpDisabled": "Fitur batas IP dinonaktifkan di server ini.",
|
||||
"password": "Kata sandi",
|
||||
"passwordDesc": "Hanya digunakan oleh klien Trojan dan Shadowsocks; diabaikan untuk VLESS, VMess, Hysteria, dan WireGuard.",
|
||||
"passwordDesc": "Digunakan oleh klien Trojan, Shadowsocks, TUIC, dan MASQUE; diabaikan untuk VLESS, VMess, Hysteria, dan WireGuard.",
|
||||
"subId": "ID Langganan",
|
||||
"subIdDesc": "Juga berfungsi sebagai kode undangan bot Telegram untuk klien ini: siapa pun yang mengirimkannya ke bot akan ditautkan ke klien ini. Buat panjang dan acak — ID yang pendek atau mudah ditebak bisa diklaim orang lain.",
|
||||
"online": "Online",
|
||||
@@ -1961,7 +1965,10 @@
|
||||
"interface": "Interface",
|
||||
"proxyProtocol": "Proxy protocol",
|
||||
"tcpUserTimeoutMs": "TCP user timeout (ms)",
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (d)"
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (d)",
|
||||
"masqueWarpDesc": "Terhubung ke Cloudflare WARP melalui MASQUE dengan kunci ECDSA P-256 yang terdaftar; host dan path default ke endpoint Cloudflare.",
|
||||
"masqueWarpPublicKey": "Kunci publik endpoint",
|
||||
"masqueWarpAddress": "Alamat terowongan"
|
||||
},
|
||||
"outbound": {
|
||||
"tag": "Tag",
|
||||
|
||||
@@ -718,6 +718,10 @@
|
||||
"holeTimeout": "欠落セグメントのタイムアウト(ms)",
|
||||
"sessionTtl": "セッション TTL(秒)",
|
||||
"concurrency": "同時実行数"
|
||||
},
|
||||
"masque": {
|
||||
"addressPool": "アドレスプール",
|
||||
"addressPoolDesc": "トンネルのアドレスはこれらのプレフィックスから割り当てられます(IPv4 と IPv6 をそれぞれ最大 1 つ)。"
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
@@ -813,7 +817,7 @@
|
||||
"limitIpFail2banWindows": "Windows では Fail2ban を利用できないため、IP 制限を適用できません。",
|
||||
"limitIpDisabled": "このサーバーでは IP 制限機能が無効になっています。",
|
||||
"password": "パスワード",
|
||||
"passwordDesc": "Trojan と Shadowsocks のクライアントのみが使用します。VLESS、VMess、Hysteria、WireGuard では無視されます。",
|
||||
"passwordDesc": "Trojan、Shadowsocks、TUIC、MASQUE のクライアントが使用します。VLESS、VMess、Hysteria、WireGuard では無視されます。",
|
||||
"subId": "サブスクリプション ID",
|
||||
"subIdDesc": "このクライアントの Telegram ボット招待コードとしても使われます。ボットに送信した人がこのクライアントに紐付けられます。長くランダムな値にしてください。短い、または推測しやすい ID は他人に取得されるおそれがあります。",
|
||||
"online": "オンライン",
|
||||
@@ -1961,7 +1965,10 @@
|
||||
"interface": "インターフェース",
|
||||
"proxyProtocol": "Proxy protocol",
|
||||
"tcpUserTimeoutMs": "TCP user timeout (ms)",
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (秒)"
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (秒)",
|
||||
"masqueWarpDesc": "登録済みの ECDSA P-256 鍵で MASQUE 経由で Cloudflare WARP に接続します。host と path の既定値は Cloudflare のエンドポイントです。",
|
||||
"masqueWarpPublicKey": "エンドポイント公開鍵",
|
||||
"masqueWarpAddress": "トンネルアドレス"
|
||||
},
|
||||
"outbound": {
|
||||
"tag": "タグ",
|
||||
|
||||
@@ -718,6 +718,10 @@
|
||||
"holeTimeout": "Timeout de segmento ausente (ms)",
|
||||
"sessionTtl": "TTL da sessão (s)",
|
||||
"concurrency": "Concorrência"
|
||||
},
|
||||
"masque": {
|
||||
"addressPool": "Pool de endereços",
|
||||
"addressPoolDesc": "Os endereços do túnel são atribuídos a partir destes prefixos — no máximo um IPv4 e um IPv6."
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
@@ -813,7 +817,7 @@
|
||||
"limitIpFail2banWindows": "O Fail2ban não está disponível no Windows, portanto o limite de IP não pode ser aplicado.",
|
||||
"limitIpDisabled": "O recurso de limite de IP está desativado neste servidor.",
|
||||
"password": "Senha",
|
||||
"passwordDesc": "Usada apenas pelos clientes Trojan e Shadowsocks; ignorada para VLESS, VMess, Hysteria e WireGuard.",
|
||||
"passwordDesc": "Usada pelos clientes Trojan, Shadowsocks, TUIC e MASQUE; ignorada para VLESS, VMess, Hysteria e WireGuard.",
|
||||
"subId": "ID da assinatura",
|
||||
"subIdDesc": "Também funciona como código de convite deste cliente no bot do Telegram: quem enviá-lo ao bot fica vinculado a este cliente. Mantenha-o longo e aleatório — um ID curto ou fácil de adivinhar pode ser reivindicado por outra pessoa.",
|
||||
"online": "Online",
|
||||
@@ -1961,7 +1965,10 @@
|
||||
"interface": "Interface",
|
||||
"proxyProtocol": "Proxy protocol",
|
||||
"tcpUserTimeoutMs": "TCP user timeout (ms)",
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (s)"
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (s)",
|
||||
"masqueWarpDesc": "Conecta ao Cloudflare WARP via MASQUE com uma chave ECDSA P-256 registrada; host e path usam por padrão o endpoint da Cloudflare.",
|
||||
"masqueWarpPublicKey": "Chave pública do endpoint",
|
||||
"masqueWarpAddress": "Endereços do túnel"
|
||||
},
|
||||
"outbound": {
|
||||
"tag": "Tag",
|
||||
|
||||
@@ -719,6 +719,10 @@
|
||||
"holeTimeout": "Тайм-аут пропущенного сегмента (мс)",
|
||||
"sessionTtl": "TTL сессии (с)",
|
||||
"concurrency": "Параллелизм"
|
||||
},
|
||||
"masque": {
|
||||
"addressPool": "Пул адресов",
|
||||
"addressPoolDesc": "Адреса туннелей выдаются из этих префиксов — не более одного IPv4 и одного IPv6."
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
@@ -813,7 +817,7 @@
|
||||
"limitIpFail2banWindows": "Fail2ban недоступен в Windows, поэтому ограничение по IP не может быть применено.",
|
||||
"limitIpDisabled": "Функция ограничения по IP отключена на этом сервере.",
|
||||
"password": "Пароль",
|
||||
"passwordDesc": "Используется клиентами Trojan, Shadowsocks и TUIC; игнорируется для VLESS, VMess, Hysteria и WireGuard.",
|
||||
"passwordDesc": "Используется клиентами Trojan, Shadowsocks, TUIC и MASQUE; игнорируется для VLESS, VMess, Hysteria и WireGuard.",
|
||||
"subId": "ID подписки",
|
||||
"subIdDesc": "Также служит кодом приглашения этого клиента в Telegram-боте: тот, кто отправит его боту, будет привязан к этому клиенту. Используйте длинное случайное значение — короткий или легко угадываемый ID может присвоить кто-то другой.",
|
||||
"online": "В сети",
|
||||
@@ -1961,7 +1965,10 @@
|
||||
"interface": "Интерфейс",
|
||||
"proxyProtocol": "Proxy protocol",
|
||||
"tcpUserTimeoutMs": "TCP user timeout (мс)",
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (с)"
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (с)",
|
||||
"masqueWarpDesc": "Подключение к Cloudflare WARP через MASQUE с зарегистрированным ключом ECDSA P-256; host и path по умолчанию указывают на конечную точку Cloudflare.",
|
||||
"masqueWarpPublicKey": "Открытый ключ конечной точки",
|
||||
"masqueWarpAddress": "Адреса туннеля"
|
||||
},
|
||||
"outbound": {
|
||||
"tag": "Тег",
|
||||
|
||||
@@ -718,6 +718,10 @@
|
||||
"holeTimeout": "Eksik segment zaman aşımı (ms)",
|
||||
"sessionTtl": "Oturum TTL (s)",
|
||||
"concurrency": "Eşzamanlılık"
|
||||
},
|
||||
"masque": {
|
||||
"addressPool": "Adres havuzu",
|
||||
"addressPoolDesc": "Tünel adresleri bu öneklerden atanır — en fazla bir IPv4 ve bir IPv6."
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
@@ -813,7 +817,7 @@
|
||||
"limitIpFail2banWindows": "Fail2ban Windows'ta kullanılamadığından IP sınırı uygulanamaz.",
|
||||
"limitIpDisabled": "IP sınırı özelliği bu sunucuda devre dışı.",
|
||||
"password": "Şifre",
|
||||
"passwordDesc": "Yalnızca Trojan ve Shadowsocks istemcileri tarafından kullanılır; VLESS, VMess, Hysteria ve WireGuard için yok sayılır.",
|
||||
"passwordDesc": "Trojan, Shadowsocks, TUIC ve MASQUE istemcileri tarafından kullanılır; VLESS, VMess, Hysteria ve WireGuard için yok sayılır.",
|
||||
"subId": "Abonelik ID'si",
|
||||
"subIdDesc": "Bu istemcinin Telegram botu davet kodu olarak da kullanılır: bunu bota gönderen kişi bu istemciye bağlanır. Uzun ve rastgele tutun — kısa veya tahmin edilebilir bir kimlik başkası tarafından sahiplenilebilir.",
|
||||
"online": "Çevrimiçi",
|
||||
@@ -1961,7 +1965,10 @@
|
||||
"interface": "Arabirim",
|
||||
"proxyProtocol": "Proxy Protocol",
|
||||
"tcpUserTimeoutMs": "TCP User Timeout (ms)",
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive bekleme süresi (saniye)"
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive bekleme süresi (saniye)",
|
||||
"masqueWarpDesc": "Kayıtlı bir ECDSA P-256 anahtarıyla MASQUE üzerinden Cloudflare WARP'a bağlanır; host ve path varsayılan olarak Cloudflare uç noktasıdır.",
|
||||
"masqueWarpPublicKey": "Uç nokta açık anahtarı",
|
||||
"masqueWarpAddress": "Tünel adresleri"
|
||||
},
|
||||
"outbound": {
|
||||
"tag": "Etiket",
|
||||
|
||||
@@ -718,6 +718,10 @@
|
||||
"holeTimeout": "Тайм-аут пропущеного сегмента (мс)",
|
||||
"sessionTtl": "TTL сесії (с)",
|
||||
"concurrency": "Паралельність"
|
||||
},
|
||||
"masque": {
|
||||
"addressPool": "Пул адрес",
|
||||
"addressPoolDesc": "Адреси тунелів видаються з цих префіксів — не більше одного IPv4 та одного IPv6."
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
@@ -813,7 +817,7 @@
|
||||
"limitIpFail2banWindows": "Fail2ban недоступний у Windows, тому обмеження за IP не може бути застосоване.",
|
||||
"limitIpDisabled": "Функцію обмеження за IP вимкнено на цьому сервері.",
|
||||
"password": "Пароль",
|
||||
"passwordDesc": "Використовується лише клієнтами Trojan і Shadowsocks; ігнорується для VLESS, VMess, Hysteria та WireGuard.",
|
||||
"passwordDesc": "Використовується клієнтами Trojan, Shadowsocks, TUIC і MASQUE; ігнорується для VLESS, VMess, Hysteria та WireGuard.",
|
||||
"subId": "ID підписки",
|
||||
"subIdDesc": "Також слугує кодом запрошення цього клієнта в Telegram-боті: той, хто надішле його боту, буде прив'язаний до цього клієнта. Використовуйте довге випадкове значення — короткий або легко вгадуваний ID може привласнити хтось інший.",
|
||||
"online": "У мережі",
|
||||
@@ -1961,7 +1965,10 @@
|
||||
"interface": "Інтерфейс",
|
||||
"proxyProtocol": "Proxy protocol",
|
||||
"tcpUserTimeoutMs": "TCP user timeout (мс)",
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (с)"
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (с)",
|
||||
"masqueWarpDesc": "Підключення до Cloudflare WARP через MASQUE із зареєстрованим ключем ECDSA P-256; host і path за замовчуванням вказують на кінцеву точку Cloudflare.",
|
||||
"masqueWarpPublicKey": "Відкритий ключ кінцевої точки",
|
||||
"masqueWarpAddress": "Адреси тунелю"
|
||||
},
|
||||
"outbound": {
|
||||
"tag": "Тег",
|
||||
|
||||
@@ -718,6 +718,10 @@
|
||||
"holeTimeout": "Thời gian chờ phân đoạn thiếu (ms)",
|
||||
"sessionTtl": "TTL phiên (s)",
|
||||
"concurrency": "Số luồng đồng thời"
|
||||
},
|
||||
"masque": {
|
||||
"addressPool": "Dải địa chỉ",
|
||||
"addressPoolDesc": "Địa chỉ đường hầm được cấp từ các tiền tố này — tối đa một IPv4 và một IPv6."
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
@@ -813,7 +817,7 @@
|
||||
"limitIpFail2banWindows": "Fail2ban không khả dụng trên Windows nên không thể áp dụng giới hạn IP.",
|
||||
"limitIpDisabled": "Tính năng giới hạn IP đã bị tắt trên máy chủ này.",
|
||||
"password": "Mật khẩu",
|
||||
"passwordDesc": "Chỉ được dùng bởi các client Trojan và Shadowsocks; bị bỏ qua đối với VLESS, VMess, Hysteria và WireGuard.",
|
||||
"passwordDesc": "Được dùng bởi các client Trojan, Shadowsocks, TUIC và MASQUE; bị bỏ qua đối với VLESS, VMess, Hysteria và WireGuard.",
|
||||
"subId": "ID đăng ký",
|
||||
"subIdDesc": "Cũng được dùng làm mã mời bot Telegram của client này: ai gửi mã này cho bot sẽ được liên kết với client. Hãy để mã dài và ngẫu nhiên — ID ngắn hoặc dễ đoán có thể bị người khác chiếm.",
|
||||
"online": "Trực tuyến",
|
||||
@@ -1961,7 +1965,10 @@
|
||||
"interface": "Giao diện",
|
||||
"proxyProtocol": "Proxy protocol",
|
||||
"tcpUserTimeoutMs": "TCP user timeout (ms)",
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (s)"
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (s)",
|
||||
"masqueWarpDesc": "Kết nối Cloudflare WARP qua MASQUE bằng khóa ECDSA P-256 đã đăng ký; host và path mặc định là endpoint của Cloudflare.",
|
||||
"masqueWarpPublicKey": "Khóa công khai endpoint",
|
||||
"masqueWarpAddress": "Địa chỉ đường hầm"
|
||||
},
|
||||
"outbound": {
|
||||
"tag": "Tag",
|
||||
|
||||
@@ -718,6 +718,10 @@
|
||||
"holeTimeout": "缺失分段超时(毫秒)",
|
||||
"sessionTtl": "会话 TTL(秒)",
|
||||
"concurrency": "并发数"
|
||||
},
|
||||
"masque": {
|
||||
"addressPool": "地址池",
|
||||
"addressPoolDesc": "隧道地址从这些前缀中分配——最多一个 IPv4 和一个 IPv6。"
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
@@ -813,7 +817,7 @@
|
||||
"limitIpFail2banWindows": "Windows 上不支持 Fail2ban,无法实施 IP 限制。",
|
||||
"limitIpDisabled": "此服务器已禁用 IP 限制功能。",
|
||||
"password": "密码",
|
||||
"passwordDesc": "用于 Trojan、Shadowsocks 和 TUIC 客户端;对 VLESS、VMess、Hysteria 和 WireGuard 忽略。",
|
||||
"passwordDesc": "用于 Trojan、Shadowsocks、TUIC 和 MASQUE 客户端;对 VLESS、VMess、Hysteria 和 WireGuard 忽略。",
|
||||
"subId": "订阅 ID",
|
||||
"subIdDesc": "同时用作该客户端的 Telegram 机器人邀请码:任何人将其发送给机器人即会绑定到此客户端。请保持足够长且随机——过短或易猜的 ID 可能被他人抢先绑定。",
|
||||
"online": "在线",
|
||||
@@ -1961,7 +1965,10 @@
|
||||
"interface": "接口",
|
||||
"proxyProtocol": "Proxy protocol",
|
||||
"tcpUserTimeoutMs": "TCP user timeout (ms)",
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (s)"
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (s)",
|
||||
"masqueWarpDesc": "使用已注册的 ECDSA P-256 密钥通过 MASQUE 连接 Cloudflare WARP;host 和 path 默认指向 Cloudflare 端点。",
|
||||
"masqueWarpPublicKey": "端点公钥",
|
||||
"masqueWarpAddress": "隧道地址"
|
||||
},
|
||||
"outbound": {
|
||||
"tag": "标签",
|
||||
|
||||
@@ -718,6 +718,10 @@
|
||||
"holeTimeout": "遺失分段逾時(毫秒)",
|
||||
"sessionTtl": "工作階段 TTL(秒)",
|
||||
"concurrency": "並行數"
|
||||
},
|
||||
"masque": {
|
||||
"addressPool": "位址池",
|
||||
"addressPoolDesc": "通道位址從這些前綴中分配——最多一個 IPv4 與一個 IPv6。"
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
@@ -813,7 +817,7 @@
|
||||
"limitIpFail2banWindows": "Windows 上不支援 Fail2ban,無法實施 IP 限制。",
|
||||
"limitIpDisabled": "此伺服器已停用 IP 限制功能。",
|
||||
"password": "密碼",
|
||||
"passwordDesc": "僅 Trojan 與 Shadowsocks 用戶端使用;VLESS、VMess、Hysteria 和 WireGuard 會忽略此項。",
|
||||
"passwordDesc": "用於 Trojan、Shadowsocks、TUIC 與 MASQUE 用戶端;VLESS、VMess、Hysteria 和 WireGuard 會忽略此項。",
|
||||
"subId": "訂閱 ID",
|
||||
"subIdDesc": "同時作為此用戶端的 Telegram 機器人邀請碼:任何人將其傳送給機器人即會綁定到此用戶端。請保持足夠長且隨機——過短或容易猜到的 ID 可能被他人搶先綁定。",
|
||||
"online": "上線",
|
||||
@@ -1961,7 +1965,10 @@
|
||||
"interface": "介面",
|
||||
"proxyProtocol": "Proxy protocol",
|
||||
"tcpUserTimeoutMs": "TCP user timeout (ms)",
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (s)"
|
||||
"tcpKeepAliveIdleS": "TCP keep-alive idle (s)",
|
||||
"masqueWarpDesc": "使用已註冊的 ECDSA P-256 金鑰透過 MASQUE 連線 Cloudflare WARP;host 與 path 預設為 Cloudflare 端點。",
|
||||
"masqueWarpPublicKey": "端點公開金鑰",
|
||||
"masqueWarpAddress": "通道位址"
|
||||
},
|
||||
"outbound": {
|
||||
"tag": "標籤",
|
||||
|
||||
Reference in New Issue
Block a user