relay.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. // Phase 2: relaying a recovered tunnel connection into Xray's own,
  2. // completely stock SOCKS5 inbound -- authenticating as the owning peer's
  3. // email -- is what gives every embedded AmneziaWG connection real, native
  4. // Xray stats/routing/sniffing with no Xray-core fork at all (Finding 3 of
  5. // the migration plan: a stock SOCKS5 inbound sets its per-connection stats
  6. // identity directly from the SOCKS5 auth username).
  7. package amneziawgnet
  8. import (
  9. "context"
  10. "encoding/binary"
  11. "encoding/json"
  12. "fmt"
  13. "io"
  14. "net"
  15. "net/netip"
  16. "sync"
  17. "sync/atomic"
  18. "time"
  19. "golang.org/x/net/proxy"
  20. "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
  21. "gvisor.dev/gvisor/pkg/tcpip/stack"
  22. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  23. )
  24. // SocksRelay describes the loopback SOCKS5 inbound decapsulated AmneziaWG
  25. // traffic gets relayed into.
  26. type SocksRelay struct {
  27. // Addr is the SOCKS5 inbound's own address, e.g. "127.0.0.1:11500".
  28. Addr string
  29. // Password is shared across every account. This traffic never leaves
  30. // loopback, so the password is not a real secrecy boundary -- it only
  31. // needs to satisfy Xray's SOCKS5 inbound requiring *some* username/
  32. // password auth before it will accept a connection and use the
  33. // username as the stats identity. Document this reasoning wherever a
  34. // caller generates or displays it, so it's never mistaken later for a
  35. // real credential.
  36. Password string
  37. }
  38. // SocksInboundSettings builds the JSON `settings` block for a stock Xray
  39. // SOCKS5 inbound with one username/password account per email, all sharing
  40. // password (see SocksRelay's doc comment). udp:true is required: RelayUDP
  41. // depends on the inbound accepting UDP ASSOCIATE, not just CONNECT.
  42. func SocksInboundSettings(emails []string, password string) ([]byte, error) {
  43. type account struct {
  44. User string `json:"user"`
  45. Pass string `json:"pass"`
  46. }
  47. settings := struct {
  48. Auth string `json:"auth"`
  49. UDP bool `json:"udp"`
  50. Accounts []account `json:"accounts"`
  51. }{Auth: "password", UDP: true}
  52. for _, email := range emails {
  53. settings.Accounts = append(settings.Accounts, account{User: email, Pass: password})
  54. }
  55. return json.Marshal(settings)
  56. }
  57. // RelayTCP dials r.Addr, authenticates as email, issues a SOCKS5 CONNECT to
  58. // dest, and pipes bytes both ways until both directions end.
  59. // Blocks until the relay ends; meant to be called from (or as) an
  60. // AttachTCPForwarder handler, which already runs each connection on its own
  61. // goroutine.
  62. func (r SocksRelay) RelayTCP(conn *gonet.TCPConn, email string, dest netip.AddrPort) {
  63. defer conn.Close()
  64. auth := &proxy.Auth{User: email, Password: r.Password}
  65. dialer, err := proxy.SOCKS5("tcp", r.Addr, auth, proxy.Direct)
  66. if err != nil {
  67. logger.Warningf("amneziawgnet: RelayTCP: build SOCKS5 dialer: %v", err)
  68. return
  69. }
  70. upstream, err := dialer.Dial("tcp", dest.String())
  71. if err != nil {
  72. logger.Warningf("amneziawgnet: RelayTCP: SOCKS5 CONNECT to %s as %q: %v", dest, email, err)
  73. return
  74. }
  75. defer upstream.Close()
  76. pipeBothWays(conn, upstream)
  77. }
  78. // halfCloseIdle bounds how long the surviving direction of a half-closed pair
  79. // may sit idle, so a peer that vanished mid-transfer cannot pin it forever.
  80. const halfCloseIdle = 2 * time.Minute
  81. // closeWriter is the half-close half of *net.TCPConn and *gonet.TCPConn.
  82. type closeWriter interface{ CloseWrite() error }
  83. // guardedReader reads one side of a relayed pair, re-arming its read deadline
  84. // on every read once armed, so the bound is an idle window, not a total one.
  85. type guardedReader struct {
  86. conn net.Conn
  87. armed atomic.Bool
  88. }
  89. func (r *guardedReader) Read(p []byte) (int, error) {
  90. if r.armed.Load() {
  91. _ = r.conn.SetReadDeadline(time.Now().Add(halfCloseIdle))
  92. }
  93. return r.conn.Read(p)
  94. }
  95. // arm bounds this side's remaining reads, including one already in flight.
  96. func (r *guardedReader) arm() {
  97. r.armed.Store(true)
  98. _ = r.conn.SetReadDeadline(time.Now().Add(halfCloseIdle))
  99. }
  100. // pipeBothWays copies a and b into each other until BOTH directions end,
  101. // half-closing each far side in turn so a half-closed peer still gets its reply.
  102. func pipeBothWays(a, b net.Conn) {
  103. ga, gb := &guardedReader{conn: a}, &guardedReader{conn: b}
  104. var wg sync.WaitGroup
  105. wg.Add(2)
  106. // Arming dst bounds the direction still reading from it -- the one this
  107. // copy just signalled EOF to.
  108. pipe := func(dst, src *guardedReader) {
  109. defer wg.Done()
  110. _, _ = io.Copy(dst.conn, src)
  111. if cw, ok := dst.conn.(closeWriter); ok {
  112. _ = cw.CloseWrite()
  113. } else {
  114. _ = dst.conn.Close()
  115. }
  116. dst.arm()
  117. }
  118. go pipe(gb, ga)
  119. go pipe(ga, gb)
  120. wg.Wait()
  121. }
  122. // socks5UDPSession is one established SOCKS5 UDP ASSOCIATE session: udpConn
  123. // is the actual socket packets are sent to (and replies read from); ctrl is
  124. // the TCP control connection that must stay open for the session's
  125. // lifetime -- per RFC 1928, closing it tears the association down.
  126. type socks5UDPSession struct {
  127. ctrl net.Conn
  128. udpConn *net.UDPConn
  129. }
  130. // Bounds dial plus the greeting/auth/associate reads: an accepted-but-silent
  131. // server otherwise parks Handle, and with it the tunnel's delivery path.
  132. var socks5AssociateTimeout = 5 * time.Second
  133. // newSocks5UDPSession performs the SOCKS5 greeting, username/password auth,
  134. // and UDP ASSOCIATE request/reply by hand: golang.org/x/net/proxy's SOCKS5
  135. // client (used by RelayTCP above) only implements CONNECT, and xray-core's
  136. // own proxy/socks/client.go is written against its internal transport
  137. // types, not reusable as a standalone dialer -- so this is a small, direct,
  138. // from-the-RFC implementation rather than an existing library call.
  139. func newSocks5UDPSession(addr, user, password string) (*socks5UDPSession, error) {
  140. deadline := time.Now().Add(socks5AssociateTimeout)
  141. dialer := net.Dialer{Deadline: deadline}
  142. ctrl, err := dialer.DialContext(context.Background(), "tcp", addr)
  143. if err != nil {
  144. return nil, fmt.Errorf("amneziawgnet: dial SOCKS5 control connection: %w", err)
  145. }
  146. _ = ctrl.SetDeadline(deadline)
  147. if err := socks5Handshake(ctrl, user, password); err != nil {
  148. ctrl.Close()
  149. return nil, err
  150. }
  151. // UDP ASSOCIATE, dst 0.0.0.0:0 ("I don't know my own source yet, and I
  152. // don't need to specify one for a loopback relay").
  153. if _, err := ctrl.Write([]byte{0x05, 0x03, 0x00, 0x01, 0, 0, 0, 0, 0, 0}); err != nil {
  154. ctrl.Close()
  155. return nil, fmt.Errorf("amneziawgnet: send UDP ASSOCIATE request: %w", err)
  156. }
  157. bind, err := readSocks5Reply(ctrl)
  158. if err != nil {
  159. ctrl.Close()
  160. return nil, err
  161. }
  162. _ = ctrl.SetDeadline(time.Time{})
  163. udpConn, err := net.DialUDP("udp", nil, net.UDPAddrFromAddrPort(bind))
  164. if err != nil {
  165. ctrl.Close()
  166. return nil, fmt.Errorf("amneziawgnet: dial SOCKS5 UDP relay endpoint %s: %w", bind, err)
  167. }
  168. return &socks5UDPSession{ctrl: ctrl, udpConn: udpConn}, nil
  169. }
  170. // socks5Handshake performs the version greeting and (if the server
  171. // requires it) username/password auth. Xray's SOCKS5 inbound with
  172. // auth:"password" always requires it; the no-auth branch exists so this
  173. // helper isn't silently wrong against a differently-configured server.
  174. func socks5Handshake(conn net.Conn, user, password string) error {
  175. if _, err := conn.Write([]byte{0x05, 0x02, 0x00, 0x02}); err != nil {
  176. return fmt.Errorf("amneziawgnet: send SOCKS5 greeting: %w", err)
  177. }
  178. var resp [2]byte
  179. if _, err := io.ReadFull(conn, resp[:]); err != nil {
  180. return fmt.Errorf("amneziawgnet: read SOCKS5 greeting reply: %w", err)
  181. }
  182. if resp[0] != 0x05 {
  183. return fmt.Errorf("amneziawgnet: unexpected SOCKS5 version %d", resp[0])
  184. }
  185. switch resp[1] {
  186. case 0x00: // no auth required
  187. return nil
  188. case 0x02: // username/password
  189. req := make([]byte, 0, 3+len(user)+len(password))
  190. req = append(req, 0x01, byte(len(user)))
  191. req = append(req, user...)
  192. req = append(req, byte(len(password)))
  193. req = append(req, password...)
  194. if _, err := conn.Write(req); err != nil {
  195. return fmt.Errorf("amneziawgnet: send SOCKS5 auth: %w", err)
  196. }
  197. var authResp [2]byte
  198. if _, err := io.ReadFull(conn, authResp[:]); err != nil {
  199. return fmt.Errorf("amneziawgnet: read SOCKS5 auth reply: %w", err)
  200. }
  201. if authResp[1] != 0x00 {
  202. return fmt.Errorf("amneziawgnet: SOCKS5 auth rejected (status %d)", authResp[1])
  203. }
  204. return nil
  205. default:
  206. return fmt.Errorf("amneziawgnet: SOCKS5 server offered unsupported auth method %d", resp[1])
  207. }
  208. }
  209. // readSocks5Reply reads a SOCKS5 reply (the common format shared by CONNECT
  210. // and UDP ASSOCIATE replies) and returns its bound address.
  211. func readSocks5Reply(r io.Reader) (netip.AddrPort, error) {
  212. var hdr [4]byte
  213. if _, err := io.ReadFull(r, hdr[:]); err != nil {
  214. return netip.AddrPort{}, fmt.Errorf("amneziawgnet: read SOCKS5 reply header: %w", err)
  215. }
  216. if hdr[0] != 0x05 {
  217. return netip.AddrPort{}, fmt.Errorf("amneziawgnet: unexpected SOCKS5 reply version %d", hdr[0])
  218. }
  219. if hdr[1] != 0x00 {
  220. return netip.AddrPort{}, fmt.Errorf("amneziawgnet: SOCKS5 request failed (reply code %d)", hdr[1])
  221. }
  222. addr, err := readSocks5Addr(r, hdr[3])
  223. if err != nil {
  224. return netip.AddrPort{}, err
  225. }
  226. var portBytes [2]byte
  227. if _, err := io.ReadFull(r, portBytes[:]); err != nil {
  228. return netip.AddrPort{}, fmt.Errorf("amneziawgnet: read SOCKS5 reply port: %w", err)
  229. }
  230. return netip.AddrPortFrom(addr, binary.BigEndian.Uint16(portBytes[:])), nil
  231. }
  232. // readSocks5Addr reads the address portion of a SOCKS5 reply for the given
  233. // address type (IPv4, IPv6, or domain -- resolved locally since a loopback
  234. // Xray inbound is not expected to reply with one, but it's cheap to handle
  235. // correctly rather than fail oddly if it ever does).
  236. func readSocks5Addr(r io.Reader, atyp byte) (netip.Addr, error) {
  237. switch atyp {
  238. case 0x01:
  239. var b [4]byte
  240. if _, err := io.ReadFull(r, b[:]); err != nil {
  241. return netip.Addr{}, err
  242. }
  243. return netip.AddrFrom4(b), nil
  244. case 0x04:
  245. var b [16]byte
  246. if _, err := io.ReadFull(r, b[:]); err != nil {
  247. return netip.Addr{}, err
  248. }
  249. return netip.AddrFrom16(b), nil
  250. case 0x03:
  251. var l [1]byte
  252. if _, err := io.ReadFull(r, l[:]); err != nil {
  253. return netip.Addr{}, err
  254. }
  255. name := make([]byte, l[0])
  256. if _, err := io.ReadFull(r, name); err != nil {
  257. return netip.Addr{}, err
  258. }
  259. resolved, err := net.ResolveIPAddr("ip", string(name))
  260. if err != nil {
  261. return netip.Addr{}, fmt.Errorf("amneziawgnet: resolve SOCKS5 domain reply %q: %w", name, err)
  262. }
  263. addr, ok := netip.AddrFromSlice(resolved.IP)
  264. if !ok {
  265. return netip.Addr{}, fmt.Errorf("amneziawgnet: unparseable resolved SOCKS5 domain reply address")
  266. }
  267. return addr, nil
  268. default:
  269. return netip.Addr{}, fmt.Errorf("amneziawgnet: unsupported SOCKS5 address type %d", atyp)
  270. }
  271. }
  272. // Close ends the UDP ASSOCIATE session: closing ctrl tells the SOCKS5
  273. // server to tear down its relay side too (RFC 1928).
  274. func (s *socks5UDPSession) Close() error {
  275. s.udpConn.Close()
  276. return s.ctrl.Close()
  277. }
  278. // sendTo wraps payload in a SOCKS5 UDP request header addressed to dest and
  279. // sends it to the session's relay endpoint.
  280. func (s *socks5UDPSession) sendTo(dest netip.AddrPort, payload []byte) error {
  281. hdr := make([]byte, 0, 3+1+16+2+len(payload))
  282. hdr = append(hdr, 0x00, 0x00, 0x00) // RSV RSV FRAG(=0, no fragmentation)
  283. if dest.Addr().Is4() {
  284. b := dest.Addr().As4()
  285. hdr = append(hdr, 0x01)
  286. hdr = append(hdr, b[:]...)
  287. } else {
  288. b := dest.Addr().As16()
  289. hdr = append(hdr, 0x04)
  290. hdr = append(hdr, b[:]...)
  291. }
  292. var portBytes [2]byte
  293. binary.BigEndian.PutUint16(portBytes[:], dest.Port())
  294. hdr = append(hdr, portBytes[:]...)
  295. hdr = append(hdr, payload...)
  296. _, err := s.udpConn.Write(hdr)
  297. return err
  298. }
  299. // receive reads one reply datagram into buf, returning the address the
  300. // SOCKS5 server says it came from and the actual payload (a sub-slice of
  301. // buf -- valid only until the next receive call).
  302. func (s *socks5UDPSession) receive(buf []byte) (netip.AddrPort, []byte, error) {
  303. n, err := s.udpConn.Read(buf)
  304. if err != nil {
  305. return netip.AddrPort{}, nil, err
  306. }
  307. data := buf[:n]
  308. if len(data) < 4 {
  309. return netip.AddrPort{}, nil, fmt.Errorf("amneziawgnet: short SOCKS5 UDP reply (%d bytes)", n)
  310. }
  311. addr, rest, err := splitSocks5Addr(data[4:], data[3])
  312. if err != nil {
  313. return netip.AddrPort{}, nil, err
  314. }
  315. if len(rest) < 2 {
  316. return netip.AddrPort{}, nil, fmt.Errorf("amneziawgnet: truncated SOCKS5 UDP reply port")
  317. }
  318. return netip.AddrPortFrom(addr, binary.BigEndian.Uint16(rest[:2])), rest[2:], nil
  319. }
  320. // splitSocks5Addr decodes the address at the head of b for address type atyp
  321. // and returns it with whatever follows, length-checked at every step.
  322. func splitSocks5Addr(b []byte, atyp byte) (netip.Addr, []byte, error) {
  323. switch atyp {
  324. case 0x01:
  325. if len(b) < 4 {
  326. return netip.Addr{}, nil, fmt.Errorf("amneziawgnet: truncated SOCKS5 IPv4 reply address")
  327. }
  328. return netip.AddrFrom4([4]byte(b[:4])), b[4:], nil
  329. case 0x04:
  330. if len(b) < 16 {
  331. return netip.Addr{}, nil, fmt.Errorf("amneziawgnet: truncated SOCKS5 IPv6 reply address")
  332. }
  333. return netip.AddrFrom16([16]byte(b[:16])), b[16:], nil
  334. case 0x03:
  335. // Resolving here would block the receive loop on DNS, and a datagram's
  336. // own source is an address already -- so only a literal is accepted.
  337. if len(b) < 1 || len(b) < 1+int(b[0]) {
  338. return netip.Addr{}, nil, fmt.Errorf("amneziawgnet: truncated SOCKS5 domain reply address")
  339. }
  340. name := string(b[1 : 1+int(b[0])])
  341. addr, err := netip.ParseAddr(name)
  342. if err != nil {
  343. return netip.Addr{}, nil, fmt.Errorf("amneziawgnet: SOCKS5 UDP reply from non-literal address %q", name)
  344. }
  345. return addr, b[1+int(b[0]):], nil
  346. default:
  347. return netip.Addr{}, nil, fmt.Errorf("amneziawgnet: unsupported SOCKS5 address type %d", atyp)
  348. }
  349. }
  350. // UDPRelay tracks one SOCKS5 UDP ASSOCIATE session per source (tunnel-
  351. // internal client) flow, relaying each into r's SOCKS5 inbound and writing
  352. // replies back through gstack -- the UDP counterpart of RelayTCP, meant to
  353. // be driven by an AttachUDPHandler callback (see udp.go).
  354. type UDPRelay struct {
  355. relay SocksRelay
  356. gstack *stack.Stack
  357. // Keyed by the comparable netip.AddrPort, like udpForwardListener's own
  358. // session map: src.String() would allocate on every relayed datagram.
  359. mu sync.Mutex
  360. sessions map[netip.AddrPort]*socks5UDPSession
  361. }
  362. // NewUDPRelay creates a UDPRelay for one embedded AmneziaWG Device's stack.
  363. func NewUDPRelay(relay SocksRelay, gstack *stack.Stack) *UDPRelay {
  364. return &UDPRelay{relay: relay, gstack: gstack, sessions: map[netip.AddrPort]*socks5UDPSession{}}
  365. }
  366. // Handle relays one packet from src (the peer's tunnel-internal source) to
  367. // dst (its real, recovered destination), opening a fresh SOCKS5 UDP
  368. // ASSOCIATE session for src the first time it's seen (authenticating as
  369. // email, so Xray attributes the whole flow's stats to the right peer) and
  370. // reusing it for subsequent packets from the same src.
  371. func (u *UDPRelay) Handle(src, dst netip.AddrPort, email string, payload []byte) {
  372. u.mu.Lock()
  373. sess, ok := u.sessions[src]
  374. u.mu.Unlock()
  375. if !ok {
  376. fresh, err := newSocks5UDPSession(u.relay.Addr, email, u.relay.Password)
  377. if err != nil {
  378. logger.Warningf("amneziawgnet: UDPRelay: SOCKS5 associate for %q: %v", email, err)
  379. return
  380. }
  381. u.mu.Lock()
  382. // Associating happens off-lock, so a concurrent Handle for the same src
  383. // may already have published one; keep it, so the key has a single pump.
  384. if existing, dup := u.sessions[src]; dup {
  385. u.mu.Unlock()
  386. fresh.Close()
  387. sess = existing
  388. } else {
  389. u.sessions[src] = fresh
  390. u.mu.Unlock()
  391. sess = fresh
  392. go u.pump(src, fresh)
  393. }
  394. }
  395. if err := sess.sendTo(dst, payload); err != nil {
  396. logger.Warningf("amneziawgnet: UDPRelay: send to %s: %v", dst, err)
  397. }
  398. }
  399. // pump reads replies from sess and writes them back into the tunnel toward
  400. // src until the session errors out or goes idle for 2 minutes, then tears
  401. // it down -- both the map entry and the underlying SOCKS5 association.
  402. func (u *UDPRelay) pump(src netip.AddrPort, sess *socks5UDPSession) {
  403. defer func() {
  404. u.mu.Lock()
  405. // Only retire our own entry: a delete by key alone would evict whichever
  406. // session currently holds src, orphaning a live one.
  407. if u.sessions[src] == sess {
  408. delete(u.sessions, src)
  409. }
  410. u.mu.Unlock()
  411. sess.Close()
  412. }()
  413. buf := make([]byte, 65536)
  414. for {
  415. _ = sess.udpConn.SetReadDeadline(time.Now().Add(2 * time.Minute))
  416. from, payload, err := sess.receive(buf)
  417. if err != nil {
  418. return
  419. }
  420. if err := WriteUDPReply(u.gstack, from, src, payload); err != nil {
  421. logger.Warningf("amneziawgnet: UDPRelay: reply write: %v", err)
  422. }
  423. }
  424. }
  425. // Close tears down every open session. Call when the owning Device is
  426. // closed.
  427. func (u *UDPRelay) Close() {
  428. u.mu.Lock()
  429. defer u.mu.Unlock()
  430. for k, s := range u.sessions {
  431. s.Close()
  432. delete(u.sessions, k)
  433. }
  434. }