portfwd.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. // Phase 3.6: per-client port-forwarding. A real Go listener bound to each
  2. // forwarded external port relays into the peer's own tunnel-internal
  3. // address via a direct gonet dial -- the mirror image of
  4. // AttachTCPForwarder/AttachUDPHandler (which relay FROM the tunnel TO the
  5. // real world), and this path's replacement for the retired kernel-module
  6. // architecture's PostUp/PostDown iptables DNAT rules: there's no real OS
  7. // network interface here for DNAT to rewrite packets on, the same root
  8. // reason Phase 3.5's IPv6 alias mechanism couldn't reuse NDP-proxy either.
  9. //
  10. // Deliberately dials straight into the gVisor stack rather than relaying
  11. // through Xray's own SOCKS5 inbound the way the outbound direction does
  12. // (relay.go): Xray runs as a genuinely separate OS process
  13. // (internal/xray/process.go), so it has no visibility into this process's
  14. // private, in-memory netstack at all -- a tunnel-internal address like
  15. // 10.8.1.5:8080 has no route from Xray's own freedom outbound; only code
  16. // holding the actual *stack.Stack can reach it. Accepted consequence:
  17. // forwarded-port bytes don't appear in Xray's per-email stats/quota
  18. // counters. This undercounts, it doesn't bypass enforcement -- a
  19. // depleted/disabled client's peer is dropped from the interface's peer list
  20. // entirely by DesiredAmneziaWGInstances, which tears its forwards down too
  21. // as a side effect of Reconcile's own diff below.
  22. package amneziawgnet
  23. import (
  24. "context"
  25. "fmt"
  26. "net"
  27. "net/netip"
  28. "sync"
  29. "time"
  30. "gvisor.dev/gvisor/pkg/tcpip"
  31. "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
  32. "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
  33. "gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
  34. "gvisor.dev/gvisor/pkg/tcpip/stack"
  35. "github.com/mhsanaei/3x-ui/v3/internal/amneziawg"
  36. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  37. )
  38. // portForwardProto distinguishes the two sockets a single forwarded port
  39. // needs -- ForwardedPorts has no per-port protocol selector (matches the
  40. // retired DNAT implementation's own unconditional-TCP+UDP contract), so
  41. // every port gets both.
  42. type portForwardProto uint8
  43. const (
  44. tcpForward portForwardProto = iota
  45. udpForward
  46. )
  47. // portForwardKey identifies one listener: a specific peer's specific port on
  48. // a specific protocol. Two different peers (even on the same inbound)
  49. // forwarding the same port number get two independent listeners under two
  50. // independent keys -- a same-port collision surfaces as an ordinary bind
  51. // failure on whichever one opens second, not something actively prevented
  52. // here (see the migration plan's Phase 3.6 notes).
  53. type portForwardKey struct {
  54. email string
  55. port int
  56. proto portForwardProto
  57. }
  58. // portForwardTargetFunc resolves a peer's current tunnel-internal target
  59. // address by email, re-checked on every new connection/session rather than
  60. // captured once at listen time -- so a peer re-IP takes effect for the next
  61. // connection with zero listener churn (see Reconcile's own comment on
  62. // this). false means the peer has no resolvable target right now (removed,
  63. // or its AllowedIPs/ForwardedPorts changed): the caller drops the
  64. // connection/packet, and Reconcile will close the now-undesired listener
  65. // shortly after, if it hasn't already.
  66. type portForwardTargetFunc func(email string) (netip.Addr, bool)
  67. // portForwardListener is the common handle both listenPortForwardTCP and
  68. // listenPortForwardUDP return, so PortForwardSet can hold either behind one
  69. // map value type without a type switch.
  70. type portForwardListener interface {
  71. Close()
  72. }
  73. // PortForwardSet owns every open port-forward listener for one embedded
  74. // AmneziaWG interface (one per amneziawgnet managed entry -- see
  75. // manager.go). Unlike v6alias.go's stateless desired/diff/apply functions,
  76. // this holds live Go resources (net.Listener/net.PacketConn) that must be
  77. // explicitly closed -- there's no OS-level idempotent recreate the way
  78. // `ip addr add` has -- so Reconcile diffs against its own live listeners
  79. // map directly instead of a remembered prior Instance.
  80. type PortForwardSet struct {
  81. gstack *stack.Stack
  82. inboundID int
  83. mu sync.Mutex
  84. peerTargets map[string]netip.Addr
  85. listeners map[portForwardKey]portForwardListener
  86. }
  87. // NewPortForwardSet creates an empty supervisor for one embedded interface's
  88. // stack. Call Reconcile to actually open any listeners.
  89. func NewPortForwardSet(gstack *stack.Stack, inboundID int) *PortForwardSet {
  90. return &PortForwardSet{
  91. gstack: gstack,
  92. inboundID: inboundID,
  93. peerTargets: map[string]netip.Addr{},
  94. listeners: map[portForwardKey]portForwardListener{},
  95. }
  96. }
  97. // desiredPeerTargets resolves each peer's tunnel-internal target address:
  98. // the first IPv4 AllowedIPs entry, falling back to the first IPv6 entry only
  99. // when no v4 entry exists and the instance has IPv6 enabled (mirrors
  100. // desiredV6Aliases' own gating in v6alias.go -- no v6 route exists on the
  101. // stack otherwise). A peer with no resolvable address at all (neither
  102. // family, or an unparseable entry) is simply absent from the result.
  103. func desiredPeerTargets(inst amneziawg.Instance) map[string]netip.Addr {
  104. out := map[string]netip.Addr{}
  105. for _, p := range inst.Peers {
  106. if p.Email == "" {
  107. continue
  108. }
  109. raw := amneziawg.FirstIPv4(p.AllowedIPs)
  110. if raw == "" && inst.IPv6Enabled {
  111. raw = amneziawg.FirstIPv6(p.AllowedIPs)
  112. }
  113. if raw == "" {
  114. continue
  115. }
  116. addr, err := netip.ParseAddr(raw)
  117. if err != nil {
  118. continue
  119. }
  120. out[p.Email] = addr
  121. }
  122. return out
  123. }
  124. // forwardingPeers is the one gate a host listener comes from: no email, port
  125. // spec and resolvable target (see desiredPeerTargets), no socket.
  126. func forwardingPeers(inst amneziawg.Instance) []amneziawg.Peer {
  127. targets := desiredPeerTargets(inst)
  128. out := make([]amneziawg.Peer, 0, len(inst.Peers))
  129. for _, p := range inst.Peers {
  130. if p.Email == "" || p.ForwardedPorts == "" {
  131. continue
  132. }
  133. if _, ok := targets[p.Email]; !ok {
  134. continue
  135. }
  136. out = append(out, p)
  137. }
  138. return out
  139. }
  140. // desiredPortForwardKeys returns every listener key inst wants right now: one
  141. // tcpForward and one udpForward per forwarded port of the forwarding peers.
  142. func desiredPortForwardKeys(inst amneziawg.Instance) map[portForwardKey]struct{} {
  143. out := map[portForwardKey]struct{}{}
  144. for _, p := range forwardingPeers(inst) {
  145. for _, port := range amneziawg.ExpandForwardedPorts(p.ForwardedPorts) {
  146. out[portForwardKey{email: p.Email, port: port, proto: tcpForward}] = struct{}{}
  147. out[portForwardKey{email: p.Email, port: port, proto: udpForward}] = struct{}{}
  148. }
  149. }
  150. return out
  151. }
  152. // ForwardedPortOwner names the peer Reconcile opens a listener on port for --
  153. // the same peers and expansion as desiredPortForwardKeys, never a silent one.
  154. func ForwardedPortOwner(inst amneziawg.Instance, port int) (string, bool) {
  155. for _, p := range forwardingPeers(inst) {
  156. for _, candidate := range amneziawg.ExpandForwardedPorts(p.ForwardedPorts) {
  157. if candidate == port {
  158. return p.Email, true
  159. }
  160. }
  161. }
  162. return "", false
  163. }
  164. // Reconcile brings the supervisor's open listeners in line with what inst
  165. // currently wants: closes anything no longer desired, opens anything newly
  166. // desired, leaves everything else untouched. Never returns an error --
  167. // matches applyV6Aliases' contract exactly: one listener failing to bind
  168. // only narrows that specific forward, never a reason to fail the whole
  169. // reconcile.
  170. func (s *PortForwardSet) Reconcile(inst amneziawg.Instance) {
  171. wantTargets := desiredPeerTargets(inst)
  172. wantKeys := desiredPortForwardKeys(inst)
  173. s.mu.Lock()
  174. s.peerTargets = wantTargets
  175. var toClose []portForwardListener
  176. for key, ln := range s.listeners {
  177. if _, ok := wantKeys[key]; ok {
  178. continue
  179. }
  180. toClose = append(toClose, ln)
  181. delete(s.listeners, key)
  182. }
  183. var toOpen []portForwardKey
  184. for key := range wantKeys {
  185. if _, ok := s.listeners[key]; ok {
  186. continue
  187. }
  188. toOpen = append(toOpen, key)
  189. }
  190. s.mu.Unlock()
  191. // Outside the lock: closing/opening real sockets shouldn't block a
  192. // concurrent targetFor lookup from an in-flight connection on some
  193. // other, unaffected listener.
  194. for _, ln := range toClose {
  195. ln.Close()
  196. }
  197. for _, key := range toOpen {
  198. ln := openPortForwardListener(s.gstack, s.inboundID, key, s.targetFor)
  199. if ln == nil {
  200. continue
  201. }
  202. s.mu.Lock()
  203. s.listeners[key] = ln
  204. s.mu.Unlock()
  205. }
  206. }
  207. // targetFor implements portForwardTargetFunc against the supervisor's
  208. // current peerTargets snapshot.
  209. func (s *PortForwardSet) targetFor(email string) (netip.Addr, bool) {
  210. s.mu.Lock()
  211. defer s.mu.Unlock()
  212. addr, ok := s.peerTargets[email]
  213. return addr, ok
  214. }
  215. // Close tears down every open listener. Call when the owning Device is
  216. // closed (or rebuilt -- see manager.go's ensureLocked, which always
  217. // constructs a fresh PortForwardSet alongside a fresh Device.Stack, the
  218. // same reason it also rebuilds udpRelay from scratch rather than reusing
  219. // one bound to a discarded stack).
  220. func (s *PortForwardSet) Close() {
  221. s.mu.Lock()
  222. listeners := s.listeners
  223. s.listeners = map[portForwardKey]portForwardListener{}
  224. s.mu.Unlock()
  225. for _, ln := range listeners {
  226. ln.Close()
  227. }
  228. }
  229. // openPortForwardListener dispatches to the protocol-specific opener and
  230. // normalizes its result to a real nil interface value on failure -- a
  231. // (*tcpForwardListener)(nil) (or *udpForwardListener(nil)) wrapped directly
  232. // into the portForwardListener interface would be a non-nil interface
  233. // holding a nil pointer, Go's classic trap, so the concrete pointer is
  234. // checked before it's ever assigned into the interface-typed return.
  235. func openPortForwardListener(gstack *stack.Stack, inboundID int, key portForwardKey, target portForwardTargetFunc) portForwardListener {
  236. switch key.proto {
  237. case tcpForward:
  238. if ln := listenPortForwardTCP(gstack, inboundID, key, target); ln != nil {
  239. return ln
  240. }
  241. case udpForward:
  242. if ln := listenPortForwardUDP(gstack, inboundID, key, target); ln != nil {
  243. return ln
  244. }
  245. }
  246. return nil
  247. }
  248. const portForwardDialTimeout = 10 * time.Second
  249. // tunnelNetwork returns the gVisor network protocol number matching addr's
  250. // address family, for dialing toward it inside the embedded stack.
  251. func tunnelNetwork(addr netip.Addr) tcpip.NetworkProtocolNumber {
  252. if addr.Is4() {
  253. return ipv4.ProtocolNumber
  254. }
  255. return ipv6.ProtocolNumber
  256. }
  257. // tunnelFullAddress builds the tcpip.FullAddress a gonet dial needs to
  258. // reach addr:port inside the embedded stack -- NIC 1, matching
  259. // createNetTUNWithStack's own CreateNIC(1, ...) (this package's stack only
  260. // ever registers one NIC, and WriteUDPReply's WriteRawPacket already
  261. // addresses it explicitly the same way elsewhere in this package, rather
  262. // than relying on NIC 0's route-table auto-selection).
  263. func tunnelFullAddress(addr netip.Addr, port int) tcpip.FullAddress {
  264. return tcpip.FullAddress{NIC: 1, Addr: tcpip.AddrFromSlice(addr.AsSlice()), Port: uint16(port)}
  265. }
  266. // tcpForwardListener is one open host-facing TCP listener for a single
  267. // portForwardKey.
  268. type tcpForwardListener struct {
  269. ln net.Listener
  270. closing chan struct{}
  271. }
  272. // listenPortForwardTCP opens a host-facing TCP listener on key.port and
  273. // starts relaying accepted connections into the tunnel toward
  274. // target(key.email). A bind failure (most commonly EADDRINUSE, whether from
  275. // an unrelated process or another AmneziaWG peer/inbound that already
  276. // claimed the same port) is logged and returns nil; Reconcile treats a nil
  277. // result as "not open this round" and retries on every future Reconcile
  278. // call for as long as the key stays desired.
  279. func listenPortForwardTCP(gstack *stack.Stack, inboundID int, key portForwardKey, target portForwardTargetFunc) *tcpForwardListener {
  280. ln, err := (&net.ListenConfig{}).Listen(context.Background(), "tcp", fmt.Sprintf(":%d", key.port))
  281. if err != nil {
  282. logger.Warningf("amneziawgnet: port-forward: inbound %d peer %q: listen tcp :%d: %v", inboundID, key.email, key.port, err)
  283. return nil
  284. }
  285. l := &tcpForwardListener{ln: ln, closing: make(chan struct{})}
  286. logger.Infof("amneziawgnet: port-forward: inbound %d peer %q: listening tcp :%d", inboundID, key.email, key.port)
  287. go l.acceptLoop(gstack, inboundID, key, target)
  288. return l
  289. }
  290. func (l *tcpForwardListener) acceptLoop(gstack *stack.Stack, inboundID int, key portForwardKey, target portForwardTargetFunc) {
  291. for {
  292. conn, err := l.ln.Accept()
  293. if err != nil {
  294. select {
  295. case <-l.closing:
  296. return // intentional shutdown, not a real accept error
  297. default:
  298. }
  299. logger.Warningf("amneziawgnet: port-forward: inbound %d peer %q: accept tcp :%d: %v", inboundID, key.email, key.port, err)
  300. return
  301. }
  302. go relayTCPForward(gstack, conn, inboundID, key, target)
  303. }
  304. }
  305. func relayTCPForward(gstack *stack.Stack, conn net.Conn, inboundID int, key portForwardKey, target portForwardTargetFunc) {
  306. defer conn.Close()
  307. addr, ok := target(key.email)
  308. if !ok {
  309. return
  310. }
  311. ctx, cancel := context.WithTimeout(context.Background(), portForwardDialTimeout)
  312. defer cancel()
  313. tunnelConn, err := gonet.DialContextTCP(ctx, gstack, tunnelFullAddress(addr, key.port), tunnelNetwork(addr))
  314. if err != nil {
  315. logger.Warningf("amneziawgnet: port-forward: inbound %d peer %q: dial tunnel %s:%d: %v", inboundID, key.email, addr, key.port, err)
  316. return
  317. }
  318. defer tunnelConn.Close()
  319. pipeBothWays(conn, tunnelConn)
  320. }
  321. // Close stops accepting new connections. Already-relaying connections are
  322. // left to finish on their own -- there's no shared state to tear down early
  323. // for, and an abrupt cut would just look like a network error to whichever
  324. // external client was mid-transfer.
  325. func (l *tcpForwardListener) Close() {
  326. close(l.closing)
  327. l.ln.Close()
  328. }