package libbox import ( "context" "net/netip" "runtime" runtimeDebug "runtime/debug" "syscall" "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/common/urltest" "github.com/sagernet/sing-box/experimental/libbox/internal/procfs" "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/filemanager" "github.com/sagernet/sing/service/pause" ) type BoxService struct { ctx context.Context cancel context.CancelFunc instance *box.Box pauseManager pause.Manager urlTestHistoryStorage *urltest.HistoryStorage } func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) { options, err := parseConfig(configContent) if err != nil { return nil, err } runtimeDebug.FreeOSMemory() ctx, cancel := context.WithCancel(context.Background()) ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) urlTestHistoryStorage := urltest.NewHistoryStorage() ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) pauseManager := pause.NewDefaultManager(ctx) ctx = pause.ContextWithManager(ctx, pauseManager) platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()} instance, err := box.New(box.Options{ Context: ctx, Options: options, PlatformInterface: platformWrapper, PlatformLogWriter: platformWrapper, }) if err != nil { cancel() return nil, E.Cause(err, "create service") } runtimeDebug.FreeOSMemory() return &BoxService{ ctx: ctx, cancel: cancel, instance: instance, urlTestHistoryStorage: urlTestHistoryStorage, pauseManager: pauseManager, }, nil } func (s *BoxService) Start() error { return s.instance.Start() } func (s *BoxService) Close() error { s.cancel() s.urlTestHistoryStorage.Close() return s.instance.Close() } func (s *BoxService) Sleep() { s.pauseManager.DevicePause() _ = s.instance.Router().ResetNetwork() } func (s *BoxService) Wake() { s.pauseManager.DeviceWake() _ = s.instance.Router().ResetNetwork() } var ( _ platform.Interface = (*platformInterfaceWrapper)(nil) _ log.PlatformWriter = (*platformInterfaceWrapper)(nil) ) type platformInterfaceWrapper struct { iif PlatformInterface useProcFS bool router adapter.Router } func (w *platformInterfaceWrapper) Initialize(ctx context.Context, router adapter.Router) error { w.router = router return nil } func (w *platformInterfaceWrapper) UsePlatformAutoDetectInterfaceControl() bool { return w.iif.UsePlatformAutoDetectInterfaceControl() } func (w *platformInterfaceWrapper) AutoDetectInterfaceControl() control.Func { return func(network, address string, conn syscall.RawConn) error { return control.Raw(conn, func(fd uintptr) error { return w.iif.AutoDetectInterfaceControl(int32(fd)) }) } } func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) { if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 { return nil, E.New("android: unsupported uid options") } if len(options.IncludeAndroidUser) > 0 { return nil, E.New("android: unsupported android_user option") } tunFd, err := w.iif.OpenTun(&tunOptions{options, platformOptions}) if err != nil { return nil, err } options.Name, err = getTunnelName(tunFd) if err != nil { return nil, E.Cause(err, "query tun name") } dupFd, err := dup(int(tunFd)) if err != nil { return nil, E.Cause(err, "dup tun file descriptor") } options.FileDescriptor = dupFd return tun.New(*options) } func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) { var uid int32 if w.useProcFS { uid = procfs.ResolveSocketByProcSearch(network, source, destination) if uid == -1 { return nil, E.New("procfs: not found") } } else { var ipProtocol int32 switch N.NetworkName(network) { case N.NetworkTCP: ipProtocol = syscall.IPPROTO_TCP case N.NetworkUDP: ipProtocol = syscall.IPPROTO_UDP default: return nil, E.New("unknown network: ", network) } var err error uid, err = w.iif.FindConnectionOwner(ipProtocol, source.Addr().String(), int32(source.Port()), destination.Addr().String(), int32(destination.Port())) if err != nil { return nil, err } } packageName, _ := w.iif.PackageNameByUid(uid) return &process.Info{UserId: uid, PackageName: packageName}, nil } func (w *platformInterfaceWrapper) UsePlatformDefaultInterfaceMonitor() bool { return w.iif.UsePlatformDefaultInterfaceMonitor() } func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor { return &platformDefaultInterfaceMonitor{ platformInterfaceWrapper: w, defaultInterfaceIndex: -1, logger: logger, } } func (w *platformInterfaceWrapper) UsePlatformInterfaceGetter() bool { return w.iif.UsePlatformInterfaceGetter() } func (w *platformInterfaceWrapper) Interfaces() ([]platform.NetworkInterface, error) { interfaceIterator, err := w.iif.GetInterfaces() if err != nil { return nil, err } var interfaces []platform.NetworkInterface for _, netInterface := range iteratorToArray[*NetworkInterface](interfaceIterator) { interfaces = append(interfaces, platform.NetworkInterface{ Index: int(netInterface.Index), MTU: int(netInterface.MTU), Name: netInterface.Name, Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix), }) } return interfaces, nil } func (w *platformInterfaceWrapper) UnderNetworkExtension() bool { return w.iif.UnderNetworkExtension() } func (w *platformInterfaceWrapper) ClearDNSCache() { w.iif.ClearDNSCache() } func (w *platformInterfaceWrapper) DisableColors() bool { return runtime.GOOS != "android" } func (w *platformInterfaceWrapper) WriteMessage(level log.Level, message string) { w.iif.WriteLog(message) }