Files
3x-ui/internal/web/service/client_amneziawg.go
T
Kuzz007 c97ab56245 Merge remote-tracking branch 'upstream/main' into sync-3.6.0
# Conflicts:
#	.github/workflows/claude-bot.yml
#	.github/workflows/release.yml
#	DockerInit.sh
#	frontend/package-lock.json
#	frontend/package.json
#	frontend/src/hooks/useClients.ts
#	frontend/src/layouts/AppSidebar.tsx
#	frontend/src/main.tsx
#	internal/config/version
#	internal/database/model/model.go
#	internal/web/service/client_wireguard.go
#	internal/web/service/inbound.go
2026-08-01 21:59:29 +03:00

114 lines
3.6 KiB
Go

package service
import (
"encoding/json"
"fmt"
"github.com/mhsanaei/3x-ui/v3/internal/amneziawg"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/util/common"
wgutil "github.com/mhsanaei/3x-ui/v3/internal/util/wireguard"
)
// defaultAmneziaWGSubnetBases resolves the /CIDR bases new peer addresses are
// allocated from, out of the inbound's own configured server subnet(s) —
// unlike WireGuard, which always falls back to a fixed 10.0.0.0/24. v6Base is
// "" when the server doesn't have IPv6 enabled.
func defaultAmneziaWGSubnetBases(settingsJSON string) (v4Base, v6Base string, err error) {
var parsed amneziawg.InboundSettings
if err := json.Unmarshal([]byte(settingsJSON), &parsed); err != nil {
return "", "", fmt.Errorf("amneziawg: invalid settings: %w", err)
}
if parsed.Server == nil {
return "", "", fmt.Errorf("amneziawg: settings missing server block")
}
cidr := parsed.Server.SubnetCIDR
if cidr <= 0 {
cidr = 24
}
v4Base = fmt.Sprintf("%s/%d", parsed.Server.SubnetIP, cidr)
if parsed.Server.IPv6Enabled && parsed.Server.IPv6Subnet != "" {
v6Base = parsed.Server.IPv6Subnet
}
return v4Base, v6Base, nil
}
// defaultAmneziaWGClients fills in blank AmneziaWG credentials for newly
// added clients: a generated keypair when none was provided, a derived
// public key when only a private key was given, and a unique tunnel address
// allocated from the inbound's own configured subnet. It mutates both the
// typed clients and the parallel raw client maps that get persisted into the
// inbound settings. Existing values are never overwritten, so editing a
// client never rotates its keys. Mirrors defaultWireguardClients, reusing
// its IP allocation and validation helpers — the only real difference is
// where the allocation base comes from.
func defaultAmneziaWGClients(settingsJSON string, existing, clients []model.Client, interfaceClients []any) error {
v4Base, v6Base, err := defaultAmneziaWGSubnetBases(settingsJSON)
if err != nil {
return err
}
used := make([]string, 0)
for i := range existing {
used = append(used, existing[i].AllowedIPs...)
}
for i := range clients {
c := &clients[i]
if c.PrivateKey == "" && c.PublicKey == "" {
priv, pub, err := wgutil.GenerateWireguardKeypair()
if err != nil {
return err
}
c.PrivateKey = priv
c.PublicKey = pub
} else if c.PublicKey == "" && c.PrivateKey != "" {
pub, err := wgutil.PublicKeyFromPrivate(c.PrivateKey)
if err != nil {
return err
}
c.PublicKey = pub
}
if len(c.AllowedIPs) == 0 {
addr, err := allocateWireguardAddress(used, v4Base, false)
if err != nil {
return err
}
allowed := []string{addr}
if v6Base != "" {
addr6, err := allocateWireguardAddress(used, v6Base, false)
if err != nil {
return err
}
allowed = append(allowed, addr6)
}
c.AllowedIPs = allowed
} else {
normalized, err := normalizeWireguardAllowedIPs(c.AllowedIPs)
if err != nil {
return err
}
if len(normalized) == 0 {
return common.NewError("amneziawg: allowedIPs has no usable entry")
}
if hit := wireguardAllowedIPsCollision(normalized, used); hit != "" {
return common.NewError("amneziawg: allowedIPs entry already used by another client:", hit)
}
c.AllowedIPs = normalized
}
used = append(used, c.AllowedIPs...)
if i < len(interfaceClients) {
if m, ok := interfaceClients[i].(map[string]any); ok {
m["privateKey"] = c.PrivateKey
m["publicKey"] = c.PublicKey
m["allowedIPs"] = c.AllowedIPs
if c.PreSharedKey != "" {
m["preSharedKey"] = c.PreSharedKey
}
interfaceClients[i] = m
}
}
}
return nil
}