From c6cff7f2de309e152fdbfd101eab320e9e65e4ea Mon Sep 17 00:00:00 2001 From: wwqgtxx Date: Mon, 26 Aug 2024 18:33:04 +0800 Subject: [PATCH] fix: hysteria1 outbound should be closed when proxy removed --- .github/workflows/build.yml | 2 ++ adapter/outbound/hysteria.go | 24 +++++++++++++++--- adapter/outbound/hysteria2.go | 5 ++++ adapter/outbound/hysteria2_test.go | 38 +++++++++++++++++++++++++++++ adapter/outbound/hysteria_test.go | 39 ++++++++++++++++++++++++++++++ adapter/outbound/wireguard_test.go | 1 + transport/hysteria/core/client.go | 5 +++- 7 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 adapter/outbound/hysteria2_test.go create mode 100644 adapter/outbound/hysteria_test.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 94854f2748..2d8f2bd14a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -207,6 +207,8 @@ jobs: if: ${{ matrix.jobs.test == 'test' }} run: | go test ./... + echo "---test with_gvisor---" + go test ./... -tags "with_gvisor" - name: Update CA run: | diff --git a/adapter/outbound/hysteria.go b/adapter/outbound/hysteria.go index dacffd106d..ccab16c12a 100644 --- a/adapter/outbound/hysteria.go +++ b/adapter/outbound/hysteria.go @@ -7,6 +7,7 @@ import ( "fmt" "net" "net/netip" + "runtime" "strconv" "time" @@ -14,6 +15,7 @@ import ( "github.com/metacubex/quic-go/congestion" M "github.com/sagernet/sing/common/metadata" + CN "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/proxydialer" @@ -43,6 +45,8 @@ type Hysteria struct { option *HysteriaOption client *core.Client + + closeCh chan struct{} // for test } func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { @@ -51,7 +55,7 @@ func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts . return nil, err } - return NewConn(tcpConn, h), nil + return NewConn(CN.NewRefConn(tcpConn, h), h), nil } func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { @@ -59,7 +63,7 @@ func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata if err != nil { return nil, err } - return newPacketConn(&hyPacketConn{udpConn}, h), nil + return newPacketConn(CN.NewRefPacketConn(&hyPacketConn{udpConn}, h), h), nil } func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer { @@ -218,7 +222,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) { if err != nil { return nil, fmt.Errorf("hysteria %s create error: %w", addr, err) } - return &Hysteria{ + outbound := &Hysteria{ Base: &Base{ name: option.Name, addr: addr, @@ -231,7 +235,19 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) { }, option: &option, client: client, - }, nil + } + runtime.SetFinalizer(outbound, closeHysteria) + + return outbound, nil +} + +func closeHysteria(h *Hysteria) { + if h.client != nil { + _ = h.client.Close() + } + if h.closeCh != nil { + close(h.closeCh) + } } type hyPacketConn struct { diff --git a/adapter/outbound/hysteria2.go b/adapter/outbound/hysteria2.go index b8abf39cc2..c1a255a766 100644 --- a/adapter/outbound/hysteria2.go +++ b/adapter/outbound/hysteria2.go @@ -38,6 +38,8 @@ type Hysteria2 struct { option *Hysteria2Option client *hysteria2.Client dialer proxydialer.SingDialer + + closeCh chan struct{} // for test } type Hysteria2Option struct { @@ -89,6 +91,9 @@ func closeHysteria2(h *Hysteria2) { if h.client != nil { _ = h.client.CloseWithError(errors.New("proxy removed")) } + if h.closeCh != nil { + close(h.closeCh) + } } func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) { diff --git a/adapter/outbound/hysteria2_test.go b/adapter/outbound/hysteria2_test.go new file mode 100644 index 0000000000..de7d82271d --- /dev/null +++ b/adapter/outbound/hysteria2_test.go @@ -0,0 +1,38 @@ +package outbound + +import ( + "context" + "runtime" + "testing" + "time" +) + +func TestHysteria2GC(t *testing.T) { + option := Hysteria2Option{} + option.Server = "127.0.0.1" + option.Ports = "200,204,401-429,501-503" + option.HopInterval = 30 + option.Password = "password" + option.Obfs = "salamander" + option.ObfsPassword = "password" + option.SNI = "example.com" + option.ALPN = []string{"h3"} + hy, err := NewHysteria2(option) + if err != nil { + t.Error(err) + return + } + closeCh := make(chan struct{}) + hy.closeCh = closeCh + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + hy = nil + runtime.GC() + select { + case <-closeCh: + return + case <-ctx.Done(): + t.Error("timeout not GC") + } +} diff --git a/adapter/outbound/hysteria_test.go b/adapter/outbound/hysteria_test.go new file mode 100644 index 0000000000..f2297c6035 --- /dev/null +++ b/adapter/outbound/hysteria_test.go @@ -0,0 +1,39 @@ +package outbound + +import ( + "context" + "runtime" + "testing" + "time" +) + +func TestHysteriaGC(t *testing.T) { + option := HysteriaOption{} + option.Server = "127.0.0.1" + option.Ports = "200,204,401-429,501-503" + option.Protocol = "udp" + option.Up = "1Mbps" + option.Down = "1Mbps" + option.HopInterval = 30 + option.Obfs = "salamander" + option.SNI = "example.com" + option.ALPN = []string{"h3"} + hy, err := NewHysteria(option) + if err != nil { + t.Error(err) + return + } + closeCh := make(chan struct{}) + hy.closeCh = closeCh + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + hy = nil + runtime.GC() + select { + case <-closeCh: + return + case <-ctx.Done(): + t.Error("timeout not GC") + } +} diff --git a/adapter/outbound/wireguard_test.go b/adapter/outbound/wireguard_test.go index 20dbdbdd6b..2248bb7b1f 100644 --- a/adapter/outbound/wireguard_test.go +++ b/adapter/outbound/wireguard_test.go @@ -29,6 +29,7 @@ func TestWireGuardGC(t *testing.T) { err = wg.init(ctx) if err != nil { t.Error(err) + return } // must do a small sleep before test GC // because it maybe deadlocks if w.device.Close call too fast after w.device.Start diff --git a/transport/hysteria/core/client.go b/transport/hysteria/core/client.go index 60db8fdf45..782948c018 100644 --- a/transport/hysteria/core/client.go +++ b/transport/hysteria/core/client.go @@ -289,7 +289,10 @@ func (c *Client) DialUDP(dialer utils.PacketDialer) (UDPConn, error) { func (c *Client) Close() error { c.reconnectMutex.Lock() defer c.reconnectMutex.Unlock() - err := c.quicSession.CloseWithError(closeErrorCodeGeneric, "") + var err error + if c.quicSession != nil { + err = c.quicSession.CloseWithError(closeErrorCodeGeneric, "") + } c.closed = true return err }