relay.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  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. "time"
  18. "golang.org/x/net/proxy"
  19. "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
  20. "gvisor.dev/gvisor/pkg/tcpip/stack"
  21. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  22. )
  23. // SocksRelay describes the loopback SOCKS5 inbound decapsulated AmneziaWG
  24. // traffic gets relayed into.
  25. type SocksRelay struct {
  26. // Addr is the SOCKS5 inbound's own address, e.g. "127.0.0.1:11500".
  27. Addr string
  28. // Password is shared across every account. This traffic never leaves
  29. // loopback, so the password is not a real secrecy boundary -- it only
  30. // needs to satisfy Xray's SOCKS5 inbound requiring *some* username/
  31. // password auth before it will accept a connection and use the
  32. // username as the stats identity. Document this reasoning wherever a
  33. // caller generates or displays it, so it's never mistaken later for a
  34. // real credential.
  35. Password string
  36. }
  37. // SocksInboundSettings builds the JSON `settings` block for a stock Xray
  38. // SOCKS5 inbound with one username/password account per email, all sharing
  39. // password (see SocksRelay's doc comment). udp:true is required: RelayUDP
  40. // depends on the inbound accepting UDP ASSOCIATE, not just CONNECT.
  41. func SocksInboundSettings(emails []string, password string) ([]byte, error) {
  42. type account struct {
  43. User string `json:"user"`
  44. Pass string `json:"pass"`
  45. }
  46. settings := struct {
  47. Auth string `json:"auth"`
  48. UDP bool `json:"udp"`
  49. Accounts []account `json:"accounts"`
  50. }{Auth: "password", UDP: true}
  51. for _, email := range emails {
  52. settings.Accounts = append(settings.Accounts, account{User: email, Pass: password})
  53. }
  54. return json.Marshal(settings)
  55. }
  56. // RelayTCP dials r.Addr, authenticates as email, issues a SOCKS5 CONNECT to
  57. // dest, and pipes bytes both ways until either side closes or errors.
  58. // Blocks until the relay ends; meant to be called from (or as) an
  59. // AttachTCPForwarder handler, which already runs each connection on its own
  60. // goroutine.
  61. func (r SocksRelay) RelayTCP(conn *gonet.TCPConn, email string, dest netip.AddrPort) {
  62. defer conn.Close()
  63. auth := &proxy.Auth{User: email, Password: r.Password}
  64. dialer, err := proxy.SOCKS5("tcp", r.Addr, auth, proxy.Direct)
  65. if err != nil {
  66. logger.Warningf("amneziawgnet: RelayTCP: build SOCKS5 dialer: %v", err)
  67. return
  68. }
  69. upstream, err := dialer.Dial("tcp", dest.String())
  70. if err != nil {
  71. logger.Warningf("amneziawgnet: RelayTCP: SOCKS5 CONNECT to %s as %q: %v", dest, email, err)
  72. return
  73. }
  74. defer upstream.Close()
  75. done := make(chan struct{}, 2)
  76. go func() { _, _ = io.Copy(upstream, conn); done <- struct{}{} }()
  77. go func() { _, _ = io.Copy(conn, upstream); done <- struct{}{} }()
  78. <-done
  79. }
  80. // socks5UDPSession is one established SOCKS5 UDP ASSOCIATE session: udpConn
  81. // is the actual socket packets are sent to (and replies read from); ctrl is
  82. // the TCP control connection that must stay open for the session's
  83. // lifetime -- per RFC 1928, closing it tears the association down.
  84. type socks5UDPSession struct {
  85. ctrl net.Conn
  86. udpConn *net.UDPConn
  87. }
  88. // newSocks5UDPSession performs the SOCKS5 greeting, username/password auth,
  89. // and UDP ASSOCIATE request/reply by hand: golang.org/x/net/proxy's SOCKS5
  90. // client (used by RelayTCP above) only implements CONNECT, and xray-core's
  91. // own proxy/socks/client.go is written against its internal transport
  92. // types, not reusable as a standalone dialer -- so this is a small, direct,
  93. // from-the-RFC implementation rather than an existing library call.
  94. func newSocks5UDPSession(addr, user, password string) (*socks5UDPSession, error) {
  95. dialer := net.Dialer{Timeout: 5 * time.Second}
  96. ctrl, err := dialer.DialContext(context.Background(), "tcp", addr)
  97. if err != nil {
  98. return nil, fmt.Errorf("amneziawgnet: dial SOCKS5 control connection: %w", err)
  99. }
  100. if err := socks5Handshake(ctrl, user, password); err != nil {
  101. ctrl.Close()
  102. return nil, err
  103. }
  104. // UDP ASSOCIATE, dst 0.0.0.0:0 ("I don't know my own source yet, and I
  105. // don't need to specify one for a loopback relay").
  106. if _, err := ctrl.Write([]byte{0x05, 0x03, 0x00, 0x01, 0, 0, 0, 0, 0, 0}); err != nil {
  107. ctrl.Close()
  108. return nil, fmt.Errorf("amneziawgnet: send UDP ASSOCIATE request: %w", err)
  109. }
  110. bind, err := readSocks5Reply(ctrl)
  111. if err != nil {
  112. ctrl.Close()
  113. return nil, err
  114. }
  115. udpConn, err := net.DialUDP("udp", nil, net.UDPAddrFromAddrPort(bind))
  116. if err != nil {
  117. ctrl.Close()
  118. return nil, fmt.Errorf("amneziawgnet: dial SOCKS5 UDP relay endpoint %s: %w", bind, err)
  119. }
  120. return &socks5UDPSession{ctrl: ctrl, udpConn: udpConn}, nil
  121. }
  122. // socks5Handshake performs the version greeting and (if the server
  123. // requires it) username/password auth. Xray's SOCKS5 inbound with
  124. // auth:"password" always requires it; the no-auth branch exists so this
  125. // helper isn't silently wrong against a differently-configured server.
  126. func socks5Handshake(conn net.Conn, user, password string) error {
  127. if _, err := conn.Write([]byte{0x05, 0x02, 0x00, 0x02}); err != nil {
  128. return fmt.Errorf("amneziawgnet: send SOCKS5 greeting: %w", err)
  129. }
  130. var resp [2]byte
  131. if _, err := io.ReadFull(conn, resp[:]); err != nil {
  132. return fmt.Errorf("amneziawgnet: read SOCKS5 greeting reply: %w", err)
  133. }
  134. if resp[0] != 0x05 {
  135. return fmt.Errorf("amneziawgnet: unexpected SOCKS5 version %d", resp[0])
  136. }
  137. switch resp[1] {
  138. case 0x00: // no auth required
  139. return nil
  140. case 0x02: // username/password
  141. req := make([]byte, 0, 3+len(user)+len(password))
  142. req = append(req, 0x01, byte(len(user)))
  143. req = append(req, user...)
  144. req = append(req, byte(len(password)))
  145. req = append(req, password...)
  146. if _, err := conn.Write(req); err != nil {
  147. return fmt.Errorf("amneziawgnet: send SOCKS5 auth: %w", err)
  148. }
  149. var authResp [2]byte
  150. if _, err := io.ReadFull(conn, authResp[:]); err != nil {
  151. return fmt.Errorf("amneziawgnet: read SOCKS5 auth reply: %w", err)
  152. }
  153. if authResp[1] != 0x00 {
  154. return fmt.Errorf("amneziawgnet: SOCKS5 auth rejected (status %d)", authResp[1])
  155. }
  156. return nil
  157. default:
  158. return fmt.Errorf("amneziawgnet: SOCKS5 server offered unsupported auth method %d", resp[1])
  159. }
  160. }
  161. // readSocks5Reply reads a SOCKS5 reply (the common format shared by CONNECT
  162. // and UDP ASSOCIATE replies) and returns its bound address.
  163. func readSocks5Reply(r io.Reader) (netip.AddrPort, error) {
  164. var hdr [4]byte
  165. if _, err := io.ReadFull(r, hdr[:]); err != nil {
  166. return netip.AddrPort{}, fmt.Errorf("amneziawgnet: read SOCKS5 reply header: %w", err)
  167. }
  168. if hdr[0] != 0x05 {
  169. return netip.AddrPort{}, fmt.Errorf("amneziawgnet: unexpected SOCKS5 reply version %d", hdr[0])
  170. }
  171. if hdr[1] != 0x00 {
  172. return netip.AddrPort{}, fmt.Errorf("amneziawgnet: SOCKS5 request failed (reply code %d)", hdr[1])
  173. }
  174. addr, err := readSocks5Addr(r, hdr[3])
  175. if err != nil {
  176. return netip.AddrPort{}, err
  177. }
  178. var portBytes [2]byte
  179. if _, err := io.ReadFull(r, portBytes[:]); err != nil {
  180. return netip.AddrPort{}, fmt.Errorf("amneziawgnet: read SOCKS5 reply port: %w", err)
  181. }
  182. return netip.AddrPortFrom(addr, binary.BigEndian.Uint16(portBytes[:])), nil
  183. }
  184. // readSocks5Addr reads the address portion of a SOCKS5 reply for the given
  185. // address type (IPv4, IPv6, or domain -- resolved locally since a loopback
  186. // Xray inbound is not expected to reply with one, but it's cheap to handle
  187. // correctly rather than fail oddly if it ever does).
  188. func readSocks5Addr(r io.Reader, atyp byte) (netip.Addr, error) {
  189. switch atyp {
  190. case 0x01:
  191. var b [4]byte
  192. if _, err := io.ReadFull(r, b[:]); err != nil {
  193. return netip.Addr{}, err
  194. }
  195. return netip.AddrFrom4(b), nil
  196. case 0x04:
  197. var b [16]byte
  198. if _, err := io.ReadFull(r, b[:]); err != nil {
  199. return netip.Addr{}, err
  200. }
  201. return netip.AddrFrom16(b), nil
  202. case 0x03:
  203. var l [1]byte
  204. if _, err := io.ReadFull(r, l[:]); err != nil {
  205. return netip.Addr{}, err
  206. }
  207. name := make([]byte, l[0])
  208. if _, err := io.ReadFull(r, name); err != nil {
  209. return netip.Addr{}, err
  210. }
  211. resolved, err := net.ResolveIPAddr("ip", string(name))
  212. if err != nil {
  213. return netip.Addr{}, fmt.Errorf("amneziawgnet: resolve SOCKS5 domain reply %q: %w", name, err)
  214. }
  215. addr, ok := netip.AddrFromSlice(resolved.IP)
  216. if !ok {
  217. return netip.Addr{}, fmt.Errorf("amneziawgnet: unparseable resolved SOCKS5 domain reply address")
  218. }
  219. return addr, nil
  220. default:
  221. return netip.Addr{}, fmt.Errorf("amneziawgnet: unsupported SOCKS5 address type %d", atyp)
  222. }
  223. }
  224. // Close ends the UDP ASSOCIATE session: closing ctrl tells the SOCKS5
  225. // server to tear down its relay side too (RFC 1928).
  226. func (s *socks5UDPSession) Close() error {
  227. s.udpConn.Close()
  228. return s.ctrl.Close()
  229. }
  230. // sendTo wraps payload in a SOCKS5 UDP request header addressed to dest and
  231. // sends it to the session's relay endpoint.
  232. func (s *socks5UDPSession) sendTo(dest netip.AddrPort, payload []byte) error {
  233. hdr := make([]byte, 0, 3+1+16+2+len(payload))
  234. hdr = append(hdr, 0x00, 0x00, 0x00) // RSV RSV FRAG(=0, no fragmentation)
  235. if dest.Addr().Is4() {
  236. b := dest.Addr().As4()
  237. hdr = append(hdr, 0x01)
  238. hdr = append(hdr, b[:]...)
  239. } else {
  240. b := dest.Addr().As16()
  241. hdr = append(hdr, 0x04)
  242. hdr = append(hdr, b[:]...)
  243. }
  244. var portBytes [2]byte
  245. binary.BigEndian.PutUint16(portBytes[:], dest.Port())
  246. hdr = append(hdr, portBytes[:]...)
  247. hdr = append(hdr, payload...)
  248. _, err := s.udpConn.Write(hdr)
  249. return err
  250. }
  251. // receive reads one reply datagram into buf, returning the address the
  252. // SOCKS5 server says it came from and the actual payload (a sub-slice of
  253. // buf -- valid only until the next receive call).
  254. func (s *socks5UDPSession) receive(buf []byte) (netip.AddrPort, []byte, error) {
  255. n, err := s.udpConn.Read(buf)
  256. if err != nil {
  257. return netip.AddrPort{}, nil, err
  258. }
  259. data := buf[:n]
  260. if len(data) < 4 {
  261. return netip.AddrPort{}, nil, fmt.Errorf("amneziawgnet: short SOCKS5 UDP reply (%d bytes)", n)
  262. }
  263. atyp := data[3]
  264. data = data[4:]
  265. addr, err := readSocks5Addr(bytesReader{data}, atyp)
  266. if err != nil {
  267. return netip.AddrPort{}, nil, err
  268. }
  269. switch atyp {
  270. case 0x01:
  271. data = data[4:]
  272. case 0x04:
  273. data = data[16:]
  274. }
  275. if len(data) < 2 {
  276. return netip.AddrPort{}, nil, fmt.Errorf("amneziawgnet: truncated SOCKS5 UDP reply port")
  277. }
  278. port := binary.BigEndian.Uint16(data[:2])
  279. return netip.AddrPortFrom(addr, port), data[2:], nil
  280. }
  281. // bytesReader is the minimal io.Reader readSocks5Addr needs, over an
  282. // in-memory slice that's already fully available (a received UDP
  283. // datagram) -- avoids pulling in bytes.Reader just for this.
  284. type bytesReader struct{ b []byte }
  285. func (r bytesReader) Read(p []byte) (int, error) {
  286. n := copy(p, r.b)
  287. if n < len(p) {
  288. return n, io.ErrUnexpectedEOF
  289. }
  290. return n, nil
  291. }
  292. // UDPRelay tracks one SOCKS5 UDP ASSOCIATE session per source (tunnel-
  293. // internal client) flow, relaying each into r's SOCKS5 inbound and writing
  294. // replies back through gstack -- the UDP counterpart of RelayTCP, meant to
  295. // be driven by an AttachUDPHandler callback (see udp.go).
  296. type UDPRelay struct {
  297. relay SocksRelay
  298. gstack *stack.Stack
  299. mu sync.Mutex
  300. sessions map[string]*socks5UDPSession
  301. }
  302. // NewUDPRelay creates a UDPRelay for one embedded AmneziaWG Device's stack.
  303. func NewUDPRelay(relay SocksRelay, gstack *stack.Stack) *UDPRelay {
  304. return &UDPRelay{relay: relay, gstack: gstack, sessions: map[string]*socks5UDPSession{}}
  305. }
  306. // Handle relays one packet from src (the peer's tunnel-internal source) to
  307. // dst (its real, recovered destination), opening a fresh SOCKS5 UDP
  308. // ASSOCIATE session for src the first time it's seen (authenticating as
  309. // email, so Xray attributes the whole flow's stats to the right peer) and
  310. // reusing it for subsequent packets from the same src.
  311. func (u *UDPRelay) Handle(src, dst netip.AddrPort, email string, payload []byte) {
  312. u.mu.Lock()
  313. sess, ok := u.sessions[src.String()]
  314. u.mu.Unlock()
  315. if !ok {
  316. var err error
  317. sess, err = newSocks5UDPSession(u.relay.Addr, email, u.relay.Password)
  318. if err != nil {
  319. logger.Warningf("amneziawgnet: UDPRelay: SOCKS5 associate for %q: %v", email, err)
  320. return
  321. }
  322. u.mu.Lock()
  323. u.sessions[src.String()] = sess
  324. u.mu.Unlock()
  325. go u.pump(src, sess)
  326. }
  327. if err := sess.sendTo(dst, payload); err != nil {
  328. logger.Warningf("amneziawgnet: UDPRelay: send to %s: %v", dst, err)
  329. }
  330. }
  331. // pump reads replies from sess and writes them back into the tunnel toward
  332. // src until the session errors out or goes idle for 2 minutes, then tears
  333. // it down -- both the map entry and the underlying SOCKS5 association.
  334. func (u *UDPRelay) pump(src netip.AddrPort, sess *socks5UDPSession) {
  335. defer func() {
  336. u.mu.Lock()
  337. delete(u.sessions, src.String())
  338. u.mu.Unlock()
  339. sess.Close()
  340. }()
  341. buf := make([]byte, 65536)
  342. for {
  343. _ = sess.udpConn.SetReadDeadline(time.Now().Add(2 * time.Minute))
  344. from, payload, err := sess.receive(buf)
  345. if err != nil {
  346. return
  347. }
  348. if err := WriteUDPReply(u.gstack, from, src, payload); err != nil {
  349. logger.Warningf("amneziawgnet: UDPRelay: reply write: %v", err)
  350. }
  351. }
  352. }
  353. // Close tears down every open session. Call when the owning Device is
  354. // closed.
  355. func (u *UDPRelay) Close() {
  356. u.mu.Lock()
  357. defer u.mu.Unlock()
  358. for k, s := range u.sessions {
  359. s.Close()
  360. delete(u.sessions, k)
  361. }
  362. }