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  }