From 8219d91678029ffc753b8aa338254d82ed9b5ac8 Mon Sep 17 00:00:00 2001 From: Lucus Date: Sat, 28 Oct 2017 02:40:55 +0900 Subject: [PATCH] Robust UDP socks5 support. --- main.go | 7 +++++ socks/socks.go | 24 ++++++++++++----- tcp.go | 2 +- udp.go | 70 +++++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 89 insertions(+), 14 deletions(-) diff --git a/main.go b/main.go index 36db9283..a69653a9 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "time" "github.com/shadowsocks/go-shadowsocks2/core" + "github.com/shadowsocks/go-shadowsocks2/socks" ) var config struct { @@ -42,6 +43,7 @@ func main() { RedirTCP6 string TCPTun string UDPTun string + UDPSocks bool } flag.BoolVar(&config.Verbose, "verbose", false, "verbose mode") @@ -52,6 +54,7 @@ func main() { flag.StringVar(&flags.Server, "s", "", "server listen address or url") flag.StringVar(&flags.Client, "c", "", "client connect address or url") flag.StringVar(&flags.Socks, "socks", "", "(client-only) SOCKS listen address") + flag.BoolVar(&flags.UDPSocks, "u", false, "(client-only) Enable UDP support for SOCKS") flag.StringVar(&flags.RedirTCP, "redir", "", "(client-only) redirect TCP from this address") flag.StringVar(&flags.RedirTCP6, "redir6", "", "(client-only) redirect TCP IPv6 from this address") flag.StringVar(&flags.TCPTun, "tcptun", "", "(client-only) TCP tunnel (laddr1=raddr1,laddr2=raddr2,...)") @@ -113,7 +116,11 @@ func main() { } if flags.Socks != "" { + socks.UDPEnabled = flags.UDPSocks go socksLocal(flags.Socks, addr, ciph.StreamConn) + if flags.UDPSocks { + go udpSocksLocal(flags.Socks, addr, ciph.PacketConn) + } } if flags.RedirTCP != "" { diff --git a/socks/socks.go b/socks/socks.go index 136ba380..95a18ea3 100644 --- a/socks/socks.go +++ b/socks/socks.go @@ -7,6 +7,9 @@ import ( "strconv" ) +// UDPEnabled is the toggle for UDP support +var UDPEnabled = false + // SOCKS request commands as defined in RFC 1928 section 4. const ( CmdConnect = 1 @@ -184,14 +187,23 @@ func Handshake(rw io.ReadWriter) (Addr, error) { if _, err := io.ReadFull(rw, buf[:3]); err != nil { return nil, err } - if buf[1] != CmdConnect { - return nil, ErrCommandNotSupported - } addr, err := readAddr(rw, buf) if err != nil { return nil, err } - // write VER REP RSV ATYP BND.ADDR BND.PORT - _, err = rw.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}) - return addr, err + // TODO: when disconnected, release the natmap + switch buf[1] { + case CmdConnect: + _, err = rw.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}) // SOCKS v5, reply succeeded + case CmdUDPAssociate: + if !UDPEnabled { + return nil, ErrCommandNotSupported + } + listenAddr := ParseAddr(rw.(net.Conn).LocalAddr().String()) + _, err = rw.Write(append([]byte{5, 0, 0}, listenAddr...)) // SOCKS v5, reply succeeded + default: + return nil, ErrCommandNotSupported + } + + return addr, err // skip VER, CMD, RSV fields } diff --git a/tcp.go b/tcp.go index d88bcf2a..dd43e5d9 100644 --- a/tcp.go +++ b/tcp.go @@ -43,7 +43,7 @@ func tcpLocal(addr, server string, shadow func(net.Conn) net.Conn, getAddr func( go func() { defer c.Close() c.(*net.TCPConn).SetKeepAlive(true) - + // TODO: keep the connection until disconnect then free the UDP socket tgt, err := getAddr(c) if err != nil { logf("failed to get target address: %v", err) diff --git a/udp.go b/udp.go index f8c787b4..66409680 100644 --- a/udp.go +++ b/udp.go @@ -10,6 +10,14 @@ import ( "github.com/shadowsocks/go-shadowsocks2/socks" ) +type mode int + +const ( + remoteServer mode = iota + relayClient + socksClient +) + const udpBufSize = 64 * 1024 // Listen on laddr for UDP packets, encrypt and send to server to reach target. @@ -55,7 +63,7 @@ func udpLocal(laddr, server, target string, shadow func(net.PacketConn) net.Pack } pc = shadow(pc) - nm.Add(raddr, c, pc, false) + nm.Add(raddr, c, pc, relayClient) } _, err = pc.WriteTo(buf[:len(tgt)+n], srvAddr) @@ -66,6 +74,51 @@ func udpLocal(laddr, server, target string, shadow func(net.PacketConn) net.Pack } } +// Listen on laddr for Socks5 UDP packets, encrypt and send to server to reach target. +func udpSocksLocal(laddr, server string, shadow func(net.PacketConn) net.PacketConn) { + srvAddr, err := net.ResolveUDPAddr("udp", server) + if err != nil { + logf("UDP server address error: %v", err) + return + } + + c, err := net.ListenPacket("udp", laddr) + if err != nil { + logf("UDP local listen error: %v", err) + return + } + defer c.Close() + + nm := newNATmap(config.UDPTimeout) + buf := make([]byte, udpBufSize) + + for { + n, raddr, err := c.ReadFrom(buf) + if err != nil { + logf("UDP local read error: %v", err) + continue + } + + pc := nm.Get(raddr.String()) + if pc == nil { + pc, err = net.ListenPacket("udp", "") + if err != nil { + logf("UDP local listen error: %v", err) + continue + } + logf("UDP socks tunnel %s <-> %s <-> %s", laddr, server, socks.Addr(buf[3:])) + pc = shadow(pc) + nm.Add(raddr, c, pc, socksClient) + } + + _, err = pc.WriteTo(buf[3:n], srvAddr) + if err != nil { + logf("UDP local write error: %v", err) + continue + } + } +} + // Listen on addr for encrypted packets and basically do UDP NAT. func udpRemote(addr string, shadow func(net.PacketConn) net.PacketConn) { c, err := net.ListenPacket("udp", addr) @@ -109,7 +162,7 @@ func udpRemote(addr string, shadow func(net.PacketConn) net.PacketConn) { continue } - nm.Add(raddr, c, pc, true) + nm.Add(raddr, c, pc, remoteServer) } _, err = pc.WriteTo(payload, tgtUDPAddr) // accept only UDPAddr despite the signature @@ -159,11 +212,11 @@ func (m *natmap) Del(key string) net.PacketConn { return nil } -func (m *natmap) Add(peer net.Addr, dst, src net.PacketConn, srcIncluded bool) { +func (m *natmap) Add(peer net.Addr, dst, src net.PacketConn, role mode) { m.Set(peer.String(), src) go func() { - timedCopy(dst, peer, src, m.timeout, srcIncluded) + timedCopy(dst, peer, src, m.timeout, role) if pc := m.Del(peer.String()); pc != nil { pc.Close() } @@ -171,7 +224,7 @@ func (m *natmap) Add(peer net.Addr, dst, src net.PacketConn, srcIncluded bool) { } // copy from src to dst at target with read timeout -func timedCopy(dst net.PacketConn, target net.Addr, src net.PacketConn, timeout time.Duration, srcIncluded bool) error { +func timedCopy(dst net.PacketConn, target net.Addr, src net.PacketConn, timeout time.Duration, role mode) error { buf := make([]byte, udpBufSize) for { @@ -181,14 +234,17 @@ func timedCopy(dst net.PacketConn, target net.Addr, src net.PacketConn, timeout return err } - if srcIncluded { // server -> client: add original packet source + switch role { + case remoteServer: // server -> client: add original packet source srcAddr := socks.ParseAddr(raddr.String()) copy(buf[len(srcAddr):], buf[:n]) copy(buf, srcAddr) _, err = dst.WriteTo(buf[:len(srcAddr)+n], target) - } else { // client -> user: strip original packet source + case relayClient: // client -> user: strip original packet source srcAddr := socks.SplitAddr(buf[:n]) _, err = dst.WriteTo(buf[len(srcAddr):n], target) + case socksClient: // client -> socks5 program: just set RSV and FRAG = 0 + _, err = dst.WriteTo(append([]byte{0, 0, 0}, buf[:n]...), target) } if err != nil {