From 142865535e179e394802ef459fdbdb6774858267 Mon Sep 17 00:00:00 2001 From: nadoo <287492+nadoo@users.noreply.github.com> Date: Tue, 1 Dec 2020 20:04:47 +0800 Subject: [PATCH] unix: support udp forwarding (#194) --- README.md | 5 +- config.go | 4 ++ go.mod | 4 +- go.sum | 8 +-- proxy/kcp/kcp.go | 5 +- proxy/udp/udp.go | 4 +- proxy/unix/client.go | 76 ++++++++++++++++++++++ proxy/unix/server.go | 149 +++++++++++++++++++++++++++++++++++++++++++ proxy/unix/unix.go | 113 ++++---------------------------- rule/group.go | 2 +- 10 files changed, 259 insertions(+), 111 deletions(-) create mode 100644 proxy/unix/client.go create mode 100644 proxy/unix/server.go diff --git a/README.md b/README.md index 35233a0..7c77f4d 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ we can set up local listeners as proxy servers, and forward requests to internet |UDP | |√| |√|udp tunnel client & server |TLS |√| |√| |transport client & server |KCP | |√|√| |transport client & server -|Unix |√| |√| |transport client & server +|Unix |√|√|√|√|transport client & server |Websocket |√| |√| |transport client & server |Simple-Obfs | | |√| |transport client only |Redir |√| | | |linux only @@ -238,6 +238,9 @@ KCP scheme: Available crypt types for KCP: none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20 +Available modes for KCP: + fast, fast2, fast3, normal, default: fast + Simple-Obfs scheme: simple-obfs://host:port[?type=TYPE&host=HOST&uri=URI&ua=UA] diff --git a/config.go b/config.go index 262fb11..11d6623 100644 --- a/config.go +++ b/config.go @@ -239,6 +239,10 @@ func usage() { fmt.Fprintf(w, " none, sm4, tea, xor, aes, aes-128, aes-192, blowfish, twofish, cast5, 3des, xtea, salsa20\n") fmt.Fprintf(w, "\n") + fmt.Fprintf(w, "Available modes for KCP:\n") + fmt.Fprintf(w, " fast, fast2, fast3, normal, default: fast\n") + fmt.Fprintf(w, "\n") + fmt.Fprintf(w, "Simple-Obfs scheme:\n") fmt.Fprintf(w, " simple-obfs://host:port[?type=TYPE&host=HOST&uri=URI&ua=UA]\n") fmt.Fprintf(w, "\n") diff --git a/go.mod b/go.mod index 07a14fe..e8bcc0a 100644 --- a/go.mod +++ b/go.mod @@ -15,8 +15,8 @@ require ( github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/xtaci/kcp-go/v5 v5.6.1 golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 - golang.org/x/sys v0.0.0-20201130072748-111129e158e2 // indirect - golang.org/x/tools v0.0.0-20201125231158-b5590deeca9b // indirect + golang.org/x/sys v0.0.0-20201130171929-760e229fe7c5 // indirect + golang.org/x/tools v0.0.0-20201201064407-fd09bd90d85c // indirect gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect ) diff --git a/go.sum b/go.sum index 9e7ddb2..25a2f7e 100644 --- a/go.sum +++ b/go.sum @@ -128,8 +128,8 @@ golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201130072748-111129e158e2 h1:zXpk15uCEAaaJcTxBqQacweHUQ0HDhDOzupNGFs4imE= -golang.org/x/sys v0.0.0-20201130072748-111129e158e2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201130171929-760e229fe7c5 h1:dMDtAap8F/+vsyXblqK90iTzYJjNix5MsXDicSYol6w= +golang.org/x/sys v0.0.0-20201130171929-760e229fe7c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -141,8 +141,8 @@ golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201105001634-bc3cf281b174 h1:0rx0F4EjJNbxTuzWe0KjKcIzs+3VEb/Mrs/d1ciNz1c= golang.org/x/tools v0.0.0-20201105001634-bc3cf281b174/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201125231158-b5590deeca9b h1:Lq5JUTFhiybGVf28jB6QRpqd13/JPOaCnET17PVzYJE= -golang.org/x/tools v0.0.0-20201125231158-b5590deeca9b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201064407-fd09bd90d85c h1:D/mVYXCk6gUcyr7WuGlAk/ShHqgARUXc2VQxo27Hmws= +golang.org/x/tools v0.0.0-20201201064407-fd09bd90d85c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/proxy/kcp/kcp.go b/proxy/kcp/kcp.go index 6627035..e16592a 100644 --- a/proxy/kcp/kcp.go +++ b/proxy/kcp/kcp.go @@ -24,8 +24,8 @@ type KCP struct { key string crypt string - mode string block kcp.BlockCrypt + mode string dataShards int parityShards int @@ -258,7 +258,8 @@ func (s *KCP) setParams(c *kcp.UDPSession) { c.SetNoDelay(1, 20, 2, 1) case "fast3": c.SetNoDelay(1, 10, 2, 1) - default: // default use fast + default: + log.F("[kcp] unkonw mode: %s, use fast mode instead", s.mode) c.SetNoDelay(0, 30, 2, 1) } diff --git a/proxy/udp/udp.go b/proxy/udp/udp.go index 90c577f..27e480f 100644 --- a/proxy/udp/udp.go +++ b/proxy/udp/udp.go @@ -77,7 +77,9 @@ func (s *UDP) ListenAndServe() { v, ok := nm.Load(lraddr.String()) if !ok && v == nil { - pc, dialer, raddr, err = s.proxy.DialUDP("udp", "") + // we know we are creating an udp tunnel, so the dial addr is meaningless, + // we use lraddr here to help the unix client to identify the source socket. + pc, dialer, raddr, err = s.proxy.DialUDP("udp", lraddr.String()) if err != nil { log.F("[udp] remote dial error: %v", err) continue diff --git a/proxy/unix/client.go b/proxy/unix/client.go new file mode 100644 index 0000000..2ab2a19 --- /dev/null +++ b/proxy/unix/client.go @@ -0,0 +1,76 @@ +package unix + +import ( + "net" + "os" + + "github.com/nadoo/glider/proxy" +) + +func init() { + proxy.RegisterDialer("unix", NewUnixDialer) +} + +// NewUnixDialer returns a unix domain socket dialer. +func NewUnixDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { + return NewUnix(s, d, nil) +} + +// Addr returns forwarder's address. +func (s *Unix) Addr() string { + if s.addr == "" { + return s.dialer.Addr() + } + return s.addr +} + +// Dial connects to the address addr on the network net via the proxy. +// NOTE: must be the first dialer in a chain +func (s *Unix) Dial(network, addr string) (net.Conn, error) { + return net.Dial("unix", s.addr) +} + +// DialUDP connects to the given address via the proxy. +// NOTE: must be the first dialer in a chain +func (s *Unix) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) { + laddru := s.addru + "_" + addr + os.Remove(laddru) + + luaddru, err := net.ResolveUnixAddr("unixgram", laddru) + if err != nil { + return nil, nil, err + } + + pc, err := net.ListenUnixgram("unixgram", luaddru) + if err != nil { + return nil, nil, err + } + + return &PktConn{pc, laddru, luaddru, s.uaddru}, s.uaddru, nil +} + +// PktConn . +type PktConn struct { + *net.UnixConn + addr string + uaddr *net.UnixAddr + writeAddr *net.UnixAddr +} + +// ReadFrom overrides the original function from net.PacketConn. +func (pc *PktConn) ReadFrom(b []byte) (int, net.Addr, error) { + n, _, err := pc.UnixConn.ReadFrom(b) + return n, pc.uaddr, err +} + +// WriteTo overrides the original function from net.PacketConn. +func (pc *PktConn) WriteTo(b []byte, addr net.Addr) (int, error) { + return pc.UnixConn.WriteTo(b, pc.writeAddr) +} + +// Close overrides the original function from net.PacketConn. +func (pc *PktConn) Close() error { + pc.UnixConn.Close() + os.Remove(pc.addr) + return nil +} diff --git a/proxy/unix/server.go b/proxy/unix/server.go new file mode 100644 index 0000000..fdf503c --- /dev/null +++ b/proxy/unix/server.go @@ -0,0 +1,149 @@ +package unix + +import ( + "net" + "os" + "strings" + "sync" + "time" + + "github.com/nadoo/glider/log" + "github.com/nadoo/glider/proxy" +) + +func init() { + proxy.RegisterServer("unix", NewUnixServer) +} + +// NewUnixServer returns a unix domain socket server. +func NewUnixServer(s string, p proxy.Proxy) (proxy.Server, error) { + transport := strings.Split(s, ",") + + unix, err := NewUnix(transport[0], nil, p) + if err != nil { + return nil, err + } + + if len(transport) > 1 { + unix.server, err = proxy.ServerFromURL(transport[1], p) + if err != nil { + return nil, err + } + } + + return unix, nil +} + +// ListenAndServe serves requests. +func (s *Unix) ListenAndServe() { + go s.ListenAndServeUDP() + s.ListenAndServeTCP() +} + +// ListenAndServe serves tcp requests. +func (s *Unix) ListenAndServeTCP() { + os.Remove(s.addr) + l, err := net.Listen("unix", s.addr) + if err != nil { + log.F("[unix] failed to listen on %s: %v", s.addr, err) + return + } + defer l.Close() + + log.F("[unix] listening on %s", s.addr) + + for { + c, err := l.Accept() + if err != nil { + log.F("[unix] failed to accept: %v", err) + continue + } + + go s.Serve(c) + } +} + +// Serve serves requests. +func (s *Unix) Serve(c net.Conn) { + if s.server != nil { + s.server.Serve(c) + return + } + + defer c.Close() + + rc, dialer, err := s.proxy.Dial("unix", "") + if err != nil { + log.F("[unix] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), s.addr, dialer.Addr(), err) + s.proxy.Record(dialer, false) + return + } + defer rc.Close() + + log.F("[unix] %s <-> %s", c.RemoteAddr(), dialer.Addr()) + + if err = proxy.Relay(c, rc); err != nil { + log.F("[unix] %s <-> %s, relay error: %v", c.RemoteAddr(), dialer.Addr(), err) + // record remote conn failure only + if !strings.Contains(err.Error(), s.addr) { + s.proxy.Record(dialer, false) + } + } +} + +// ListenAndServe serves udp requests. +func (s *Unix) ListenAndServeUDP() { + os.Remove(s.addru) + c, err := net.ListenPacket("unixgram", s.addru) + if err != nil { + log.F("[unix] failed to ListenPacket on %s: %v", s.addru, err) + return + } + defer c.Close() + + log.F("[unix] ListenPacket on %s", s.addru) + + var nm sync.Map + buf := make([]byte, proxy.UDPBufSize) + + for { + n, lraddr, err := c.ReadFrom(buf) + if err != nil { + log.F("[unix] read error: %v", err) + continue + } + + var raddr net.Addr + var pc net.PacketConn + var dialer proxy.UDPDialer + + v, ok := nm.Load(lraddr.String()) + if !ok && v == nil { + pc, dialer, raddr, err = s.proxy.DialUDP("udp", "") + if err != nil { + log.F("[unix] remote dial error: %v", err) + continue + } + + nm.Store(lraddr.String(), pc) + + go func(c, pc net.PacketConn, lraddr net.Addr) { + proxy.RelayUDP(c, lraddr, pc, 2*time.Minute) + pc.Close() + nm.Delete(lraddr.String()) + }(c, pc, lraddr) + + } else { + pc = v.(net.PacketConn) + } + + _, err = pc.WriteTo(buf[:n], raddr) + if err != nil { + log.F("[unix] remote write error: %v", err) + continue + } + + log.F("[unix] %s <-> %s", s.addru, dialer.Addr()) + + } +} diff --git a/proxy/unix/unix.go b/proxy/unix/unix.go index 37ac01a..ed21ba1 100644 --- a/proxy/unix/unix.go +++ b/proxy/unix/unix.go @@ -1,11 +1,8 @@ package unix import ( - "errors" "net" "net/url" - "os" - "strings" "github.com/nadoo/glider/log" "github.com/nadoo/glider/proxy" @@ -15,21 +12,20 @@ import ( type Unix struct { dialer proxy.Dialer proxy proxy.Proxy - addr string - server proxy.Server + + addr string // addr for tcp + uaddr *net.UnixAddr + + addru string // addr for udp (datagram) + uaddru *net.UnixAddr } -func init() { - proxy.RegisterServer("unix", NewUnixServer) - proxy.RegisterDialer("unix", NewUnixDialer) -} - -// NewUnix returns unix fomain socket proxy. +// NewUnix returns unix domain socket proxy. func NewUnix(s string, d proxy.Dialer, p proxy.Proxy) (*Unix, error) { u, err := url.Parse(s) if err != nil { - log.F("parse url err: %s", err) + log.F("[unix] parse url err: %s", err) return nil, err } @@ -37,101 +33,18 @@ func NewUnix(s string, d proxy.Dialer, p proxy.Proxy) (*Unix, error) { dialer: d, proxy: p, addr: u.Path, + addru: u.Path + "u", } - return unix, nil -} - -// NewUnixDialer returns a unix domain socket dialer. -func NewUnixDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { - return NewUnix(s, d, nil) -} - -// NewUnixServer returns a unix domain socket server. -func NewUnixServer(s string, p proxy.Proxy) (proxy.Server, error) { - transport := strings.Split(s, ",") - - unix, err := NewUnix(transport[0], nil, p) + unix.uaddr, err = net.ResolveUnixAddr("unixgram", unix.addr) if err != nil { return nil, err } - if len(transport) > 1 { - unix.server, err = proxy.ServerFromURL(transport[1], p) - if err != nil { - return nil, err - } + unix.uaddru, err = net.ResolveUnixAddr("unixgram", unix.addru) + if err != nil { + return nil, err } return unix, nil } - -// ListenAndServe serves requests. -func (s *Unix) ListenAndServe() { - os.Remove(s.addr) - l, err := net.Listen("unix", s.addr) - if err != nil { - log.F("[unix] failed to listen on %s: %v", s.addr, err) - return - } - defer l.Close() - - log.F("[unix] listening on %s", s.addr) - - for { - c, err := l.Accept() - if err != nil { - log.F("[unix] failed to accept: %v", err) - continue - } - - go s.Serve(c) - } -} - -// Serve serves requests. -func (s *Unix) Serve(c net.Conn) { - if s.server != nil { - s.server.Serve(c) - return - } - - defer c.Close() - - rc, dialer, err := s.proxy.Dial("unix", "") - if err != nil { - log.F("[unix] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), s.addr, dialer.Addr(), err) - s.proxy.Record(dialer, false) - return - } - defer rc.Close() - - log.F("[unix] %s <-> %s", c.RemoteAddr(), dialer.Addr()) - - if err = proxy.Relay(c, rc); err != nil { - log.F("[unix] %s <-> %s, relay error: %v", c.RemoteAddr(), dialer.Addr(), err) - // record remote conn failure only - if !strings.Contains(err.Error(), s.addr) { - s.proxy.Record(dialer, false) - } - } -} - -// Addr returns forwarder's address. -func (s *Unix) Addr() string { - if s.addr == "" { - return s.dialer.Addr() - } - return s.addr -} - -// Dial connects to the address addr on the network net via the proxy. -func (s *Unix) Dial(network, addr string) (net.Conn, error) { - // NOTE: must be the first dialer in a chain - return net.Dial("unix", s.addr) -} - -// DialUDP connects to the given address via the proxy. -func (s *Unix) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) { - return nil, nil, errors.New("unix domain socket client does not support udp now") -} diff --git a/rule/group.go b/rule/group.go index bb2d858..1edd699 100644 --- a/rule/group.go +++ b/rule/group.go @@ -214,7 +214,7 @@ func (p *FwdrGroup) Check() { case "file": checker = newFileChecker(u.Host + u.Path) default: - log.F("[group] invalid check config `%s`, disable health checking", p.config.Check) + log.F("[group] check config `%s`, disable health checking", p.config.Check) return }