mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-03 11:45:04 +03:00
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.
This commit is contained in:
@@ -432,6 +432,13 @@ func markInboundNodesDirty(inboundIds []int) error {
|
||||
}
|
||||
|
||||
func (s *ClientService) fillProtocolDefaults(c *model.Client, ib *model.Inbound) error {
|
||||
return s.fillProtocolDefaultsAgainstExisting(c, ib, nil)
|
||||
}
|
||||
|
||||
func (s *ClientService) fillProtocolDefaultsAgainstExisting(c *model.Client, ib *model.Inbound, existing []model.Client, identityEmail ...string) error {
|
||||
if err := validateClientKeepAliveAgainstExisting(ib.Protocol, *c, existing, identityEmail...); err != nil {
|
||||
return err
|
||||
}
|
||||
switch ib.Protocol {
|
||||
case model.VMESS, model.VLESS:
|
||||
if c.ID == "" {
|
||||
@@ -688,6 +695,23 @@ func (s *ClientService) Update(inboundSvc *InboundService, id int, updated model
|
||||
if err := validateClientTrafficReset(updated.TrafficReset, updated.TrafficResetDay); err != nil {
|
||||
return false, err
|
||||
}
|
||||
existingClientsByInbound := make(map[int][]model.Client, len(attachedIds))
|
||||
if updated.KeepAlive != nil {
|
||||
for _, inboundID := range attachedIds {
|
||||
inbound, getErr := inboundSvc.GetInbound(inboundID)
|
||||
if getErr != nil {
|
||||
return false, fmt.Errorf("inbound %d: %w", inboundID, getErr)
|
||||
}
|
||||
clients, getErr := inboundSvc.GetClients(inbound)
|
||||
if getErr != nil {
|
||||
return false, fmt.Errorf("inbound %d: %w", inboundID, getErr)
|
||||
}
|
||||
existingClientsByInbound[inboundID] = clients
|
||||
if err := validateClientKeepAliveAgainstExisting(inbound.Protocol, updated, clients, existing.Email); err != nil {
|
||||
return false, fmt.Errorf("inbound %d: %w", inboundID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
normalizeClientTrafficReset(&updated)
|
||||
if updated.SubID == "" {
|
||||
updated.SubID = existing.SubID
|
||||
@@ -765,7 +789,7 @@ func (s *ClientService) Update(inboundSvc *InboundService, id int, updated model
|
||||
if existing.Email == "" {
|
||||
continue
|
||||
}
|
||||
if err := s.fillProtocolDefaults(&updated, inbound); err != nil {
|
||||
if err := s.fillProtocolDefaultsAgainstExisting(&updated, inbound, existingClientsByInbound[ibId], existing.Email); err != nil {
|
||||
return false, err
|
||||
}
|
||||
clientForInbound := updated
|
||||
@@ -822,32 +846,37 @@ func (s *ClientService) Update(inboundSvc *InboundService, id int, updated model
|
||||
if len(inboundIds) == 0 {
|
||||
merged := *existing
|
||||
applyClientRecordMerge(&merged, updated.ToRecord())
|
||||
keepAliveLegacy, keepAliveRange, keepAliveErr := model.KeepAliveStorage(merged.KeepAlive)
|
||||
if keepAliveErr != nil {
|
||||
return needRestart, keepAliveErr
|
||||
}
|
||||
if err := database.GetDB().Model(&model.ClientRecord{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]any{
|
||||
"sub_id": merged.SubID,
|
||||
"uuid": merged.UUID,
|
||||
"password": merged.Password,
|
||||
"auth": merged.Auth,
|
||||
"secret": merged.Secret,
|
||||
"flow": merged.Flow,
|
||||
"security": merged.Security,
|
||||
"wg_private_key": merged.PrivateKey,
|
||||
"wg_public_key": merged.PublicKey,
|
||||
"wg_allowed_ips": merged.AllowedIPs,
|
||||
"wg_pre_shared_key": merged.PreSharedKey,
|
||||
"wg_keep_alive": merged.KeepAlive,
|
||||
"limit_ip": merged.LimitIP,
|
||||
"total_gb": merged.TotalGB,
|
||||
"expiry_time": merged.ExpiryTime,
|
||||
"tg_id": merged.TgID,
|
||||
"comment": merged.Comment,
|
||||
"reset": merged.Reset,
|
||||
"reset_day": merged.ResetDay,
|
||||
"reset_weekday": merged.ResetWeekday,
|
||||
"reset_max": merged.ResetMax,
|
||||
"traffic_reset": merged.TrafficReset,
|
||||
"traffic_reset_day": merged.TrafficResetDay,
|
||||
"sub_id": merged.SubID,
|
||||
"uuid": merged.UUID,
|
||||
"password": merged.Password,
|
||||
"auth": merged.Auth,
|
||||
"secret": merged.Secret,
|
||||
"flow": merged.Flow,
|
||||
"security": merged.Security,
|
||||
"wg_private_key": merged.PrivateKey,
|
||||
"wg_public_key": merged.PublicKey,
|
||||
"wg_allowed_ips": merged.AllowedIPs,
|
||||
"wg_pre_shared_key": merged.PreSharedKey,
|
||||
"wg_keep_alive": keepAliveLegacy,
|
||||
"wg_keep_alive_range": keepAliveRange,
|
||||
"limit_ip": merged.LimitIP,
|
||||
"total_gb": merged.TotalGB,
|
||||
"expiry_time": merged.ExpiryTime,
|
||||
"tg_id": merged.TgID,
|
||||
"comment": merged.Comment,
|
||||
"reset": merged.Reset,
|
||||
"reset_day": merged.ResetDay,
|
||||
"reset_weekday": merged.ResetWeekday,
|
||||
"reset_max": merged.ResetMax,
|
||||
"traffic_reset": merged.TrafficReset,
|
||||
"traffic_reset_day": merged.TrafficResetDay,
|
||||
}).Error; err != nil {
|
||||
return needRestart, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user