Skip to content

Commit

Permalink
feat: TProxy
Browse files Browse the repository at this point in the history
  • Loading branch information
tobyxdd committed Aug 11, 2023
1 parent 4060bcb commit ceb3c7f
Show file tree
Hide file tree
Showing 10 changed files with 472 additions and 70 deletions.
26 changes: 19 additions & 7 deletions app/client.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,23 @@ http:
# password: pass
# realm: my_private_realm

forwarding:
- listen: 127.0.0.1:6666
remote: 127.0.0.1:5201
protocol: tcp
tcpForwarding:
- listen: 127.0.0.1:8088
remote: example.com:80
- listen: 127.0.0.1:9099
remote: example.com:90

udpForwarding:
- listen: 127.0.0.1:5353
remote: 1.1.1.1:53
protocol: udp
udpTimeout: 30s
remote: example.com:53
timeout: 50s
- listen: 127.0.0.1:6464
remote: example.com:64
timeout: 20s

tcpTProxy:
listen: 127.0.0.1:2500

udpTProxy:
listen: 127.0.0.1:2501
timeout: 20s
218 changes: 167 additions & 51 deletions app/cmd/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/apernet/hysteria/app/internal/forwarding"
"github.com/apernet/hysteria/app/internal/http"
"github.com/apernet/hysteria/app/internal/socks5"
"github.com/apernet/hysteria/app/internal/tproxy"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/extras/obfs"
)
Expand All @@ -43,17 +44,20 @@ func initClientFlags() {
}

type clientConfig struct {
Server string `mapstructure:"server"`
Auth string `mapstructure:"auth"`
Obfs clientConfigObfs `mapstructure:"obfs"`
TLS clientConfigTLS `mapstructure:"tls"`
QUIC clientConfigQUIC `mapstructure:"quic"`
Bandwidth clientConfigBandwidth `mapstructure:"bandwidth"`
FastOpen bool `mapstructure:"fastOpen"`
Lazy bool `mapstructure:"lazy"`
SOCKS5 *socks5Config `mapstructure:"socks5"`
HTTP *httpConfig `mapstructure:"http"`
Forwarding []forwardingEntry `mapstructure:"forwarding"`
Server string `mapstructure:"server"`
Auth string `mapstructure:"auth"`
Obfs clientConfigObfs `mapstructure:"obfs"`
TLS clientConfigTLS `mapstructure:"tls"`
QUIC clientConfigQUIC `mapstructure:"quic"`
Bandwidth clientConfigBandwidth `mapstructure:"bandwidth"`
FastOpen bool `mapstructure:"fastOpen"`
Lazy bool `mapstructure:"lazy"`
SOCKS5 *socks5Config `mapstructure:"socks5"`
HTTP *httpConfig `mapstructure:"http"`
TCPForwarding []tcpForwardingEntry `mapstructure:"tcpForwarding"`
UDPForwarding []udpForwardingEntry `mapstructure:"udpForwarding"`
TCPTProxy *tcpTProxyConfig `mapstructure:"tcpTProxy"`
UDPTProxy *udpTProxyConfig `mapstructure:"udpTProxy"`
}

type clientConfigObfsSalamander struct {
Expand Down Expand Up @@ -100,11 +104,24 @@ type httpConfig struct {
Realm string `mapstructure:"realm"`
}

type forwardingEntry struct {
Listen string `mapstructure:"listen"`
Remote string `mapstructure:"remote"`
Protocol string `mapstructure:"protocol"`
UDPTimeout time.Duration `mapstructure:"udpTimeout"`
type tcpForwardingEntry struct {
Listen string `mapstructure:"listen"`
Remote string `mapstructure:"remote"`
}

type udpForwardingEntry struct {
Listen string `mapstructure:"listen"`
Remote string `mapstructure:"remote"`
Timeout time.Duration `mapstructure:"timeout"`
}

type tcpTProxyConfig struct {
Listen string `mapstructure:"listen"`
}

type udpTProxyConfig struct {
Listen string `mapstructure:"listen"`
Timeout time.Duration `mapstructure:"timeout"`
}

func (c *clientConfig) fillConnFactory(hyConfig *client.Config) error {
Expand Down Expand Up @@ -355,13 +372,43 @@ func runClient(cmd *cobra.Command, args []string) {
}
}()
}
if len(config.Forwarding) > 0 {
if len(config.TCPForwarding) > 0 {
hasMode = true
wg.Add(1)
go func() {
defer wg.Done()
if err := clientForwarding(config.Forwarding, c); err != nil {
logger.Fatal("failed to run forwarding", zap.Error(err))
if err := clientTCPForwarding(config.TCPForwarding, c); err != nil {
logger.Fatal("failed to run TCP forwarding", zap.Error(err))
}
}()
}
if len(config.UDPForwarding) > 0 {
hasMode = true
wg.Add(1)
go func() {
defer wg.Done()
if err := clientUDPForwarding(config.UDPForwarding, c); err != nil {
logger.Fatal("failed to run UDP forwarding", zap.Error(err))
}
}()
}
if config.TCPTProxy != nil {
hasMode = true
wg.Add(1)
go func() {
defer wg.Done()
if err := clientTCPTProxy(*config.TCPTProxy, c); err != nil {
logger.Fatal("failed to run TCP transparent proxy", zap.Error(err))
}
}()
}
if config.UDPTProxy != nil {
hasMode = true
wg.Add(1)
go func() {
defer wg.Done()
if err := clientUDPTProxy(*config.UDPTProxy, c); err != nil {
logger.Fatal("failed to run UDP transparent proxy", zap.Error(err))
}
}()
}
Expand Down Expand Up @@ -425,7 +472,7 @@ func clientHTTP(config httpConfig, c client.Client) error {
return h.Serve(l)
}

func clientForwarding(entries []forwardingEntry, c client.Client) error {
func clientTCPForwarding(entries []tcpForwardingEntry, c client.Client) error {
errChan := make(chan error, len(entries))
for _, e := range entries {
if e.Listen == "" {
Expand All @@ -434,44 +481,85 @@ func clientForwarding(entries []forwardingEntry, c client.Client) error {
if e.Remote == "" {
return configError{Field: "remote", Err: errors.New("remote address is empty")}
}
switch strings.ToLower(e.Protocol) {
case "tcp":
l, err := net.Listen("tcp", e.Listen)
if err != nil {
return configError{Field: "listen", Err: err}
}
logger.Info("TCP forwarding listening", zap.String("addr", e.Listen), zap.String("remote", e.Remote))
go func(remote string) {
t := &forwarding.TCPTunnel{
HyClient: c,
Remote: remote,
EventLogger: &tcpLogger{},
}
errChan <- t.Serve(l)
}(e.Remote)
case "udp":
l, err := net.ListenPacket("udp", e.Listen)
if err != nil {
return configError{Field: "listen", Err: err}
l, err := net.Listen("tcp", e.Listen)
if err != nil {
return configError{Field: "listen", Err: err}
}
logger.Info("TCP forwarding listening", zap.String("addr", e.Listen), zap.String("remote", e.Remote))
go func(remote string) {
t := &forwarding.TCPTunnel{
HyClient: c,
Remote: remote,
EventLogger: &tcpLogger{},
}
logger.Info("UDP forwarding listening", zap.String("addr", e.Listen), zap.String("remote", e.Remote))
go func(remote string, timeout time.Duration) {
u := &forwarding.UDPTunnel{
HyClient: c,
Remote: remote,
Timeout: timeout,
EventLogger: &udpLogger{},
}
errChan <- u.Serve(l)
}(e.Remote, e.UDPTimeout)
default:
return configError{Field: "protocol", Err: errors.New("unsupported protocol")}
errChan <- t.Serve(l)
}(e.Remote)
}
// Return if any one of the forwarding fails
return <-errChan
}

func clientUDPForwarding(entries []udpForwardingEntry, c client.Client) error {
errChan := make(chan error, len(entries))
for _, e := range entries {
if e.Listen == "" {
return configError{Field: "listen", Err: errors.New("listen address is empty")}
}
if e.Remote == "" {
return configError{Field: "remote", Err: errors.New("remote address is empty")}
}
l, err := net.ListenPacket("udp", e.Listen)
if err != nil {
return configError{Field: "listen", Err: err}
}
logger.Info("UDP forwarding listening", zap.String("addr", e.Listen), zap.String("remote", e.Remote))
go func(remote string, timeout time.Duration) {
u := &forwarding.UDPTunnel{
HyClient: c,
Remote: remote,
Timeout: timeout,
EventLogger: &udpLogger{},
}
errChan <- u.Serve(l)
}(e.Remote, e.Timeout)
}
// Return if any one of the forwarding fails
return <-errChan
}

func clientTCPTProxy(config tcpTProxyConfig, c client.Client) error {
if config.Listen == "" {
return configError{Field: "listen", Err: errors.New("listen address is empty")}
}
laddr, err := net.ResolveTCPAddr("tcp", config.Listen)
if err != nil {
return configError{Field: "listen", Err: err}
}
p := &tproxy.TCPTProxy{
HyClient: c,
EventLogger: &tcpTProxyLogger{},
}
logger.Info("TCP transparent proxy listening", zap.String("addr", config.Listen))
return p.ListenAndServe(laddr)
}

func clientUDPTProxy(config udpTProxyConfig, c client.Client) error {
if config.Listen == "" {
return configError{Field: "listen", Err: errors.New("listen address is empty")}
}
laddr, err := net.ResolveUDPAddr("udp", config.Listen)
if err != nil {
return configError{Field: "listen", Err: err}
}
p := &tproxy.UDPTProxy{
HyClient: c,
Timeout: config.Timeout,
EventLogger: &udpTProxyLogger{},
}
logger.Info("UDP transparent proxy listening", zap.String("addr", config.Listen))
return p.ListenAndServe(laddr)
}

// parseServerAddrString parses server address string.
// Server address can be in either "host:port" or "host" format (in which case we assume port 443).
func parseServerAddrString(addrStr string) (host, hostPort string) {
Expand Down Expand Up @@ -584,3 +672,31 @@ func (l *udpLogger) Error(addr net.Addr, err error) {
logger.Error("UDP forwarding error", zap.String("addr", addr.String()), zap.Error(err))
}
}

type tcpTProxyLogger struct{}

func (l *tcpTProxyLogger) Connect(addr, reqAddr net.Addr) {
logger.Debug("TCP transparent proxy connect", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr.String()))
}

func (l *tcpTProxyLogger) Error(addr, reqAddr net.Addr, err error) {
if err == nil {
logger.Debug("TCP transparent proxy closed", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr.String()))
} else {
logger.Error("TCP transparent proxy error", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr.String()), zap.Error(err))
}
}

type udpTProxyLogger struct{}

func (l *udpTProxyLogger) Connect(addr, reqAddr net.Addr) {
logger.Debug("UDP transparent proxy connect", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr.String()))
}

func (l *udpTProxyLogger) Error(addr, reqAddr net.Addr, err error) {
if err == nil {
logger.Debug("UDP transparent proxy closed", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr.String()))
} else {
logger.Error("UDP transparent proxy error", zap.String("addr", addr.String()), zap.String("reqAddr", reqAddr.String()), zap.Error(err))
}
}
23 changes: 15 additions & 8 deletions app/cmd/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,26 @@ func TestClientConfig(t *testing.T) {
Password: "bruh",
Realm: "martian",
},
Forwarding: []forwardingEntry{
TCPForwarding: []tcpForwardingEntry{
{
Listen: "127.0.0.1:8088",
Remote: "internal.example.com:80",
Protocol: "tcp",
Listen: "127.0.0.1:8088",
Remote: "internal.example.com:80",
},
},
UDPForwarding: []udpForwardingEntry{
{
Listen: "127.0.0.1:5353",
Remote: "internal.example.com:53",
Protocol: "udp",
UDPTimeout: 50 * time.Second,
Listen: "127.0.0.1:5353",
Remote: "internal.example.com:53",
Timeout: 50 * time.Second,
},
},
TCPTProxy: &tcpTProxyConfig{
Listen: "127.0.0.1:2500",
},
UDPTProxy: &udpTProxyConfig{
Listen: "127.0.0.1:2501",
Timeout: 20 * time.Second,
},
})
}

Expand Down
15 changes: 11 additions & 4 deletions app/cmd/client_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,18 @@ http:
password: bruh
realm: martian

forwarding:
tcpForwarding:
- listen: 127.0.0.1:8088
remote: internal.example.com:80
protocol: tcp

udpForwarding:
- listen: 127.0.0.1:5353
remote: internal.example.com:53
protocol: udp
udpTimeout: 50s
timeout: 50s

tcpTProxy:
listen: 127.0.0.1:2500

udpTProxy:
listen: 127.0.0.1:2501
timeout: 20s
1 change: 1 addition & 0 deletions app/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
)

require (
github.com/apernet/go-tproxy v0.0.0-20230809025308-8f4723fd742f // indirect
github.com/apernet/quic-go v0.37.5-0.20230809210726-5508a358d07e // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions app/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/apernet/go-tproxy v0.0.0-20230809025308-8f4723fd742f h1:uVh0qpEslrWjgzx9vOcyCqsOY3c9kofDZ1n+qaw35ZY=
github.com/apernet/go-tproxy v0.0.0-20230809025308-8f4723fd742f/go.mod h1:xkkq9D4ygcldQQhKS/w9CadiCKwCngU7K9E3DaKahpM=
github.com/apernet/quic-go v0.37.5-0.20230809210726-5508a358d07e h1:hWrd6A3QZQX2pXT1JJA2x1vgqNf5jZH8po0oa2GsbeI=
github.com/apernet/quic-go v0.37.5-0.20230809210726-5508a358d07e/go.mod h1:Gqxx9qMiutRcTLNlbdPwuI9dF8+GV2GQG+5mVW0E34I=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
Expand Down
Loading

0 comments on commit ceb3c7f

Please sign in to comment.