github.com/sagernet/sing-box@v1.9.0-rc.20/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/common/taskmonitor"
    13  	C "github.com/sagernet/sing-box/constant"
    14  	"github.com/sagernet/sing-box/experimental"
    15  	"github.com/sagernet/sing-box/experimental/cachefile"
    16  	"github.com/sagernet/sing-box/experimental/libbox/platform"
    17  	"github.com/sagernet/sing-box/inbound"
    18  	"github.com/sagernet/sing-box/log"
    19  	"github.com/sagernet/sing-box/option"
    20  	"github.com/sagernet/sing-box/outbound"
    21  	"github.com/sagernet/sing-box/route"
    22  	"github.com/sagernet/sing/common"
    23  	E "github.com/sagernet/sing/common/exceptions"
    24  	F "github.com/sagernet/sing/common/format"
    25  	"github.com/sagernet/sing/service"
    26  	"github.com/sagernet/sing/service/pause"
    27  )
    28  
    29  var _ adapter.Service = (*Box)(nil)
    30  
    31  type Box struct {
    32  	createdAt    time.Time
    33  	router       adapter.Router
    34  	inbounds     []adapter.Inbound
    35  	outbounds    []adapter.Outbound
    36  	logFactory   log.Factory
    37  	logger       log.ContextLogger
    38  	preServices1 map[string]adapter.Service
    39  	preServices2 map[string]adapter.Service
    40  	postServices map[string]adapter.Service
    41  	done         chan struct{}
    42  }
    43  
    44  type Options struct {
    45  	option.Options
    46  	Context           context.Context
    47  	PlatformInterface platform.Interface
    48  	PlatformLogWriter log.PlatformWriter
    49  }
    50  
    51  func New(options Options) (*Box, error) {
    52  	createdAt := time.Now()
    53  	ctx := options.Context
    54  	if ctx == nil {
    55  		ctx = context.Background()
    56  	}
    57  	ctx = service.ContextWithDefaultRegistry(ctx)
    58  	ctx = pause.WithDefaultManager(ctx)
    59  	experimentalOptions := common.PtrValueOrDefault(options.Experimental)
    60  	applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
    61  	var needCacheFile bool
    62  	var needClashAPI bool
    63  	var needV2RayAPI bool
    64  	if experimentalOptions.CacheFile != nil && experimentalOptions.CacheFile.Enabled || options.PlatformLogWriter != nil {
    65  		needCacheFile = true
    66  	}
    67  	if experimentalOptions.ClashAPI != nil || options.PlatformLogWriter != nil {
    68  		needClashAPI = true
    69  	}
    70  	if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
    71  		needV2RayAPI = true
    72  	}
    73  	var defaultLogWriter io.Writer
    74  	if options.PlatformInterface != nil {
    75  		defaultLogWriter = io.Discard
    76  	}
    77  	logFactory, err := log.New(log.Options{
    78  		Context:        ctx,
    79  		Options:        common.PtrValueOrDefault(options.Log),
    80  		Observable:     needClashAPI,
    81  		DefaultWriter:  defaultLogWriter,
    82  		BaseTime:       createdAt,
    83  		PlatformWriter: options.PlatformLogWriter,
    84  	})
    85  	if err != nil {
    86  		return nil, E.Cause(err, "create log factory")
    87  	}
    88  	router, err := route.NewRouter(
    89  		ctx,
    90  		logFactory,
    91  		common.PtrValueOrDefault(options.Route),
    92  		common.PtrValueOrDefault(options.DNS),
    93  		common.PtrValueOrDefault(options.NTP),
    94  		options.Inbounds,
    95  		options.PlatformInterface,
    96  	)
    97  	if err != nil {
    98  		return nil, E.Cause(err, "parse route options")
    99  	}
   100  	inbounds := make([]adapter.Inbound, 0, len(options.Inbounds))
   101  	outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
   102  	for i, inboundOptions := range options.Inbounds {
   103  		var in adapter.Inbound
   104  		var tag string
   105  		if inboundOptions.Tag != "" {
   106  			tag = inboundOptions.Tag
   107  		} else {
   108  			tag = F.ToString(i)
   109  		}
   110  		in, err = inbound.New(
   111  			ctx,
   112  			router,
   113  			logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
   114  			inboundOptions,
   115  			options.PlatformInterface,
   116  		)
   117  		if err != nil {
   118  			return nil, E.Cause(err, "parse inbound[", i, "]")
   119  		}
   120  		inbounds = append(inbounds, in)
   121  	}
   122  	for i, outboundOptions := range options.Outbounds {
   123  		var out adapter.Outbound
   124  		var tag string
   125  		if outboundOptions.Tag != "" {
   126  			tag = outboundOptions.Tag
   127  		} else {
   128  			tag = F.ToString(i)
   129  		}
   130  		out, err = outbound.New(
   131  			ctx,
   132  			router,
   133  			logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
   134  			tag,
   135  			outboundOptions)
   136  		if err != nil {
   137  			return nil, E.Cause(err, "parse outbound[", i, "]")
   138  		}
   139  		outbounds = append(outbounds, out)
   140  	}
   141  	err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
   142  		out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"})
   143  		common.Must(oErr)
   144  		outbounds = append(outbounds, out)
   145  		return out
   146  	})
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  	if options.PlatformInterface != nil {
   151  		err = options.PlatformInterface.Initialize(ctx, router)
   152  		if err != nil {
   153  			return nil, E.Cause(err, "initialize platform interface")
   154  		}
   155  	}
   156  	preServices1 := make(map[string]adapter.Service)
   157  	preServices2 := make(map[string]adapter.Service)
   158  	postServices := make(map[string]adapter.Service)
   159  	if needCacheFile {
   160  		cacheFile := service.FromContext[adapter.CacheFile](ctx)
   161  		if cacheFile == nil {
   162  			cacheFile = cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
   163  			service.MustRegister[adapter.CacheFile](ctx, cacheFile)
   164  		}
   165  		preServices1["cache file"] = cacheFile
   166  	}
   167  	if needClashAPI {
   168  		clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
   169  		clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
   170  		clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), clashAPIOptions)
   171  		if err != nil {
   172  			return nil, E.Cause(err, "create clash api server")
   173  		}
   174  		router.SetClashServer(clashServer)
   175  		preServices2["clash api"] = clashServer
   176  	}
   177  	if needV2RayAPI {
   178  		v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
   179  		if err != nil {
   180  			return nil, E.Cause(err, "create v2ray api server")
   181  		}
   182  		router.SetV2RayServer(v2rayServer)
   183  		preServices2["v2ray api"] = v2rayServer
   184  	}
   185  	return &Box{
   186  		router:       router,
   187  		inbounds:     inbounds,
   188  		outbounds:    outbounds,
   189  		createdAt:    createdAt,
   190  		logFactory:   logFactory,
   191  		logger:       logFactory.Logger(),
   192  		preServices1: preServices1,
   193  		preServices2: preServices2,
   194  		postServices: postServices,
   195  		done:         make(chan struct{}),
   196  	}, nil
   197  }
   198  
   199  func (s *Box) PreStart() error {
   200  	err := s.preStart()
   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 pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
   215  	return nil
   216  }
   217  
   218  func (s *Box) Start() error {
   219  	err := s.start()
   220  	if err != nil {
   221  		// TODO: remove catch error
   222  		defer func() {
   223  			v := recover()
   224  			if v != nil {
   225  				log.Error(E.Cause(err, "origin error"))
   226  				debug.PrintStack()
   227  				panic("panic on early close: " + fmt.Sprint(v))
   228  			}
   229  		}()
   230  		s.Close()
   231  		return err
   232  	}
   233  	s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
   234  	return nil
   235  }
   236  
   237  func (s *Box) preStart() error {
   238  	monitor := taskmonitor.New(s.logger, C.StartTimeout)
   239  	monitor.Start("start logger")
   240  	err := s.logFactory.Start()
   241  	monitor.Finish()
   242  	if err != nil {
   243  		return E.Cause(err, "start logger")
   244  	}
   245  	for serviceName, service := range s.preServices1 {
   246  		if preService, isPreService := service.(adapter.PreStarter); isPreService {
   247  			monitor.Start("pre-start ", serviceName)
   248  			err := preService.PreStart()
   249  			monitor.Finish()
   250  			if err != nil {
   251  				return E.Cause(err, "pre-start ", serviceName)
   252  			}
   253  		}
   254  	}
   255  	for serviceName, service := range s.preServices2 {
   256  		if preService, isPreService := service.(adapter.PreStarter); isPreService {
   257  			monitor.Start("pre-start ", serviceName)
   258  			err := preService.PreStart()
   259  			monitor.Finish()
   260  			if err != nil {
   261  				return E.Cause(err, "pre-start ", serviceName)
   262  			}
   263  		}
   264  	}
   265  	err = s.router.PreStart()
   266  	if err != nil {
   267  		return E.Cause(err, "pre-start router")
   268  	}
   269  	err = s.startOutbounds()
   270  	if err != nil {
   271  		return err
   272  	}
   273  	return s.router.Start()
   274  }
   275  
   276  func (s *Box) start() error {
   277  	err := s.preStart()
   278  	if err != nil {
   279  		return err
   280  	}
   281  	for serviceName, service := range s.preServices1 {
   282  		err = service.Start()
   283  		if err != nil {
   284  			return E.Cause(err, "start ", serviceName)
   285  		}
   286  	}
   287  	for serviceName, service := range s.preServices2 {
   288  		err = service.Start()
   289  		if err != nil {
   290  			return E.Cause(err, "start ", serviceName)
   291  		}
   292  	}
   293  	for i, in := range s.inbounds {
   294  		var tag string
   295  		if in.Tag() == "" {
   296  			tag = F.ToString(i)
   297  		} else {
   298  			tag = in.Tag()
   299  		}
   300  		err = in.Start()
   301  		if err != nil {
   302  			return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
   303  		}
   304  	}
   305  	return s.postStart()
   306  }
   307  
   308  func (s *Box) postStart() error {
   309  	for serviceName, service := range s.postServices {
   310  		err := service.Start()
   311  		if err != nil {
   312  			return E.Cause(err, "start ", serviceName)
   313  		}
   314  	}
   315  	for _, outbound := range s.outbounds {
   316  		if lateOutbound, isLateOutbound := outbound.(adapter.PostStarter); isLateOutbound {
   317  			err := lateOutbound.PostStart()
   318  			if err != nil {
   319  				return E.Cause(err, "post-start outbound/", outbound.Tag())
   320  			}
   321  		}
   322  	}
   323  
   324  	return s.router.PostStart()
   325  }
   326  
   327  func (s *Box) Close() error {
   328  	select {
   329  	case <-s.done:
   330  		return os.ErrClosed
   331  	default:
   332  		close(s.done)
   333  	}
   334  	monitor := taskmonitor.New(s.logger, C.StopTimeout)
   335  	var errors error
   336  	for serviceName, service := range s.postServices {
   337  		monitor.Start("close ", serviceName)
   338  		errors = E.Append(errors, service.Close(), func(err error) error {
   339  			return E.Cause(err, "close ", serviceName)
   340  		})
   341  		monitor.Finish()
   342  	}
   343  	for i, in := range s.inbounds {
   344  		monitor.Start("close inbound/", in.Type(), "[", i, "]")
   345  		errors = E.Append(errors, in.Close(), func(err error) error {
   346  			return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
   347  		})
   348  		monitor.Finish()
   349  	}
   350  	for i, out := range s.outbounds {
   351  		monitor.Start("close outbound/", out.Type(), "[", i, "]")
   352  		errors = E.Append(errors, common.Close(out), func(err error) error {
   353  			return E.Cause(err, "close outbound/", out.Type(), "[", i, "]")
   354  		})
   355  		monitor.Finish()
   356  	}
   357  	monitor.Start("close router")
   358  	if err := common.Close(s.router); err != nil {
   359  		errors = E.Append(errors, err, func(err error) error {
   360  			return E.Cause(err, "close router")
   361  		})
   362  	}
   363  	monitor.Finish()
   364  	for serviceName, service := range s.preServices1 {
   365  		monitor.Start("close ", serviceName)
   366  		errors = E.Append(errors, service.Close(), func(err error) error {
   367  			return E.Cause(err, "close ", serviceName)
   368  		})
   369  		monitor.Finish()
   370  	}
   371  	for serviceName, service := range s.preServices2 {
   372  		monitor.Start("close ", serviceName)
   373  		errors = E.Append(errors, service.Close(), func(err error) error {
   374  			return E.Cause(err, "close ", serviceName)
   375  		})
   376  		monitor.Finish()
   377  	}
   378  	if err := common.Close(s.logFactory); err != nil {
   379  		errors = E.Append(errors, err, func(err error) error {
   380  			return E.Cause(err, "close logger")
   381  		})
   382  	}
   383  	return errors
   384  }
   385  
   386  func (s *Box) Router() adapter.Router {
   387  	return s.router
   388  }