github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/box.go (about) 1 package box 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "os" 8 "runtime/debug" 9 "time" 10 11 "github.com/inazumav/sing-box/adapter" 12 "github.com/inazumav/sing-box/experimental" 13 "github.com/inazumav/sing-box/experimental/libbox/platform" 14 "github.com/inazumav/sing-box/inbound" 15 "github.com/inazumav/sing-box/log" 16 "github.com/inazumav/sing-box/option" 17 "github.com/inazumav/sing-box/outbound" 18 "github.com/inazumav/sing-box/route" 19 "github.com/sagernet/sing/common" 20 E "github.com/sagernet/sing/common/exceptions" 21 F "github.com/sagernet/sing/common/format" 22 "github.com/sagernet/sing/service" 23 "github.com/sagernet/sing/service/pause" 24 ) 25 26 var _ adapter.Service = (*Box)(nil) 27 28 type Box struct { 29 createdAt time.Time 30 router adapter.Router 31 inbounds []adapter.Inbound 32 outbounds []adapter.Outbound 33 logFactory log.Factory 34 logger log.ContextLogger 35 preServices map[string]adapter.Service 36 postServices map[string]adapter.Service 37 done chan struct{} 38 } 39 40 type Options struct { 41 option.Options 42 Context context.Context 43 PlatformInterface platform.Interface 44 } 45 46 func New(options Options) (*Box, error) { 47 ctx := options.Context 48 if ctx == nil { 49 ctx = context.Background() 50 } 51 ctx = service.ContextWithDefaultRegistry(ctx) 52 ctx = pause.ContextWithDefaultManager(ctx) 53 createdAt := time.Now() 54 experimentalOptions := common.PtrValueOrDefault(options.Experimental) 55 applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug)) 56 var needClashAPI bool 57 var needV2RayAPI bool 58 if experimentalOptions.ClashAPI != nil || options.PlatformInterface != nil { 59 needClashAPI = true 60 } 61 if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" { 62 needV2RayAPI = true 63 } 64 var defaultLogWriter io.Writer 65 if options.PlatformInterface != nil { 66 defaultLogWriter = io.Discard 67 } 68 logFactory, err := log.New(log.Options{ 69 Context: ctx, 70 Options: common.PtrValueOrDefault(options.Log), 71 Observable: needClashAPI, 72 DefaultWriter: defaultLogWriter, 73 BaseTime: createdAt, 74 PlatformWriter: options.PlatformInterface, 75 }) 76 if err != nil { 77 return nil, E.Cause(err, "create log factory") 78 } 79 router, err := route.NewRouter( 80 ctx, 81 logFactory, 82 common.PtrValueOrDefault(options.Route), 83 common.PtrValueOrDefault(options.DNS), 84 common.PtrValueOrDefault(options.NTP), 85 options.Inbounds, 86 options.PlatformInterface, 87 ) 88 if err != nil { 89 return nil, E.Cause(err, "parse route options") 90 } 91 inbounds := make([]adapter.Inbound, 0, len(options.Inbounds)) 92 outbounds := make([]adapter.Outbound, 0, len(options.Outbounds)) 93 for i, inboundOptions := range options.Inbounds { 94 var in adapter.Inbound 95 var tag string 96 if inboundOptions.Tag != "" { 97 tag = inboundOptions.Tag 98 } else { 99 tag = F.ToString(i) 100 } 101 in, err = inbound.New( 102 ctx, 103 router, 104 logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")), 105 inboundOptions, 106 options.PlatformInterface, 107 ) 108 if err != nil { 109 return nil, E.Cause(err, "parse inbound[", i, "]") 110 } 111 inbounds = append(inbounds, in) 112 } 113 for i, outboundOptions := range options.Outbounds { 114 var out adapter.Outbound 115 var tag string 116 if outboundOptions.Tag != "" { 117 tag = outboundOptions.Tag 118 } else { 119 tag = F.ToString(i) 120 } 121 out, err = outbound.New( 122 ctx, 123 router, 124 logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")), 125 tag, 126 outboundOptions) 127 if err != nil { 128 return nil, E.Cause(err, "parse outbound[", i, "]") 129 } 130 outbounds = append(outbounds, out) 131 } 132 err = router.Initialize(inbounds, outbounds, func() adapter.Outbound { 133 out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"}) 134 common.Must(oErr) 135 outbounds = append(outbounds, out) 136 return out 137 }) 138 if err != nil { 139 return nil, err 140 } 141 if options.PlatformInterface != nil { 142 err = options.PlatformInterface.Initialize(ctx, router) 143 if err != nil { 144 return nil, E.Cause(err, "initialize platform interface") 145 } 146 } 147 preServices := make(map[string]adapter.Service) 148 postServices := make(map[string]adapter.Service) 149 if needClashAPI { 150 clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI) 151 clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options) 152 clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), clashAPIOptions) 153 if err != nil { 154 return nil, E.Cause(err, "create clash api server") 155 } 156 router.SetClashServer(clashServer) 157 preServices["clash api"] = clashServer 158 } 159 if needV2RayAPI { 160 v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI)) 161 if err != nil { 162 return nil, E.Cause(err, "create v2ray api server") 163 } 164 router.SetV2RayServer(v2rayServer) 165 preServices["v2ray api"] = v2rayServer 166 } 167 return &Box{ 168 router: router, 169 inbounds: inbounds, 170 outbounds: outbounds, 171 createdAt: createdAt, 172 logFactory: logFactory, 173 logger: logFactory.Logger(), 174 preServices: preServices, 175 postServices: postServices, 176 done: make(chan struct{}), 177 }, nil 178 } 179 180 func (s *Box) PreStart() error { 181 err := s.preStart() 182 if err != nil { 183 // TODO: remove catch error 184 defer func() { 185 v := recover() 186 if v != nil { 187 log.Error(E.Cause(err, "origin error")) 188 debug.PrintStack() 189 panic("panic on early close: " + fmt.Sprint(v)) 190 } 191 }() 192 s.Close() 193 return err 194 } 195 s.logger.Info("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)") 196 return nil 197 } 198 199 func (s *Box) Start() error { 200 err := s.start() 201 if err != nil { 202 // TODO: remove catch error 203 defer func() { 204 v := recover() 205 if v != nil { 206 log.Error(E.Cause(err, "origin error")) 207 debug.PrintStack() 208 panic("panic on early close: " + fmt.Sprint(v)) 209 } 210 }() 211 s.Close() 212 return err 213 } 214 s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)") 215 return nil 216 } 217 218 func (s *Box) preStart() error { 219 for serviceName, service := range s.preServices { 220 if preService, isPreService := service.(adapter.PreStarter); isPreService { 221 s.logger.Trace("pre-start ", serviceName) 222 err := preService.PreStart() 223 if err != nil { 224 return E.Cause(err, "pre-starting ", serviceName) 225 } 226 } 227 } 228 err := s.startOutbounds() 229 if err != nil { 230 return err 231 } 232 return s.router.Start() 233 } 234 235 func (s *Box) start() error { 236 err := s.preStart() 237 if err != nil { 238 return err 239 } 240 for serviceName, service := range s.preServices { 241 s.logger.Trace("starting ", serviceName) 242 err = service.Start() 243 if err != nil { 244 return E.Cause(err, "start ", serviceName) 245 } 246 } 247 for i, in := range s.inbounds { 248 var tag string 249 if in.Tag() == "" { 250 tag = F.ToString(i) 251 } else { 252 tag = in.Tag() 253 } 254 s.logger.Trace("initializing inbound/", in.Type(), "[", tag, "]") 255 err = in.Start() 256 if err != nil { 257 return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]") 258 } 259 } 260 return nil 261 } 262 263 func (s *Box) postStart() error { 264 for serviceName, service := range s.postServices { 265 s.logger.Trace("starting ", service) 266 err := service.Start() 267 if err != nil { 268 return E.Cause(err, "start ", serviceName) 269 } 270 } 271 for serviceName, service := range s.outbounds { 272 if lateService, isLateService := service.(adapter.PostStarter); isLateService { 273 s.logger.Trace("post-starting ", service) 274 err := lateService.PostStart() 275 if err != nil { 276 return E.Cause(err, "post-start ", serviceName) 277 } 278 } 279 } 280 return nil 281 } 282 283 func (s *Box) Close() error { 284 select { 285 case <-s.done: 286 return os.ErrClosed 287 default: 288 close(s.done) 289 } 290 var errors error 291 for serviceName, service := range s.postServices { 292 s.logger.Trace("closing ", serviceName) 293 errors = E.Append(errors, service.Close(), func(err error) error { 294 return E.Cause(err, "close ", serviceName) 295 }) 296 } 297 for i, in := range s.inbounds { 298 s.logger.Trace("closing inbound/", in.Type(), "[", i, "]") 299 errors = E.Append(errors, in.Close(), func(err error) error { 300 return E.Cause(err, "close inbound/", in.Type(), "[", i, "]") 301 }) 302 } 303 for i, out := range s.outbounds { 304 s.logger.Trace("closing outbound/", out.Type(), "[", i, "]") 305 errors = E.Append(errors, common.Close(out), func(err error) error { 306 return E.Cause(err, "close outbound/", out.Type(), "[", i, "]") 307 }) 308 } 309 s.logger.Trace("closing router") 310 if err := common.Close(s.router); err != nil { 311 errors = E.Append(errors, err, func(err error) error { 312 return E.Cause(err, "close router") 313 }) 314 } 315 for serviceName, service := range s.preServices { 316 s.logger.Trace("closing ", serviceName) 317 errors = E.Append(errors, service.Close(), func(err error) error { 318 return E.Cause(err, "close ", serviceName) 319 }) 320 } 321 s.logger.Trace("closing log factory") 322 if err := common.Close(s.logFactory); err != nil { 323 errors = E.Append(errors, err, func(err error) error { 324 return E.Cause(err, "close log factory") 325 }) 326 } 327 return errors 328 } 329 330 func (s *Box) Router() adapter.Router { 331 return s.router 332 }