From 30f340dc866f3d62cac97089458be0b5a549c2a1 Mon Sep 17 00:00:00 2001 From: nadoo <287492+nadoo@users.noreply.github.com> Date: Wed, 6 Sep 2017 18:52:22 +0800 Subject: [PATCH] ss: add udp support as server mode --- conn.go | 27 ++++++++++++++ socks5.go | 16 +++++++-- ss.go | 103 ++++++++++++++++++++++++++++++++++++++++++++++-------- tcptun.go | 2 +- 4 files changed, 130 insertions(+), 18 deletions(-) diff --git a/conn.go b/conn.go index 944e6ef..d217c1f 100644 --- a/conn.go +++ b/conn.go @@ -52,3 +52,30 @@ func relay(left, right net.Conn) (int64, int64, error) { } return n, rs.N, err } + +// 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 { + buf := make([]byte, udpBufSize) + + for { + src.SetReadDeadline(time.Now().Add(timeout)) + n, raddr, err := src.ReadFrom(buf) + if err != nil { + return err + } + + if srcIncluded { // server -> client: add original packet source + srcAddr := 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 + srcAddr := SplitAddr(buf[:n]) + _, err = dst.WriteTo(buf[len(srcAddr):n], target) + } + + if err != nil { + return err + } + } +} diff --git a/socks5.go b/socks5.go index 5b6326f..12c30b8 100644 --- a/socks5.go +++ b/socks5.go @@ -326,7 +326,7 @@ func (s *SOCKS5) handshake(rw io.ReadWriter) (Addr, error) { func (a Addr) String() string { var host, port string - switch a[0] { // address type + switch ATYP(a[0]) { // address type case socks5Domain: host = string(a[2 : 2+int(a[1])]) port = strconv.Itoa((int(a[2+int(a[1])]) << 8) | int(a[2+int(a[1])+1])) @@ -341,6 +341,16 @@ func (a Addr) String() string { return net.JoinHostPort(host, port) } +// UoT udp over tcp +func UoT(b byte) bool { + return b&0x8 == 0x8 +} + +// ATYP return the address type +func ATYP(b byte) int { + return int(b &^ 0x8) +} + func readAddr(r io.Reader, b []byte) (Addr, error) { if len(b) < MaxAddrLen { return nil, io.ErrShortBuffer @@ -350,7 +360,7 @@ func readAddr(r io.Reader, b []byte) (Addr, error) { return nil, err } - switch b[0] { + switch ATYP(b[0]) { case socks5Domain: _, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length if err != nil { @@ -381,7 +391,7 @@ func SplitAddr(b []byte) Addr { return nil } - switch b[0] { + switch ATYP(b[0]) { case socks5Domain: if len(b) < 2 { return nil diff --git a/ss.go b/ss.go index 923f220..f22cb49 100644 --- a/ss.go +++ b/ss.go @@ -5,16 +5,21 @@ import ( "log" "net" "strings" + "sync" + "time" "github.com/shadowsocks/go-shadowsocks2/core" + "github.com/shadowsocks/go-shadowsocks2/socks" ) +const udpBufSize = 64 * 1024 + // SS . type SS struct { *Forwarder sDialer Dialer - core.StreamConnCipher + core.Cipher } // NewSS returns a shadowsocks proxy. @@ -25,53 +30,59 @@ func NewSS(addr, method, pass string, cDialer Dialer, sDialer Dialer) (*SS, erro } s := &SS{ - Forwarder: NewForwarder(addr, cDialer), - sDialer: sDialer, - StreamConnCipher: ciph, + Forwarder: NewForwarder(addr, cDialer), + sDialer: sDialer, + Cipher: ciph, } return s, nil } -// ListenAndServe shadowsocks requests as a server. +// ListenAndServe serves ss requests. func (s *SS) ListenAndServe() { + go s.ListenAndServeUDP() + s.ListenAndServeTCP() +} + +// ListenAndServeTCP serves tcp ss requests. +func (s *SS) ListenAndServeTCP() { l, err := net.Listen("tcp", s.addr) if err != nil { - logf("failed to listen on %s: %v", s.addr, err) + logf("proxy-ss failed to listen on %s: %v", s.addr, err) return } - logf("listening TCP on %s", s.addr) + logf("proxy-ss listening TCP on %s", s.addr) for { c, err := l.Accept() if err != nil { - logf("failed to accept: %v", err) + logf("proxy-ss failed to accept: %v", err) continue } - go s.Serve(c) + go s.ServeTCP(c) } } -// Serve . -func (s *SS) Serve(c net.Conn) { +// ServeTCP . +func (s *SS) ServeTCP(c net.Conn) { defer c.Close() if c, ok := c.(*net.TCPConn); ok { c.SetKeepAlive(true) } - c = s.StreamConnCipher.StreamConn(c) + c = s.StreamConn(c) tgt, err := ReadAddr(c) if err != nil { - logf("failed to get target address: %v", err) + logf("proxy-ss failed to get target address: %v", err) return } rc, err := s.sDialer.Dial("tcp", tgt.String()) if err != nil { - logf("failed to connect to target: %v", err) + logf("proxy-ss failed to connect to target: %v", err) return } defer rc.Close() @@ -88,6 +99,70 @@ func (s *SS) Serve(c net.Conn) { } +// ListenAndServeUDP serves udp ss requests. +func (s *SS) ListenAndServeUDP() { + c, err := net.ListenPacket("udp", s.addr) + if err != nil { + logf("proxy-ss failed to listen on %s: %v", s.addr, err) + return + } + defer c.Close() + + logf("proxy-ss listening UDP on %s", s.addr) + + c = s.PacketConn(c) + + var nm sync.Map + buf := make([]byte, udpBufSize) + + for { + n, raddr, err := c.ReadFrom(buf) + if err != nil { + logf("UDP remote read error: %v", err) + continue + } + + tgtAddr := socks.SplitAddr(buf[:n]) + if tgtAddr == nil { + logf("failed to split target address from packet: %q", buf[:n]) + continue + } + + tgtUDPAddr, err := net.ResolveUDPAddr("udp", tgtAddr.String()) + if err != nil { + logf("failed to resolve target UDP address: %v", err) + continue + } + + payload := buf[len(tgtAddr):n] + + var pc net.PacketConn + v, _ := nm.Load(raddr.String()) + if v == nil { + pc, err = net.ListenPacket("udp", "") + if err != nil { + logf("UDP remote listen error: %v", err) + continue + } + + nm.Store(raddr.String(), pc) + go func() { + timedCopy(c, raddr, pc, 5*time.Minute, true) + pc.Close() + nm.Delete(raddr.String()) + }() + } + + pc = pc.(net.PacketConn) + _, err = pc.WriteTo(payload, tgtUDPAddr) // accept only UDPAddr despite the signature + if err != nil { + logf("UDP remote write error: %v", err) + continue + } + + } +} + // Dial connects to the address addr on the network net via the proxy. func (s *SS) Dial(network, addr string) (net.Conn, error) { diff --git a/tcptun.go b/tcptun.go index c9fe687..9a89196 100644 --- a/tcptun.go +++ b/tcptun.go @@ -10,7 +10,7 @@ type TCPTun struct { raddr string } -// NewTCPTun returns a redirect proxy. +// NewTCPTun returns a tcptun proxy. func NewTCPTun(addr, raddr string, sDialer Dialer) (*TCPTun, error) { s := &TCPTun{ Forwarder: NewForwarder(addr, nil),