fix: hysteria1 outbound should be closed when proxy removed

This commit is contained in:
wwqgtxx 2024-08-26 18:33:04 +08:00
parent 81756fc927
commit 9cf3eb39f5
7 changed files with 109 additions and 5 deletions

View File

@ -207,6 +207,8 @@ jobs:
if: ${{ matrix.jobs.test == 'test' }} if: ${{ matrix.jobs.test == 'test' }}
run: | run: |
go test ./... go test ./...
echo "---test with_gvisor---"
go test ./... -tags "with_gvisor" -count=1
- name: Update CA - name: Update CA
run: | run: |

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"runtime"
"strconv" "strconv"
"time" "time"
@ -14,6 +15,7 @@ import (
"github.com/metacubex/quic-go/congestion" "github.com/metacubex/quic-go/congestion"
M "github.com/sagernet/sing/common/metadata" 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/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
@ -43,6 +45,8 @@ type Hysteria struct {
option *HysteriaOption option *HysteriaOption
client *core.Client client *core.Client
closeCh chan struct{} // for test
} }
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { 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 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) { 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 { if err != nil {
return nil, err 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 { 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 { if err != nil {
return nil, fmt.Errorf("hysteria %s create error: %w", addr, err) return nil, fmt.Errorf("hysteria %s create error: %w", addr, err)
} }
return &Hysteria{ outbound := &Hysteria{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: addr, addr: addr,
@ -231,7 +235,19 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
}, },
option: &option, option: &option,
client: client, 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 { type hyPacketConn struct {

View File

@ -38,6 +38,8 @@ type Hysteria2 struct {
option *Hysteria2Option option *Hysteria2Option
client *hysteria2.Client client *hysteria2.Client
dialer proxydialer.SingDialer dialer proxydialer.SingDialer
closeCh chan struct{} // for test
} }
type Hysteria2Option struct { type Hysteria2Option struct {
@ -89,6 +91,9 @@ func closeHysteria2(h *Hysteria2) {
if h.client != nil { if h.client != nil {
_ = h.client.CloseWithError(errors.New("proxy removed")) _ = h.client.CloseWithError(errors.New("proxy removed"))
} }
if h.closeCh != nil {
close(h.closeCh)
}
} }
func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) { func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {

View File

@ -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")
}
}

View File

@ -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")
}
}

View File

@ -29,6 +29,7 @@ func TestWireGuardGC(t *testing.T) {
err = wg.init(ctx) err = wg.init(ctx)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return
} }
// must do a small sleep before test GC // must do a small sleep before test GC
// because it maybe deadlocks if w.device.Close call too fast after w.device.Start // because it maybe deadlocks if w.device.Close call too fast after w.device.Start

View File

@ -289,7 +289,10 @@ func (c *Client) DialUDP(dialer utils.PacketDialer) (UDPConn, error) {
func (c *Client) Close() error { func (c *Client) Close() error {
c.reconnectMutex.Lock() c.reconnectMutex.Lock()
defer c.reconnectMutex.Unlock() defer c.reconnectMutex.Unlock()
err := c.quicSession.CloseWithError(closeErrorCodeGeneric, "") var err error
if c.quicSession != nil {
err = c.quicSession.CloseWithError(closeErrorCodeGeneric, "")
}
c.closed = true c.closed = true
return err return err
} }