diff --git a/frontend/src/lib/xray/inbound-defaults.ts b/frontend/src/lib/xray/inbound-defaults.ts
index 6098e5b16..3aa491294 100644
--- a/frontend/src/lib/xray/inbound-defaults.ts
+++ b/frontend/src/lib/xray/inbound-defaults.ts
@@ -257,12 +257,20 @@ export interface WireguardInboundSeed {
mtu?: number;
secretKey?: string;
noKernelTun?: boolean;
+ subnetIp?: string;
+ subnetCidr?: number;
}
// WireGuard is multi-client now: a new inbound holds only the server identity
// (secretKey/mtu) and starts with no clients. Clients (peers) are added later
// through the client modal, which generates each one's keypair and a unique
// tunnel address. peers stays empty for backward-compatible parsing.
+//
+// subnetIp/subnetCidr default to 10.0.0.0/24 here — the same value the Go
+// backend has always fallen back to for an inbound with no clients yet — so
+// a freshly created inbound shows an explicit, editable value from the
+// start (matching AmneziaWG's own subnet field), rather than an empty one
+// that silently relies on server-side inference until an admin fills it in.
export function createDefaultWireguardInboundSettings(
seed: WireguardInboundSeed = {},
): WireguardInboundSettings {
@@ -272,6 +280,8 @@ export function createDefaultWireguardInboundSettings(
peers: [],
clients: [],
noKernelTun: seed.noKernelTun ?? false,
+ subnetIp: seed.subnetIp ?? '10.0.0.0',
+ subnetCidr: seed.subnetCidr ?? 24,
};
}
diff --git a/frontend/src/pages/inbounds/form/protocols/wireguard.tsx b/frontend/src/pages/inbounds/form/protocols/wireguard.tsx
index 595a5bfb4..39eb2eda6 100644
--- a/frontend/src/pages/inbounds/form/protocols/wireguard.tsx
+++ b/frontend/src/pages/inbounds/form/protocols/wireguard.tsx
@@ -24,6 +24,12 @@ export default function WireguardFields({ wgPubKey, regenInboundWg }: WireguardF
+
+
+
+
+
+
diff --git a/frontend/src/schemas/protocols/inbound/wireguard.ts b/frontend/src/schemas/protocols/inbound/wireguard.ts
index 033716619..b984cd694 100644
--- a/frontend/src/schemas/protocols/inbound/wireguard.ts
+++ b/frontend/src/schemas/protocols/inbound/wireguard.ts
@@ -66,5 +66,12 @@ export const WireguardInboundSettingsSchema = z.object({
clients: z.array(WireguardClientSchema).default([]),
noKernelTun: z.boolean().default(false),
domainStrategy: WireguardDomainStrategySchema.optional(),
+ // Admin-configurable base subnet new clients are auto-allocated from —
+ // mirrors AmneziaWG's settings.server.subnetIp/subnetCidr. Optional and
+ // left blank by default: an inbound that never sets this keeps the
+ // pre-existing behavior (infer from existing clients' own addresses, else
+ // fall back to 10.0.0.0/24 server-side).
+ subnetIp: z.string().default(''),
+ subnetCidr: optionalClearedInt(z.number().int().min(1).max(32)),
});
export type WireguardInboundSettings = z.infer;
diff --git a/frontend/src/test/__snapshots__/inbound-defaults.test.ts.snap b/frontend/src/test/__snapshots__/inbound-defaults.test.ts.snap
index 9766a4f5b..4e36cb1e4 100644
--- a/frontend/src/test/__snapshots__/inbound-defaults.test.ts.snap
+++ b/frontend/src/test/__snapshots__/inbound-defaults.test.ts.snap
@@ -54,6 +54,8 @@ exports[`createDefault*InboundSettings factories > wireguard 1`] = `
"noKernelTun": false,
"peers": [],
"secretKey": "QGVlb2dXc1ZTWGw0ZXBzZndsWmtMaUM5MUlNYjBHWFdYbz0=",
+ "subnetCidr": 24,
+ "subnetIp": "10.0.0.0",
}
`;
diff --git a/frontend/src/test/__snapshots__/inbound-full.test.ts.snap b/frontend/src/test/__snapshots__/inbound-full.test.ts.snap
index 8f2cd9538..453873ed0 100644
--- a/frontend/src/test/__snapshots__/inbound-full.test.ts.snap
+++ b/frontend/src/test/__snapshots__/inbound-full.test.ts.snap
@@ -622,6 +622,7 @@ exports[`InboundSchema (full) fixtures > parses wireguard-server byte-stably 1`]
},
],
"secretKey": "iJ2cBkrSGqRwIfYIDIxk7hr5RXfdR93MfJUL7yqkkH8=",
+ "subnetIp": "",
},
"shareAddr": "",
"shareAddrStrategy": "node",
diff --git a/frontend/src/test/__snapshots__/protocols.test.ts.snap b/frontend/src/test/__snapshots__/protocols.test.ts.snap
index c2eddb307..7ffc28223 100644
--- a/frontend/src/test/__snapshots__/protocols.test.ts.snap
+++ b/frontend/src/test/__snapshots__/protocols.test.ts.snap
@@ -248,6 +248,7 @@ exports[`InboundSettingsSchema fixtures > parses wireguard-basic byte-stably 1`]
},
],
"secretKey": "QGVlb2dXc1ZTWGw0ZXBzZndsWmtMaUM5MUlNYjBHWFdYbz0=",
+ "subnetIp": "",
},
}
`;
diff --git a/internal/web/service/client_inbound_apply.go b/internal/web/service/client_inbound_apply.go
index fa5da1ecc..9ea26fa84 100644
--- a/internal/web/service/client_inbound_apply.go
+++ b/internal/web/service/client_inbound_apply.go
@@ -422,7 +422,7 @@ func (s *ClientService) addInboundClient(inboundSvc *InboundService, data *model
return false, cErr
}
if oldInbound.Protocol == model.WireGuard {
- if dErr := defaultWireguardClients(existingClients, clients, interfaceClients, crossUsed); dErr != nil {
+ if dErr := defaultWireguardClients(oldInbound.Settings, existingClients, clients, interfaceClients, crossUsed); dErr != nil {
return false, dErr
}
}
diff --git a/internal/web/service/client_wireguard.go b/internal/web/service/client_wireguard.go
index cda8d4842..71f38e51f 100644
--- a/internal/web/service/client_wireguard.go
+++ b/internal/web/service/client_wireguard.go
@@ -1,6 +1,8 @@
package service
import (
+ "encoding/json"
+ "fmt"
"net/netip"
"strconv"
"strings"
@@ -12,6 +14,41 @@ import (
const defaultWireguardBase = "10.0.0.0/24"
+// wireguardSubnetSettings is the subset of a WireGuard inbound's top-level
+// settings JSON this package cares about for subnet resolution. Unlike
+// AmneziaWG (whose whole settings shape is a typed struct in
+// internal/amneziawg), plain WireGuard has no dedicated Go struct on this
+// fork's side at all -- everything else is handled as untyped
+// map[string]any -- so this stays a narrow, local decode rather than
+// introducing a full struct just for two fields.
+type wireguardSubnetSettings struct {
+ SubnetIP string `json:"subnetIp"`
+ SubnetCIDR int `json:"subnetCidr"`
+}
+
+// explicitWireguardSubnetBase resolves an admin-configured subnet base out
+// of settingsJSON's own subnetIp/subnetCidr fields, mirroring AmneziaWG's
+// defaultAmneziaWGSubnetBases. Returns "" when either field is unset/empty
+// or doesn't parse as a valid prefix -- callers fall back to
+// wireguardAllocationBase's existing infer-from-clients behavior in that
+// case, so an inbound saved before this field existed (or one that simply
+// never set it) keeps behaving exactly as it always has.
+func explicitWireguardSubnetBase(settingsJSON string) string {
+ var parsed wireguardSubnetSettings
+ if err := json.Unmarshal([]byte(settingsJSON), &parsed); err != nil {
+ return ""
+ }
+ ip := strings.TrimSpace(parsed.SubnetIP)
+ if ip == "" || parsed.SubnetCIDR <= 0 {
+ return ""
+ }
+ base := fmt.Sprintf("%s/%d", ip, parsed.SubnetCIDR)
+ if _, err := netip.ParsePrefix(base); err != nil {
+ return ""
+ }
+ return base
+}
+
func keepAliveStr(seconds int) string {
if seconds <= 0 {
return ""
@@ -149,16 +186,25 @@ func wireguardAllowedIPsCollision(entries, used []string) string {
// crossInboundUsed maps AllowedIPs already claimed by clients on every OTHER
// WireGuard/AmneziaWG inbound on this panel to a human-readable description
// of which inbound holds it (see otherTunnelAllowedIPs). It is folded into
-// used only AFTER wireguardAllocationBase runs, so an unrelated inbound's
-// subnet can never skew this inbound's own base-subnet inference — it only
+// used only AFTER the base subnet is resolved, so an unrelated inbound's
+// subnet can never skew this inbound's own base-subnet resolution — it only
// ever narrows which addresses are free to hand out or accept, and lets a
// manual-entry collision name the other inbound instead of just the address.
-func defaultWireguardClients(existing, clients []model.Client, interfaceClients []any, crossInboundUsed map[string]string) error {
+//
+// settingsJSON is checked first for an admin-configured subnetIp/subnetCidr
+// (see explicitWireguardSubnetBase) — set explicitly, that always wins.
+// Only when it's unset does base fall back to inferring from existing
+// clients' own addresses, and finally to defaultWireguardBase, exactly as
+// before this field existed.
+func defaultWireguardClients(settingsJSON string, existing, clients []model.Client, interfaceClients []any, crossInboundUsed map[string]string) error {
used := make([]string, 0)
for i := range existing {
used = append(used, existing[i].AllowedIPs...)
}
- base := wireguardAllocationBase(used, defaultWireguardBase)
+ base := explicitWireguardSubnetBase(settingsJSON)
+ if base == "" {
+ base = wireguardAllocationBase(used, defaultWireguardBase)
+ }
for addr := range crossInboundUsed {
used = append(used, addr)
}
diff --git a/internal/web/service/client_wireguard_test.go b/internal/web/service/client_wireguard_test.go
index c1ea136a7..2a1ff5020 100644
--- a/internal/web/service/client_wireguard_test.go
+++ b/internal/web/service/client_wireguard_test.go
@@ -47,7 +47,7 @@ func TestAllocateWireguardAddress(t *testing.T) {
func TestDefaultWireguardClientsGeneratesKeypair(t *testing.T) {
clients := []model.Client{{Email: "a@wg"}}
ifaces := []any{map[string]any{"email": "a@wg"}}
- if err := defaultWireguardClients(nil, clients, ifaces, nil); err != nil {
+ if err := defaultWireguardClients("", nil, clients, ifaces, nil); err != nil {
t.Fatalf("defaultWireguardClients: %v", err)
}
c := clients[0]
@@ -74,7 +74,7 @@ func TestDefaultWireguardClientsDerivesPublicKey(t *testing.T) {
}
clients := []model.Client{{Email: "b@wg", PrivateKey: priv}}
ifaces := []any{map[string]any{"email": "b@wg"}}
- if err := defaultWireguardClients(nil, clients, ifaces, nil); err != nil {
+ if err := defaultWireguardClients("", nil, clients, ifaces, nil); err != nil {
t.Fatalf("defaultWireguardClients: %v", err)
}
if clients[0].PublicKey != wantPub {
@@ -90,7 +90,7 @@ func TestDefaultWireguardClientsPreservesProvided(t *testing.T) {
AllowedIPs: []string{"10.0.0.50/32"},
}}
ifaces := []any{map[string]any{"email": "c@wg"}}
- if err := defaultWireguardClients(nil, clients, ifaces, nil); err != nil {
+ if err := defaultWireguardClients("", nil, clients, ifaces, nil); err != nil {
t.Fatalf("defaultWireguardClients: %v", err)
}
if clients[0].PrivateKey != "keep-priv" || clients[0].PublicKey != "keep-pub" {
@@ -125,7 +125,7 @@ func TestDefaultWireguardClientsHonorsExistingSubnet(t *testing.T) {
existing := []model.Client{{Email: "old@wg", AllowedIPs: []string{"172.16.0.2/32"}}}
clients := []model.Client{{Email: "new@wg"}}
ifaces := []any{map[string]any{"email": "new@wg"}}
- if err := defaultWireguardClients(existing, clients, ifaces, nil); err != nil {
+ if err := defaultWireguardClients("", existing, clients, ifaces, nil); err != nil {
t.Fatalf("defaultWireguardClients: %v", err)
}
if got := clients[0].AllowedIPs[0]; got != "172.16.0.3/32" {
@@ -184,7 +184,7 @@ func TestAllocateWireguardAddressWithoutWideningFailsClosed(t *testing.T) {
func TestDefaultWireguardClientsAllocatesDistinctIPs(t *testing.T) {
clients := []model.Client{{Email: "x@wg"}, {Email: "y@wg"}}
ifaces := []any{map[string]any{"email": "x@wg"}, map[string]any{"email": "y@wg"}}
- if err := defaultWireguardClients(nil, clients, ifaces, nil); err != nil {
+ if err := defaultWireguardClients("", nil, clients, ifaces, nil); err != nil {
t.Fatalf("defaultWireguardClients: %v", err)
}
if clients[0].AllowedIPs[0] == clients[1].AllowedIPs[0] {
@@ -237,7 +237,7 @@ func TestDefaultWireguardClientsHonorsAndValidatesSuppliedAllowedIPs(t *testing.
clients := []model.Client{{Email: "c@wg", AllowedIPs: []string{"10.0.0.9"}}}
ifaces := []any{map[string]any{"email": "c@wg"}}
- if err := defaultWireguardClients(existing, clients, ifaces, nil); err != nil {
+ if err := defaultWireguardClients("", existing, clients, ifaces, nil); err != nil {
t.Fatalf("defaultWireguardClients: %v", err)
}
if len(clients[0].AllowedIPs) != 1 || clients[0].AllowedIPs[0] != "10.0.0.9/32" {
@@ -245,13 +245,13 @@ func TestDefaultWireguardClientsHonorsAndValidatesSuppliedAllowedIPs(t *testing.
}
dup := []model.Client{{Email: "d@wg", AllowedIPs: []string{"10.0.0.2/32"}}}
- err := defaultWireguardClients(existing, dup, []any{map[string]any{"email": "d@wg"}}, nil)
+ err := defaultWireguardClients("", existing, dup, []any{map[string]any{"email": "d@wg"}}, nil)
if err == nil {
t.Fatal("duplicate allowedIPs across clients must be rejected")
}
bad := []model.Client{{Email: "e@wg", AllowedIPs: []string{"not-an-ip"}}}
- if err := defaultWireguardClients(existing, bad, []any{map[string]any{"email": "e@wg"}}, nil); err == nil {
+ if err := defaultWireguardClients("", existing, bad, []any{map[string]any{"email": "e@wg"}}, nil); err == nil {
t.Fatal("invalid allowedIPs entry must be rejected")
}
}
@@ -264,7 +264,7 @@ func TestDefaultWireguardClientsHonorsAndValidatesSuppliedAllowedIPs(t *testing.
func TestDefaultWireguardClientsRejectsCrossInboundDuplicate(t *testing.T) {
crossUsed := map[string]string{"10.8.1.21/32": "inbound 'awg' (#10)"}
dup := []model.Client{{Email: "d@wg", AllowedIPs: []string{"10.8.1.21/32"}}}
- err := defaultWireguardClients(nil, dup, []any{map[string]any{"email": "d@wg"}}, crossUsed)
+ err := defaultWireguardClients("", nil, dup, []any{map[string]any{"email": "d@wg"}}, crossUsed)
if err == nil {
t.Fatal("allowedIPs already used on another inbound must be rejected")
}
@@ -279,7 +279,7 @@ func TestDefaultWireguardClientsAutoAllocateSkipsCrossInboundUsed(t *testing.T)
crossUsed := map[string]string{"10.0.0.2/32": "inbound 'other-wg' (#7)"}
clients := []model.Client{{Email: "f@wg"}}
ifaces := []any{map[string]any{"email": "f@wg"}}
- if err := defaultWireguardClients(nil, clients, ifaces, crossUsed); err != nil {
+ if err := defaultWireguardClients("", nil, clients, ifaces, crossUsed); err != nil {
t.Fatalf("defaultWireguardClients: %v", err)
}
if clients[0].AllowedIPs[0] != "10.0.0.3/32" {
@@ -296,10 +296,70 @@ func TestDefaultWireguardClientsCrossInboundUsedDoesNotSkewSubnetInference(t *te
crossUsed := map[string]string{"10.8.1.21/32": "inbound 'awg' (#10)"}
clients := []model.Client{{Email: "g@wg"}}
ifaces := []any{map[string]any{"email": "g@wg"}}
- if err := defaultWireguardClients(nil, clients, ifaces, crossUsed); err != nil {
+ if err := defaultWireguardClients("", nil, clients, ifaces, crossUsed); err != nil {
t.Fatalf("defaultWireguardClients: %v", err)
}
if got := clients[0].AllowedIPs[0]; got != "10.0.0.2/32" {
t.Fatalf("base subnet must stay the default 10.0.0.0/24, not be skewed by a cross-inbound address; got %v", got)
}
}
+
+func TestExplicitWireguardSubnetBase(t *testing.T) {
+ tests := []struct {
+ name string
+ settingsJSON string
+ want string
+ }{
+ {name: "unset settings", settingsJSON: `{"secretKey":"x"}`, want: ""},
+ {name: "empty subnetIp", settingsJSON: `{"subnetIp":"","subnetCidr":24}`, want: ""},
+ {name: "zero cidr", settingsJSON: `{"subnetIp":"10.8.1.0","subnetCidr":0}`, want: ""},
+ {name: "invalid ip", settingsJSON: `{"subnetIp":"not-an-ip","subnetCidr":24}`, want: ""},
+ {name: "invalid json", settingsJSON: `not json`, want: ""},
+ {name: "configured subnet", settingsJSON: `{"subnetIp":"10.8.1.0","subnetCidr":24}`, want: "10.8.1.0/24"},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := explicitWireguardSubnetBase(tt.settingsJSON); got != tt.want {
+ t.Fatalf("got %q, want %q", got, tt.want)
+ }
+ })
+ }
+}
+
+// TestDefaultWireguardClientsPrefersExplicitSubnetOverInference is the
+// backend half of a user-requested feature: WireGuard previously had no
+// admin-configurable subnet at all, only an implicit one (inferred from
+// existing clients' own addresses, or a hardcoded 10.0.0.0/24 fallback when
+// none exist yet) -- unlike AmneziaWG, which has always had a real
+// server.subnetIp/subnetCidr field. An explicit subnetIp/subnetCidr in the
+// inbound's own settings must now win outright, even when existing clients
+// would otherwise suggest a different base via wireguardAllocationBase.
+func TestDefaultWireguardClientsPrefersExplicitSubnetOverInference(t *testing.T) {
+ existing := []model.Client{{Email: "old@wg", AllowedIPs: []string{"172.16.0.2/32"}}}
+ clients := []model.Client{{Email: "new@wg"}}
+ ifaces := []any{map[string]any{"email": "new@wg"}}
+ settingsJSON := `{"subnetIp":"10.8.1.0","subnetCidr":24}`
+ if err := defaultWireguardClients(settingsJSON, existing, clients, ifaces, nil); err != nil {
+ t.Fatalf("defaultWireguardClients: %v", err)
+ }
+ if got := clients[0].AllowedIPs[0]; got != "10.8.1.2/32" {
+ t.Fatalf("explicit subnet must win over inference from existing clients (172.16.0.0/24); got %v", got)
+ }
+}
+
+// TestDefaultWireguardClientsFallsBackWhenNoExplicitSubnet locks in the
+// backward-compat half of the same feature: an inbound saved before this
+// field existed (settingsJSON carries no subnetIp/subnetCidr at all) must
+// keep allocating exactly as it always has.
+func TestDefaultWireguardClientsFallsBackWhenNoExplicitSubnet(t *testing.T) {
+ existing := []model.Client{{Email: "old@wg", AllowedIPs: []string{"172.16.0.2/32"}}}
+ clients := []model.Client{{Email: "new@wg"}}
+ ifaces := []any{map[string]any{"email": "new@wg"}}
+ settingsJSON := `{"secretKey":"x","peers":[],"clients":[]}`
+ if err := defaultWireguardClients(settingsJSON, existing, clients, ifaces, nil); err != nil {
+ t.Fatalf("defaultWireguardClients: %v", err)
+ }
+ if got := clients[0].AllowedIPs[0]; got != "172.16.0.3/32" {
+ t.Fatalf("with no explicit subnet, inference from existing clients must still apply; got %v", got)
+ }
+}
diff --git a/internal/web/translation/ar-EG.json b/internal/web/translation/ar-EG.json
index b6634e7f0..ae6b5abb5 100644
--- a/internal/web/translation/ar-EG.json
+++ b/internal/web/translation/ar-EG.json
@@ -1635,6 +1635,8 @@
"wireguard": {
"secretKey": "المفتاح السري",
"publicKey": "المفتاح العام",
+ "subnetIp": "الشبكة الفرعية",
+ "subnetCidr": "بادئة الشبكة الفرعية (CIDR)",
"allowedIPs": "عناوين IP المسموح بها",
"endpoint": "النهاية",
"domainStrategy": "استراتيجية الدومين"
diff --git a/internal/web/translation/en-US.json b/internal/web/translation/en-US.json
index f89e4cec9..d1de2f533 100644
--- a/internal/web/translation/en-US.json
+++ b/internal/web/translation/en-US.json
@@ -1752,6 +1752,8 @@
"wireguard": {
"secretKey": "Secret Key",
"publicKey": "Public Key",
+ "subnetIp": "Subnet",
+ "subnetCidr": "Subnet CIDR",
"allowedIPs": "Allowed IPs",
"endpoint": "Endpoint",
"domainStrategy": "Domain Strategy"
diff --git a/internal/web/translation/es-ES.json b/internal/web/translation/es-ES.json
index ffc241017..d519e567c 100644
--- a/internal/web/translation/es-ES.json
+++ b/internal/web/translation/es-ES.json
@@ -1635,6 +1635,8 @@
"wireguard": {
"secretKey": "Llave secreta",
"publicKey": "Llave pública",
+ "subnetIp": "Subred",
+ "subnetCidr": "CIDR de la subred",
"allowedIPs": "IP permitidas",
"endpoint": "Punto final",
"domainStrategy": "Estrategia de dominio"
diff --git a/internal/web/translation/fa-IR.json b/internal/web/translation/fa-IR.json
index 2272d1add..32ec2a022 100644
--- a/internal/web/translation/fa-IR.json
+++ b/internal/web/translation/fa-IR.json
@@ -1635,6 +1635,8 @@
"wireguard": {
"secretKey": "کلید شخصی",
"publicKey": "کلید عمومی",
+ "subnetIp": "زیرشبکه",
+ "subnetCidr": "پیشوند زیرشبکه (CIDR)",
"allowedIPs": "آیپیهای مجاز",
"endpoint": "نقطه پایانی",
"domainStrategy": "استراتژی حل دامنه"
diff --git a/internal/web/translation/id-ID.json b/internal/web/translation/id-ID.json
index fc40a1f5e..da74f95b6 100644
--- a/internal/web/translation/id-ID.json
+++ b/internal/web/translation/id-ID.json
@@ -1635,6 +1635,8 @@
"wireguard": {
"secretKey": "Kunci Rahasia",
"publicKey": "Kunci Publik",
+ "subnetIp": "Subnet",
+ "subnetCidr": "CIDR Subnet",
"allowedIPs": "IP yang Diizinkan",
"endpoint": "Titik Akhir",
"domainStrategy": "Strategi Domain"
diff --git a/internal/web/translation/ja-JP.json b/internal/web/translation/ja-JP.json
index c3137077f..278cd1909 100644
--- a/internal/web/translation/ja-JP.json
+++ b/internal/web/translation/ja-JP.json
@@ -1635,6 +1635,8 @@
"wireguard": {
"secretKey": "シークレットキー",
"publicKey": "公開鍵",
+ "subnetIp": "サブネット",
+ "subnetCidr": "サブネットCIDR",
"allowedIPs": "許可されたIP",
"endpoint": "エンドポイント",
"domainStrategy": "ドメイン戦略"
diff --git a/internal/web/translation/pt-BR.json b/internal/web/translation/pt-BR.json
index c456efb6a..5efa59b28 100644
--- a/internal/web/translation/pt-BR.json
+++ b/internal/web/translation/pt-BR.json
@@ -1635,6 +1635,8 @@
"wireguard": {
"secretKey": "Chave Secreta",
"publicKey": "Chave Pública",
+ "subnetIp": "Sub-rede",
+ "subnetCidr": "CIDR da Sub-rede",
"allowedIPs": "IPs Permitidos",
"endpoint": "Ponto Final",
"domainStrategy": "Estratégia de Domínio"
diff --git a/internal/web/translation/ru-RU.json b/internal/web/translation/ru-RU.json
index 6a153008d..6b4bb4e5b 100644
--- a/internal/web/translation/ru-RU.json
+++ b/internal/web/translation/ru-RU.json
@@ -1635,6 +1635,8 @@
"wireguard": {
"secretKey": "Секретный ключ",
"publicKey": "Публичный ключ",
+ "subnetIp": "Подсеть",
+ "subnetCidr": "Маска подсети (CIDR)",
"allowedIPs": "Разрешенные IP-адреса",
"endpoint": "Конечная точка",
"domainStrategy": "Стратегия домена"
diff --git a/internal/web/translation/tr-TR.json b/internal/web/translation/tr-TR.json
index 82bf0998d..fc571fe74 100644
--- a/internal/web/translation/tr-TR.json
+++ b/internal/web/translation/tr-TR.json
@@ -1635,6 +1635,8 @@
"wireguard": {
"secretKey": "Gizli Anahtar",
"publicKey": "Genel Anahtar",
+ "subnetIp": "Alt Ağ",
+ "subnetCidr": "Alt Ağ CIDR",
"allowedIPs": "İzin Verilen IP'ler",
"endpoint": "Uç Nokta",
"domainStrategy": "Alan Adı Stratejisi"
diff --git a/internal/web/translation/uk-UA.json b/internal/web/translation/uk-UA.json
index 29f9c6cc6..b96cd72a8 100644
--- a/internal/web/translation/uk-UA.json
+++ b/internal/web/translation/uk-UA.json
@@ -1635,6 +1635,8 @@
"wireguard": {
"secretKey": "Приватний ключ",
"publicKey": "Публічний ключ",
+ "subnetIp": "Підмережа",
+ "subnetCidr": "CIDR підмережі",
"allowedIPs": "Дозволені IP-адреси",
"endpoint": "Кінцева точка",
"domainStrategy": "Стратегія домену"
diff --git a/internal/web/translation/vi-VN.json b/internal/web/translation/vi-VN.json
index e15ffb7e2..874084d0d 100644
--- a/internal/web/translation/vi-VN.json
+++ b/internal/web/translation/vi-VN.json
@@ -1635,6 +1635,8 @@
"wireguard": {
"secretKey": "Khoá bí mật",
"publicKey": "Khóa công khai",
+ "subnetIp": "Mạng con",
+ "subnetCidr": "CIDR mạng con",
"allowedIPs": "IP được phép",
"endpoint": "Điểm cuối",
"domainStrategy": "Chiến lược tên miền"
diff --git a/internal/web/translation/zh-CN.json b/internal/web/translation/zh-CN.json
index a65d541d7..8de788f43 100644
--- a/internal/web/translation/zh-CN.json
+++ b/internal/web/translation/zh-CN.json
@@ -1635,6 +1635,8 @@
"wireguard": {
"secretKey": "密钥",
"publicKey": "公钥",
+ "subnetIp": "子网",
+ "subnetCidr": "子网 CIDR",
"allowedIPs": "允许的 IP",
"endpoint": "端点",
"domainStrategy": "域策略"
diff --git a/internal/web/translation/zh-TW.json b/internal/web/translation/zh-TW.json
index f396f1bc3..08020688a 100644
--- a/internal/web/translation/zh-TW.json
+++ b/internal/web/translation/zh-TW.json
@@ -1635,6 +1635,8 @@
"wireguard": {
"secretKey": "金鑰",
"publicKey": "公鑰",
+ "subnetIp": "子網路",
+ "subnetCidr": "子網路 CIDR",
"allowedIPs": "允許的 IP",
"endpoint": "端點",
"domainStrategy": "域策略"