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