diff --git a/frontend/src/pages/inbounds/form/protocols/mtproto.tsx b/frontend/src/pages/inbounds/form/protocols/mtproto.tsx index 12ca391e0..b5bebf87f 100644 --- a/frontend/src/pages/inbounds/form/protocols/mtproto.tsx +++ b/frontend/src/pages/inbounds/form/protocols/mtproto.tsx @@ -74,6 +74,13 @@ export default function MtprotoFields() { > + + + = 1.16.0); zero leaves the limit off. + PendingHandshakesMaxPerIP int + // PublicIPv4/PublicIPv6 pin the proxy's reachable address the Telegram // middle proxy needs when clients carry advertising tags; they are omitted // when empty so mtg auto-detects, and a change forces a restart. @@ -93,6 +97,7 @@ func (inst Instance) structuralFingerprint() string { strconv.Itoa(inst.FrontingPort), strconv.FormatBool(inst.FrontingProxyProtocol), strconv.Itoa(inst.ThrottleMaxConnections), + strconv.Itoa(inst.PendingHandshakesMaxPerIP), strconv.FormatBool(inst.RouteThroughXray), strconv.Itoa(inst.XrayRoutePort), inst.PublicIPv4, @@ -179,13 +184,14 @@ func InstanceFromInbound(ib *model.Inbound) (Instance, bool) { Port int `json:"port"` ProxyProtocol bool `json:"proxyProtocol"` } `json:"domainFronting"` - PreferIP string `json:"preferIp"` - ThrottleMaxConnections int `json:"throttleMaxConnections"` - RouteThroughXray bool `json:"routeThroughXray"` - RouteXrayPort int `json:"routeXrayPort"` - PublicIPv4 string `json:"publicIpv4"` - PublicIPv6 string `json:"publicIpv6"` - Clients []struct { + PreferIP string `json:"preferIp"` + ThrottleMaxConnections int `json:"throttleMaxConnections"` + PendingHandshakesMaxPerIP int `json:"pendingHandshakesMaxPerIp"` + RouteThroughXray bool `json:"routeThroughXray"` + RouteXrayPort int `json:"routeXrayPort"` + PublicIPv4 string `json:"publicIpv4"` + PublicIPv6 string `json:"publicIpv6"` + Clients []struct { Email string `json:"email"` Secret string `json:"secret"` AdTag string `json:"adTag"` @@ -215,22 +221,23 @@ func InstanceFromInbound(ib *model.Inbound) (Instance, bool) { return Instance{}, false } return Instance{ - Id: ib.Id, - Tag: ib.Tag, - Listen: ib.Listen, - Port: ib.Port, - Secrets: secrets, - Debug: parsed.Debug, - ProxyProtocolListener: parsed.ProxyProtocolListener, - PreferIP: parsed.PreferIP, - FrontingIP: parsed.DomainFronting.IP, - FrontingPort: parsed.DomainFronting.Port, - FrontingProxyProtocol: parsed.DomainFronting.ProxyProtocol, - ThrottleMaxConnections: parsed.ThrottleMaxConnections, - RouteThroughXray: parsed.RouteThroughXray, - XrayRoutePort: parsed.RouteXrayPort, - PublicIPv4: strings.TrimSpace(parsed.PublicIPv4), - PublicIPv6: strings.TrimSpace(parsed.PublicIPv6), + Id: ib.Id, + Tag: ib.Tag, + Listen: ib.Listen, + Port: ib.Port, + Secrets: secrets, + Debug: parsed.Debug, + ProxyProtocolListener: parsed.ProxyProtocolListener, + PreferIP: parsed.PreferIP, + FrontingIP: parsed.DomainFronting.IP, + FrontingPort: parsed.DomainFronting.Port, + FrontingProxyProtocol: parsed.DomainFronting.ProxyProtocol, + ThrottleMaxConnections: parsed.ThrottleMaxConnections, + PendingHandshakesMaxPerIP: parsed.PendingHandshakesMaxPerIP, + RouteThroughXray: parsed.RouteThroughXray, + XrayRoutePort: parsed.RouteXrayPort, + PublicIPv4: strings.TrimSpace(parsed.PublicIPv4), + PublicIPv6: strings.TrimSpace(parsed.PublicIPv6), }, true } @@ -523,7 +530,7 @@ func FreeLocalPort() (int, error) { // precede any [section] header in TOML, and [secrets] must be the final section // so trailing keys are not swallowed by another table. The layout is therefore: // top-level scalars (incl. api-bind-to and api-token), then [domain-fronting], -// [network] and [throttle], then [secret-ad-tags] for clients overriding the +// [network], [throttle] and [defense.pending-handshakes], then [secret-ad-tags] for clients overriding the // global advertising tag, and finally [secrets] with one named secret per // active client. func renderConfig(inst Instance, apiPort int, apiToken string) string { @@ -569,6 +576,9 @@ func renderConfig(inst Instance, apiPort int, apiToken string) string { if inst.ThrottleMaxConnections > 0 { fmt.Fprintf(&b, "\n[throttle]\nmax-connections = %d\n", inst.ThrottleMaxConnections) } + if inst.PendingHandshakesMaxPerIP > 0 { + fmt.Fprintf(&b, "\n[defense.pending-handshakes]\nmax-per-ip = %d\n", inst.PendingHandshakesMaxPerIP) + } // Only clients present in [secrets] may appear here: mtg rejects a config // whose [secret-ad-tags] names an unknown secret, so a disabled client's // override must vanish together with its secret. diff --git a/internal/mtproto/manager_test.go b/internal/mtproto/manager_test.go index 8114e6f09..079840507 100644 --- a/internal/mtproto/manager_test.go +++ b/internal/mtproto/manager_test.go @@ -18,7 +18,7 @@ func TestInstanceFromInbound(t *testing.T) { Settings: `{"fakeTlsDomain":"example.com",` + `"debug":true,"proxyProtocolListener":true,"preferIp":"prefer-ipv4",` + `"domainFronting":{"ip":"127.0.0.1","port":9443,"proxyProtocol":true},` + - `"throttleMaxConnections":5000,` + + `"throttleMaxConnections":5000,"pendingHandshakesMaxPerIp":64,` + `"routeThroughXray":true,"routeXrayPort":50000,` + `"adTag":" 0123456789abcdef0123456789abcdef ",` + `"clients":[` + @@ -60,6 +60,9 @@ func TestInstanceFromInbound(t *testing.T) { if inst.ThrottleMaxConnections != 5000 { t.Fatalf("throttle not parsed: %+v", inst) } + if inst.PendingHandshakesMaxPerIP != 64 { + t.Fatalf("pending-handshake limit not parsed: %+v", inst) + } if !inst.RouteThroughXray || inst.XrayRoutePort != 50000 { t.Fatalf("xray routing not parsed: %+v", inst) } @@ -90,7 +93,7 @@ func TestRenderConfig(t *testing.T) { Secrets: []SecretEntry{{Name: "alice", Secret: "ee00"}}, Listen: "0.0.0.0", Port: 8443, }, 5000, "") - for _, unwanted := range []string{"debug", "proxy-protocol-listener", "prefer-ip", "[domain-fronting]", "[stats.prometheus]", "[throttle]", "[secret-ad-tags]", "api-token"} { + for _, unwanted := range []string{"debug", "proxy-protocol-listener", "prefer-ip", "[domain-fronting]", "[stats.prometheus]", "[throttle]", "[defense.pending-handshakes]", "[secret-ad-tags]", "api-token"} { if strings.Contains(bare, unwanted) { t.Fatalf("bare config should not contain %q:\n%s", unwanted, bare) } @@ -116,9 +119,10 @@ func TestRenderConfig(t *testing.T) { Listen: "0.0.0.0", Port: 443, Debug: true, ProxyProtocolListener: true, PreferIP: "only-ipv6", FrontingIP: "127.0.0.1", FrontingPort: 9443, FrontingProxyProtocol: true, - ThrottleMaxConnections: 5000, - PublicIPv4: "1.2.3.4", - PublicIPv6: "2001:db8::1", + ThrottleMaxConnections: 5000, + PendingHandshakesMaxPerIP: 64, + PublicIPv4: "1.2.3.4", + PublicIPv6: "2001:db8::1", }, 6000, "sesame") for _, want := range []string{ "debug = true\n", @@ -133,6 +137,7 @@ func TestRenderConfig(t *testing.T) { "proxy-protocol = true\n", "[throttle]", "max-connections = 5000", + "[defense.pending-handshakes]\nmax-per-ip = 64\n", "[secret-ad-tags]", `"bob" = "fedcba9876543210fedcba9876543210"`, } { @@ -160,6 +165,9 @@ func TestRenderConfig(t *testing.T) { if strings.LastIndex(full, "[secrets]") < strings.Index(full, "[throttle]") { t.Fatalf("[throttle] must precede [secrets]:\n%s", full) } + if strings.LastIndex(full, "[secrets]") < strings.Index(full, "[defense.pending-handshakes]") { + t.Fatalf("[defense.pending-handshakes] must precede [secrets]:\n%s", full) + } if strings.LastIndex(full, "[secrets]") < strings.Index(full, "[secret-ad-tags]") { t.Fatalf("[secret-ad-tags] must precede [secrets]:\n%s", full) } @@ -244,19 +252,20 @@ func TestFingerprintSplit(t *testing.T) { base := Instance{Secrets: []SecretEntry{{Name: "a", Secret: "ee"}}, Listen: "0.0.0.0", Port: 443} for name, mutate := range map[string]func(*Instance){ - "debug": func(i *Instance) { i.Debug = true }, - "listener": func(i *Instance) { i.ProxyProtocolListener = true }, - "preferIp": func(i *Instance) { i.PreferIP = "only-ipv4" }, - "frontingIP": func(i *Instance) { i.FrontingIP = "127.0.0.1" }, - "frontingPort": func(i *Instance) { i.FrontingPort = 9443 }, - "frontingProxy": func(i *Instance) { i.FrontingProxyProtocol = true }, - "throttle": func(i *Instance) { i.ThrottleMaxConnections = 5000 }, - "routeXray": func(i *Instance) { i.RouteThroughXray = true }, - "routeXrayPort": func(i *Instance) { i.XrayRoutePort = 50000 }, - "port": func(i *Instance) { i.Port = 8443 }, - "listen": func(i *Instance) { i.Listen = "127.0.0.1" }, - "publicIpv4": func(i *Instance) { i.PublicIPv4 = "1.2.3.4" }, - "publicIpv6": func(i *Instance) { i.PublicIPv6 = "2001:db8::1" }, + "debug": func(i *Instance) { i.Debug = true }, + "listener": func(i *Instance) { i.ProxyProtocolListener = true }, + "preferIp": func(i *Instance) { i.PreferIP = "only-ipv4" }, + "frontingIP": func(i *Instance) { i.FrontingIP = "127.0.0.1" }, + "frontingPort": func(i *Instance) { i.FrontingPort = 9443 }, + "frontingProxy": func(i *Instance) { i.FrontingProxyProtocol = true }, + "throttle": func(i *Instance) { i.ThrottleMaxConnections = 5000 }, + "pendingHandshakes": func(i *Instance) { i.PendingHandshakesMaxPerIP = 64 }, + "routeXray": func(i *Instance) { i.RouteThroughXray = true }, + "routeXrayPort": func(i *Instance) { i.XrayRoutePort = 50000 }, + "port": func(i *Instance) { i.Port = 8443 }, + "listen": func(i *Instance) { i.Listen = "127.0.0.1" }, + "publicIpv4": func(i *Instance) { i.PublicIPv4 = "1.2.3.4" }, + "publicIpv6": func(i *Instance) { i.PublicIPv6 = "2001:db8::1" }, } { t.Run("structural/"+name, func(t *testing.T) { changed := base diff --git a/internal/web/translation/ar-EG.json b/internal/web/translation/ar-EG.json index 16e143831..f66b4531c 100644 --- a/internal/web/translation/ar-EG.json +++ b/internal/web/translation/ar-EG.json @@ -505,6 +505,8 @@ "mtprotoFakeTlsDomainHint": "نطاق FakeTLS الافتراضي المستخدم لإنشاء سر عميل جديد. يمكن لكل عميل استخدام نطاقه الخاص.", "mtgThrottleMaxConnections": "الحد الأقصى للاتصالات", "mtgThrottleMaxConnectionsHint": "تحديد الاتصالات المتزامنة لجميع المستخدمين بتوزيع عادل. القيمة 0 تعطّل الميزة.", + "mtgPendingHandshakesMaxPerIp": "الحد الأقصى للمصافحات المعلقة لكل IP", + "mtgPendingHandshakesMaxPerIpHint": "تُغلق الاتصالات الجديدة من عنوان IP لديه هذا العدد من المصافحات غير المكتملة. يتشارك المستخدمون خلف عنوان IP مشترك (NAT لدى المشغّل) هذا الحد. القيمة 0 تعطّله.", "mtgAdTagInvalid": "يجب أن تتكوّن العلامة الإعلانية من 32 حرفًا ست عشريًا بالضبط.", "mtgPublicIpv4": "عنوان IPv4 العام", "mtgPublicIpv6": "عنوان IPv6 العام", diff --git a/internal/web/translation/en-US.json b/internal/web/translation/en-US.json index 97290aa34..2ceb3b546 100644 --- a/internal/web/translation/en-US.json +++ b/internal/web/translation/en-US.json @@ -507,6 +507,8 @@ "mtprotoFakeTlsDomainHint": "Default FakeTLS domain used to generate a new client's secret. Each client can front its own domain.", "mtgThrottleMaxConnections": "Max connections", "mtgThrottleMaxConnectionsHint": "Cap concurrent connections across all users with a fair-share limit. 0 disables throttling.", + "mtgPendingHandshakesMaxPerIp": "Max pending handshakes per IP", + "mtgPendingHandshakesMaxPerIpHint": "Close new connections from an IP that already has this many unfinished handshakes. Clients behind a shared IP (carrier NAT) share the limit. 0 disables it.", "mtgAdTagInvalid": "Ad-tag must be exactly 32 hexadecimal characters.", "mtgPublicIpv4": "Public IPv4", "mtgPublicIpv6": "Public IPv6", diff --git a/internal/web/translation/es-ES.json b/internal/web/translation/es-ES.json index d6953176c..6fa03153e 100644 --- a/internal/web/translation/es-ES.json +++ b/internal/web/translation/es-ES.json @@ -505,6 +505,8 @@ "mtprotoFakeTlsDomainHint": "Dominio FakeTLS predeterminado para generar el secreto de un nuevo cliente. Cada cliente puede usar su propio dominio.", "mtgThrottleMaxConnections": "Conexiones máximas", "mtgThrottleMaxConnectionsHint": "Limita las conexiones simultáneas de todos los usuarios con reparto equitativo. 0 desactiva el límite.", + "mtgPendingHandshakesMaxPerIp": "Máx. handshakes pendientes por IP", + "mtgPendingHandshakesMaxPerIpHint": "Cierra las conexiones nuevas de una IP que ya tiene esta cantidad de handshakes sin completar. Los clientes detrás de una IP compartida (NAT del operador) comparten el límite. 0 lo desactiva.", "mtgAdTagInvalid": "El ad-tag debe tener exactamente 32 caracteres hexadecimales.", "mtgPublicIpv4": "IPv4 pública", "mtgPublicIpv6": "IPv6 pública", diff --git a/internal/web/translation/fa-IR.json b/internal/web/translation/fa-IR.json index 3a4e934c3..3b7cf60ec 100644 --- a/internal/web/translation/fa-IR.json +++ b/internal/web/translation/fa-IR.json @@ -505,6 +505,8 @@ "mtprotoFakeTlsDomainHint": "دامنه پیش‌فرض FakeTLS برای ساخت سکرت کلاینت جدید. هر کلاینت می‌تواند دامنه مخصوص خود را داشته باشد.", "mtgThrottleMaxConnections": "حداکثر اتصالات", "mtgThrottleMaxConnectionsHint": "محدود کردن اتصالات همزمان همه کاربران با تقسیم منصفانه. مقدار ۰ غیرفعال است.", + "mtgPendingHandshakesMaxPerIp": "حداکثر handshake معلق برای هر IP", + "mtgPendingHandshakesMaxPerIpHint": "اتصال‌های جدید از IPای که این تعداد handshake ناتمام دارد بسته می‌شوند. کاربرانی که پشت یک IP مشترک (NAT اپراتور) هستند این سقف را با هم شریک‌اند. مقدار ۰ غیرفعال است.", "mtgAdTagInvalid": "برچسب تبلیغاتی باید دقیقاً ۳۲ کاراکتر هگزادسیمال باشد.", "mtgPublicIpv4": "IPv4 عمومی", "mtgPublicIpv6": "IPv6 عمومی", diff --git a/internal/web/translation/id-ID.json b/internal/web/translation/id-ID.json index 1af42df6f..23895aaaf 100644 --- a/internal/web/translation/id-ID.json +++ b/internal/web/translation/id-ID.json @@ -505,6 +505,8 @@ "mtprotoFakeTlsDomainHint": "Domain FakeTLS default untuk membuat secret klien baru. Setiap klien bisa memakai domainnya sendiri.", "mtgThrottleMaxConnections": "Koneksi maksimum", "mtgThrottleMaxConnectionsHint": "Batasi koneksi bersamaan semua pengguna dengan pembagian adil. 0 menonaktifkan.", + "mtgPendingHandshakesMaxPerIp": "Maks. handshake tertunda per IP", + "mtgPendingHandshakesMaxPerIpHint": "Tutup koneksi baru dari IP yang sudah memiliki handshake belum selesai sebanyak ini. Klien di balik IP bersama (NAT operator) berbagi batas ini. 0 menonaktifkan.", "mtgAdTagInvalid": "Ad-tag harus tepat 32 karakter heksadesimal.", "mtgPublicIpv4": "IPv4 Publik", "mtgPublicIpv6": "IPv6 Publik", diff --git a/internal/web/translation/ja-JP.json b/internal/web/translation/ja-JP.json index d8927f102..2ba08d6ba 100644 --- a/internal/web/translation/ja-JP.json +++ b/internal/web/translation/ja-JP.json @@ -526,6 +526,8 @@ "mtprotoFakeTlsDomainHint": "新しいクライアントのシークレット生成に使う既定の FakeTLS ドメイン。クライアントごとに別のドメインを使用できます。", "mtgThrottleMaxConnections": "最大接続数", "mtgThrottleMaxConnectionsHint": "全ユーザーの同時接続数を公平配分で制限します。0 で無効。", + "mtgPendingHandshakesMaxPerIp": "IP あたりの未完了ハンドシェイク上限", + "mtgPendingHandshakesMaxPerIpHint": "未完了のハンドシェイクをこの数だけ保持している IP からの新規接続を閉じます。共有 IP(キャリア NAT)配下のクライアントはこの上限を共有します。0 で無効。", "mtgAdTagInvalid": "広告タグは正確に 32 文字の 16 進数である必要があります。", "mtgPublicIpv4": "パブリック IPv4", "mtgPublicIpv6": "パブリック IPv6", diff --git a/internal/web/translation/pt-BR.json b/internal/web/translation/pt-BR.json index b78050110..a4331047e 100644 --- a/internal/web/translation/pt-BR.json +++ b/internal/web/translation/pt-BR.json @@ -526,6 +526,8 @@ "mtprotoFakeTlsDomainHint": "Domínio FakeTLS padrão usado para gerar o segredo de um novo cliente. Cada cliente pode usar seu próprio domínio.", "mtgThrottleMaxConnections": "Conexões máximas", "mtgThrottleMaxConnectionsHint": "Limita conexões simultâneas de todos os usuários com distribuição justa. 0 desativa o limite.", + "mtgPendingHandshakesMaxPerIp": "Máx. handshakes pendentes por IP", + "mtgPendingHandshakesMaxPerIpHint": "Fecha novas conexões de um IP que já tem esta quantidade de handshakes não concluídos. Clientes atrás de um IP compartilhado (NAT da operadora) dividem o limite. 0 desativa.", "mtgAdTagInvalid": "A ad-tag deve ter exatamente 32 caracteres hexadecimais.", "mtgPublicIpv4": "IPv4 público", "mtgPublicIpv6": "IPv6 público", diff --git a/internal/web/translation/ru-RU.json b/internal/web/translation/ru-RU.json index 59c308fb4..959047619 100644 --- a/internal/web/translation/ru-RU.json +++ b/internal/web/translation/ru-RU.json @@ -528,6 +528,8 @@ "mtprotoFakeTlsDomainHint": "Домен FakeTLS по умолчанию для генерации секрета нового клиента. Каждый клиент может использовать свой домен.", "mtgThrottleMaxConnections": "Макс. подключений", "mtgThrottleMaxConnectionsHint": "Ограничение одновременных подключений всех пользователей по справедливому распределению. 0 — отключено.", + "mtgPendingHandshakesMaxPerIp": "Макс. незавершённых рукопожатий на IP", + "mtgPendingHandshakesMaxPerIpHint": "Новые подключения с IP, у которого уже столько незавершённых рукопожатий, закрываются. Клиенты за общим IP (NAT оператора) делят этот лимит. 0 — отключено.", "mtgAdTagInvalid": "Рекламный тег должен содержать ровно 32 шестнадцатеричных символа.", "mtgPublicIpv4": "Публичный IPv4", "mtgPublicIpv6": "Публичный IPv6", diff --git a/internal/web/translation/tr-TR.json b/internal/web/translation/tr-TR.json index 656485cf4..cefea4fd2 100644 --- a/internal/web/translation/tr-TR.json +++ b/internal/web/translation/tr-TR.json @@ -505,6 +505,8 @@ "mtprotoFakeTlsDomainHint": "Yeni bir istemcinin sırrını oluştururken kullanılan varsayılan FakeTLS alan adı. Her istemci kendi alan adını kullanabilir.", "mtgThrottleMaxConnections": "Maks. bağlantı", "mtgThrottleMaxConnectionsHint": "Tüm kullanıcıların eşzamanlı bağlantılarını adil paylaşımla sınırlar. 0 devre dışı bırakır.", + "mtgPendingHandshakesMaxPerIp": "IP başına maks. bekleyen el sıkışma", + "mtgPendingHandshakesMaxPerIpHint": "Bu sayıda tamamlanmamış el sıkışması olan bir IP'den gelen yeni bağlantıları kapatır. Paylaşılan IP arkasındaki istemciler (operatör NAT) sınırı paylaşır. 0 devre dışı bırakır.", "mtgAdTagInvalid": "Reklam etiketi tam olarak 32 onaltılık karakter olmalıdır.", "mtgPublicIpv4": "Genel IPv4", "mtgPublicIpv6": "Genel IPv6", diff --git a/internal/web/translation/uk-UA.json b/internal/web/translation/uk-UA.json index f365d3f08..358ec5eec 100644 --- a/internal/web/translation/uk-UA.json +++ b/internal/web/translation/uk-UA.json @@ -505,6 +505,8 @@ "mtprotoFakeTlsDomainHint": "Домен FakeTLS за замовчуванням для генерації секрету нового клієнта. Кожен клієнт може використовувати власний домен.", "mtgThrottleMaxConnections": "Макс. з'єднань", "mtgThrottleMaxConnectionsHint": "Обмеження одночасних з'єднань усіх користувачів зі справедливим розподілом. 0 — вимкнено.", + "mtgPendingHandshakesMaxPerIp": "Макс. незавершених рукостискань на IP", + "mtgPendingHandshakesMaxPerIpHint": "Нові з'єднання з IP, що вже має стільки незавершених рукостискань, закриваються. Клієнти за спільним IP (NAT оператора) ділять цей ліміт. 0 — вимкнено.", "mtgAdTagInvalid": "Рекламний тег має містити рівно 32 шістнадцяткові символи.", "mtgPublicIpv4": "Публічний IPv4", "mtgPublicIpv6": "Публічний IPv6", diff --git a/internal/web/translation/vi-VN.json b/internal/web/translation/vi-VN.json index 98a6ddcab..277f25272 100644 --- a/internal/web/translation/vi-VN.json +++ b/internal/web/translation/vi-VN.json @@ -526,6 +526,8 @@ "mtprotoFakeTlsDomainHint": "Tên miền FakeTLS mặc định dùng để tạo secret cho client mới. Mỗi client có thể dùng tên miền riêng.", "mtgThrottleMaxConnections": "Số kết nối tối đa", "mtgThrottleMaxConnectionsHint": "Giới hạn kết nối đồng thời của tất cả người dùng theo phân bổ công bằng. 0 để tắt.", + "mtgPendingHandshakesMaxPerIp": "Số bắt tay chờ tối đa mỗi IP", + "mtgPendingHandshakesMaxPerIpHint": "Đóng kết nối mới từ IP đang có số bắt tay chưa hoàn tất bằng mức này. Các máy khách dùng chung IP (NAT nhà mạng) chia sẻ giới hạn. 0 để tắt.", "mtgAdTagInvalid": "Ad-tag phải có đúng 32 ký tự thập lục phân.", "mtgPublicIpv4": "IPv4 công khai", "mtgPublicIpv6": "IPv6 công khai", diff --git a/internal/web/translation/zh-CN.json b/internal/web/translation/zh-CN.json index 816bf5ee1..c26aa4ca2 100644 --- a/internal/web/translation/zh-CN.json +++ b/internal/web/translation/zh-CN.json @@ -525,6 +525,8 @@ "mtprotoFakeTlsDomainHint": "生成新客户端密钥时使用的默认 FakeTLS 域名。每个客户端可使用各自的域名。", "mtgThrottleMaxConnections": "最大连接数", "mtgThrottleMaxConnectionsHint": "按公平分配限制所有用户的并发连接数。0 表示不限制。", + "mtgPendingHandshakesMaxPerIp": "每 IP 最大未完成握手数", + "mtgPendingHandshakesMaxPerIpHint": "当某个 IP 的未完成握手达到此数量时,关闭其新连接。共享 IP(运营商 NAT)后的客户端共用此上限。0 表示禁用。", "mtgAdTagInvalid": "广告标签必须为 32 位十六进制字符。", "mtgPublicIpv4": "公网 IPv4", "mtgPublicIpv6": "公网 IPv6", diff --git a/internal/web/translation/zh-TW.json b/internal/web/translation/zh-TW.json index fc1949802..ebbecebf5 100644 --- a/internal/web/translation/zh-TW.json +++ b/internal/web/translation/zh-TW.json @@ -505,6 +505,8 @@ "mtprotoFakeTlsDomainHint": "產生新用戶端金鑰時使用的預設 FakeTLS 網域。每個用戶端可使用各自的網域。", "mtgThrottleMaxConnections": "最大連線數", "mtgThrottleMaxConnectionsHint": "以公平分配限制所有使用者的並行連線數。0 表示不限制。", + "mtgPendingHandshakesMaxPerIp": "每 IP 最大未完成交握數", + "mtgPendingHandshakesMaxPerIpHint": "當某個 IP 的未完成交握達到此數量時,關閉其新連線。共用 IP(電信 NAT)後的用戶端共用此上限。0 表示停用。", "mtgAdTagInvalid": "廣告標籤必須為 32 位十六進位字元。", "mtgPublicIpv4": "公網 IPv4", "mtgPublicIpv6": "公網 IPv6",