github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/outbound/selector.go (about) 1 package outbound 2 3 import ( 4 "context" 5 "net" 6 7 "github.com/inazumav/sing-box/adapter" 8 C "github.com/inazumav/sing-box/constant" 9 "github.com/inazumav/sing-box/log" 10 "github.com/inazumav/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 dependencies: options.Outbounds, 37 }, 38 tags: options.Outbounds, 39 defaultTag: options.Default, 40 outbounds: make(map[string]adapter.Outbound), 41 } 42 if len(outbound.tags) == 0 { 43 return nil, E.New("missing tags") 44 } 45 return outbound, nil 46 } 47 48 func (s *Selector) Network() []string { 49 if s.selected == nil { 50 return []string{N.NetworkTCP, N.NetworkUDP} 51 } 52 return s.selected.Network() 53 } 54 55 func (s *Selector) Start() error { 56 for i, tag := range s.tags { 57 detour, loaded := s.router.Outbound(tag) 58 if !loaded { 59 return E.New("outbound ", i, " not found: ", tag) 60 } 61 s.outbounds[tag] = detour 62 } 63 64 if s.tag != "" { 65 if clashServer := s.router.ClashServer(); clashServer != nil && clashServer.StoreSelected() { 66 selected := clashServer.CacheFile().LoadSelected(s.tag) 67 if selected != "" { 68 detour, loaded := s.outbounds[selected] 69 if loaded { 70 s.selected = detour 71 return nil 72 } 73 } 74 } 75 } 76 77 if s.defaultTag != "" { 78 detour, loaded := s.outbounds[s.defaultTag] 79 if !loaded { 80 return E.New("default outbound not found: ", s.defaultTag) 81 } 82 s.selected = detour 83 return nil 84 } 85 86 s.selected = s.outbounds[s.tags[0]] 87 return nil 88 } 89 90 func (s *Selector) Now() string { 91 return s.selected.Tag() 92 } 93 94 func (s *Selector) All() []string { 95 return s.tags 96 } 97 98 func (s *Selector) SelectOutbound(tag string) bool { 99 detour, loaded := s.outbounds[tag] 100 if !loaded { 101 return false 102 } 103 s.selected = detour 104 if s.tag != "" { 105 if clashServer := s.router.ClashServer(); clashServer != nil && clashServer.StoreSelected() { 106 err := clashServer.CacheFile().StoreSelected(s.tag, tag) 107 if err != nil { 108 s.logger.Error("store selected: ", err) 109 } 110 } 111 } 112 return true 113 } 114 115 func (s *Selector) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { 116 return s.selected.DialContext(ctx, network, destination) 117 } 118 119 func (s *Selector) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { 120 return s.selected.ListenPacket(ctx, destination) 121 } 122 123 func (s *Selector) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { 124 return s.selected.NewConnection(ctx, conn, metadata) 125 } 126 127 func (s *Selector) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { 128 return s.selected.NewPacketConnection(ctx, conn, metadata) 129 } 130 131 func RealTag(detour adapter.Outbound) string { 132 if group, isGroup := detour.(adapter.OutboundGroup); isGroup { 133 return group.Now() 134 } 135 return detour.Tag() 136 }