| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- // 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 no
- // longer manages any OS-level interface itself: that was the kernel-module
- // (DKMS) + awg-quick + TPROXY architecture this fork shipped originally,
- // retired in favor of an embedded, pure-Go one (amneziawg-go over a gVisor
- // netstack, see internal/amneziawgnet) in a hard cutover. This package's
- // remaining code is deliberately protocol-shape-only, with no OS dependency
- // at all, so both the (now-removed) kernel-module path and the embedded
- // path could read -- and, historically, did read -- it identically.
- package amneziawg
- import (
- "encoding/json"
- "fmt"
- "net/netip"
- "github.com/mhsanaei/3x-ui/v3/internal/database/model"
- )
- // InstanceFromInbound derives a desired Instance from an AmneziaWG inbound,
- // building one peer per active client. Returns false when the inbound is not
- // a usable AmneziaWG inbound (wrong protocol, unparseable settings, or no
- // server block) or has no enabled peer to serve — mirroring
- // mtproto.InstanceFromInbound, which skips the sidecar entirely rather than
- // run it with nothing to serve.
- func InstanceFromInbound(ib *model.Inbound) (Instance, bool) {
- if ib == nil || ib.Protocol != model.AmneziaWG {
- return Instance{}, false
- }
- var parsed InboundSettings
- if err := json.Unmarshal([]byte(ib.Settings), &parsed); err != nil || parsed.Server == nil {
- return Instance{}, false
- }
- server := parsed.Server
- peers := make([]Peer, 0, len(parsed.Clients))
- for _, c := range parsed.Clients {
- if !c.Enable || c.PublicKey == "" || len(c.AllowedIPs) == 0 {
- continue
- }
- peers = append(peers, Peer{
- Email: c.Email,
- PublicKey: c.PublicKey,
- PresharedKey: c.PreSharedKey,
- AllowedIPs: c.AllowedIPs,
- ForwardedPorts: c.ForwardedPorts,
- })
- }
- if len(peers) == 0 {
- return Instance{}, false
- }
- addresses := []string{serverAddress(server.SubnetIP, server.SubnetCIDR)}
- if server.IPv6Enabled {
- if v6, ok := serverAddressV6(server.IPv6Subnet); ok {
- addresses = append(addresses, v6)
- }
- }
- return Instance{
- Id: ib.Id,
- Tag: ib.Tag,
- InterfaceName: interfaceNameForID(ib.Id),
- ListenPort: ib.Port,
- PrivateKey: server.PrivateKey,
- PublicKey: server.PublicKey,
- Address: addresses,
- MTU: server.MTU,
- Obfuscation: server.Obfuscation(),
- Peers: peers,
- ExternalInterface: server.ExternalInterface,
- IPv6Enabled: server.IPv6Enabled,
- IPv6ExternalInterface: server.IPv6ExternalInterface,
- RouteThroughXray: server.RouteThroughXray,
- }, true
- }
- // interfaceNameForID derives the OS-level interface name for an inbound, e.g.
- // "awg42". Kept even though the embedded path has no real kernel interface
- // of its own: internal/amneziawgnet still uses the same name as a purely
- // cosmetic/log-friendly label, so an existing peer's identity/history
- // doesn't shift across the cutover.
- func interfaceNameForID(id int) string {
- return fmt.Sprintf("awg%d", id)
- }
- // serverAddress returns the server's own tunnel address for a subnet base,
- // e.g. "10.8.1.1/24" for base "10.8.1.0" or "10.8.1.5". The server always
- // holds the first usable host of the network subnetIP/cidr actually
- // describes -- derived via netip rather than assuming subnetIP already ends
- // in ".0", so a subnetIP that isn't a bare network address (a typo, or a
- // manually edited value) can never collide with peer addresses, which are
- // allocated starting from the network's second host upward (see
- // allocateWireguardAddress). Falls back to the previous literal behavior
- // only if subnetIP/cidr doesn't parse as an IPv4 network at all -- normal
- // saves never reach that path since ValidateSubnetIPv4 already rejects it.
- func serverAddress(subnetIP string, cidr int) string {
- if cidr <= 0 {
- cidr = 24
- }
- // A /32 has no host bits at all -- "first usable host" is meaningless,
- // and Next() would step outside the block entirely -- so a single-host
- // base is used exactly as given, same as before this fix.
- prefix, err := netip.ParsePrefix(fmt.Sprintf("%s/%d", subnetIP, cidr))
- if err != nil || !prefix.Addr().Is4() || cidr >= 32 {
- return fmt.Sprintf("%s/%d", subnetIP, cidr)
- }
- host := prefix.Masked().Addr().Next()
- return fmt.Sprintf("%s/%d", host, cidr)
- }
- // serverAddressV6 returns the server's own IPv6 tunnel address for a subnet
- // CIDR (e.g. "fd86:ea04:1115::1/64" for "fd86:ea04:1115::/64"), the first
- // usable host in the prefix. ok is false when subnetCIDR is empty or not a
- // valid IPv6 prefix.
- func serverAddressV6(subnetCIDR string) (addr string, ok bool) {
- prefix, err := netip.ParsePrefix(subnetCIDR)
- if err != nil || !prefix.Addr().Is6() {
- return "", false
- }
- host := prefix.Masked().Addr().Next()
- return fmt.Sprintf("%s/%d", host, prefix.Bits()), true
- }
- // FirstIPv4 returns the first IPv4 address (mask stripped) among allowedIPs,
- // or "" if none — used to derive a peer's tunnel IPv4 address.
- func FirstIPv4(allowedIPs []string) string {
- for _, a := range allowedIPs {
- if prefix, err := netip.ParsePrefix(a); err == nil {
- if prefix.Addr().Is4() {
- return prefix.Addr().String()
- }
- continue
- }
- if addr, err := netip.ParseAddr(a); err == nil && addr.Is4() {
- return addr.String()
- }
- }
- return ""
- }
- // FirstIPv6 returns the first IPv6 address (mask stripped) among allowedIPs,
- // or "" if none — the IPv6 counterpart of FirstIPv4, used by
- // internal/amneziawgnet's IPv6-address-alias mechanism to find which
- // address, if any, a peer wants aliased onto the host, and by
- // internal/web/service/xray.go's injectAmneziawgV6Egress to build that
- // peer's own freedom outbound (sendThrough). Only the first match is
- // returned, exactly like FirstIPv4 — more than one IPv6 AllowedIPs entry
- // per peer is not a supported configuration for either feature.
- func FirstIPv6(allowedIPs []string) string {
- for _, a := range allowedIPs {
- if prefix, err := netip.ParsePrefix(a); err == nil {
- if prefix.Addr().Is6() && !prefix.Addr().Is4In6() {
- return prefix.Addr().String()
- }
- continue
- }
- if addr, err := netip.ParseAddr(a); err == nil && addr.Is6() && !addr.Is4In6() {
- return addr.String()
- }
- }
- return ""
- }
|