From 787ed14c4db778ce90af7a692a03f3d52ae44bb3 Mon Sep 17 00:00:00 2001 From: Toby Date: Sat, 24 Apr 2021 02:56:17 -0700 Subject: [PATCH] TCP TProxy implementation, no UDP or ACL support yet --- cmd/client.go | 33 +++++++++++++++++++++ cmd/config.go | 19 +++++++++++-- go.mod | 1 + go.sum | 2 ++ pkg/tproxy/tcp_linux.go | 63 +++++++++++++++++++++++++++++++++++++++++ pkg/tproxy/tcp_stub.go | 21 ++++++++++++++ pkg/utils/pipe.go | 2 +- 7 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 pkg/tproxy/tcp_linux.go create mode 100644 pkg/tproxy/tcp_stub.go diff --git a/cmd/client.go b/cmd/client.go index d7b39e9..8284448 100644 --- a/cmd/client.go +++ b/cmd/client.go @@ -13,6 +13,7 @@ import ( "github.com/tobyxdd/hysteria/pkg/obfs" "github.com/tobyxdd/hysteria/pkg/relay" "github.com/tobyxdd/hysteria/pkg/socks5" + "github.com/tobyxdd/hysteria/pkg/tproxy" "io" "io/ioutil" "net" @@ -239,6 +240,38 @@ func client(config *clientConfig) { }() } + if len(config.TCPTProxy.Listen) > 0 { + go func() { + rl, err := tproxy.NewTCPTProxy(client, config.TCPTProxy.Listen, + time.Duration(config.TCPTProxy.Timeout)*time.Second, + func(addr, reqAddr net.Addr) { + logrus.WithFields(logrus.Fields{ + "src": addr.String(), + "dst": reqAddr.String(), + }).Debug("TCP TProxy request") + }, + func(addr, reqAddr net.Addr, err error) { + if err != io.EOF { + logrus.WithFields(logrus.Fields{ + "error": err, + "src": addr.String(), + "dst": reqAddr.String(), + }).Info("TCP TProxy error") + } else { + logrus.WithFields(logrus.Fields{ + "src": addr.String(), + "dst": reqAddr.String(), + }).Debug("TCP TProxy EOF") + } + }) + if err != nil { + logrus.WithField("error", err).Fatal("Failed to initialize TCP TProxy") + } + logrus.WithField("addr", config.TCPTProxy.Listen).Info("TCP TProxy up and running") + errChan <- rl.ListenAndServe() + }() + } + err = <-errChan logrus.WithField("error", err).Fatal("Client shutdown") } diff --git a/cmd/config.go b/cmd/config.go index dfceb63..328af07 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -90,6 +90,14 @@ type clientConfig struct { Remote string `json:"remote"` Timeout int `json:"timeout"` } `json:"relay_udp"` + TCPTProxy struct { + Listen string `json:"listen"` + Timeout int `json:"timeout"` + } `json:"tproxy_tcp"` + UDPTProxy struct { + Listen string `json:"listen"` + Timeout int `json:"timeout"` + } `json:"tproxy_udp"` ACL string `json:"acl"` Obfs string `json:"obfs"` Auth []byte `json:"auth"` @@ -102,8 +110,9 @@ type clientConfig struct { func (c *clientConfig) Check() error { if len(c.SOCKS5.Listen) == 0 && len(c.HTTP.Listen) == 0 && - len(c.TCPRelay.Listen) == 0 && len(c.UDPRelay.Listen) == 0 { - return errors.New("no SOCKS5, HTTP, TCP relay or UDP relay listen address") + 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") } if len(c.TCPRelay.Listen) > 0 && len(c.TCPRelay.Remote) == 0 { return errors.New("no TCP relay remote address") @@ -123,6 +132,12 @@ func (c *clientConfig) Check() error { if c.UDPRelay.Timeout != 0 && c.UDPRelay.Timeout <= 4 { return errors.New("invalid UDP relay timeout") } + if c.TCPTProxy.Timeout != 0 && c.TCPTProxy.Timeout <= 4 { + return errors.New("invalid TCP TProxy timeout") + } + if c.UDPTProxy.Timeout != 0 && c.UDPTProxy.Timeout <= 4 { + return errors.New("invalid UDP TProxy timeout") + } if len(c.Server) == 0 { return errors.New("no server address") } diff --git a/go.mod b/go.mod index 59c6577..564bfed 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/tobyxdd/hysteria go 1.14 require ( + github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed github.com/elazarl/goproxy v0.0.0-20200426045556-49ad98f6dac1 github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 github.com/hashicorp/golang-lru v0.5.4 diff --git a/go.sum b/go.sum index c40f603..1d2c52e 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,8 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed h1:eqa6queieK8SvoszxCu0WwH7lSVeL4/N/f1JwOMw1G4= +github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed/go.mod h1:rA52xkgZwql9LRZXWb2arHEFP6qSR48KY2xOfWzEciQ= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= diff --git a/pkg/tproxy/tcp_linux.go b/pkg/tproxy/tcp_linux.go new file mode 100644 index 0000000..bf9163a --- /dev/null +++ b/pkg/tproxy/tcp_linux.go @@ -0,0 +1,63 @@ +package tproxy + +import ( + "github.com/LiamHaworth/go-tproxy" + "github.com/tobyxdd/hysteria/pkg/core" + "github.com/tobyxdd/hysteria/pkg/utils" + "net" + "time" +) + +type TCPTProxy struct { + HyClient *core.Client + ListenAddr *net.TCPAddr + Timeout time.Duration + + ConnFunc func(addr, reqAddr net.Addr) + ErrorFunc func(addr, reqAddr net.Addr, err error) +} + +func NewTCPTProxy(hyClient *core.Client, listen string, timeout time.Duration, + connFunc func(addr, reqAddr net.Addr), errorFunc func(addr, reqAddr net.Addr, err error)) (*TCPTProxy, error) { + tAddr, err := net.ResolveTCPAddr("tcp", listen) + if err != nil { + return nil, err + } + r := &TCPTProxy{ + HyClient: hyClient, + ListenAddr: tAddr, + Timeout: timeout, + ConnFunc: connFunc, + ErrorFunc: errorFunc, + } + return r, nil +} + +func (r *TCPTProxy) ListenAndServe() error { + listener, err := tproxy.ListenTCP("tcp", r.ListenAddr) + if err != nil { + return err + } + defer listener.Close() + for { + c, err := listener.Accept() + if err != nil { + return err + } + go func() { + defer c.Close() + // Under TPROXY mode, we are effectively acting as the remote server + // So our LocalAddr is actually the target to which the user is trying to connect + // and our RemoteAddr is the local address where the user initiates the connection + r.ConnFunc(c.RemoteAddr(), c.LocalAddr()) + rc, err := r.HyClient.DialTCP(c.LocalAddr().String()) + if err != nil { + r.ErrorFunc(c.RemoteAddr(), c.LocalAddr(), err) + return + } + defer rc.Close() + err = utils.PipePairWithTimeout(c, rc, r.Timeout) + r.ErrorFunc(c.RemoteAddr(), c.LocalAddr(), err) + }() + } +} diff --git a/pkg/tproxy/tcp_stub.go b/pkg/tproxy/tcp_stub.go new file mode 100644 index 0000000..d89057e --- /dev/null +++ b/pkg/tproxy/tcp_stub.go @@ -0,0 +1,21 @@ +// +build !linux + +package tproxy + +import ( + "errors" + "github.com/tobyxdd/hysteria/pkg/core" + "net" + "time" +) + +type TCPTProxy struct{} + +func NewTCPTProxy(hyClient *core.Client, listen string, timeout time.Duration, + connFunc func(addr, reqAddr net.Addr), errorFunc func(addr, reqAddr net.Addr, err error)) (*TCPTProxy, error) { + return nil, errors.New("not supported on the current system") +} + +func (r *TCPTProxy) ListenAndServe() error { + return nil +} diff --git a/pkg/utils/pipe.go b/pkg/utils/pipe.go index 14ec68e..caf053b 100644 --- a/pkg/utils/pipe.go +++ b/pkg/utils/pipe.go @@ -46,7 +46,7 @@ func Pipe2Way(rw1, rw2 io.ReadWriter, count func(int)) error { return <-errChan } -func PipePairWithTimeout(conn *net.TCPConn, stream io.ReadWriteCloser, timeout time.Duration) error { +func PipePairWithTimeout(conn net.Conn, stream io.ReadWriteCloser, timeout time.Duration) error { errChan := make(chan error, 2) // TCP to stream go func() {