mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-17 15:47:14 +00:00
feat(amneziawg): add native AmneziaWG protocol backend
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>
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/goccy/go-json"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/amneziawg"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
||||
@@ -616,6 +617,8 @@ func (s *SubService) GetLink(inbound *model.Inbound, email string) string {
|
||||
return s.genMtprotoLink(inbound, email)
|
||||
case "wireguard":
|
||||
return s.genWireguardLink(inbound, email)
|
||||
case "amneziawg":
|
||||
return s.genAmneziaWGLink(inbound, email)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -662,6 +665,82 @@ func (s *SubService) genWireguardLink(inbound *model.Inbound, email string) stri
|
||||
return buildLinkWithParams(link, params, s.genRemark(inbound, email, "", ""))
|
||||
}
|
||||
|
||||
// genAmneziaWGLink builds a per-client amneziawg:// share link mirroring
|
||||
// genWireguardLink: the client's private key is the userinfo, the server
|
||||
// public key and obfuscation parameters plus the client's tunnel address ride
|
||||
// in the query. Returns "" when the client or server has no key.
|
||||
func (s *SubService) genAmneziaWGLink(inbound *model.Inbound, email string) string {
|
||||
if inbound.Protocol != model.AmneziaWG {
|
||||
return ""
|
||||
}
|
||||
var parsed amneziawg.InboundSettings
|
||||
if err := json.Unmarshal([]byte(inbound.Settings), &parsed); err != nil || parsed.Server == nil {
|
||||
return ""
|
||||
}
|
||||
server := parsed.Server
|
||||
|
||||
resolved, ok := s.clientForLink(inbound, email)
|
||||
if !ok || resolved.PrivateKey == "" {
|
||||
return ""
|
||||
}
|
||||
client := &resolved
|
||||
|
||||
link := fmt.Sprintf("amneziawg://%s@%s", encodeUserinfo(client.PrivateKey), joinHostPort(s.resolveInboundAddress(inbound), inbound.Port))
|
||||
params := make(map[string]string)
|
||||
if server.PublicKey != "" {
|
||||
params["publickey"] = server.PublicKey
|
||||
}
|
||||
if joined := strings.Join(client.AllowedIPs, ","); joined != "" {
|
||||
params["address"] = joined
|
||||
}
|
||||
if server.MTU > 0 {
|
||||
params["mtu"] = strconv.Itoa(server.MTU)
|
||||
}
|
||||
var dnsParts []string
|
||||
if server.PrimaryDNS != "" {
|
||||
dnsParts = append(dnsParts, server.PrimaryDNS)
|
||||
}
|
||||
if server.SecondaryDNS != "" {
|
||||
dnsParts = append(dnsParts, server.SecondaryDNS)
|
||||
}
|
||||
if len(dnsParts) > 0 {
|
||||
params["dns"] = strings.Join(dnsParts, ",")
|
||||
}
|
||||
if client.PreSharedKey != "" {
|
||||
params["presharedkey"] = client.PreSharedKey
|
||||
}
|
||||
if client.KeepAlive > 0 {
|
||||
params["keepalive"] = strconv.Itoa(client.KeepAlive)
|
||||
}
|
||||
params["jc"] = strconv.Itoa(server.Jc)
|
||||
params["jmin"] = strconv.Itoa(server.Jmin)
|
||||
params["jmax"] = strconv.Itoa(server.Jmax)
|
||||
params["s1"] = strconv.Itoa(server.S1)
|
||||
params["s2"] = strconv.Itoa(server.S2)
|
||||
if server.S3 > 0 {
|
||||
params["s3"] = strconv.Itoa(server.S3)
|
||||
}
|
||||
if server.S4 > 0 {
|
||||
params["s4"] = strconv.Itoa(server.S4)
|
||||
}
|
||||
if server.H1 != "" {
|
||||
params["h1"] = server.H1
|
||||
}
|
||||
if server.H2 != "" {
|
||||
params["h2"] = server.H2
|
||||
}
|
||||
if server.H3 != "" {
|
||||
params["h3"] = server.H3
|
||||
}
|
||||
if server.H4 != "" {
|
||||
params["h4"] = server.H4
|
||||
}
|
||||
if server.I1 != "" {
|
||||
params["i1"] = server.I1
|
||||
}
|
||||
return buildLinkWithParams(link, params, s.genRemark(inbound, email, "", ""))
|
||||
}
|
||||
|
||||
// genMtprotoLink builds a per-client Telegram proxy deep link for an mtproto
|
||||
// inbound: the server/port pair plus the client's own FakeTLS secret. The link
|
||||
// carries no remark fragment — Telegram proxy deep links have no name field, and
|
||||
|
||||
Reference in New Issue
Block a user