diff --git a/adapter/adapter.go b/adapter/adapter.go index b1867998..1be877c7 100644 --- a/adapter/adapter.go +++ b/adapter/adapter.go @@ -151,25 +151,32 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) { } client := http.Client{ + Timeout: 30 * time.Second, Transport: transport, CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } + defer client.CloseIdleConnections() + resp, err := client.Do(req) + if err != nil { return } + _ = resp.Body.Close() + if unifiedDelay { - start = time.Now() + second := time.Now() resp, err = client.Do(req) - if err != nil { - return + if err == nil { + _ = resp.Body.Close() + start = second } } - _ = resp.Body.Close() + t = uint16(time.Since(start) / time.Millisecond) return } diff --git a/adapter/outbound/hysteria.go b/adapter/outbound/hysteria.go index a3eca0eb..7b4bcd78 100644 --- a/adapter/outbound/hysteria.go +++ b/adapter/outbound/hysteria.go @@ -41,24 +41,32 @@ var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`) type Hysteria struct { *Base - client *core.Client - clientTransport *transport.ClientTransport + client *core.Client } func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { - tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), hyDialer(func() (net.PacketConn, error) { - return dialer.ListenPacket(ctx, "udp", "", h.Base.DialOptions(opts...)...) - })) + hdc := hyDialerWithContext{ + ctx: ctx, + hyDialer: func() (net.PacketConn, error) { + return dialer.ListenPacket(ctx, "udp", "", h.Base.DialOptions(opts...)...) + }, + } + tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), &hdc) if err != nil { return nil, err } + return NewConn(tcpConn, h), nil } func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { - udpConn, err := h.client.DialUDP(hyDialer(func() (net.PacketConn, error) { - return dialer.ListenPacket(ctx, "udp", "", h.Base.DialOptions(opts...)...) - })) + hdc := hyDialerWithContext{ + ctx: ctx, + hyDialer: func() (net.PacketConn, error) { + return dialer.ListenPacket(ctx, "udp", "", h.Base.DialOptions(opts...)...) + }, + } + udpConn, err := h.client.DialUDP(&hdc) if err != nil { return nil, err } @@ -93,7 +101,7 @@ func (c *HysteriaOption) Speed() (uint64, uint64, error) { } down = stringToBps(c.Down) - if up == 0 { + if down == 0 { return 0, 0, fmt.Errorf("invaild download speed: %s", c.Down) } @@ -191,8 +199,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) { iface: option.Interface, rmark: option.RoutingMark, }, - client: client, - clientTransport: clientTransport, + client: client, }, nil } @@ -255,8 +262,15 @@ func (c *hyPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { return } -type hyDialer func() (net.PacketConn, error) - -func (h hyDialer) ListenPacket() (net.PacketConn, error) { - return h() +type hyDialerWithContext struct { + hyDialer func() (net.PacketConn, error) + ctx context.Context +} + +func (h *hyDialerWithContext) ListenPacket() (net.PacketConn, error) { + return h.hyDialer() +} + +func (h *hyDialerWithContext) Context() context.Context { + return h.ctx } diff --git a/adapter/outboundgroup/groupbase.go b/adapter/outboundgroup/groupbase.go index 42b61dd4..5ec0c999 100644 --- a/adapter/outboundgroup/groupbase.go +++ b/adapter/outboundgroup/groupbase.go @@ -111,11 +111,11 @@ func (gb *GroupBase) URLTest(ctx context.Context, url string) (map[string]uint16 wg.Add(1) go func() { delay, err := proxy.URLTest(ctx, url) - lock.Lock() if err == nil { + lock.Lock() mp[proxy.Name()] = delay + lock.Unlock() } - lock.Unlock() wg.Done() }() diff --git a/adapter/provider/fetcher.go b/adapter/provider/fetcher.go index 47b364e6..07fb1237 100644 --- a/adapter/provider/fetcher.go +++ b/adapter/provider/fetcher.go @@ -202,6 +202,7 @@ func newFetcher[V any](name string, interval time.Duration, vehicle types.Vehicl parser: parser, done: make(chan struct{}, 1), onUpdate: onUpdate, + interval: interval, } } diff --git a/adapter/provider/healthcheck.go b/adapter/provider/healthcheck.go index cc664c9e..8e736f6c 100644 --- a/adapter/provider/healthcheck.go +++ b/adapter/provider/healthcheck.go @@ -2,6 +2,7 @@ package provider import ( "context" + "github.com/Dreamacro/clash/common/singledo" "time" "github.com/Dreamacro/clash/common/batch" @@ -26,6 +27,7 @@ type HealthCheck struct { lazy bool lastTouch *atomic.Int64 done chan struct{} + singleDo *singledo.Single[struct{}] } func (hc *HealthCheck) process() { @@ -63,17 +65,21 @@ func (hc *HealthCheck) touch() { } func (hc *HealthCheck) check() { - b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10)) - for _, proxy := range hc.proxies { - p := proxy - b.Go(p.Name(), func() (bool, error) { - ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) - defer cancel() - _, _ = p.URLTest(ctx, hc.url) - return false, nil - }) - } - b.Wait() + _, _, _ = hc.singleDo.Do(func() (struct{}, error) { + b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10)) + for _, proxy := range hc.proxies { + p := proxy + b.Go(p.Name(), func() (bool, error) { + ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) + defer cancel() + _, _ = p.URLTest(ctx, hc.url) + return false, nil + }) + } + + b.Wait() + return struct{}{}, nil + }) } func (hc *HealthCheck) close() { @@ -88,5 +94,6 @@ func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool) *He lazy: lazy, lastTouch: atomic.NewInt64(0), done: make(chan struct{}, 1), + singleDo: singledo.NewSingle[struct{}](time.Second), } } diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go index e0401d42..bfba0079 100644 --- a/component/dialer/dialer.go +++ b/component/dialer/dialer.go @@ -4,11 +4,10 @@ import ( "context" "errors" "fmt" + "github.com/Dreamacro/clash/component/resolver" "net" "net/netip" "sync" - - "github.com/Dreamacro/clash/component/resolver" ) var ( @@ -171,25 +170,31 @@ func dualStackDialContext(ctx context.Context, network, address string, opt *opt go startRacer(ctx, network+"4", host, opt.direct, false) go startRacer(ctx, network+"6", host, opt.direct, true) - for res := range results { - if res.error == nil { - return res.Conn, nil - } - - if !res.ipv6 { - primary = res - } else { - fallback = res - } - - if primary.done && fallback.done { - if primary.resolved { - return nil, primary.error - } else if fallback.resolved { - return nil, fallback.error - } else { - return nil, primary.error + count := 2 + for i := 0; i < count; i++ { + select { + case res := <-results: + if res.error == nil { + return res.Conn, nil } + + if !res.ipv6 { + primary = res + } else { + fallback = res + } + + if primary.done && fallback.done { + if primary.resolved { + return nil, primary.error + } else if fallback.resolved { + return nil, fallback.error + } else { + return nil, primary.error + } + } + case <-ctx.Done(): + break } } @@ -225,7 +230,6 @@ func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr } results := make(chan dialResult) - tcpRacer := func(ctx context.Context, ip netip.Addr) { result := dialResult{ip: ip} @@ -252,13 +256,13 @@ func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr } connCount := len(ips) - for res := range results { - connCount-- - if res.error == nil { - return res.Conn, nil - } - - if connCount == 0 { + for i := 0; i < connCount; i++ { + select { + case res := <-results: + if res.error == nil { + return res.Conn, nil + } + case <-ctx.Done(): break } } diff --git a/dns/doq.go b/dns/doq.go index af4c3833..aafea97f 100644 --- a/dns/doq.go +++ b/dns/doq.go @@ -129,7 +129,7 @@ func (dc *quicClient) getSession(ctx context.Context) (quic.Connection, error) { func (dc *quicClient) openSession(ctx context.Context) (quic.Connection, error) { tlsConfig := &tls.Config{ - InsecureSkipVerify: true, + InsecureSkipVerify: false, NextProtos: []string{ NextProtoDQ, }, diff --git a/dns/util.go b/dns/util.go index 4ea511a9..7abbbe97 100644 --- a/dns/util.go +++ b/dns/util.go @@ -152,17 +152,9 @@ func (wpc *wrapPacketConn) LocalAddr() net.Addr { } func dialContextExtra(ctx context.Context, adapterName string, network string, dstIP netip.Addr, port string, opts ...dialer.Option) (net.Conn, error) { - adapter, ok := tunnel.Proxies()[adapterName] - if !ok { - opts = append(opts, dialer.WithInterface(adapterName)) - adapter, _ = tunnel.Proxies()[tunnel.Direct.String()] - } - networkType := C.TCP if network == "udp" { - if !adapter.SupportUDP() { - return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", adapterName) - } + networkType = C.UDP } @@ -179,6 +171,29 @@ func dialContextExtra(ctx context.Context, adapterName string, network string, d DstPort: port, } + adapter, ok := tunnel.Proxies()[adapterName] + if !ok { + opts = append(opts, dialer.WithInterface(adapterName)) + if C.TCP == networkType { + return dialer.DialContext(ctx, network, dstIP.String()+":"+port, opts...) + } else { + packetConn, err := dialer.ListenPacket(ctx, network, dstIP.String()+":"+port, opts...) + if err != nil { + return nil, err + } + + return &wrapPacketConn{ + PacketConn: packetConn, + rAddr: metadata.UDPAddr(), + }, nil + + } + } + + if networkType == C.UDP && !adapter.SupportUDP() { + return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", adapterName) + } + if networkType == C.UDP { packetConn, err := adapter.ListenPacketContext(ctx, metadata, opts...) if err != nil { diff --git a/hub/route/groups.go b/hub/route/groups.go index e877bfd0..13133e9c 100644 --- a/hub/route/groups.go +++ b/hub/route/groups.go @@ -64,7 +64,7 @@ func getGroupDelay(w http.ResponseWriter, r *http.Request) { return } - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout)) + ctx, cancel := context.WithTimeout(r.Context(), time.Millisecond*time.Duration(timeout)) defer cancel() dm, err := group.URLTest(ctx, url)