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  }