mirror of
https://github.com/apernet/hysteria.git
synced 2025-04-03 20:47:38 +03:00
feat: TProxy
This commit is contained in:
parent
4060bcb806
commit
ceb3c7f6a8
10 changed files with 473 additions and 71 deletions
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
@ -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 == "" {
|
||||
|
@ -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}
|
||||
}
|
||||
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")}
|
||||
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)
|
||||
}
|
||||
// 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) {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=
|
||||
|
|
69
app/internal/tproxy/tcp_linux.go
Normal file
69
app/internal/tproxy/tcp_linux.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package tproxy
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/apernet/go-tproxy"
|
||||
"github.com/apernet/hysteria/core/client"
|
||||
)
|
||||
|
||||
type TCPTProxy struct {
|
||||
HyClient client.Client
|
||||
EventLogger TCPEventLogger
|
||||
}
|
||||
|
||||
type TCPEventLogger interface {
|
||||
Connect(addr, reqAddr net.Addr)
|
||||
Error(addr, reqAddr net.Addr, err error)
|
||||
}
|
||||
|
||||
func (r *TCPTProxy) ListenAndServe(laddr *net.TCPAddr) error {
|
||||
listener, err := tproxy.ListenTCP("tcp", laddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer listener.Close()
|
||||
for {
|
||||
c, err := listener.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go r.handle(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *TCPTProxy) handle(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
// In TProxy mode, we are masquerading as the remote server.
|
||||
// So LocalAddr is actually the target the user is trying to connect to,
|
||||
// and RemoteAddr is the local address.
|
||||
if r.EventLogger != nil {
|
||||
r.EventLogger.Connect(conn.RemoteAddr(), conn.LocalAddr())
|
||||
}
|
||||
var closeErr error
|
||||
defer func() {
|
||||
if r.EventLogger != nil {
|
||||
r.EventLogger.Error(conn.RemoteAddr(), conn.LocalAddr(), closeErr)
|
||||
}
|
||||
}()
|
||||
|
||||
rc, err := r.HyClient.TCP(conn.LocalAddr().String())
|
||||
if err != nil {
|
||||
closeErr = err
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
// Start forwarding
|
||||
copyErrChan := make(chan error, 2)
|
||||
go func() {
|
||||
_, copyErr := io.Copy(rc, conn)
|
||||
copyErrChan <- copyErr
|
||||
}()
|
||||
go func() {
|
||||
_, copyErr := io.Copy(conn, rc)
|
||||
copyErrChan <- copyErr
|
||||
}()
|
||||
closeErr = <-copyErrChan
|
||||
}
|
24
app/internal/tproxy/tcp_others.go
Normal file
24
app/internal/tproxy/tcp_others.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
//go:build !linux
|
||||
|
||||
package tproxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/apernet/hysteria/core/client"
|
||||
)
|
||||
|
||||
type TCPTProxy struct {
|
||||
HyClient client.Client
|
||||
EventLogger TCPEventLogger
|
||||
}
|
||||
|
||||
type TCPEventLogger interface {
|
||||
Connect(addr, reqAddr net.Addr)
|
||||
Error(addr, reqAddr net.Addr, err error)
|
||||
}
|
||||
|
||||
func (r *TCPTProxy) ListenAndServe(laddr *net.TCPAddr) error {
|
||||
return errors.New("not supported on this platform")
|
||||
}
|
138
app/internal/tproxy/udp_linux.go
Normal file
138
app/internal/tproxy/udp_linux.go
Normal file
|
@ -0,0 +1,138 @@
|
|||
package tproxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/go-tproxy"
|
||||
"github.com/apernet/hysteria/core/client"
|
||||
)
|
||||
|
||||
const (
|
||||
udpBufferSize = 4096
|
||||
defaultTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
type UDPTProxy struct {
|
||||
HyClient client.Client
|
||||
Timeout time.Duration
|
||||
EventLogger UDPEventLogger
|
||||
}
|
||||
|
||||
type UDPEventLogger interface {
|
||||
Connect(addr, reqAddr net.Addr)
|
||||
Error(addr, reqAddr net.Addr, err error)
|
||||
}
|
||||
|
||||
func (r *UDPTProxy) ListenAndServe(laddr *net.UDPAddr) error {
|
||||
conn, err := tproxy.ListenUDP("udp", laddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
buf := make([]byte, udpBufferSize)
|
||||
for {
|
||||
// We will only get the first packet of each src/dst pair here,
|
||||
// because newPair will create a TProxy connection and take over
|
||||
// the src/dst pair. Later packets will be sent there instead of here.
|
||||
n, srcAddr, dstAddr, err := tproxy.ReadFromUDP(conn, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.newPair(srcAddr, dstAddr, buf[:n])
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UDPTProxy) newPair(srcAddr, dstAddr *net.UDPAddr, initPkt []byte) {
|
||||
if r.EventLogger != nil {
|
||||
r.EventLogger.Connect(srcAddr, dstAddr)
|
||||
}
|
||||
var closeErr error
|
||||
defer func() {
|
||||
// If closeErr is nil, it means we at least successfully sent the first packet
|
||||
// and started forwarding, in which case we don't call the error logger.
|
||||
if r.EventLogger != nil && closeErr != nil {
|
||||
r.EventLogger.Error(srcAddr, dstAddr, closeErr)
|
||||
}
|
||||
}()
|
||||
conn, err := tproxy.DialUDP("udp", dstAddr, srcAddr)
|
||||
if err != nil {
|
||||
closeErr = err
|
||||
return
|
||||
}
|
||||
hyConn, err := r.HyClient.UDP()
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
closeErr = err
|
||||
return
|
||||
}
|
||||
// Send the first packet
|
||||
err = hyConn.Send(initPkt, dstAddr.String())
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
_ = hyConn.Close()
|
||||
closeErr = err
|
||||
return
|
||||
}
|
||||
// Start forwarding
|
||||
go r.forwarding(conn, hyConn, dstAddr.String())
|
||||
}
|
||||
|
||||
func (r *UDPTProxy) forwarding(conn *net.UDPConn, hyConn client.HyUDPConn, dst string) {
|
||||
errChan := make(chan error, 2)
|
||||
// Local <- Remote
|
||||
go func() {
|
||||
for {
|
||||
bs, _, err := hyConn.Receive()
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
_, err = conn.Write(bs)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
_ = r.updateConnDeadline(conn)
|
||||
}
|
||||
}()
|
||||
// Local -> Remote
|
||||
go func() {
|
||||
buf := make([]byte, udpBufferSize)
|
||||
for {
|
||||
_ = r.updateConnDeadline(conn)
|
||||
n, err := conn.Read(buf)
|
||||
if n > 0 {
|
||||
err := hyConn.Send(buf[:n], dst)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
err := <-errChan
|
||||
_ = conn.Close()
|
||||
_ = hyConn.Close()
|
||||
if r.EventLogger != nil {
|
||||
var netErr net.Error
|
||||
if errors.As(err, &netErr) && netErr.Timeout() {
|
||||
// We don't consider deadline exceeded (timeout) an error
|
||||
err = nil
|
||||
}
|
||||
r.EventLogger.Error(conn.LocalAddr(), conn.RemoteAddr(), err)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UDPTProxy) updateConnDeadline(conn *net.UDPConn) error {
|
||||
if r.Timeout == 0 {
|
||||
return conn.SetReadDeadline(time.Now().Add(defaultTimeout))
|
||||
} else {
|
||||
return conn.SetReadDeadline(time.Now().Add(r.Timeout))
|
||||
}
|
||||
}
|
26
app/internal/tproxy/udp_others.go
Normal file
26
app/internal/tproxy/udp_others.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
//go:build !linux
|
||||
|
||||
package tproxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/client"
|
||||
)
|
||||
|
||||
type UDPTProxy struct {
|
||||
HyClient client.Client
|
||||
Timeout time.Duration
|
||||
EventLogger UDPEventLogger
|
||||
}
|
||||
|
||||
type UDPEventLogger interface {
|
||||
Connect(addr, reqAddr net.Addr)
|
||||
Error(addr, reqAddr net.Addr, err error)
|
||||
}
|
||||
|
||||
func (r *UDPTProxy) ListenAndServe(laddr *net.UDPAddr) error {
|
||||
return errors.New("not supported on this platform")
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue