package libcore import ( "context" "errors" "fmt" "io" "libcore/device" "log" "net/http" "reflect" "runtime" "runtime/debug" "strings" "time" "unsafe" "github.com/matsuridayo/sing-box-extra/boxbox" _ "github.com/matsuridayo/sing-box-extra/distro/all" "github.com/matsuridayo/libneko/neko_common" "github.com/matsuridayo/libneko/protect_server" "github.com/matsuridayo/libneko/speedtest" "github.com/matsuridayo/sing-box-extra/boxapi" "github.com/sagernet/sing-box/common/dialer/conntrack" sblog "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/outbound" ) var mainInstance *BoxInstance func init() { neko_common.GetProxyHttpClient = func() *http.Client { if mainInstance == nil { return nil } return boxapi.GetProxyHttpClient(mainInstance.Box) } } func VersionBox() string { version := []string{ "sing-box-extra: " + boxbox.Version(), runtime.Version() + "@" + runtime.GOOS + "/" + runtime.GOARCH, } var tags string debugInfo, loaded := debug.ReadBuildInfo() if loaded { for _, setting := range debugInfo.Settings { switch setting.Key { case "-tags": tags = setting.Value } } } if tags != "" { version = append(version, tags) } return strings.Join(version, "\n") } func ResetAllConnections(system bool) { if system { conntrack.Close() log.Println("Reset system connections done") } } type BoxInstance struct { *boxbox.Box cancel context.CancelFunc state int v2api *boxapi.SbV2rayServer selector *outbound.Selector ForTest bool } func NewSingBoxInstance(config string) (b *BoxInstance, err error) { defer device.DeferPanicToError("NewSingBoxInstance", func(err_ error) { err = err_ }) // parse options var options option.Options err = options.UnmarshalJSON([]byte(config)) if err != nil { return nil, fmt.Errorf("decode config: %v", err) } // create box ctx, cancel := context.WithCancel(context.Background()) instance, err := boxbox.New(boxbox.Options{ Options: options, Context: ctx, PlatformInterface: boxPlatformInterfaceInstance, }) if err != nil { cancel() return nil, fmt.Errorf("create service: %v", err) } b = &BoxInstance{ Box: instance, cancel: cancel, } // fuck your sing-box platformFormatter logFactory_ := reflect.Indirect(reflect.ValueOf(instance)).FieldByName("logFactory") logFactory_ = reflect.NewAt(logFactory_.Type(), unsafe.Pointer(logFactory_.UnsafeAddr())).Elem() // get unexported logFactory logFactory_ = logFactory_.Elem().Elem() // get struct platformFormatter_ := logFactory_.FieldByName("platformFormatter") platformFormatter_ = reflect.NewAt(platformFormatter_.Type(), unsafe.Pointer(platformFormatter_.UnsafeAddr())) // get unexported Formatter platformFormatter := platformFormatter_.Interface().(*sblog.Formatter) platformFormatter.DisableColors = true // selector if proxy, ok := b.Router().Outbound("proxy"); ok { if selector, ok := proxy.(*outbound.Selector); ok { b.selector = selector } } return b, nil } func (b *BoxInstance) Start() (err error) { defer device.DeferPanicToError("box.Start", func(err_ error) { err = err_ }) if outdated != "" { return errors.New(outdated) } if b.state == 0 { b.state = 1 return b.Box.Start() } return errors.New("already started") } func (b *BoxInstance) Close() (err error) { defer device.DeferPanicToError("box.Close", func(err_ error) { err = err_ }) // no double close if b.state == 2 { return nil } b.state = 2 // clear main instance if mainInstance == b { mainInstance = nil goServeProtect(false) } // close box t := time.NewTimer(time.Second * 2) c := make(chan struct{}, 1) disableSingBoxLog = true go func(cancel context.CancelFunc, closer io.Closer) { cancel() closer.Close() c <- struct{}{} close(c) }(b.cancel, b.Box) select { case <-t.C: log.Println("[Warning] sing-box close takes longer than expected.") case <-c: } disableSingBoxLog = false t.Stop() return nil } func (b *BoxInstance) SetAsMain() { mainInstance = b goServeProtect(true) } func (b *BoxInstance) SetConnectionPoolEnabled(enable bool) { // TODO api } func (b *BoxInstance) SetV2rayStats(outbounds string) { b.v2api = boxapi.NewSbV2rayServer(option.V2RayStatsServiceOptions{ Enabled: true, Outbounds: strings.Split(outbounds, "\n"), }) b.Box.Router().SetV2RayServer(b.v2api) } func (b *BoxInstance) QueryStats(tag, direct string) int64 { if b.v2api == nil { return 0 } return b.v2api.QueryStats(fmt.Sprintf("outbound>>>%s>>>traffic>>>%s", tag, direct)) } func (b *BoxInstance) SelectOutbound(tag string) bool { if b.selector != nil { var result = b.selector.SelectOutbound(tag) if result { ResetAllConnections(true) } return result } return false } func UrlTest(i *BoxInstance, link string, timeout int32) (latency int32, err error) { defer device.DeferPanicToError("box.UrlTest", func(err_ error) { err = err_ }) if i == nil { // test current return speedtest.UrlTest(neko_common.GetProxyHttpClient(), link, timeout) } return speedtest.UrlTest(boxapi.GetProxyHttpClient(i.Box), link, timeout) } var protectCloser io.Closer func goServeProtect(start bool) { if protectCloser != nil { protectCloser.Close() protectCloser = nil } if start { protectCloser = protect_server.ServeProtect("protect_path", false, 0, func(fd int) { intfBox.AutoDetectInterfaceControl(int32(fd)) }) } }