feat(clients): let admins set PersistentKeepalive on tunnel clients

model.Client already carries KeepAlive, and every AmneziaWG/WireGuard client
config emitter already writes PersistentKeepalive when it is above zero -- but
nothing in the UI could set it, so it stayed 0 and the line was never emitted.

Without it a peer that goes quiet has nothing to trigger a handshake: WireGuard
only initiates when it has data to send. An idle client stays disconnected
after any interruption -- a NAT mapping timing out, a device sleeping, the
panel restarting -- until the user generates traffic themselves.

New clients default to 25, the conventional value, which also keeps the NAT
mapping open. Existing clients keep whatever they have, and 0 remains valid and
means "do not send keepalives".
This commit is contained in:
YoungReckless4
2026-08-31 14:39:58 +03:00
parent f727d04f65
commit b70e17ed73
14 changed files with 37 additions and 0 deletions
@@ -132,6 +132,7 @@ type Values = ClientFormValues & {
wgAllowedIPs: string;
awgAllowedIPs: string;
awgForwardedPorts: string;
wgKeepAlive: number;
secret: string;
adTag: string;
};
@@ -168,6 +169,7 @@ const EMPTY: Values = {
wgAllowedIPs: '',
awgAllowedIPs: '',
awgForwardedPorts: '',
wgKeepAlive: 25,
secret: '',
adTag: '',
};
@@ -378,6 +380,7 @@ export default function ClientFormModal({
wgAllowedIPs: wgTunnelIPs ?? client.allowedIPs ?? '',
awgAllowedIPs: awgTunnelIPs ?? client.allowedIPs ?? '',
awgForwardedPorts: client.forwardedPorts || '',
wgKeepAlive: client.keepAlive ?? 0,
secret: client.secret || '',
adTag: client.adTag || '',
};
@@ -693,6 +696,7 @@ export default function ClientFormModal({
// so both protocols share this one field set — see wgPrivateKey etc.
// below and the AmneziaWG-labeled variants of the same inputs.
clientPayload.privateKey = values.wgPrivateKey;
clientPayload.keepAlive = values.wgKeepAlive;
clientPayload.publicKey = values.wgPublicKey;
if (values.wgPreSharedKey) {
clientPayload.preSharedKey = values.wgPreSharedKey;
@@ -1271,6 +1275,13 @@ export default function ClientFormModal({
<Input placeholder="10.8.1.2/32" />
</FormField>
)}
<FormField
name="wgKeepAlive"
label={t('pages.clients.tunnelKeepAlive')}
extra={t('pages.clients.tunnelKeepAliveHint')}
>
<InputNumber min={0} max={65535} style={{ width: '100%' }} />
</FormField>
{showAmneziawg && (
<FormField
name="awgForwardedPorts"