CI / go-test (push) Canceled after 0s
CI / postgres-durable-first (push) Canceled after 0s
CI / codegen (push) Canceled after 0s
CI / govulncheck (push) Canceled after 0s
CI / race (push) Canceled after 0s
CI / fuzz-smoke (push) Canceled after 0s
CI / golangci (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CodeQL Advanced / Analyze (go) (push) Canceled after 0s
CodeQL Advanced / Analyze (actions) (push) Canceled after 0s
CodeQL Advanced / Analyze (javascript-typescript) (push) Canceled after 0s
Docs CI / build (push) Canceled after 0s
Docs Deploy (GitHub Pages) / build (push) Canceled after 0s
Release 3X-UI / build (386) (push) Canceled after 0s
Release 3X-UI / build (amd64) (push) Canceled after 0s
Release 3X-UI / build (arm64) (push) Canceled after 0s
Release 3X-UI / build (armv5) (push) Canceled after 0s
Release 3X-UI / build (armv6) (push) Canceled after 0s
Release 3X-UI / build (armv7) (push) Canceled after 0s
Release 3X-UI / build (s390x) (push) Canceled after 0s
Release 3X-UI / Build for Windows (amd64) (push) Canceled after 0s
Deploy Smoke Tests / noninteractive-install (ubuntu-24.04-arm) (push) Canceled after 0s
Deploy Smoke Tests / noninteractive-install (ubuntu-latest) (push) Canceled after 0s
Deploy Smoke Tests / release-tag-install (ubuntu-24.04-arm) (push) Canceled after 0s
Deploy Smoke Tests / release-tag-install (ubuntu-latest) (push) Canceled after 0s
Docs Deploy (GitHub Pages) / deploy (push) Canceled after 0s
Release 3X-UI / Publish rolling dev release (push) Canceled after 0s
120 lines
3.9 KiB
Go
120 lines
3.9 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 credentials and a free tunnel address
|
|
// for new clients, mutating both the typed clients and the parallel raw maps
|
|
// persisted into the settings. Existing values are never overwritten, so an
|
|
// edit never rotates keys. Mirrors defaultWireguardClients; crossInboundUsed
|
|
// (see otherTunnelAllowedIPs) narrows which addresses are still free.
|
|
func defaultAmneziaWGClients(settingsJSON string, existing, clients []model.Client, interfaceClients []any, crossInboundUsed map[string]string) 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 addr := range crossInboundUsed {
|
|
used = append(used, addr)
|
|
}
|
|
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 {
|
|
// allowWidening=false: unlike WireGuard's Xray-native inbound,
|
|
// AmneziaWG's kernel interface Address is exactly the configured
|
|
// subnet, so an address allocated outside it would be silently
|
|
// unroutable. Exhaustion here must fail loudly instead.
|
|
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 != "" {
|
|
if where := crossInboundUsed[hit]; where != "" {
|
|
return common.NewError("amneziawg: allowedIPs entry", hit, "is already used by a client on", where)
|
|
}
|
|
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
|
|
}
|