github.com/sagernet/sing-box@v1.9.0-rc.20/outbound/selector.go (about) 1 package outbound 2 3 import ( 4 "context" 5 "net" 6 7 "github.com/sagernet/sing-box/adapter" 8 "github.com/sagernet/sing-box/common/interrupt" 9 C "github.com/sagernet/sing-box/constant" 10 "github.com/sagernet/sing-box/log" 11 "github.com/sagernet/sing-box/option" 12 E "github.com/sagernet/sing/common/exceptions" 13 M "github.com/sagernet/sing/common/metadata" 14 N "github.com/sagernet/sing/common/network" 15 "github.com/sagernet/sing/service" 16 ) 17 18 var ( 19 _ adapter.Outbound = (*Selector)(nil) 20 _ adapter.OutboundGroup = (*Selector)(nil) 21 ) 22 23 type Selector struct { 24 myOutboundAdapter 25 ctx context.Context 26 tags []string 27 defaultTag string 28 outbounds map[string]adapter.Outbound 29 selected adapter.Outbound 30 interruptGroup *interrupt.Group 31 interruptExternalConnections bool 32 } 33 34 func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SelectorOutboundOptions) (*Selector, error) { 35 outbound := &Selector{ 36 myOutboundAdapter: myOutboundAdapter{ 37 protocol: C.TypeSelector, 38 router: router, 39 logger: logger, 40 tag: tag, 41 dependencies: options.Outbounds, 42 }, 43 ctx: ctx, 44 tags: options.Outbounds, 45 defaultTag: options.Default, 46 outbounds: make(map[string]adapter.Outbound), 47 interruptGroup: interrupt.NewGroup(), 48 interruptExternalConnections: options.InterruptExistConnections, 49 } 50 if len(outbound.tags) == 0 { 51 return nil, E.New("missing tags") 52 } 53 return outbound, nil 54 } 55 56 func (s *Selector) Network() []string { 57 if s.selected == nil { 58 return []string{N.NetworkTCP, N.NetworkUDP} 59 } 60 return s.selected.Network() 61 } 62 63 func (s *Selector) Start() error { 64 for i, tag := range s.tags { 65 detour, loaded := s.router.Outbound(tag) 66 if !loaded { 67 return E.New("outbound ", i, " not found: ", tag) 68 } 69 s.outbounds[tag] = detour 70 } 71 72 if s.tag != "" { 73 cacheFile := service.FromContext[adapter.CacheFile](s.ctx) 74 if cacheFile != nil { 75 selected := cacheFile.LoadSelected(s.tag) 76 if selected != "" { 77 detour, loaded := s.outbounds[selected] 78 if loaded { 79 s.selected = detour 80 return nil 81 } 82 } 83 } 84 } 85 86 if s.defaultTag != "" { 87 detour, loaded := s.outbounds[s.defaultTag] 88 if !loaded { 89 return E.New("default outbound not found: ", s.defaultTag) 90 } 91 s.selected = detour 92 return nil 93 } 94 95 s.selected = s.outbounds[s.tags[0]] 96 return nil 97 } 98 99 func (s *Selector) Now() string { 100 return s.selected.Tag() 101 } 102 103 func (s *Selector) All() []string { 104 return s.tags 105 } 106 107 func (s *Selector) SelectOutbound(tag string) bool { 108 detour, loaded := s.outbounds[tag] 109 if !loaded { 110 return false 111 } 112 if s.selected == detour { 113 return true 114 } 115 s.selected = detour 116 if s.tag != "" { 117 cacheFile := service.FromContext[adapter.CacheFile](s.ctx) 118 if cacheFile != nil { 119 err := cacheFile.StoreSelected(s.tag, tag) 120 if err != nil { 121 s.logger.Error("store selected: ", err) 122 } 123 } 124 } 125 s.interruptGroup.Interrupt(s.interruptExternalConnections) 126 return true 127 } 128 129 func (s *Selector) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { 130 conn, err := s.selected.DialContext(ctx, network, destination) 131 if err != nil { 132 return nil, err 133 } 134 return s.interruptGroup.NewConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil 135 } 136 137 func (s *Selector) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { 138 conn, err := s.selected.ListenPacket(ctx, destination) 139 if err != nil { 140 return nil, err 141 } 142 return s.interruptGroup.NewPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil 143 } 144 145 func (s *Selector) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { 146 ctx = interrupt.ContextWithIsExternalConnection(ctx) 147 return s.selected.NewConnection(ctx, conn, metadata) 148 } 149 150 func (s *Selector) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { 151 ctx = interrupt.ContextWithIsExternalConnection(ctx) 152 return s.selected.NewPacketConnection(ctx, conn, metadata) 153 } 154 155 func RealTag(detour adapter.Outbound) string { 156 if group, isGroup := detour.(adapter.OutboundGroup); isGroup { 157 return group.Now() 158 } 159 return detour.Tag() 160 }