mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-05 04:14:21 +00:00
79069d2b64
defaultWireguardClients always allocated new tunnel addresses from the hardcoded 10.0.0.0/24 base, so a legacy or migrated inbound whose peers live in a different subnet (e.g. 172.16.0.0/24) got new clients in an unrelated, unroutable range. Derive the allocation base from the existing peers' /24 and fall back to 10.0.0.0/24 only when there are none.
130 lines
3.4 KiB
Go
130 lines
3.4 KiB
Go
package service
|
|
|
|
import (
|
|
"net/netip"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"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"
|
|
)
|
|
|
|
const defaultWireguardBase = "10.0.0.0/24"
|
|
|
|
func keepAliveStr(seconds int) string {
|
|
if seconds <= 0 {
|
|
return ""
|
|
}
|
|
return strconv.Itoa(seconds)
|
|
}
|
|
|
|
func wireguardHostAddr(s string) netip.Addr {
|
|
s = strings.TrimSpace(s)
|
|
if s == "" {
|
|
return netip.Addr{}
|
|
}
|
|
if p, err := netip.ParsePrefix(s); err == nil {
|
|
return p.Addr()
|
|
}
|
|
if a, err := netip.ParseAddr(s); err == nil {
|
|
return a
|
|
}
|
|
return netip.Addr{}
|
|
}
|
|
|
|
func wireguardAllocationBase(used []string, fallback string) string {
|
|
for _, u := range used {
|
|
a := wireguardHostAddr(u)
|
|
if !a.IsValid() || !a.Is4() || a.IsUnspecified() {
|
|
continue
|
|
}
|
|
if p, err := a.Prefix(24); err == nil {
|
|
return p.String()
|
|
}
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
// allocateWireguardAddress returns the first free /32 host address in base that
|
|
// is not already present in used. The server holds the first host (.1), so
|
|
// allocation starts at the second host (.2).
|
|
func allocateWireguardAddress(used []string, base string) (string, error) {
|
|
if base == "" {
|
|
base = defaultWireguardBase
|
|
}
|
|
prefix, err := netip.ParsePrefix(base)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
taken := make(map[netip.Addr]struct{}, len(used))
|
|
for _, u := range used {
|
|
if a := wireguardHostAddr(u); a.IsValid() {
|
|
taken[a] = struct{}{}
|
|
}
|
|
}
|
|
addr := prefix.Masked().Addr().Next().Next()
|
|
for prefix.Contains(addr) {
|
|
if _, ok := taken[addr]; !ok {
|
|
return addr.String() + "/32", nil
|
|
}
|
|
addr = addr.Next()
|
|
}
|
|
return "", common.NewError("wireguard: no free address available in", base)
|
|
}
|
|
|
|
// defaultWireguardClients fills in blank WireGuard 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 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.
|
|
func defaultWireguardClients(existing, clients []model.Client, interfaceClients []any) error {
|
|
used := make([]string, 0)
|
|
for i := range existing {
|
|
used = append(used, existing[i].AllowedIPs...)
|
|
}
|
|
base := wireguardAllocationBase(used, defaultWireguardBase)
|
|
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, base)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.AllowedIPs = []string{addr}
|
|
}
|
|
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
|
|
}
|
|
if c.KeepAlive > 0 {
|
|
m["keepAlive"] = c.KeepAlive
|
|
}
|
|
interfaceClients[i] = m
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|