From 6b4d2582cabc496fbd8d755f27ff4ed340c370eb Mon Sep 17 00:00:00 2001 From: nadoo <287492+nadoo@users.noreply.github.com> Date: Sat, 20 Jan 2018 22:13:04 +0800 Subject: [PATCH] socks5: support udp in server mode --- README.md | 6 +- http.go | 2 +- mixed.go | 15 +++-- socks5.go | 196 +++++++++++++++++++++++++++++++++++++++++++++++------- ss.go | 2 +- udptun.go | 1 + uottun.go | 7 +- 7 files changed, 195 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 2c0f799..5e43056 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Listen(local proxy server): - SS proxy - Linux transparent proxy(iptables redirect) - TCP tunnel -- DNS Tunnel(udp2tcp) +- UDP over TCP tunnel Forward(upstream proxy server): - Socks5 proxy @@ -47,7 +47,9 @@ General: - Rule proxy based on destinations: [Config Examples](config/examples) TODO: -- [x] UDP over TCP Tunnel (client <--udp--> glider/uottun <--tcp--> ss <--udp--> target) + +- [x] UDP tunnel +- [x] UDP over TCP Tunnel (client <--udp--> glider/uottun <--tcp--> glider/ss <--udp--> target) - [ ] Transparent UDP proxy (iptables tproxy) - [ ] DNS Cache - [ ] TUN/TAP device support diff --git a/http.go b/http.go index 218efbb..dc5f877 100644 --- a/http.go +++ b/http.go @@ -249,7 +249,7 @@ func (s *HTTP) Dial(network, addr string) (net.Conn, error) { // DialUDP connects to the given address via the proxy. func (s *HTTP) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) { - return nil, nil, errors.New("DialUDP not supported") + return nil, nil, errors.New("http client does not support udp") } // parseFirstLine parses "GET /foo HTTP/1.1" OR "HTTP/1.1 200 OK" into its three parts. diff --git a/mixed.go b/mixed.go index 93b69df..05cc724 100644 --- a/mixed.go +++ b/mixed.go @@ -41,18 +41,21 @@ func NewMixedProxy(addr, user, pass, rawQuery string, sDialer Dialer) (*MixedPro // ListenAndServe . func (p *MixedProxy) ListenAndServe() { + + go p.socks5.ListenAndServeUDP() + l, err := net.Listen("tcp", p.addr) if err != nil { - logf("failed to listen on %s: %v", p.addr, err) + logf("proxy-mixed failed to listen on %s: %v", p.addr, err) return } - logf("listening TCP on %s", p.addr) + logf("proxy-mixed listening TCP on %s", p.addr) for { c, err := l.Accept() if err != nil { - logf("failed to accept: %v", err) + logf("proxy-mixed failed to accept: %v", err) continue } @@ -73,13 +76,13 @@ func (p *MixedProxy) Serve(conn net.Conn) { if p.socks5 != nil { head, err := c.Peek(1) if err != nil { - logf("peek error: %s", err) + logf("proxy-mixed peek error: %s", err) return } // check socks5, client send socksversion: 5 as the first byte if head[0] == socks5Version { - p.socks5.Serve(c) + p.socks5.ServeTCP(c) return } } @@ -87,7 +90,7 @@ func (p *MixedProxy) Serve(conn net.Conn) { if p.http != nil { head, err := c.Peek(8) if err != nil { - logf("peek error: %s", err) + logf("proxy-mixed peek error: %s", err) return } diff --git a/socks5.go b/socks5.go index e025144..ac7154c 100644 --- a/socks5.go +++ b/socks5.go @@ -14,6 +14,8 @@ import ( "io" "net" "strconv" + "sync" + "time" ) const socks5Version = 5 @@ -43,16 +45,17 @@ const MaxAddrLen = 1 + 1 + 255 + 2 // Addr represents a SOCKS address as defined in RFC 1928 section 5. type Addr []byte -var socks5Errors = []string{ - "", - "general failure", - "connection forbidden", - "network unreachable", - "host unreachable", - "connection refused", - "TTL expired", - "command not supported", - "address type not supported", +var socks5Errors = []error{ + errors.New(""), + errors.New("general failure"), + errors.New("connection forbidden"), + errors.New("network unreachable"), + errors.New("host unreachable"), + errors.New("connection refused"), + errors.New("TTL expired"), + errors.New("command not supported"), + errors.New("address type not supported"), + errors.New("socks5UDPAssociate"), } // SOCKS5 struct @@ -77,8 +80,14 @@ func NewSOCKS5(addr, user, pass string, cDialer Dialer, sDialer Dialer) (*SOCKS5 return s, nil } -// ListenAndServe . +// ListenAndServe serves socks5 requests. func (s *SOCKS5) ListenAndServe() { + go s.ListenAndServeUDP() + s.ListenAndServeTCP() +} + +// ListenAndServeTCP . +func (s *SOCKS5) ListenAndServeTCP() { l, err := net.Listen("tcp", s.addr) if err != nil { logf("proxy-socks5 failed to listen on %s: %v", s.addr, err) @@ -94,12 +103,12 @@ func (s *SOCKS5) ListenAndServe() { continue } - go s.Serve(c) + go s.ServeTCP(c) } } -// Serve . -func (s *SOCKS5) Serve(c net.Conn) { +// ServeTCP . +func (s *SOCKS5) ServeTCP(c net.Conn) { defer c.Close() if c, ok := c.(*net.TCPConn); ok { @@ -108,6 +117,20 @@ func (s *SOCKS5) Serve(c net.Conn) { tgt, err := s.handshake(c) if err != nil { + // UDP: keep the connection until disconnect then free the UDP socket + if err == socks5Errors[9] { + buf := []byte{} + // block here + for { + _, err := c.Read(buf) + if err, ok := err.(net.Error); ok && err.Timeout() { + continue + } + logf("UDP Associate End.") + return + } + } + logf("proxy-socks5 failed to get target address: %v", err) return } @@ -130,6 +153,62 @@ func (s *SOCKS5) Serve(c net.Conn) { } } +// ListenAndServeUDP serves udp requests. +func (s *SOCKS5) ListenAndServeUDP() { + lc, err := net.ListenPacket("udp", s.addr) + if err != nil { + logf("proxy-socks5-udp failed to listen on %s: %v", s.addr, err) + return + } + defer lc.Close() + + logf("proxy-socks5-udp listening UDP on %s", s.addr) + + var nm sync.Map + buf := make([]byte, udpBufSize) + + for { + c := NewSocks5PktConn(lc, nil, nil, true) + + n, raddr, err := c.ReadFrom(buf) + if err != nil { + logf("proxy-socks5-udp remote read error: %v", err) + continue + } + + var pc *Socks5PktConn + v, ok := nm.Load(raddr.String()) + if !ok && v == nil { + lpc, nextHop, err := s.sDialer.DialUDP("udp", c.tgtAddr.String()) + if err != nil { + logf("proxy-socks5-udp remote dial error: %v", err) + continue + } + + pc = NewSocks5PktConn(lpc, nextHop, nil, false) + nm.Store(raddr.String(), pc) + + go func() { + timedCopy(c, raddr, pc, 2*time.Minute) + pc.Close() + nm.Delete(raddr.String()) + }() + + } else { + pc = v.(*Socks5PktConn) + } + + _, err = pc.WriteTo(buf[:n], pc.writeAddr) + if err != nil { + logf("proxy-socks5-udp remote write error: %v", err) + continue + } + + logf("proxy-socks5-udp %s <-> %s", raddr, c.tgtAddr) + } + +} + // Dial connects to the address addr on the network net via the SOCKS5 proxy. func (s *SOCKS5) Dial(network, addr string) (net.Conn, error) { switch network { @@ -158,7 +237,7 @@ func (s *SOCKS5) Dial(network, addr string) (net.Conn, error) { // DialUDP connects to the given address via the proxy. func (s *SOCKS5) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) { - return nil, nil, errors.New("DialUDP not supported") + return nil, nil, errors.New("sock5 client does not support udp now") } // connect takes an existing connection to a socks5 proxy server, @@ -254,7 +333,7 @@ func (s *SOCKS5) connect(conn net.Conn, target string) error { failure := "unknown error" if int(buf[1]) < len(socks5Errors) { - failure = socks5Errors[buf[1]] + failure = socks5Errors[buf[1]].Error() } if len(failure) > 0 { @@ -314,16 +393,26 @@ func (s *SOCKS5) handshake(rw io.ReadWriter) (Addr, error) { if _, err := io.ReadFull(rw, buf[:3]); err != nil { return nil, err } - if buf[1] != socks5Connect { - return nil, errors.New(socks5Errors[7]) - } + cmd := buf[1] 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 + switch cmd { + case socks5Connect: + _, err = rw.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}) // SOCKS v5, reply succeeded + case socks5UDPAssociate: + listenAddr := ParseAddr(rw.(net.Conn).LocalAddr().String()) + _, err = rw.Write(append([]byte{5, 0, 0}, listenAddr...)) // SOCKS v5, reply succeeded + if err != nil { + return nil, socks5Errors[7] + } + err = socks5Errors[9] + default: + return nil, socks5Errors[7] + } + + return addr, err // skip VER, CMD, RSV fields } // String serializes SOCKS address a to string form. @@ -380,7 +469,7 @@ func readAddr(r io.Reader, b []byte) (Addr, error) { return b[:1+net.IPv6len+2], err } - return nil, errors.New(socks5Errors[8]) + return nil, socks5Errors[8] } // ReadAddr reads just enough bytes from r to get a valid Addr. @@ -453,3 +542,64 @@ func ParseAddr(s string) Addr { return addr } + +// Socks5PktConn . +type Socks5PktConn struct { + net.PacketConn + + writeAddr net.Addr // write to and read from addr + + tgtAddr Addr + tgtHeader bool +} + +// NewSocks5PktConn returns a Socks5PktConn +func NewSocks5PktConn(c net.PacketConn, writeAddr net.Addr, tgtAddr Addr, tgtHeader bool) *Socks5PktConn { + pc := &Socks5PktConn{ + PacketConn: c, + writeAddr: writeAddr, + tgtAddr: tgtAddr, + tgtHeader: tgtHeader} + return pc +} + +func (pc *Socks5PktConn) ReadFrom(b []byte) (int, net.Addr, error) { + if !pc.tgtHeader { + return pc.PacketConn.ReadFrom(b) + } + + buf := make([]byte, len(b)) + n, raddr, err := pc.PacketConn.ReadFrom(buf) + if err != nil { + return n, raddr, err + } + + // https://tools.ietf.org/html/rfc1928#section-7 + // +----+------+------+----------+----------+----------+ + // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | + // +----+------+------+----------+----------+----------+ + // | 2 | 1 | 1 | Variable | 2 | Variable | + // +----+------+------+----------+----------+----------+ + tgtAddr := SplitAddr(buf[3:]) + copy(b, buf[3+len(tgtAddr):]) + + //test + if pc.writeAddr == nil { + pc.writeAddr = raddr + } + + if pc.tgtAddr == nil { + pc.tgtAddr = tgtAddr + } + + return n - len(tgtAddr), raddr, err +} + +func (pc *Socks5PktConn) WriteTo(b []byte, addr net.Addr) (int, error) { + if !pc.tgtHeader { + return pc.PacketConn.WriteTo(b, addr) + } + + buf := append(append([]byte{0, 0, 0}, b[:]...)) + return pc.PacketConn.WriteTo(buf, pc.writeAddr) +} diff --git a/ss.go b/ss.go index d85bd33..a63952e 100644 --- a/ss.go +++ b/ss.go @@ -243,7 +243,7 @@ func (s *SS) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) { return pkc, nextHop, err } -// PktConn wraps a net.PacketConn and support Write method like net.Conn +// PktConn . type PktConn struct { net.PacketConn diff --git a/udptun.go b/udptun.go index 7c14cbf..4c6f16f 100644 --- a/udptun.go +++ b/udptun.go @@ -59,6 +59,7 @@ func (s *UDPTun) ListenAndServe() { } nm.Store(raddr.String(), pc) + go func() { timedCopy(c, raddr, pc, 2*time.Minute) pc.Close() diff --git a/uottun.go b/uottun.go index 7b99c9a..27c9330 100644 --- a/uottun.go +++ b/uottun.go @@ -69,7 +69,12 @@ func (s *UoTTun) ListenAndServe() { c.WriteTo(resp, clientAddr) }() - rc.Write(buf[:n]) + _, err = rc.Write(buf[:n]) + if err != nil { + logf("proxy-uottun remote write error: %v", err) + continue + } + logf("proxy-uottun %s <-> %s", clientAddr, s.raddr) } }