| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- package service
- import (
- "encoding/json"
- "fmt"
- "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"
- // wireguardSubnetSettings is the subset of a WireGuard inbound's top-level
- // settings JSON this package cares about for subnet resolution. Unlike
- // AmneziaWG (whose whole settings shape is a typed struct in
- // internal/amneziawg), plain WireGuard has no dedicated Go struct on this
- // fork's side at all -- everything else is handled as untyped
- // map[string]any -- so this stays a narrow, local decode rather than
- // introducing a full struct just for two fields.
- type wireguardSubnetSettings struct {
- SubnetIP string `json:"subnetIp"`
- SubnetCIDR int `json:"subnetCidr"`
- }
- // explicitWireguardSubnetBase resolves an admin-configured subnet base out
- // of settingsJSON's own subnetIp/subnetCidr fields, mirroring AmneziaWG's
- // defaultAmneziaWGSubnetBases. Returns "" when either field is unset/empty
- // or doesn't parse as a valid prefix -- callers fall back to
- // wireguardAllocationBase's existing infer-from-clients behavior in that
- // case, so an inbound saved before this field existed (or one that simply
- // never set it) keeps behaving exactly as it always has.
- func explicitWireguardSubnetBase(settingsJSON string) string {
- var parsed wireguardSubnetSettings
- if err := json.Unmarshal([]byte(settingsJSON), &parsed); err != nil {
- return ""
- }
- ip := strings.TrimSpace(parsed.SubnetIP)
- if ip == "" || parsed.SubnetCIDR <= 0 {
- return ""
- }
- base := fmt.Sprintf("%s/%d", ip, parsed.SubnetCIDR)
- if _, err := netip.ParsePrefix(base); err != nil {
- return ""
- }
- return base
- }
- 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
- }
- const wireguardPoolFloorBits = 16
- // allocateWireguardAddress returns the first free single-host address in base
- // not already in used, starting at the second host (the server holds the first).
- //
- // allowWidening retries in the containing /16 once base's pool is exhausted.
- // True for Xray-native WireGuard, whose AllowedIPs aren't tied to a kernel
- // interface subnet; AmneziaWG must pass false and fail loudly instead, since an
- // address outside its interface's own Address would be silently unroutable.
- func allocateWireguardAddress(used []string, base string, allowWidening bool) (string, error) {
- if base == "" {
- base = defaultWireguardBase
- }
- prefix, err := netip.ParsePrefix(base)
- if err != nil {
- return "", err
- }
- hostBits := "32"
- if prefix.Addr().Is6() {
- hostBits = "128"
- }
- taken := make(map[netip.Addr]struct{}, len(used))
- for _, u := range used {
- if a := wireguardHostAddr(u); a.IsValid() {
- taken[a] = struct{}{}
- }
- }
- scopes := []netip.Prefix{prefix}
- if allowWidening && prefix.Addr().Is4() && prefix.Bits() > wireguardPoolFloorBits {
- if wider, wErr := prefix.Addr().Prefix(wireguardPoolFloorBits); wErr == nil {
- scopes = append(scopes, wider)
- }
- }
- for _, scope := range scopes {
- addr := scope.Masked().Addr().Next().Next()
- for scope.Contains(addr) {
- if _, ok := taken[addr]; !ok {
- return addr.String() + "/" + hostBits, nil
- }
- addr = addr.Next()
- }
- }
- return "", common.NewError("wireguard: no free address available in", scopes[len(scopes)-1].String())
- }
- // normalizeWireguardAllowedIPs validates user-supplied allowedIPs entries and
- // canonicalizes them: bare addresses become single-host prefixes, duplicates drop.
- func normalizeWireguardAllowedIPs(values []string) ([]string, error) {
- out := make([]string, 0, len(values))
- seen := make(map[string]struct{}, len(values))
- for _, v := range values {
- v = strings.TrimSpace(v)
- if v == "" {
- continue
- }
- p, err := netip.ParsePrefix(v)
- if err != nil {
- a, aErr := netip.ParseAddr(v)
- if aErr != nil {
- return nil, common.NewError("wireguard: invalid allowedIPs entry:", v)
- }
- p = netip.PrefixFrom(a, a.BitLen())
- }
- norm := p.String()
- if _, dup := seen[norm]; dup {
- continue
- }
- seen[norm] = struct{}{}
- out = append(out, norm)
- }
- return out, nil
- }
- func wireguardAllowedIPsCollision(entries, used []string) string {
- taken := make(map[string]struct{}, len(used))
- for _, u := range used {
- taken[strings.TrimSpace(u)] = struct{}{}
- }
- for _, e := range entries {
- if _, ok := taken[e]; ok {
- return e
- }
- }
- return ""
- }
- // 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.
- //
- // crossInboundUsed maps AllowedIPs already claimed by clients on every OTHER
- // WireGuard/AmneziaWG inbound on this panel to a human-readable description
- // of which inbound holds it (see otherTunnelAllowedIPs). It is folded into
- // used only AFTER the base subnet is resolved, so an unrelated inbound's
- // subnet can never skew this inbound's own base-subnet resolution — it only
- // ever narrows which addresses are free to hand out or accept, and lets a
- // manual-entry collision name the other inbound instead of just the address.
- //
- // settingsJSON is checked first for an admin-configured subnetIp/subnetCidr
- // (see explicitWireguardSubnetBase) — set explicitly, that always wins.
- // Only when it's unset does base fall back to inferring from existing
- // clients' own addresses, and finally to defaultWireguardBase, exactly as
- // before this field existed.
- func defaultWireguardClients(settingsJSON string, existing, clients []model.Client, interfaceClients []any, crossInboundUsed map[string]string) error {
- used := make([]string, 0)
- for i := range existing {
- used = append(used, existing[i].AllowedIPs...)
- }
- base := explicitWireguardSubnetBase(settingsJSON)
- if base == "" {
- base = wireguardAllocationBase(used, defaultWireguardBase)
- }
- 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 {
- addr, err := allocateWireguardAddress(used, base, true)
- if err != nil {
- return err
- }
- c.AllowedIPs = []string{addr}
- } else {
- normalized, err := normalizeWireguardAllowedIPs(c.AllowedIPs)
- if err != nil {
- return err
- }
- if len(normalized) == 0 {
- return common.NewError("wireguard: allowedIPs has no usable entry")
- }
- if hit := wireguardAllowedIPsCollision(normalized, used); hit != "" {
- if where := crossInboundUsed[hit]; where != "" {
- return common.NewError("wireguard: allowedIPs entry", hit, "is already used by a client on", where)
- }
- return common.NewError("wireguard: 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
- }
- if c.KeepAlive > 0 {
- m["keepAlive"] = c.KeepAlive
- }
- interfaceClients[i] = m
- }
- }
- }
- return nil
- }
|