Files
3x-ui/internal/web/service/client_keepalive.go
T
YoungReckless4 35e09191de feat(amneziawg): support persistent keepalive ranges (#6881)
AmneziaWG clients accept a randomised PersistentKeepalive range such as 15-25,
which makes keepalive timing harder to fingerprint; the panel only stored a
single number. A client's keepalive may now be a range, validated the way
AmneziaWG parses it. The downloadable .conf and vpn:// profile carry the range,
Clash/Mihomo output gets its lower bound, and plain WireGuard stays scalar-only.
The range lives in an additive wg_keep_alive_range column, so the integer
column and an older panel binary keep working after a rollback.
2026-10-24 06:36:49 +02:00

66 lines
1.8 KiB
Go

package service
import (
"fmt"
"strconv"
"strings"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
func validateClientKeepAlive(protocol model.Protocol, client model.Client) error {
if client.KeepAlive == nil {
return nil
}
switch protocol {
case model.AmneziaWG:
return client.KeepAlive.Validate(true)
case model.WireGuard:
return client.KeepAlive.Validate(false)
default:
return nil
}
}
func validateClientsKeepAlive(protocol model.Protocol, clients []model.Client) error {
for _, client := range clients {
if err := validateClientKeepAlive(protocol, client); err != nil {
return fmt.Errorf("client %q: %w", client.Email, err)
}
}
return nil
}
func validateClientKeepAliveAgainstExisting(protocol model.Protocol, client model.Client, existing []model.Client, identityEmail ...string) error {
if err := validateClientKeepAlive(protocol, client); err == nil || client.KeepAlive == nil {
return err
}
if protocol != model.AmneziaWG && protocol != model.WireGuard {
return nil
}
value := client.KeepAlive.String()
identity := client.Email
if len(identityEmail) > 0 && identityEmail[0] != "" {
identity = identityEmail[0]
}
parsed, err := strconv.ParseInt(value, 10, 64)
if err != nil || strconv.FormatInt(parsed, 10) != value {
return validateClientKeepAlive(protocol, client)
}
for _, old := range existing {
if strings.EqualFold(old.Email, identity) && old.KeepAlive != nil && old.KeepAlive.String() == value {
return nil
}
}
return validateClientKeepAlive(protocol, client)
}
func validateClientsKeepAliveAgainstExisting(protocol model.Protocol, clients, existing []model.Client) error {
for _, client := range clients {
if err := validateClientKeepAliveAgainstExisting(protocol, client, existing); err != nil {
return fmt.Errorf("client %q: %w", client.Email, err)
}
}
return nil
}