mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-06 18:27:14 +00:00
be5ee3e0e1
Half-close. Both TCP relays -- RelayTCP into Xray's SOCKS5 inbound and relayTCPForward into a peer's tunnel address -- waited on a single `done` receive and then closed both sides. A client that finished sending and shut down its write side therefore had the connection torn down before the response came back. pipeBothWays now runs both directions to completion and propagates the half-close via CloseWrite (which *net.TCPConn and *gonet.TCPConn both implement), falling back to a full Close for anything that does not. Waiting for both directions reintroduces the risk the old single-receive was implicitly avoiding: a peer that vanishes mid-transfer would pin the pair forever. guardedReader bounds that, but as an idle window rather than a total one -- the deadline is re-armed on every read once armed -- so a slow transfer is never cut, while a silent peer is. Two minutes matches the idle window UDPRelay.pump and portForwardUDPIdleTimeout already use. UDP session retirement. pump's teardown deleted the map entry by key alone, so a session that lost a create race evicted whichever session currently held that source, orphaning a live flow. It now retires only its own entry, and Handle keeps the already-published session when it loses the race. The map is keyed on netip.AddrPort rather than src.String(), matching udpForwardListener next door and dropping one allocation per relayed datagram. SOCKS5 reply decoding. bytesReader had a value receiver, so each Read restarted at the head of the slice, and receive never advanced past a domain-form address because its switch only handled ATYP 0x01 and 0x04 -- a 0x03 reply decoded to a wrong source, port and payload. splitSocks5Addr replaces it: all three address forms, length-checked at every step, with the domain form accepting only a literal. Resolving there would have put a blocking DNS lookup on the receive path, and a datagram's own source is an address already. Unreachable against Xray's own inbound, which always answers with an IP, so this is a latent-bug fix rather than an observed one.