mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-16 15:17:14 +00:00
7a41c59494
* fix(amneziawg): honor inbound listen when binding UDP socket AmneziaWG inbounds ignored the listen field and always opened awgconn.NewDefaultBind(), so multi-IP hosts replied from the primary address and handshakes to a secondary IP never completed (#6367). Carry Inbound.Listen on amneziawg.Instance, open a Bind pinned to that address (wildcard when empty/0.0.0.0/::), and include listen in addressFingerprint so edits rebuild the Device. Fixes #6367 * fix(amneziawg): fall back to wildcard when listen is unusable Invalid or non-local listen values no longer hard-fail inbound startup; treat ::0/[::0] as wildcards and normalize listen in the bind fingerprint. * fix(amneziawg): use ListenConfig.ListenPacket for noctx --------- Co-authored-by: mrchatam <mrchatam@users.noreply.github.com> Co-authored-by: mrchatam <287639636+mrchatam@users.noreply.github.com>
249 lines
11 KiB
Go
249 lines
11 KiB
Go
// Package amneziawg holds the AmneziaWG protocol's shared, DB-backed shapes
|
|
// (Instance, Peer, Obfuscation31, ServerSettings/InboundSettings) and the
|
|
// pure functions that derive an Instance from a stored inbound row. It has
|
|
// no OS dependency of its own: internal/amneziawgnet embeds amneziawg-go
|
|
// over a gVisor netstack and owns the actual running interfaces, one
|
|
// Manager-managed Device per desired Instance -- see that package's Manager
|
|
// for the reconcile-on-tick lifecycle (modeled on internal/mtproto's own
|
|
// Manager), and instance.go's own doc comment for how this package's role
|
|
// narrowed to protocol-shape-only after the kernel-module (DKMS) + awg-quick
|
|
// architecture this fork originally shipped was retired.
|
|
package amneziawg
|
|
|
|
import "github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
|
|
|
// Obfuscation31 is an AmneziaWG 3.1 obfuscation parameter set (junk packets,
|
|
// padding, magic headers, the five CPS signature-packet slots, and the 3.x
|
|
// header-protection/content-padding/timing/boolean fields). The same values
|
|
// must be applied on both ends of a tunnel, so the server stores them and
|
|
// every client config inherits them verbatim.
|
|
type Obfuscation31 struct {
|
|
Jc int `json:"jc"`
|
|
Jmin int `json:"jmin"`
|
|
Jmax int `json:"jmax"`
|
|
S1 int `json:"s1"`
|
|
S2 int `json:"s2"`
|
|
S3 int `json:"s3"`
|
|
S4 int `json:"s4"`
|
|
H1 string `json:"h1"`
|
|
H2 string `json:"h2"`
|
|
H3 string `json:"h3"`
|
|
H4 string `json:"h4"`
|
|
// I1-I5 are the real protocol's five CPS signature-packet slots
|
|
// (confirmed against amneziawg-go v3.0.3's device/uapi.go: "i1"
|
|
// through "i5" are five independent UAPI setters, device.ipackets[0..4],
|
|
// all parsed via the identical newObfChain grammar).
|
|
I1 string `json:"i1,omitempty"`
|
|
I2 string `json:"i2,omitempty"`
|
|
I3 string `json:"i3,omitempty"`
|
|
I4 string `json:"i4,omitempty"`
|
|
I5 string `json:"i5,omitempty"`
|
|
|
|
// HeaderProtectionKey is a base64 32-byte key shared by both ends; the
|
|
// ranges/booleans below are 3.x-only and optional.
|
|
HeaderProtectionKey string `json:"headerProtectionKey,omitempty"`
|
|
ContentPaddingAddition string `json:"contentPaddingAddition,omitempty"`
|
|
RekeyAfterTime string `json:"rekeyAfterTime,omitempty"`
|
|
RekeyTimeout string `json:"rekeyTimeout,omitempty"`
|
|
RejectAfterTime string `json:"rejectAfterTime,omitempty"`
|
|
KeepaliveTimeout string `json:"keepaliveTimeout,omitempty"`
|
|
MaxHandshakeAttempts string `json:"maxHandshakeAttempts,omitempty"`
|
|
RandomTrailers bool `json:"randomTrailers,omitempty"`
|
|
DisableCookies bool `json:"disableCookies,omitempty"`
|
|
}
|
|
|
|
// Peer is one desired AmneziaWG peer: a client device the interface accepts.
|
|
// Email attributes traffic and online status back to the owning client, the
|
|
// same role SecretEntry.Name plays for mtproto.
|
|
type Peer struct {
|
|
Email string
|
|
PublicKey string
|
|
PresharedKey string
|
|
AllowedIPs []string
|
|
|
|
// ForwardedPorts is a raw, user-supplied port list ("80, 443, 8000-8100")
|
|
// forwarded to this peer's tunnel address by internal/amneziawgnet's
|
|
// PortForwardSet listener supervisor. Empty means no port-forwarding.
|
|
ForwardedPorts string
|
|
}
|
|
|
|
// Instance is the desired runtime configuration of one AmneziaWG inbound: a
|
|
// single interface (e.g. awg1) with a set of peers, mirroring how one mtproto
|
|
// inbound maps to one mtg process (internal/mtproto.Instance).
|
|
type Instance struct {
|
|
Id int
|
|
Tag string
|
|
InterfaceName string
|
|
ListenPort int
|
|
// Listen is an optional host bind address (e.g. "203.0.113.10").
|
|
// Empty/wildcard keeps dual-stack StdNetBind; a real IP pins the UDP socket.
|
|
Listen string
|
|
PrivateKey string
|
|
PublicKey string
|
|
// Address holds the interface's own tunnel address(es), e.g. "10.8.1.1/24".
|
|
// Carries both the IPv4 and (when enabled) IPv6 server address.
|
|
Address []string
|
|
MTU int
|
|
|
|
// Obfuscation carries the full AmneziaWG 3.1 parameter set, including
|
|
// the 3.x header-protection/content-padding/timing/boolean fields (see
|
|
// Obfuscation31's own doc comment) -- amneziawgnet.DeviceOptions is
|
|
// what actually consumes it when building the embedded Device's UAPI
|
|
// config.
|
|
Obfuscation Obfuscation31
|
|
|
|
Peers []Peer
|
|
|
|
// ExternalInterface named the host NIC PostUp/PostDown NAT rules
|
|
// attached to under the retired kernel-module architecture. Also the
|
|
// fallback host NIC internal/amneziawgnet's IPv6-address-alias
|
|
// mechanism (desiredV6Aliases) uses when IPv6ExternalInterface is left
|
|
// blank.
|
|
ExternalInterface string
|
|
|
|
// IPv6Enabled/IPv6ExternalInterface gate internal/amneziawgnet's
|
|
// IPv6-address-alias mechanism (desiredV6Aliases,
|
|
// internal/web/service/xray.go's injectAmneziawgV6Egress): each peer
|
|
// with an IPv6 AllowedIPs entry gets that address aliased onto this
|
|
// host NIC (ip -6 addr add) and a dedicated Xray freedom outbound bound
|
|
// to it, giving that peer's own outbound connections a distinct public
|
|
// source identity. Narrower in scope than these identically-named
|
|
// fields' role under the retired kernel-module architecture, which used
|
|
// per-peer NDP-proxy entries (ip -6 neigh add proxy) to also support
|
|
// unsolicited inbound connections toward the peer -- that capability is
|
|
// the separate, not-yet-built Phase 3.6 (port-forwarding).
|
|
IPv6Enabled bool
|
|
IPv6ExternalInterface string
|
|
|
|
// RouteThroughXray gated the kernel-module architecture's opt-in
|
|
// TPROXY-into-Xray bridge. The embedded path (internal/amneziawgnet)
|
|
// has no equivalent opt-in at all -- every peer's traffic already goes
|
|
// through Xray's own SOCKS5 inbound unconditionally, since there's no
|
|
// other way for decapsulated gVisor traffic to reach the real internet
|
|
// -- so this field is now vestigial: read from existing stored settings
|
|
// for backward compatibility, but not acted on by anything. Slated for
|
|
// removal alongside the frontend toggle in a follow-up.
|
|
RouteThroughXray bool
|
|
}
|
|
|
|
// ServerSettings is the "server" block of an AmneziaWG inbound's Settings
|
|
// JSON: the interface-level configuration shared by every client/peer. The
|
|
// listen port is deliberately not duplicated here — it lives on the inbound
|
|
// row itself (Inbound.Port), like every other protocol.
|
|
type ServerSettings struct {
|
|
PrivateKey string `json:"privateKey"`
|
|
PublicKey string `json:"publicKey"`
|
|
|
|
SubnetIP string `json:"subnetIp"`
|
|
SubnetCIDR int `json:"subnetCidr"`
|
|
MTU int `json:"mtu,omitempty"`
|
|
|
|
// PrimaryDNS/SecondaryDNS seed client configs' DNS line. Blank is
|
|
// meaningful, so no omitempty: a dropped key resurrects frontend defaults.
|
|
PrimaryDNS string `json:"primaryDns"`
|
|
SecondaryDNS string `json:"secondaryDns"`
|
|
|
|
// ExternalInterface, IPv6Enabled, and IPv6ExternalInterface are live
|
|
// again as of Phase 3.5 -- see the matching fields on Instance for what
|
|
// they gate (internal/amneziawgnet's IPv6-address-alias mechanism).
|
|
// IPv6Subnet was never actually vestigial either: InstanceFromInbound
|
|
// already consumes it (via serverAddressV6) to build the server's own
|
|
// tunnel address, same as always. Only RouteThroughXray, below, remains
|
|
// genuinely vestigial as of the hard cutover to the embedded path
|
|
// (internal/amneziawgnet) -- read from existing stored settings for
|
|
// backward compatibility, but not acted on by anything.
|
|
ExternalInterface string `json:"externalInterface,omitempty"`
|
|
|
|
IPv6Enabled bool `json:"ipv6Enabled,omitempty"`
|
|
IPv6Subnet string `json:"ipv6Subnet,omitempty"`
|
|
IPv6ExternalInterface string `json:"ipv6ExternalInterface,omitempty"`
|
|
|
|
RouteThroughXray bool `json:"routeThroughXray,omitempty"`
|
|
|
|
// Obfuscation31's fields, repeated flat (not embedded) rather than
|
|
// nested under their own key: encoding/json would happily inline an
|
|
// embedded Obfuscation31 the same way, but the frontend's Go->Zod/TS
|
|
// generator (tools/openapigen) does not — it emits a genuinely nested
|
|
// `obfuscation31` object, which would silently diverge from the real
|
|
// wire JSON. See Obfuscation() below for the manager-facing conversion.
|
|
Jc int `json:"jc"`
|
|
Jmin int `json:"jmin"`
|
|
Jmax int `json:"jmax"`
|
|
S1 int `json:"s1"`
|
|
S2 int `json:"s2"`
|
|
S3 int `json:"s3"`
|
|
S4 int `json:"s4"`
|
|
H1 string `json:"h1"`
|
|
H2 string `json:"h2"`
|
|
H3 string `json:"h3"`
|
|
H4 string `json:"h4"`
|
|
I1 string `json:"i1,omitempty"`
|
|
I2 string `json:"i2,omitempty"`
|
|
I3 string `json:"i3,omitempty"`
|
|
I4 string `json:"i4,omitempty"`
|
|
I5 string `json:"i5,omitempty"`
|
|
|
|
// HeaderProtectionKey and ContentPaddingAddition are AmneziaWG 3.0
|
|
// fields, flat and top-level for the same tools/openapigen reason as
|
|
// the block above; Obfuscation() below folds them back into
|
|
// Obfuscation31's own identically named fields.
|
|
// HeaderProtectionKey is a base64 32-byte key; empty (the default)
|
|
// disables AWG 3.0 header protection. A non-empty value requires
|
|
// every one of S1-S4 above to be >= 12 -- ValidateObfuscation
|
|
// enforces this at save time, not just at IpcSet time.
|
|
// ContentPaddingAddition is a "low-high" range or bare integer, the
|
|
// same grammar and uint32 cap as H1-H4.
|
|
HeaderProtectionKey string `json:"headerProtectionKey,omitempty"`
|
|
ContentPaddingAddition string `json:"contentPaddingAddition,omitempty"`
|
|
|
|
// RekeyAfterTime/RekeyTimeout/RejectAfterTime/KeepaliveTimeout/
|
|
// MaxHandshakeAttempts mirror Instance's identically named fields --
|
|
// see that type's own doc comment for the grammar/width/real-default
|
|
// details. Flat and top-level for the same tools/openapigen reason as
|
|
// the rest of this struct.
|
|
RekeyAfterTime string `json:"rekeyAfterTime,omitempty"`
|
|
RekeyTimeout string `json:"rekeyTimeout,omitempty"`
|
|
RejectAfterTime string `json:"rejectAfterTime,omitempty"`
|
|
KeepaliveTimeout string `json:"keepaliveTimeout,omitempty"`
|
|
MaxHandshakeAttempts string `json:"maxHandshakeAttempts,omitempty"`
|
|
|
|
// RandomTrailers/DisableCookies mirror Instance's identically named
|
|
// AmneziaWG 3.1 fields -- see that type's own doc comment for the real
|
|
// protocol/interop details. Both real bool fields (not omitempty):
|
|
// buildUAPIConfig always emits both lines explicitly so the
|
|
// reconfigure-in-place diff correctly notices a true->false edit, not
|
|
// just false->true.
|
|
RandomTrailers bool `json:"randomTrailers"`
|
|
DisableCookies bool `json:"disableCookies"`
|
|
}
|
|
|
|
// Obfuscation extracts the Obfuscation31 parameter set from a ServerSettings
|
|
// block, for callers (the Manager, ValidateObfuscation) that want the
|
|
// grouped type rather than the flat wire fields.
|
|
func (s ServerSettings) Obfuscation() Obfuscation31 {
|
|
return Obfuscation31{
|
|
Jc: s.Jc, Jmin: s.Jmin, Jmax: s.Jmax,
|
|
S1: s.S1, S2: s.S2, S3: s.S3, S4: s.S4,
|
|
H1: s.H1, H2: s.H2, H3: s.H3, H4: s.H4,
|
|
I1: s.I1, I2: s.I2, I3: s.I3, I4: s.I4, I5: s.I5,
|
|
HeaderProtectionKey: s.HeaderProtectionKey,
|
|
ContentPaddingAddition: s.ContentPaddingAddition,
|
|
RekeyAfterTime: s.RekeyAfterTime,
|
|
RekeyTimeout: s.RekeyTimeout,
|
|
RejectAfterTime: s.RejectAfterTime,
|
|
KeepaliveTimeout: s.KeepaliveTimeout,
|
|
MaxHandshakeAttempts: s.MaxHandshakeAttempts,
|
|
RandomTrailers: s.RandomTrailers,
|
|
DisableCookies: s.DisableCookies,
|
|
}
|
|
}
|
|
|
|
// InboundSettings is the full Settings JSON shape stored on an AmneziaWG
|
|
// inbound row: one server block plus the usual generic client list, so bulk
|
|
// operations, the QR modal and subscriptions all come from the same shared
|
|
// infrastructure every other protocol uses.
|
|
type InboundSettings struct {
|
|
Server *ServerSettings `json:"server"`
|
|
Clients []model.Client `json:"clients"`
|
|
}
|