package selector import ( "context" "errors" "net" "sync" "github.com/go-gost/core/chain" "github.com/go-gost/core/connector" "github.com/go-gost/core/logger" "github.com/go-gost/core/selector" xnet "github.com/go-gost/x/internal/net" ) type parallelStrategy[T any] struct{} // ParallelStrategy is a strategy for node selector. // It dials all nodes concurrently and uses the first successful connection. func ParallelStrategy[T any]() selector.Strategy[T] { return ¶llelStrategy[T]{} } func (s *parallelStrategy[T]) Apply(ctx context.Context, vs ...T) (v T) { if len(vs) == 0 { return } if len(vs) == 1 { return vs[0] } nodes := make([]*chain.Node, 0, len(vs)) for _, node := range vs { n, ok := any(node).(*chain.Node) if !ok { return } nodes = append(nodes, n) } vn := chain.NewNode("parallel", "", chain.TransportNodeOption(¶llelTransporter{nodes: nodes})) return any(vn).(T) } type parallelConn struct { net.Conn node *chain.Node } type parallelTransporter struct { nodes []*chain.Node } func (tr *parallelTransporter) Dial(ctx context.Context, addr string) (net.Conn, error) { if len(tr.nodes) == 0 { return nil, errors.New("no nodes available") } ctx, cancel := context.WithCancel(ctx) type result struct { conn net.Conn node *chain.Node err error } resCh := make(chan result, len(tr.nodes)) var wg sync.WaitGroup for _, node := range tr.nodes { wg.Add(1) go func(n *chain.Node) { defer wg.Done() raddr, err := xnet.Resolve(ctx, "ip", n.Addr, n.Options().Resolver, n.Options().HostMapper, logger.Default()) if err != nil { resCh <- result{err: err, node: n} return } cc, err := n.Options().Transport.Dial(ctx, raddr) resCh <- result{conn: cc, node: n, err: err} }(node) } var best result var errs []error for i := 0; i < len(tr.nodes); i++ { res := <-resCh if res.err != nil { errs = append(errs, res.err) continue } best = res break } cancel() if best.conn == nil { go func() { wg.Wait() close(resCh) }() return nil, errors.Join(errs...) } go func() { // Drain the rest of the channel to clean up late successful connections for i := len(errs) + 1; i < len(tr.nodes); i++ { res := <-resCh if res.err == nil && res.conn != nil { res.conn.Close() } } wg.Wait() close(resCh) }() return ¶llelConn{ Conn: best.conn, node: best.node, }, nil } func (tr *parallelTransporter) Handshake(ctx context.Context, conn net.Conn) (net.Conn, error) { pc, ok := conn.(*parallelConn) if !ok { return conn, nil } cc, err := pc.node.Options().Transport.Handshake(ctx, pc.Conn) if err != nil { return nil, err } pc.Conn = cc return pc, nil } func (tr *parallelTransporter) Connect(ctx context.Context, conn net.Conn, network, address string) (net.Conn, error) { pc, ok := conn.(*parallelConn) if !ok { return nil, errors.New("invalid connection type") } return pc.node.Options().Transport.Connect(ctx, pc.Conn, network, address) } func (tr *parallelTransporter) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) { pc, ok := conn.(*parallelConn) if !ok { return nil, errors.New("invalid connection type") } return pc.node.Options().Transport.Bind(ctx, pc.Conn, network, address, opts...) } func (tr *parallelTransporter) Multiplex() bool { return false } func (tr *parallelTransporter) Options() *chain.TransportOptions { return &chain.TransportOptions{} } // Copy implements chain.Transporter.Copy. func (tr *parallelTransporter) Copy() chain.Transporter { return ¶llelTransporter{ nodes: append([]*chain.Node(nil), tr.nodes...), } }