diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index 31811c51..09d000b0 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -13,8 +13,6 @@ import ( "github.com/Dreamacro/clash/transport/gun" "github.com/Dreamacro/clash/transport/trojan" "github.com/Dreamacro/clash/transport/vless" - - "golang.org/x/net/http2" ) type Trojan struct { @@ -25,7 +23,7 @@ type Trojan struct { // for gun mux gunTLSConfig *tls.Config gunConfig *gun.Config - transport *http2.Transport + transport *gun.TransportWrap } type TrojanOption struct { diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go index 8ac78b92..72ceb84c 100644 --- a/adapter/outbound/vless.go +++ b/adapter/outbound/vless.go @@ -18,8 +18,6 @@ import ( "github.com/Dreamacro/clash/transport/gun" "github.com/Dreamacro/clash/transport/vless" "github.com/Dreamacro/clash/transport/vmess" - - "golang.org/x/net/http2" ) const ( @@ -35,7 +33,7 @@ type Vless struct { // for gun mux gunTLSConfig *tls.Config gunConfig *gun.Config - transport *http2.Transport + transport *gun.TransportWrap } type VlessOption struct { diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index bf0ac7a8..7ef00c59 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -15,8 +15,6 @@ import ( C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/gun" "github.com/Dreamacro/clash/transport/vmess" - - "golang.org/x/net/http2" ) type Vmess struct { @@ -27,7 +25,7 @@ type Vmess struct { // for gun mux gunTLSConfig *tls.Config gunConfig *gun.Config - transport *http2.Transport + transport *gun.TransportWrap } type VmessOption struct { diff --git a/adapter/outboundgroup/relay.go b/adapter/outboundgroup/relay.go index 40df8e44..79403eaf 100644 --- a/adapter/outboundgroup/relay.go +++ b/adapter/outboundgroup/relay.go @@ -170,6 +170,11 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) ([]C.Proxy, []C.Proxy) return targetProxies, chainProxies } +func (r *Relay) Addr() string { + proxies, _ := r.proxies(nil, true) + return proxies[len(proxies)-1].Addr() +} + func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay { return &Relay{ GroupBase: NewGroupBase(GroupBaseOption{ diff --git a/constant/metadata.go b/constant/metadata.go index 980e1847..16b0a916 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -86,8 +86,12 @@ type Metadata struct { Uid *int32 `json:"uid"` Process string `json:"process"` ProcessPath string `json:"processPath"` + RemoteDst string `json:"remoteDestination"` } +// avoid stack overflow +type jsonMetadata Metadata + func (m *Metadata) RemoteAddress() string { return net.JoinHostPort(m.String(), m.DstPort) } diff --git a/transport/gun/gun.go b/transport/gun/gun.go index 43988004..f65a1929 100644 --- a/transport/gun/gun.go +++ b/transport/gun/gun.go @@ -39,14 +39,13 @@ type DialFn = func(network, addr string) (net.Conn, error) type Conn struct { response *http.Response request *http.Request - transport *http2.Transport + transport *TransportWrap writer *io.PipeWriter once sync.Once close *atomic.Bool err error remain int br *bufio.Reader - // deadlines deadline *time.Timer } @@ -150,8 +149,8 @@ func (g *Conn) Close() error { return g.writer.Close() } -func (g *Conn) LocalAddr() net.Addr { return &net.TCPAddr{IP: net.IPv4zero, Port: 0} } -func (g *Conn) RemoteAddr() net.Addr { return &net.TCPAddr{IP: net.IPv4zero, Port: 0} } +func (g *Conn) LocalAddr() net.Addr { return g.transport.LocalAddr() } +func (g *Conn) RemoteAddr() net.Addr { return g.transport.RemoteAddr() } func (g *Conn) SetReadDeadline(t time.Time) error { return g.SetDeadline(t) } func (g *Conn) SetWriteDeadline(t time.Time) error { return g.SetDeadline(t) } @@ -167,13 +166,15 @@ func (g *Conn) SetDeadline(t time.Time) error { return nil } -func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config) *http2.Transport { +func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config) *TransportWrap { + wrap := TransportWrap{} dialFunc := func(network, addr string, cfg *tls.Config) (net.Conn, error) { pconn, err := dialFn(network, addr) if err != nil { return nil, err } + wrap.remoteAddr = pconn.RemoteAddr() cn := tls.Client(pconn, cfg) // fix tls handshake not timeout @@ -191,16 +192,18 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config) *http2.Transport { return cn, nil } - return &http2.Transport{ + wrap.Transport = &http2.Transport{ DialTLS: dialFunc, TLSClientConfig: tlsConfig, AllowHTTP: false, DisableCompression: true, PingTimeout: 0, } + + return &wrap } -func StreamGunWithTransport(transport *http2.Transport, cfg *Config) (net.Conn, error) { +func StreamGunWithTransport(transport *TransportWrap, cfg *Config) (net.Conn, error) { serviceName := "GunService" if cfg.ServiceName != "" { serviceName = cfg.ServiceName diff --git a/transport/gun/gun_xtls.go b/transport/gun/gun_xtls.go index d97dd7bf..0d110727 100644 --- a/transport/gun/gun_xtls.go +++ b/transport/gun/gun_xtls.go @@ -12,13 +12,15 @@ import ( "golang.org/x/net/http2" ) -func NewHTTP2XTLSClient(dialFn DialFn, tlsConfig *tls.Config) *http2.Transport { +func NewHTTP2XTLSClient(dialFn DialFn, tlsConfig *tls.Config) *TransportWrap { + wrap := TransportWrap{} dialFunc := func(network, addr string, cfg *tls.Config) (net.Conn, error) { pconn, err := dialFn(network, addr) if err != nil { return nil, err } + wrap.remoteAddr = pconn.RemoteAddr() xtlsConfig := &xtls.Config{ InsecureSkipVerify: cfg.InsecureSkipVerify, ServerName: cfg.ServerName, @@ -37,13 +39,15 @@ func NewHTTP2XTLSClient(dialFn DialFn, tlsConfig *tls.Config) *http2.Transport { return cn, nil } - return &http2.Transport{ + wrap.Transport = &http2.Transport{ DialTLS: dialFunc, TLSClientConfig: tlsConfig, AllowHTTP: false, DisableCompression: true, PingTimeout: 0, } + + return &wrap } func StreamGunWithXTLSConn(conn net.Conn, tlsConfig *tls.Config, cfg *Config) (net.Conn, error) { diff --git a/transport/gun/transport.go b/transport/gun/transport.go new file mode 100644 index 00000000..12ccfd5c --- /dev/null +++ b/transport/gun/transport.go @@ -0,0 +1,20 @@ +package gun + +import ( + "golang.org/x/net/http2" + "net" +) + +type TransportWrap struct { + *http2.Transport + remoteAddr net.Addr + localAddr net.Addr +} + +func (tw *TransportWrap) RemoteAddr() net.Addr { + return tw.remoteAddr +} + +func (tw *TransportWrap) LocalAddr() net.Addr { + return tw.localAddr +} diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 2108e814..315fae5e 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -9,6 +9,7 @@ import ( "path/filepath" "runtime" "strconv" + "strings" "sync" "time" @@ -269,6 +270,18 @@ func handleUDPConn(packet *inbound.PacketAdapter) { return } pCtx.InjectPacketConn(rawPc) + + actualProxy := proxy.Unwrap(nil) + if actualProxy != nil { + if dst, _, err := net.SplitHostPort(actualProxy.Addr()); err == nil { + metadata.RemoteDst = dst + } else { + if addrError, ok := err.(*net.AddrError); ok && strings.Contains(addrError.Err, "missing port") { + metadata.RemoteDst = actualProxy.Addr() + } + } + } + pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule) switch true { @@ -332,6 +345,11 @@ func handleTCPConn(connCtx C.ConnContext) { } return } + + if tcpAddr, ok := remoteConn.RemoteAddr().(*net.TCPAddr); ok { + metadata.RemoteDst = tcpAddr.IP.String() + } + remoteConn = statistic.NewTCPTracker(remoteConn, statistic.DefaultManager, metadata, rule) defer func(remoteConn C.Conn) { _ = remoteConn.Close()