| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578 |
- package tuic
- import (
- "context"
- "crypto/rand"
- "encoding/base64"
- "encoding/binary"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net"
- "net/netip"
- "sync"
- "sync/atomic"
- "time"
- )
- var ErrUdpPayloadTooLarge = errors.New("tuic socks: UDP packet exceeds the maximum SOCKS datagram size")
- const maxSocksUdpDatagramSize = 65507
- // SocksRelay describes the loopback SOCKS5 endpoint where decrypted TUIC traffic is forwarded.
- type SocksRelay struct {
- Addr string // e.g. "127.0.0.1:63201"
- Password string // internal shared password for the SOCKS inbound
- }
- // CountingConn wraps a net.Conn and tracks bytes read and written atomically.
- type CountingConn struct {
- net.Conn
- bytesRead *atomic.Int64
- bytesWritten *atomic.Int64
- }
- func (c *CountingConn) Read(p []byte) (int, error) {
- n, err := c.Conn.Read(p)
- if n > 0 && c.bytesRead != nil {
- c.bytesRead.Add(int64(n))
- }
- return n, err
- }
- func (c *CountingConn) Write(p []byte) (int, error) {
- n, err := c.Conn.Write(p)
- if n > 0 && c.bytesWritten != nil {
- c.bytesWritten.Add(int64(n))
- }
- return n, err
- }
- // DialTCP establishes a SOCKS5 CONNECT tunnel to the target address on behalf of user.
- func (r *SocksRelay) DialTCP(ctx context.Context, user string, target *Address) (net.Conn, error) {
- dialer := net.Dialer{Timeout: 10 * time.Second}
- conn, err := dialer.DialContext(ctx, "tcp", r.Addr)
- if err != nil {
- return nil, fmt.Errorf("tuic socks: dial relay %s: %w", r.Addr, err)
- }
- _ = conn.SetDeadline(time.Now().Add(10 * time.Second))
- if err := socks5Handshake(conn, user, r.Password); err != nil {
- conn.Close()
- return nil, err
- }
- // Send SOCKS5 CONNECT request
- req := buildSocks5ConnectRequest(target)
- if req == nil {
- conn.Close()
- return nil, ErrInvalidAddr
- }
- if _, err := conn.Write(req); err != nil {
- conn.Close()
- return nil, fmt.Errorf("tuic socks: send CONNECT request: %w", err)
- }
- if _, err := readSocks5Reply(conn); err != nil {
- conn.Close()
- return nil, err
- }
- _ = conn.SetDeadline(time.Time{})
- return conn, nil
- }
- func buildSocks5ConnectRequest(target *Address) []byte {
- if target == nil {
- return nil
- }
- var req []byte
- switch target.Type {
- case AddrTypeIPv4:
- ip4 := target.IP.To4()
- if len(ip4) != 4 {
- return nil
- }
- req = make([]byte, 4+4+2)
- req[0] = 0x05 // SOCKS5
- req[1] = 0x01 // CONNECT
- req[2] = 0x00 // RSV
- req[3] = 0x01 // ATYP IPv4
- copy(req[4:8], ip4)
- binary.BigEndian.PutUint16(req[8:10], target.Port)
- case AddrTypeIPv6:
- ip16 := target.IP.To16()
- if len(ip16) != 16 {
- return nil
- }
- req = make([]byte, 4+16+2)
- req[0] = 0x05
- req[1] = 0x01
- req[2] = 0x00
- req[3] = 0x04 // ATYP IPv6
- copy(req[4:20], ip16)
- binary.BigEndian.PutUint16(req[20:22], target.Port)
- case AddrTypeDomain:
- dLen := len(target.Host)
- if dLen == 0 || dLen > 255 {
- return nil
- }
- req = make([]byte, 4+1+dLen+2)
- req[0] = 0x05
- req[1] = 0x01
- req[2] = 0x00
- req[3] = 0x03 // ATYP Domain
- req[4] = byte(dLen)
- copy(req[5:5+dLen], []byte(target.Host))
- binary.BigEndian.PutUint16(req[5+dLen:7+dLen], target.Port)
- default:
- return nil
- }
- return req
- }
- // SocksUDPSession manages a SOCKS5 UDP ASSOCIATE tunnel to Xray.
- type SocksUDPSession struct {
- ctrl net.Conn
- udpConn *net.UDPConn
- targetEP net.Addr
- user string
- closed atomic.Bool
- }
- // DialUDP establishes a SOCKS5 UDP ASSOCIATE tunnel to Xray.
- func (r *SocksRelay) DialUDP(ctx context.Context, user string) (*SocksUDPSession, error) {
- dialer := net.Dialer{Timeout: 10 * time.Second}
- ctrl, err := dialer.DialContext(ctx, "tcp", r.Addr)
- if err != nil {
- return nil, fmt.Errorf("tuic socks: dial UDP control connection: %w", err)
- }
- _ = ctrl.SetDeadline(time.Now().Add(10 * time.Second))
- if err := socks5Handshake(ctrl, user, r.Password); err != nil {
- ctrl.Close()
- return nil, err
- }
- // SOCKS5 UDP ASSOCIATE (0x03), dst 0.0.0.0:0
- if _, err := ctrl.Write([]byte{0x05, 0x03, 0x00, 0x01, 0, 0, 0, 0, 0, 0}); err != nil {
- ctrl.Close()
- return nil, fmt.Errorf("tuic socks: send UDP ASSOCIATE request: %w", err)
- }
- bind, err := readSocks5Reply(ctrl)
- if err != nil {
- ctrl.Close()
- return nil, err
- }
- _ = ctrl.SetDeadline(time.Time{})
- udpConn, err := net.DialUDP("udp", nil, net.UDPAddrFromAddrPort(bind))
- if err != nil {
- ctrl.Close()
- return nil, fmt.Errorf("tuic socks: dial UDP relay endpoint %s: %w", bind, err)
- }
- return &SocksUDPSession{
- ctrl: ctrl,
- udpConn: udpConn,
- targetEP: udpConn.RemoteAddr(),
- user: user,
- }, nil
- }
- // Send sends a UDP payload to target via the SOCKS5 UDP ASSOCIATE relay.
- func (s *SocksUDPSession) Send(target *Address, payload []byte) (int, error) {
- if s.closed.Load() {
- return 0, net.ErrClosed
- }
- packet, err := buildSocks5UDPRequest(target, payload)
- if err != nil {
- return 0, err
- }
- return s.udpConn.Write(packet)
- }
- func buildSocks5UDPRequest(target *Address, payload []byte) ([]byte, error) {
- hdr := buildSocks5UDPHeader(target)
- if hdr == nil {
- return nil, ErrInvalidAddr
- }
- if len(hdr)+len(payload) > maxSocksUdpDatagramSize {
- return nil, ErrUdpPayloadTooLarge
- }
- packet := make([]byte, len(hdr)+len(payload))
- copy(packet, hdr)
- copy(packet[len(hdr):], payload)
- return packet, nil
- }
- // Receive reads a relayed UDP payload and extracts its original source address.
- func (s *SocksUDPSession) Receive(buf []byte) (*Address, []byte, error) {
- if s.closed.Load() {
- return nil, nil, net.ErrClosed
- }
- n, err := s.udpConn.Read(buf)
- if err != nil {
- return nil, nil, err
- }
- if n < 4 {
- return nil, nil, fmt.Errorf("tuic socks: UDP packet too short (%d bytes)", n)
- }
- // SOCKS5 UDP header: [RSV(2)][FRAG(1)][ATYP(1)]
- atyp := buf[3]
- var addr *Address
- var offset int
- switch atyp {
- case 0x01: // IPv4
- if n < 10 {
- return nil, nil, fmt.Errorf("tuic socks: truncated IPv4 UDP reply")
- }
- ip := net.IP(buf[4:8])
- port := binary.BigEndian.Uint16(buf[8:10])
- addr = &Address{Type: AddrTypeIPv4, IP: ip, Host: ip.String(), Port: port}
- offset = 10
- case 0x04: // IPv6
- if n < 22 {
- return nil, nil, fmt.Errorf("tuic socks: truncated IPv6 UDP reply")
- }
- ip := net.IP(buf[4:20])
- port := binary.BigEndian.Uint16(buf[20:22])
- addr = &Address{Type: AddrTypeIPv6, IP: ip, Host: ip.String(), Port: port}
- offset = 22
- case 0x03: // Domain
- dLen := int(buf[4])
- if n < 5+dLen+2 {
- return nil, nil, fmt.Errorf("tuic socks: truncated domain UDP reply")
- }
- host := string(buf[5 : 5+dLen])
- port := binary.BigEndian.Uint16(buf[5+dLen : 7+dLen])
- addr = &Address{Type: AddrTypeDomain, Host: host, Port: port}
- offset = 7 + dLen
- default:
- return nil, nil, fmt.Errorf("tuic socks: unsupported reply ATYP 0x%02x", atyp)
- }
- return addr, buf[offset:n], nil
- }
- // Close closes the SOCKS5 UDP session.
- func (s *SocksUDPSession) Close() error {
- if s.closed.Swap(true) {
- return nil
- }
- _ = s.udpConn.Close()
- return s.ctrl.Close()
- }
- func buildSocks5UDPHeader(target *Address) []byte {
- if target == nil {
- return nil
- }
- var hdr []byte
- switch target.Type {
- case AddrTypeIPv4:
- ip4 := target.IP.To4()
- if len(ip4) != 4 {
- return nil
- }
- hdr = make([]byte, 10)
- hdr[0] = 0x00 // RSV
- hdr[1] = 0x00 // RSV
- hdr[2] = 0x00 // FRAG
- hdr[3] = 0x01 // ATYP IPv4
- copy(hdr[4:8], ip4)
- binary.BigEndian.PutUint16(hdr[8:10], target.Port)
- case AddrTypeIPv6:
- ip16 := target.IP.To16()
- if len(ip16) != 16 {
- return nil
- }
- hdr = make([]byte, 22)
- hdr[0] = 0x00
- hdr[1] = 0x00
- hdr[2] = 0x00
- hdr[3] = 0x04 // ATYP IPv6
- copy(hdr[4:20], ip16)
- binary.BigEndian.PutUint16(hdr[20:22], target.Port)
- case AddrTypeDomain:
- dLen := len(target.Host)
- if dLen == 0 || dLen > 255 {
- return nil
- }
- hdr = make([]byte, 4+1+dLen+2)
- hdr[0] = 0x00
- hdr[1] = 0x00
- hdr[2] = 0x00
- hdr[3] = 0x03 // ATYP Domain
- hdr[4] = byte(dLen)
- copy(hdr[5:5+dLen], []byte(target.Host))
- binary.BigEndian.PutUint16(hdr[5+dLen:7+dLen], target.Port)
- default:
- return nil
- }
- return hdr
- }
- func socks5Handshake(conn net.Conn, user, password string) error {
- if _, err := conn.Write([]byte{0x05, 0x02, 0x00, 0x02}); err != nil {
- return fmt.Errorf("tuic socks: send greeting: %w", err)
- }
- var resp [2]byte
- if _, err := io.ReadFull(conn, resp[:]); err != nil {
- return fmt.Errorf("tuic socks: read greeting reply: %w", err)
- }
- if resp[0] != 0x05 {
- return fmt.Errorf("tuic socks: unexpected SOCKS version %d", resp[0])
- }
- switch resp[1] {
- case 0x00: // no auth
- return nil
- case 0x02: // username/password
- req := make([]byte, 0, 3+len(user)+len(password))
- req = append(req, 0x01, byte(len(user)))
- req = append(req, user...)
- req = append(req, byte(len(password)))
- req = append(req, password...)
- if _, err := conn.Write(req); err != nil {
- return fmt.Errorf("tuic socks: send auth: %w", err)
- }
- var authResp [2]byte
- if _, err := io.ReadFull(conn, authResp[:]); err != nil {
- return fmt.Errorf("tuic socks: read auth reply: %w", err)
- }
- if authResp[1] != 0x00 {
- return fmt.Errorf("tuic socks: auth rejected (code %d)", authResp[1])
- }
- return nil
- default:
- return fmt.Errorf("tuic socks: unsupported auth method %d", resp[1])
- }
- }
- func readSocks5Reply(r io.Reader) (netip.AddrPort, error) {
- var hdr [4]byte
- if _, err := io.ReadFull(r, hdr[:]); err != nil {
- return netip.AddrPort{}, fmt.Errorf("tuic socks: read reply header: %w", err)
- }
- if hdr[0] != 0x05 {
- return netip.AddrPort{}, fmt.Errorf("tuic socks: unexpected SOCKS version %d", hdr[0])
- }
- if hdr[1] != 0x00 {
- return netip.AddrPort{}, fmt.Errorf("tuic socks: request rejected (code %d)", hdr[1])
- }
- addr, err := readSocks5Addr(r, hdr[3])
- if err != nil {
- return netip.AddrPort{}, err
- }
- var portBytes [2]byte
- if _, err := io.ReadFull(r, portBytes[:]); err != nil {
- return netip.AddrPort{}, fmt.Errorf("tuic socks: read reply port: %w", err)
- }
- return netip.AddrPortFrom(addr, binary.BigEndian.Uint16(portBytes[:])), nil
- }
- func readSocks5Addr(r io.Reader, atyp byte) (netip.Addr, error) {
- switch atyp {
- case 0x01:
- var b [4]byte
- if _, err := io.ReadFull(r, b[:]); err != nil {
- return netip.Addr{}, err
- }
- return netip.AddrFrom4(b), nil
- case 0x04:
- var b [16]byte
- if _, err := io.ReadFull(r, b[:]); err != nil {
- return netip.Addr{}, err
- }
- return netip.AddrFrom16(b), nil
- case 0x03:
- var l [1]byte
- if _, err := io.ReadFull(r, l[:]); err != nil {
- return netip.Addr{}, err
- }
- name := make([]byte, l[0])
- if _, err := io.ReadFull(r, name); err != nil {
- return netip.Addr{}, err
- }
- resolved, err := net.ResolveIPAddr("ip", string(name))
- if err != nil {
- return netip.Addr{}, fmt.Errorf("tuic socks: resolve domain reply %q: %w", name, err)
- }
- addr, ok := netip.AddrFromSlice(resolved.IP)
- if !ok {
- return netip.Addr{}, fmt.Errorf("tuic socks: unparseable domain reply address")
- }
- return addr, nil
- default:
- return netip.Addr{}, fmt.Errorf("tuic socks: unsupported SOCKS5 address type %d", atyp)
- }
- }
- // halfCloseIdle bounds how long the surviving direction of a half-closed pair
- // may sit idle, so a peer that vanished mid-transfer cannot pin it forever.
- const halfCloseIdle = 2 * time.Minute
- type closeWriter interface {
- CloseWrite() error
- }
- type readDeadliner interface {
- SetReadDeadline(t time.Time) error
- }
- type guardedReader struct {
- r io.Reader
- dl readDeadliner
- armed atomic.Bool
- }
- func newGuardedReader(r io.Reader) *guardedReader {
- gr := &guardedReader{r: r}
- if dl, ok := r.(readDeadliner); ok {
- gr.dl = dl
- }
- return gr
- }
- func (g *guardedReader) Read(p []byte) (int, error) {
- if g.armed.Load() && g.dl != nil {
- _ = g.dl.SetReadDeadline(time.Now().Add(halfCloseIdle))
- }
- return g.r.Read(p)
- }
- func (g *guardedReader) arm() {
- g.armed.Store(true)
- if g.dl != nil {
- _ = g.dl.SetReadDeadline(time.Now().Add(halfCloseIdle))
- }
- }
- // PipeBiDirectional pipes data between two connections and tracks byte counts in each direction.
- func PipeBiDirectional(a, b io.ReadWriteCloser, upCounter, downCounter *atomic.Int64) {
- PipeBiDirectionalContext(context.Background(), a, b, upCounter, downCounter)
- }
- func PipeBiDirectionalContext(ctx context.Context, a, b io.ReadWriteCloser, upCounter, downCounter *atomic.Int64) {
- closeBoth := func() { _ = a.Close(); _ = b.Close() }
- stop := context.AfterFunc(ctx, closeBoth)
- defer stop()
- ga := newGuardedReader(a)
- gb := newGuardedReader(b)
- var wg sync.WaitGroup
- wg.Add(2)
- pipe := func(dst io.Writer, dstGuard *guardedReader, src *guardedReader, counter *atomic.Int64) {
- defer wg.Done()
- buf := make([]byte, 32*1024)
- for {
- n, err := src.Read(buf)
- if n > 0 {
- if counter != nil {
- counter.Add(int64(n))
- }
- if _, werr := dst.Write(buf[:n]); werr != nil {
- closeBoth()
- break
- }
- }
- if err != nil {
- if !errors.Is(err, io.EOF) {
- closeBoth()
- }
- break
- }
- }
- if cw, ok := dst.(closeWriter); ok {
- _ = cw.CloseWrite()
- } else if closer, ok := dst.(io.Closer); ok {
- _ = closer.Close()
- }
- dstGuard.arm()
- }
- // a -> b (upload: client to upstream)
- go pipe(b, gb, ga, upCounter)
- // b -> a (download: upstream to client)
- go pipe(a, ga, gb, downCounter)
- wg.Wait()
- _ = a.Close()
- _ = b.Close()
- }
- // SOCKSBasePort is the first loopback port used for a TUIC inbound's
- // internal Xray SOCKS5 relay inbound.
- const SOCKSBasePort = 64000
- // relayPortSlots is how many ids fit in the TUIC relay port window (64001..65000).
- const relayPortSlots = 1000
- // SOCKSPortForInbound derives one inbound's loopback SOCKS5 relay port from
- // its id, bounded within the dedicated range 64001..65000 so it never collides
- // with AmneziaWG (65101..65535), API (62789), or node egress (62800..63800).
- func SOCKSPortForInbound(inboundID int) int {
- if inboundID <= 0 {
- return SOCKSBasePort + 1
- }
- return SOCKSBasePort + 1 + (inboundID-1)%relayPortSlots
- }
- var (
- socksPasswordOnce sync.Once
- socksPassword string
- )
- // SocksPassword returns the process-wide password used to authenticate into
- // every TUIC SOCKS5 relay inbound.
- func SocksPassword() string {
- socksPasswordOnce.Do(func() {
- var b [24]byte
- if _, err := rand.Read(b[:]); err != nil {
- socksPassword = fmt.Sprintf("tuic-fallback-%x", b)
- return
- }
- socksPassword = base64.RawURLEncoding.EncodeToString(b[:])
- })
- return socksPassword
- }
- // SocksInboundSettings builds the JSON `settings` block for a stock Xray
- // SOCKS5 inbound with one username/password account per email, all sharing
- // password. UDP is enabled for UDP ASSOCIATE proxying.
- func SocksInboundSettings(emails []string, password string) ([]byte, error) {
- type account struct {
- User string `json:"user"`
- Pass string `json:"pass"`
- }
- settings := struct {
- Auth string `json:"auth"`
- UDP bool `json:"udp"`
- Accounts []account `json:"accounts"`
- }{Auth: "password", UDP: true}
- for _, email := range emails {
- settings.Accounts = append(settings.Accounts, account{User: email, Pass: password})
- }
- return json.Marshal(settings)
- }
|