mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-08 19:27:14 +00:00
c97ab56245
# 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
114 lines
3.6 KiB
Go
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
|
|
}
|