From 13ec19cbfe9709d8f0489788762da4e74bce1c34 Mon Sep 17 00:00:00 2001 From: Haruue Icymoon Date: Sat, 24 Apr 2021 20:18:58 +0800 Subject: [PATCH] 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) + } +}