mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-08 11:17:13 +00:00
83cc545953
AmneziaWG (WireGuard plus DPI-resistant obfuscation) needs no Docker here — it runs as a genuine kernel interface via awg-quick/awg, managed the same way internal/mtproto manages mtg: one Inbound row is one desired Instance, and a Manager reconciles running interfaces toward the database every 10s (internal/web/job/amneziawg_job.go) plus immediately after a client edit (applyLocalAmneziaWG). Clients reuse model.Client verbatim (the same PrivateKey/PublicKey/ PreSharedKey/AllowedIPs fields WireGuard already uses), so bulk operations, the QR/share-link modal and subscriptions come from the shared inbound infrastructure instead of a parallel implementation. internal/amneziawg owns the obfuscation param generator/validator (ported from coinman-dev/3ax-ui, upgraded to AmneziaWG 2.0's S3/S4 padding and I1 signature packet) and the exec wrapper around awg-quick/awg, with fingerprint-based reconcile (noop / reload-via- syncconf / full restart) mirroring mtproto.Manager so a same-protocol edit doesn't force an unnecessary interface bounce that would drop every peer's connection. Frontend and install.sh's DKMS/awg-tools setup are tracked separately; this is backend-only. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
101 lines
3.2 KiB
Go
101 lines
3.2 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"
|
|
)
|
|
|
|
// defaultAmneziaWGSubnetBase resolves the /CIDR base new peer addresses are
|
|
// allocated from, out of the inbound's own configured server subnet — unlike
|
|
// WireGuard, which always falls back to a fixed 10.0.0.0/24.
|
|
func defaultAmneziaWGSubnetBase(settingsJSON string) (string, 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
|
|
}
|
|
return fmt.Sprintf("%s/%d", parsed.Server.SubnetIP, cidr), 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 {
|
|
base, err := defaultAmneziaWGSubnetBase(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, base)
|
|
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("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
|
|
}
|