From 13ec19cbfe9709d8f0489788762da4e74bce1c34 Mon Sep 17 00:00:00 2001 From: Haruue Icymoon Date: Sat, 24 Apr 2021 20:18:58 +0800 Subject: [PATCH 1/6] Implement TUN, no ACL support yet --- README.md | 9 +++++ README.zh.md | 9 +++++ cmd/client.go | 12 +++++++ cmd/config.go | 14 +++++++- go.mod | 1 + go.sum | 5 +++ pkg/tun/server.go | 59 ++++++++++++++++++++++++++++++++ pkg/tun/tcp.go | 73 ++++++++++++++++++++++++++++++++++++++++ pkg/tun/udp.go | 85 +++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 pkg/tun/server.go create mode 100644 pkg/tun/tcp.go create mode 100644 pkg/tun/udp.go diff --git a/README.md b/README.md index 8202222..a074e32 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,15 @@ hysteria_traffic_uplink_bytes_total{auth="aGFja2VyISE="} 37452 "cert": "/home/ubuntu/my_cert.crt", // Cert file (HTTPS proxy) "key": "/home/ubuntu/my_key.crt" // Key file (HTTPS proxy) }, + "tun": { + "name": "tun-hy", // TUN interface name + "timeout": 300, // UDP timeout in seconds + "address": "192.0.2.2", // TUN interface address, not applicable for Linux + "gateway": "192.0.2.1", // TUN interface gateway, not applicable for Linux + "mask": "255.255.255.252", // TUN interface mask, not applicable for Linux + "dns": [ "8.8.8.8", "8.8.4.4" ], // TUN interface DNS, only applicable for Windows + "persist": false // Persist TUN interface after exit, only applicable for Linux + }, "relay_tcp": { "listen": "127.0.0.1:2222", // TCP relay listen address "remote": "123.123.123.123:22", // TCP relay remote address diff --git a/README.zh.md b/README.zh.md index d31a6a1..1ab9216 100644 --- a/README.zh.md +++ b/README.zh.md @@ -232,6 +232,15 @@ hysteria_traffic_uplink_bytes_total{auth="aGFja2VyISE="} 37452 "cert": "/home/ubuntu/my_cert.crt", // 证书 (变为 HTTPS 代理) "key": "/home/ubuntu/my_key.crt" // 证书密钥 (变为 HTTPS 代理) }, + "tun": { + "name": "tun-hy", // TUN 接口名称 + "timeout": 300, // UDP 超时秒数 + "address": "192.0.2.2", // TUN 接口地址(不适用于 Linux) + "gateway": "192.0.2.1", // TUN 接口网关(不适用于 Linux) + "mask": "255.255.255.252", // TUN 接口子网掩码(不适用于 Linux) + "dns": [ "8.8.8.8", "8.8.4.4" ], // TUN 接口 DNS 服务器(仅适用于 Windows) + "persist": false // 在程序退出之后保留接口(仅适用于 Linux) + }, "relay_tcp": { "listen": "127.0.0.1:2222", // TCP 转发监听地址 "remote": "123.123.123.123:22", // TCP 转发目标地址 diff --git a/cmd/client.go b/cmd/client.go index c57c14e..a32f0ce 100644 --- a/cmd/client.go +++ b/cmd/client.go @@ -15,6 +15,7 @@ import ( "github.com/tobyxdd/hysteria/pkg/socks5" "github.com/tobyxdd/hysteria/pkg/tproxy" "github.com/tobyxdd/hysteria/pkg/transport" + "github.com/tobyxdd/hysteria/pkg/tun" "io" "io/ioutil" "net" @@ -188,6 +189,17 @@ func client(config *clientConfig) { }() } + if len(config.TUN.Name) != 0 { + go func() { + tunServer, err := tun.NewServer(client, time.Duration(config.TUN.Timeout)*time.Second, + config.TUN.Name, config.TUN.Address, config.TUN.Gateway, config.TUN.Mask, config.TUN.DNS, config.TUN.Persist) + if err != nil { + logrus.WithField("error", err).Fatal("Failed to initialize TUN server") + } + errChan <- tunServer.ListenAndServe() + }() + } + if len(config.TCPRelay.Listen) > 0 { go func() { rl, err := relay.NewTCPRelay(client, transport.DefaultTransport, diff --git a/cmd/config.go b/cmd/config.go index f448edc..fdd47f0 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -88,6 +88,15 @@ type clientConfig struct { Cert string `json:"cert"` Key string `json:"key"` } `json:"http"` + TUN struct { + Name string `json:"name"` + Timeout int `json:"timeout"` + Address string `json:"address"` + Gateway string `json:"gateway"` + Mask string `json:"mask"` + DNS []string `json:"dns"` + Persist bool `json:"persist"` + } `json:"tun"` TCPRelay struct { Listen string `json:"listen"` Remote string `json:"remote"` @@ -117,7 +126,7 @@ type clientConfig struct { } func (c *clientConfig) Check() error { - if len(c.SOCKS5.Listen) == 0 && len(c.HTTP.Listen) == 0 && + if len(c.SOCKS5.Listen) == 0 && len(c.HTTP.Listen) == 0 && len(c.TUN.Name) == 0 && len(c.TCPRelay.Listen) == 0 && len(c.UDPRelay.Listen) == 0 && len(c.TCPTProxy.Listen) == 0 && len(c.UDPTProxy.Listen) == 0 { return errors.New("no SOCKS5, HTTP, relay or TProxy listen address") @@ -134,6 +143,9 @@ func (c *clientConfig) Check() error { if c.HTTP.Timeout != 0 && c.HTTP.Timeout <= 4 { return errors.New("invalid HTTP timeout") } + if c.TUN.Timeout != 0 && c.TUN.Timeout < 4 { + return errors.New("invalid TUN timeout") + } if c.TCPRelay.Timeout != 0 && c.TCPRelay.Timeout <= 4 { return errors.New("invalid TCP relay timeout") } diff --git a/go.mod b/go.mod index 7ebb0da..899c66e 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/caddyserver/certmagic v0.13.0 github.com/elazarl/goproxy v0.0.0-20210110162100-a92cc753f88e github.com/elazarl/goproxy/ext v0.0.0-20210110162100-a92cc753f88e + github.com/eycorsican/go-tun2socks v1.16.11 github.com/hashicorp/golang-lru v0.5.4 github.com/lucas-clemente/quic-go v0.20.1 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 diff --git a/go.sum b/go.sum index 5b4de64..1ea9836 100644 --- a/go.sum +++ b/go.sum @@ -78,6 +78,8 @@ github.com/elazarl/goproxy/ext v0.0.0-20210110162100-a92cc753f88e/go.mod h1:gNh8 github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/eycorsican/go-tun2socks v1.16.11 h1:+hJDNgisrYaGEqoSxhdikMgMJ4Ilfwm/IZDrWRrbaH8= +github.com/eycorsican/go-tun2socks v1.16.11/go.mod h1:wgB2BFT8ZaPKyKOQ/5dljMG/YIow+AIXyq4KBwJ5sGQ= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= @@ -369,6 +371,8 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b h1:+y4hCMc/WKsDbAPsOQZgBSaSZ26uh2afyaWeVg/3s/c= +github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= @@ -469,6 +473,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= diff --git a/pkg/tun/server.go b/pkg/tun/server.go new file mode 100644 index 0000000..a4d8863 --- /dev/null +++ b/pkg/tun/server.go @@ -0,0 +1,59 @@ +package tun + +import ( + tun2socks "github.com/eycorsican/go-tun2socks/core" + "github.com/eycorsican/go-tun2socks/tun" + "github.com/tobyxdd/hysteria/pkg/core" + "io" + "sync" + "time" +) + +type Server struct { + HyClient *core.Client + Timeout time.Duration + TunDev io.ReadWriteCloser + + udpConnMap map[tun2socks.UDPConn]*UDPConnInfo + udpConnMapLock sync.RWMutex +} + +const ( + MTU = 1500 +) + +func NewServerWithTunDev(hyClient *core.Client, timeout time.Duration, + tunDev io.ReadWriteCloser) (*Server, error) { + s := &Server{ + HyClient: hyClient, + Timeout: timeout, + TunDev: tunDev, + udpConnMap: make(map[tun2socks.UDPConn]*UDPConnInfo), + } + return s, nil +} + +func NewServer(hyClient *core.Client, timeout time.Duration, + name, address, gateway, mask string, dnsServers []string, persist bool) (*Server, error) { + tunDev, err := tun.OpenTunDevice(name, address, gateway, mask, dnsServers, persist) + if err != nil { + return nil, err + } + return NewServerWithTunDev(hyClient, timeout, tunDev) +} + +func (s *Server) ListenAndServe() error { + lwipWriter := tun2socks.NewLWIPStack().(io.Writer) + + tun2socks.RegisterTCPConnHandler(s) + tun2socks.RegisterUDPConnHandler(s) + tun2socks.RegisterOutputFn(func(data []byte) (int, error) { + return s.TunDev.Write(data) + }) + + _, err := io.CopyBuffer(lwipWriter, s.TunDev, make([]byte, MTU)) + if err != nil { + return err + } + return nil +} diff --git a/pkg/tun/tcp.go b/pkg/tun/tcp.go new file mode 100644 index 0000000..bb82b36 --- /dev/null +++ b/pkg/tun/tcp.go @@ -0,0 +1,73 @@ +package tun + +import ( + "io" + "net" +) + +func (s *Server) Handle(conn net.Conn, target *net.TCPAddr) error { + hyConn, err := s.HyClient.DialTCP(target.String()) + if err != nil { + return err + } + go s.relay(conn, hyConn) + return nil +} + +type direction byte + +const ( + directionUplink direction = iota + directionDownlink +) + +type duplexConn interface { + net.Conn + CloseRead() error + CloseWrite() error +} + +func (s *Server) relay(clientConn, relayConn net.Conn) { + uplinkDone := make(chan struct{}) + + halfCloseConn := func(dir direction, interrupt bool) { + clientDuplexConn, ok1 := clientConn.(duplexConn) + relayDuplexConn, ok2 := relayConn.(duplexConn) + if !interrupt && ok1 && ok2 { + switch dir { + case directionUplink: + clientDuplexConn.CloseRead() + relayDuplexConn.CloseWrite() + case directionDownlink: + clientDuplexConn.CloseWrite() + relayDuplexConn.CloseRead() + } + } else { + clientConn.Close() + relayConn.Close() + } + } + + // Uplink + go func() { + var err error + _, err = io.Copy(relayConn, clientConn) + if err != nil { + halfCloseConn(directionUplink, true) + } else { + halfCloseConn(directionUplink, false) + } + uplinkDone <- struct{}{} + }() + + // Downlink + var err error + _, err = io.Copy(clientConn, relayConn) + if err != nil { + halfCloseConn(directionDownlink, true) + } else { + halfCloseConn(directionDownlink, false) + } + + <-uplinkDone +} diff --git a/pkg/tun/udp.go b/pkg/tun/udp.go new file mode 100644 index 0000000..9c737a4 --- /dev/null +++ b/pkg/tun/udp.go @@ -0,0 +1,85 @@ +package tun + +import ( + "fmt" + tun2socks "github.com/eycorsican/go-tun2socks/core" + "github.com/tobyxdd/hysteria/pkg/core" + "log" + "net" + "sync/atomic" + "time" +) + +type UDPConnInfo struct { + hyConn core.UDPConn + expire atomic.Value +} + +func (s *Server) fetchUDPInput(conn tun2socks.UDPConn, ci *UDPConnInfo) { + defer func() { + s.closeUDPConn(conn) + }() + + if s.Timeout > 0 { + go func() { + for { + life := ci.expire.Load().(time.Time).Sub(time.Now()) + if life < 0 { + s.closeUDPConn(conn) + break + } else { + time.Sleep(life) + } + } + }() + } + + for { + bs, from, err := ci.hyConn.ReadFrom() + if err != nil { + break + } + ci.expire.Store(time.Now().Add(s.Timeout)) + udpAddr, _ := net.ResolveUDPAddr("udp", from) + _, _ = conn.WriteFrom(bs, udpAddr) + } +} + +func (s *Server) Connect(conn tun2socks.UDPConn, target *net.UDPAddr) error { + c, err := s.HyClient.DialUDP() + if err != nil { + return err + } + ci := UDPConnInfo{ + hyConn: c, + } + ci.expire.Store(time.Now().Add(s.Timeout)) + s.udpConnMapLock.Lock() + s.udpConnMap[conn] = &ci + s.udpConnMapLock.Unlock() + go s.fetchUDPInput(conn, &ci) + return nil +} + +func (s *Server) ReceiveTo(conn tun2socks.UDPConn, data []byte, addr *net.UDPAddr) error { + s.udpConnMapLock.RLock() + ci, ok := s.udpConnMap[conn] + s.udpConnMapLock.RUnlock() + if !ok { + log.Printf("not connected: %s <-> %s\n", conn.LocalAddr().String(), addr.String()) + return fmt.Errorf("not connected: %s <-> %s", conn.LocalAddr().String(), addr.String()) + } + ci.expire.Store(time.Now().Add(s.Timeout)) + _ = ci.hyConn.WriteTo(data, addr.String()) + return nil +} + +func (s *Server) closeUDPConn(conn tun2socks.UDPConn) { + conn.Close() + s.udpConnMapLock.Lock() + defer s.udpConnMapLock.Unlock() + if c, ok := s.udpConnMap[conn]; ok { + c.hyConn.Close() + delete(s.udpConnMap, conn) + } +} From 81128a7626b89512709f5a0bbe0ef3a8d0c96abb Mon Sep 17 00:00:00 2001 From: Haruue Icymoon Date: Thu, 29 Apr 2021 01:37:32 +0800 Subject: [PATCH 2/6] ACL for TUN --- README.md | 2 +- README.zh.md | 2 +- cmd/client.go | 32 +++++++++++- pkg/tun/server.go | 27 ++++++++--- pkg/tun/tcp.go | 121 +++++++++++++++++++++++----------------------- pkg/tun/udp.go | 119 ++++++++++++++++++++++++++++++++++++++++----- 6 files changed, 220 insertions(+), 83 deletions(-) diff --git a/README.md b/README.md index a074e32..38e822a 100644 --- a/README.md +++ b/README.md @@ -247,7 +247,7 @@ hysteria_traffic_uplink_bytes_total{auth="aGFja2VyISE="} 37452 }, "tun": { "name": "tun-hy", // TUN interface name - "timeout": 300, // UDP timeout in seconds + "timeout": 300, // Timeout in seconds "address": "192.0.2.2", // TUN interface address, not applicable for Linux "gateway": "192.0.2.1", // TUN interface gateway, not applicable for Linux "mask": "255.255.255.252", // TUN interface mask, not applicable for Linux diff --git a/README.zh.md b/README.zh.md index 1ab9216..44290d7 100644 --- a/README.zh.md +++ b/README.zh.md @@ -234,7 +234,7 @@ hysteria_traffic_uplink_bytes_total{auth="aGFja2VyISE="} 37452 }, "tun": { "name": "tun-hy", // TUN 接口名称 - "timeout": 300, // UDP 超时秒数 + "timeout": 300, // 超时秒数 "address": "192.0.2.2", // TUN 接口地址(不适用于 Linux) "gateway": "192.0.2.1", // TUN 接口网关(不适用于 Linux) "mask": "255.255.255.252", // TUN 接口子网掩码(不适用于 Linux) diff --git a/cmd/client.go b/cmd/client.go index a32f0ce..13fbc9f 100644 --- a/cmd/client.go +++ b/cmd/client.go @@ -20,6 +20,7 @@ import ( "io/ioutil" "net" "net/http" + "strings" "time" ) @@ -191,11 +192,40 @@ func client(config *clientConfig) { if len(config.TUN.Name) != 0 { go func() { - tunServer, err := tun.NewServer(client, time.Duration(config.TUN.Timeout)*time.Second, + tunServer, err := tun.NewServer(client, transport.DefaultTransport, + time.Duration(config.TUN.Timeout)*time.Second, config.TUN.Name, config.TUN.Address, config.TUN.Gateway, config.TUN.Mask, config.TUN.DNS, config.TUN.Persist) if err != nil { logrus.WithField("error", err).Fatal("Failed to initialize TUN server") } + tunServer.RequestFunc = func(addr net.Addr, reqAddr string, action acl.Action, arg string) { + logrus.WithFields(logrus.Fields{ + "action": actionToString(action, arg), + "src": addr.String(), + "dst": reqAddr, + }).Debugf("TUN %s request", strings.ToUpper(addr.Network())) + } + tunServer.ErrorFunc = func(addr net.Addr, reqAddr string, err error) { + if err != nil { + if err == io.EOF { + logrus.WithFields(logrus.Fields{ + "src": addr.String(), + "dst": reqAddr, + }).Debugf("TUN %s EOF", strings.ToUpper(addr.Network())) + } else if err == core.ErrClosed && strings.HasPrefix(addr.Network(), "udp") { + logrus.WithFields(logrus.Fields{ + "src": addr.String(), + "dst": reqAddr, + }).Debugf("TUN %s closed for timeout", strings.ToUpper(addr.Network())) + } else { + logrus.WithFields(logrus.Fields{ + "error": err, + "src": addr.String(), + "dst": reqAddr, + }).Infof("TUN %s error", strings.ToUpper(addr.Network())) + } + } + } errChan <- tunServer.ListenAndServe() }() } diff --git a/pkg/tun/server.go b/pkg/tun/server.go index a4d8863..b66cc8b 100644 --- a/pkg/tun/server.go +++ b/pkg/tun/server.go @@ -3,18 +3,26 @@ package tun import ( tun2socks "github.com/eycorsican/go-tun2socks/core" "github.com/eycorsican/go-tun2socks/tun" + "github.com/tobyxdd/hysteria/pkg/acl" "github.com/tobyxdd/hysteria/pkg/core" + "github.com/tobyxdd/hysteria/pkg/transport" "io" + "net" "sync" "time" ) type Server struct { - HyClient *core.Client - Timeout time.Duration - TunDev io.ReadWriteCloser + HyClient *core.Client + Timeout time.Duration + TunDev io.ReadWriteCloser + Transport transport.Transport + ACLEngine *acl.Engine - udpConnMap map[tun2socks.UDPConn]*UDPConnInfo + RequestFunc func(addr net.Addr, reqAddr string, action acl.Action, arg string) + ErrorFunc func(addr net.Addr, reqAddr string, err error) + + udpConnMap map[tun2socks.UDPConn]*udpConnInfo udpConnMapLock sync.RWMutex } @@ -22,24 +30,27 @@ const ( MTU = 1500 ) -func NewServerWithTunDev(hyClient *core.Client, timeout time.Duration, +func NewServerWithTunDev(hyClient *core.Client, transport transport.Transport, + timeout time.Duration, tunDev io.ReadWriteCloser) (*Server, error) { s := &Server{ HyClient: hyClient, + Transport: transport, Timeout: timeout, TunDev: tunDev, - udpConnMap: make(map[tun2socks.UDPConn]*UDPConnInfo), + udpConnMap: make(map[tun2socks.UDPConn]*udpConnInfo), } return s, nil } -func NewServer(hyClient *core.Client, timeout time.Duration, +func NewServer(hyClient *core.Client, transport transport.Transport, + timeout time.Duration, name, address, gateway, mask string, dnsServers []string, persist bool) (*Server, error) { tunDev, err := tun.OpenTunDevice(name, address, gateway, mask, dnsServers, persist) if err != nil { return nil, err } - return NewServerWithTunDev(hyClient, timeout, tunDev) + return NewServerWithTunDev(hyClient, transport, timeout, tunDev) } func (s *Server) ListenAndServe() error { diff --git a/pkg/tun/tcp.go b/pkg/tun/tcp.go index bb82b36..d0e8ce7 100644 --- a/pkg/tun/tcp.go +++ b/pkg/tun/tcp.go @@ -1,73 +1,74 @@ package tun import ( - "io" + "errors" + "fmt" + "github.com/tobyxdd/hysteria/pkg/acl" + "github.com/tobyxdd/hysteria/pkg/utils" "net" + "strconv" ) func (s *Server) Handle(conn net.Conn, target *net.TCPAddr) error { - hyConn, err := s.HyClient.DialTCP(target.String()) - if err != nil { - return err + action, arg := acl.ActionProxy, "" + var resErr error + if s.ACLEngine != nil { + action, arg, _, resErr = s.ACLEngine.ResolveAndMatch(target.IP.String()) } - go s.relay(conn, hyConn) - return nil -} - -type direction byte - -const ( - directionUplink direction = iota - directionDownlink -) - -type duplexConn interface { - net.Conn - CloseRead() error - CloseWrite() error -} - -func (s *Server) relay(clientConn, relayConn net.Conn) { - uplinkDone := make(chan struct{}) - - halfCloseConn := func(dir direction, interrupt bool) { - clientDuplexConn, ok1 := clientConn.(duplexConn) - relayDuplexConn, ok2 := relayConn.(duplexConn) - if !interrupt && ok1 && ok2 { - switch dir { - case directionUplink: - clientDuplexConn.CloseRead() - relayDuplexConn.CloseWrite() - case directionDownlink: - clientDuplexConn.CloseWrite() - relayDuplexConn.CloseRead() - } - } else { - clientConn.Close() - relayConn.Close() - } + if s.RequestFunc != nil { + s.RequestFunc(conn.LocalAddr(), target.String(), action, arg) } - - // Uplink - go func() { - var err error - _, err = io.Copy(relayConn, clientConn) - if err != nil { - halfCloseConn(directionUplink, true) - } else { - halfCloseConn(directionUplink, false) + var closeErr error + defer func() { + if s.ErrorFunc != nil && closeErr != nil { + s.ErrorFunc(conn.LocalAddr(), target.String(), closeErr) } - uplinkDone <- struct{}{} }() - - // Downlink - var err error - _, err = io.Copy(clientConn, relayConn) - if err != nil { - halfCloseConn(directionDownlink, true) - } else { - halfCloseConn(directionDownlink, false) + switch action { + case acl.ActionDirect: + if resErr != nil { + closeErr = resErr + return resErr + } + rc, err := s.Transport.LocalDialTCP(nil, target) + if err != nil { + closeErr = err + return err + } + go s.relayTCP(conn, rc) + return nil + case acl.ActionProxy: + rc, err := s.HyClient.DialTCP(target.String()) + if err != nil { + closeErr = err + return err + } + go s.relayTCP(conn, rc) + return nil + case acl.ActionBlock: + closeErr = errors.New("blocked in ACL") + // caller will abort the connection when err != nil + return closeErr + case acl.ActionHijack: + rc, err := s.Transport.LocalDial("tcp", net.JoinHostPort(arg, strconv.Itoa(target.Port))) + if err != nil { + closeErr = err + return err + } + go s.relayTCP(conn, rc) + return nil + default: + closeErr = fmt.Errorf("unknown action %d", action) + // caller will abort the connection when err != nil + return closeErr } - - <-uplinkDone +} + +func (s *Server) relayTCP(clientConn, relayConn net.Conn) { + closeErr := utils.PipePairWithTimeout(clientConn, relayConn, s.Timeout) + if s.ErrorFunc != nil { + s.ErrorFunc(clientConn.LocalAddr(), relayConn.RemoteAddr().String(), closeErr) + } + relayConn.Close() + clientConn.Close() } diff --git a/pkg/tun/udp.go b/pkg/tun/udp.go index 9c737a4..9c74e27 100644 --- a/pkg/tun/udp.go +++ b/pkg/tun/udp.go @@ -1,21 +1,28 @@ package tun import ( + "bytes" + "errors" "fmt" tun2socks "github.com/eycorsican/go-tun2socks/core" + "github.com/tobyxdd/hysteria/pkg/acl" "github.com/tobyxdd/hysteria/pkg/core" - "log" + "io" "net" + "strconv" "sync/atomic" "time" ) -type UDPConnInfo struct { +const udpBufferSize = 65535 + +type udpConnInfo struct { hyConn core.UDPConn + target string expire atomic.Value } -func (s *Server) fetchUDPInput(conn tun2socks.UDPConn, ci *UDPConnInfo) { +func (s *Server) fetchUDPInput(conn tun2socks.UDPConn, ci *udpConnInfo) { defer func() { s.closeUDPConn(conn) }() @@ -34,24 +41,85 @@ func (s *Server) fetchUDPInput(conn tun2socks.UDPConn, ci *UDPConnInfo) { }() } + var err error + for { - bs, from, err := ci.hyConn.ReadFrom() + var bs []byte + var from string + bs, from, err = ci.hyConn.ReadFrom() if err != nil { break } ci.expire.Store(time.Now().Add(s.Timeout)) udpAddr, _ := net.ResolveUDPAddr("udp", from) - _, _ = conn.WriteFrom(bs, udpAddr) + _, err = conn.WriteFrom(bs, udpAddr) + if err != nil { + break + } + } + + if s.ErrorFunc != nil { + s.ErrorFunc(conn.LocalAddr(), ci.target, err) } } func (s *Server) Connect(conn tun2socks.UDPConn, target *net.UDPAddr) error { - c, err := s.HyClient.DialUDP() - if err != nil { - return err + action, arg := acl.ActionProxy, "" + var resErr error + if s.ACLEngine != nil { + action, arg, _, resErr = s.ACLEngine.ResolveAndMatch(target.IP.String()) } - ci := UDPConnInfo{ - hyConn: c, + if s.RequestFunc != nil { + s.RequestFunc(conn.LocalAddr(), target.String(), action, arg) + } + var hyConn core.UDPConn + var closeErr error + defer func() { + if s.ErrorFunc != nil && closeErr != nil { + s.ErrorFunc(conn.LocalAddr(), target.String(), closeErr) + } + }() + switch action { + case acl.ActionDirect: + if resErr != nil { + closeErr = resErr + return resErr + } + var relayConn net.Conn + relayConn, closeErr = s.Transport.LocalDial("udp", target.String()) + if closeErr != nil { + return closeErr + } + hyConn = &delegatedUDPConn{ + underlayConn: relayConn, + delegatedRemoteAddr: target.String(), + } + case acl.ActionProxy: + hyConn, closeErr = s.HyClient.DialUDP() + if closeErr != nil { + return closeErr + } + case acl.ActionBlock: + closeErr = errors.New("blocked in ACL") + return closeErr + case acl.ActionHijack: + hijackAddr := net.JoinHostPort(arg, strconv.Itoa(target.Port)) + var relayConn net.Conn + relayConn, closeErr = s.Transport.LocalDial("udp", hijackAddr) + if closeErr != nil { + return closeErr + } + hyConn = &delegatedUDPConn{ + underlayConn: relayConn, + delegatedRemoteAddr: target.String(), + } + default: + closeErr = fmt.Errorf("unknown action %d", action) + return nil + } + ci := udpConnInfo{ + hyConn: hyConn, + target: net.JoinHostPort(target.IP.String(), strconv.Itoa(target.Port)), } ci.expire.Store(time.Now().Add(s.Timeout)) s.udpConnMapLock.Lock() @@ -66,8 +134,9 @@ func (s *Server) ReceiveTo(conn tun2socks.UDPConn, data []byte, addr *net.UDPAdd ci, ok := s.udpConnMap[conn] s.udpConnMapLock.RUnlock() if !ok { - log.Printf("not connected: %s <-> %s\n", conn.LocalAddr().String(), addr.String()) - return fmt.Errorf("not connected: %s <-> %s", conn.LocalAddr().String(), addr.String()) + err := errors.New("previous connection closed for timeout") + s.ErrorFunc(conn.LocalAddr(), addr.String(), err) + return err } ci.expire.Store(time.Now().Add(s.Timeout)) _ = ci.hyConn.WriteTo(data, addr.String()) @@ -83,3 +152,29 @@ func (s *Server) closeUDPConn(conn tun2socks.UDPConn) { delete(s.udpConnMap, conn) } } + +type delegatedUDPConn struct { + underlayConn net.Conn + delegatedRemoteAddr string +} + +func (c *delegatedUDPConn) ReadFrom() (bs []byte, addr string, err error) { + buf := make([]byte, udpBufferSize) + n, err := c.underlayConn.Read(buf) + if n > 0 { + bs = append(bs, buf[0:n]...) + } + if err != nil || err == io.EOF { + addr = c.delegatedRemoteAddr + } + return +} + +func (c *delegatedUDPConn) WriteTo(bs []byte, addr string) error { + _, err := io.Copy(c.underlayConn, bytes.NewReader(bs)) + return err +} + +func (c *delegatedUDPConn) Close() error { + return c.underlayConn.Close() +} From 284efce6db1a852ecb54fc4caee88dbe509e6db8 Mon Sep 17 00:00:00 2001 From: Haruue Icymoon Date: Thu, 29 Apr 2021 16:59:36 +0800 Subject: [PATCH 3/6] Set default timeout to 300s for TUN --- cmd/client.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/client.go b/cmd/client.go index 13fbc9f..71ed9a6 100644 --- a/cmd/client.go +++ b/cmd/client.go @@ -192,6 +192,10 @@ func client(config *clientConfig) { if len(config.TUN.Name) != 0 { go func() { + timeout := time.Duration(config.TUN.Timeout) * time.Second + if timeout == 0 { + timeout = 300 * time.Second + } tunServer, err := tun.NewServer(client, transport.DefaultTransport, time.Duration(config.TUN.Timeout)*time.Second, config.TUN.Name, config.TUN.Address, config.TUN.Gateway, config.TUN.Mask, config.TUN.DNS, config.TUN.Persist) From abfacebd40cce61cc77136d09213c832ea2e3efd Mon Sep 17 00:00:00 2001 From: Haruue Icymoon Date: Thu, 29 Apr 2021 17:21:19 +0800 Subject: [PATCH 4/6] Fix acl engine not set in TUN server --- cmd/client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/client.go b/cmd/client.go index 71ed9a6..e934146 100644 --- a/cmd/client.go +++ b/cmd/client.go @@ -202,6 +202,7 @@ func client(config *clientConfig) { if err != nil { logrus.WithField("error", err).Fatal("Failed to initialize TUN server") } + tunServer.ACLEngine = aclEngine tunServer.RequestFunc = func(addr net.Addr, reqAddr string, action acl.Action, arg string) { logrus.WithFields(logrus.Fields{ "action": actionToString(action, arg), From 68d8cd4043c545ad08531b774b627aa84264212c Mon Sep 17 00:00:00 2001 From: Haruue Icymoon Date: Mon, 3 May 2021 15:43:25 +0800 Subject: [PATCH 5/6] Fix TCP timeout not works in TUN server --- cmd/client.go | 5 +++++ pkg/tun/tcp.go | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/cmd/client.go b/cmd/client.go index e934146..1cb4263 100644 --- a/cmd/client.go +++ b/cmd/client.go @@ -222,6 +222,11 @@ func client(config *clientConfig) { "src": addr.String(), "dst": reqAddr, }).Debugf("TUN %s closed for timeout", strings.ToUpper(addr.Network())) + } else if err.Error() == "deadline exceeded" && strings.HasPrefix(addr.Network(), "tcp") { + logrus.WithFields(logrus.Fields{ + "src": addr.String(), + "dst": reqAddr, + }).Debugf("TUN %s closed for timeout", strings.ToUpper(addr.Network())) } else { logrus.WithFields(logrus.Fields{ "error": err, diff --git a/pkg/tun/tcp.go b/pkg/tun/tcp.go index d0e8ce7..8a9ef9e 100644 --- a/pkg/tun/tcp.go +++ b/pkg/tun/tcp.go @@ -3,6 +3,7 @@ package tun import ( "errors" "fmt" + tun2socks "github.com/eycorsican/go-tun2socks/core" "github.com/tobyxdd/hysteria/pkg/acl" "github.com/tobyxdd/hysteria/pkg/utils" "net" @@ -65,10 +66,15 @@ func (s *Server) Handle(conn net.Conn, target *net.TCPAddr) error { } func (s *Server) relayTCP(clientConn, relayConn net.Conn) { - closeErr := utils.PipePairWithTimeout(clientConn, relayConn, s.Timeout) + closeErr := utils.PipePairWithTimeout(relayConn, clientConn, s.Timeout) if s.ErrorFunc != nil { s.ErrorFunc(clientConn.LocalAddr(), relayConn.RemoteAddr().String(), closeErr) } relayConn.Close() clientConn.Close() + if closeErr != nil && closeErr.Error() == "deadline exceeded" { + if clientConn, ok := clientConn.(tun2socks.TCPConn); ok { + clientConn.Abort() + } + } } From 54b525d55960e28cdf739fb5e0b354e63b5cda77 Mon Sep 17 00:00:00 2001 From: Toby Date: Sat, 8 May 2021 18:04:22 -0700 Subject: [PATCH 6/6] Improve config error text --- cmd/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/config.go b/cmd/config.go index fdd47f0..20bf883 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -129,7 +129,7 @@ func (c *clientConfig) Check() error { if len(c.SOCKS5.Listen) == 0 && len(c.HTTP.Listen) == 0 && len(c.TUN.Name) == 0 && len(c.TCPRelay.Listen) == 0 && len(c.UDPRelay.Listen) == 0 && len(c.TCPTProxy.Listen) == 0 && len(c.UDPTProxy.Listen) == 0 { - return errors.New("no SOCKS5, HTTP, relay or TProxy listen address") + return errors.New("please enable at least one mode") } if len(c.TCPRelay.Listen) > 0 && len(c.TCPRelay.Remote) == 0 { return errors.New("no TCP relay remote address")