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