package dialer import ( "context" "net" "net/netip" "time" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) func DialParallel(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.DomainStrategy, fallbackDelay time.Duration) (net.Conn, error) { // kanged form net.Dial returned := make(chan struct{}) defer close(returned) addresses4 := common.Filter(destinationAddresses, func(address netip.Addr) bool { return address.Is4() || address.Is4In6() }) addresses6 := common.Filter(destinationAddresses, func(address netip.Addr) bool { return address.Is6() && !address.Is4In6() }) if len(addresses4) == 0 || len(addresses6) == 0 { return DialSerial(ctx, dialer, network, destination, destinationAddresses) } var primaries, fallbacks []netip.Addr switch strategy { case C.DomainStrategyPreferIPv6: primaries = addresses6 fallbacks = addresses4 default: primaries = addresses4 fallbacks = addresses6 } type dialResult struct { net.Conn error primary bool done bool } results := make(chan dialResult) // unbuffered startRacer := func(ctx context.Context, primary bool) { ras := primaries if !primary { ras = fallbacks } c, err := DialSerial(ctx, dialer, network, destination, ras) select { case results <- dialResult{Conn: c, error: err, primary: primary, done: true}: case <-returned: if c != nil { c.Close() } } } var primary, fallback dialResult primaryCtx, primaryCancel := context.WithCancel(ctx) defer primaryCancel() go startRacer(primaryCtx, true) fallbackTimer := time.NewTimer(fallbackDelay) defer fallbackTimer.Stop() for { select { case <-fallbackTimer.C: fallbackCtx, fallbackCancel := context.WithCancel(ctx) defer fallbackCancel() go startRacer(fallbackCtx, false) case res := <-results: if res.error == nil { return res.Conn, nil } if res.primary { primary = res } else { fallback = res } if primary.done && fallback.done { return nil, primary.error } if res.primary && fallbackTimer.Stop() { fallbackTimer.Reset(0) } } } }