package group import ( "context" "net" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/interrupt" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" ) func RegisterSelector(registry *outbound.Registry) { outbound.Register[option.SelectorOutboundOptions](registry, C.TypeSelector, NewSelector) } var _ adapter.OutboundGroup = (*Selector)(nil) type Selector struct { outbound.Adapter ctx context.Context router adapter.Router logger logger.ContextLogger tags []string defaultTag string outbounds map[string]adapter.Outbound selected adapter.Outbound interruptGroup *interrupt.Group interruptExternalConnections bool } func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SelectorOutboundOptions) (adapter.Outbound, error) { outbound := &Selector{ Adapter: outbound.NewAdapter(C.TypeSelector, nil, tag, options.Outbounds), ctx: ctx, router: router, logger: logger, tags: options.Outbounds, defaultTag: options.Default, outbounds: make(map[string]adapter.Outbound), interruptGroup: interrupt.NewGroup(), interruptExternalConnections: options.InterruptExistConnections, } if len(outbound.tags) == 0 { return nil, E.New("missing tags") } return outbound, nil } func (s *Selector) Network() []string { if s.selected == nil { return []string{N.NetworkTCP, N.NetworkUDP} } return s.selected.Network() } func (s *Selector) Start() error { for i, tag := range s.tags { detour, loaded := s.router.Outbound(tag) if !loaded { return E.New("outbound ", i, " not found: ", tag) } s.outbounds[tag] = detour } if s.Tag() != "" { cacheFile := service.FromContext[adapter.CacheFile](s.ctx) if cacheFile != nil { selected := cacheFile.LoadSelected(s.Tag()) if selected != "" { detour, loaded := s.outbounds[selected] if loaded { s.selected = detour return nil } } } } if s.defaultTag != "" { detour, loaded := s.outbounds[s.defaultTag] if !loaded { return E.New("default outbound not found: ", s.defaultTag) } s.selected = detour return nil } s.selected = s.outbounds[s.tags[0]] return nil } func (s *Selector) Now() string { return s.selected.Tag() } func (s *Selector) All() []string { return s.tags } func (s *Selector) SelectOutbound(tag string) bool { detour, loaded := s.outbounds[tag] if !loaded { return false } if s.selected == detour { return true } s.selected = detour if s.Tag() != "" { cacheFile := service.FromContext[adapter.CacheFile](s.ctx) if cacheFile != nil { err := cacheFile.StoreSelected(s.Tag(), tag) if err != nil { s.logger.Error("store selected: ", err) } } } s.interruptGroup.Interrupt(s.interruptExternalConnections) return true } func (s *Selector) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { conn, err := s.selected.DialContext(ctx, network, destination) if err != nil { return nil, err } return s.interruptGroup.NewConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil } func (s *Selector) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { conn, err := s.selected.ListenPacket(ctx, destination) if err != nil { return nil, err } return s.interruptGroup.NewPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil } // TODO // Deprecated func (s *Selector) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { ctx = interrupt.ContextWithIsExternalConnection(ctx) if legacyHandler, ok := s.selected.(adapter.ConnectionHandler); ok { return legacyHandler.NewConnection(ctx, conn, metadata) } else { return outbound.NewConnection(ctx, s.selected, conn, metadata) } } // TODO // Deprecated func (s *Selector) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { ctx = interrupt.ContextWithIsExternalConnection(ctx) if legacyHandler, ok := s.selected.(adapter.PacketConnectionHandler); ok { return legacyHandler.NewPacketConnection(ctx, conn, metadata) } else { return outbound.NewPacketConnection(ctx, s.selected, conn, metadata) } } func RealTag(detour adapter.Outbound) string { if group, isGroup := detour.(adapter.OutboundGroup); isGroup { return group.Now() } return detour.Tag() }