separate frontend repository
This commit is contained in:
+408
@@ -0,0 +1,408 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"s-ui/util/common"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||
"github.com/sagernet/sing-box/adapter/inbound"
|
||||
"github.com/sagernet/sing-box/adapter/outbound"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/protocol/direct"
|
||||
"github.com/sagernet/sing-box/route"
|
||||
sbCommon "github.com/sagernet/sing/common"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
"github.com/sagernet/sing/service"
|
||||
"github.com/sagernet/sing/service/pause"
|
||||
)
|
||||
|
||||
var _ adapter.Service = (*Box)(nil)
|
||||
|
||||
type Box struct {
|
||||
createdAt time.Time
|
||||
logFactory log.Factory
|
||||
logger log.ContextLogger
|
||||
network *route.NetworkManager
|
||||
endpoint *endpoint.Manager
|
||||
inbound *inbound.Manager
|
||||
outbound *outbound.Manager
|
||||
connection *route.ConnectionManager
|
||||
router *route.Router
|
||||
services []adapter.LifecycleService
|
||||
connTracker *ConnTracker
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
option.Options
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
func Context(
|
||||
ctx context.Context,
|
||||
inboundRegistry adapter.InboundRegistry,
|
||||
outboundRegistry adapter.OutboundRegistry,
|
||||
endpointRegistry adapter.EndpointRegistry,
|
||||
) context.Context {
|
||||
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||
ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry)
|
||||
ctx = service.ContextWith[adapter.InboundRegistry](ctx, inboundRegistry)
|
||||
}
|
||||
if service.FromContext[option.OutboundOptionsRegistry](ctx) == nil ||
|
||||
service.FromContext[adapter.OutboundRegistry](ctx) == nil {
|
||||
ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry)
|
||||
ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry)
|
||||
}
|
||||
if service.FromContext[option.EndpointOptionsRegistry](ctx) == nil ||
|
||||
service.FromContext[adapter.EndpointRegistry](ctx) == nil {
|
||||
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
|
||||
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func NewBox(options Options) (*Box, error) {
|
||||
var err error
|
||||
createdAt := time.Now()
|
||||
ctx := options.Context
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
ctx = service.ContextWithDefaultRegistry(ctx)
|
||||
|
||||
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
|
||||
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||
|
||||
if endpointRegistry == nil {
|
||||
return nil, common.NewError("missing endpoint registry in context")
|
||||
}
|
||||
if inboundRegistry == nil {
|
||||
return nil, common.NewError("missing inbound registry in context")
|
||||
}
|
||||
if outboundRegistry == nil {
|
||||
return nil, common.NewError("missing outbound registry in context")
|
||||
}
|
||||
|
||||
ctx = pause.WithDefaultManager(ctx)
|
||||
experimentalOptions := sbCommon.PtrValueOrDefault(options.Experimental)
|
||||
var needCacheFile bool
|
||||
if experimentalOptions.CacheFile != nil && experimentalOptions.CacheFile.Enabled {
|
||||
needCacheFile = true
|
||||
}
|
||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||
var defaultLogWriter io.Writer
|
||||
if platformInterface != nil {
|
||||
defaultLogWriter = io.Discard
|
||||
}
|
||||
var logFactory log.Factory
|
||||
logFactory, err = NewFactory(log.Options{
|
||||
Context: ctx,
|
||||
Options: sbCommon.PtrValueOrDefault(options.Log),
|
||||
DefaultWriter: defaultLogWriter,
|
||||
BaseTime: createdAt,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, common.NewError("create log factory", err)
|
||||
}
|
||||
factory = logFactory
|
||||
|
||||
routeOptions := sbCommon.PtrValueOrDefault(options.Route)
|
||||
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
||||
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
||||
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
||||
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
||||
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
||||
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
||||
|
||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
||||
if err != nil {
|
||||
return nil, common.NewError("initialize network manager", err)
|
||||
}
|
||||
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
||||
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
|
||||
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
||||
router, err := route.NewRouter(ctx, logFactory, routeOptions, sbCommon.PtrValueOrDefault(options.DNS))
|
||||
if err != nil {
|
||||
return nil, common.NewError("initialize router", err)
|
||||
}
|
||||
for i, endpointOptions := range options.Endpoints {
|
||||
var tag string
|
||||
if endpointOptions.Tag != "" {
|
||||
tag = endpointOptions.Tag
|
||||
} else {
|
||||
tag = F.ToString(i)
|
||||
}
|
||||
err = endpointManager.Create(ctx,
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
|
||||
tag,
|
||||
endpointOptions.Type,
|
||||
endpointOptions.Options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, common.NewError("initialize endpoint["+F.ToString(i)+"] "+tag, err)
|
||||
}
|
||||
}
|
||||
for i, inboundOptions := range options.Inbounds {
|
||||
var tag string
|
||||
if inboundOptions.Tag != "" {
|
||||
tag = inboundOptions.Tag
|
||||
} else {
|
||||
tag = F.ToString(i)
|
||||
}
|
||||
err = inboundManager.Create(ctx,
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||
tag,
|
||||
inboundOptions.Type,
|
||||
inboundOptions.Options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, common.NewError("initialize inbound[", i, "] ", tag, err)
|
||||
}
|
||||
}
|
||||
for i, outboundOptions := range options.Outbounds {
|
||||
var tag string
|
||||
if outboundOptions.Tag != "" {
|
||||
tag = outboundOptions.Tag
|
||||
} else {
|
||||
tag = F.ToString(i)
|
||||
}
|
||||
outboundCtx := ctx
|
||||
if tag != "" {
|
||||
// TODO: remove this
|
||||
outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{
|
||||
Outbound: tag,
|
||||
})
|
||||
}
|
||||
err = outboundManager.Create(
|
||||
outboundCtx,
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
||||
tag,
|
||||
outboundOptions.Type,
|
||||
outboundOptions.Options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, common.NewError("initialize outbound["+F.ToString(i)+"] "+tag, err)
|
||||
}
|
||||
}
|
||||
outboundManager.Initialize(sbCommon.Must1(
|
||||
direct.NewOutbound(
|
||||
ctx,
|
||||
router,
|
||||
logFactory.NewLogger("outbound/direct"),
|
||||
"direct",
|
||||
option.DirectOutboundOptions{},
|
||||
),
|
||||
))
|
||||
if platformInterface != nil {
|
||||
err = platformInterface.Initialize(networkManager)
|
||||
if err != nil {
|
||||
return nil, common.NewError("initialize platform interface", err)
|
||||
}
|
||||
}
|
||||
if connTracker == nil {
|
||||
connTracker = NewConnTracker()
|
||||
}
|
||||
router.SetTracker(connTracker)
|
||||
|
||||
var services []adapter.LifecycleService
|
||||
|
||||
if needCacheFile {
|
||||
cacheFile := cachefile.New(ctx, sbCommon.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||
services = append(services, cacheFile)
|
||||
}
|
||||
ntpOptions := sbCommon.PtrValueOrDefault(options.NTP)
|
||||
if ntpOptions.Enabled {
|
||||
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions)
|
||||
if err != nil {
|
||||
return nil, common.NewError(err, "create NTP service")
|
||||
}
|
||||
timeService := ntp.NewService(ntp.Options{
|
||||
Context: ctx,
|
||||
Dialer: ntpDialer,
|
||||
Logger: logFactory.NewLogger("ntp"),
|
||||
Server: ntpOptions.ServerOptions.Build(),
|
||||
Interval: time.Duration(ntpOptions.Interval),
|
||||
WriteToSystem: ntpOptions.WriteToSystem,
|
||||
})
|
||||
service.MustRegister[ntp.TimeService](ctx, timeService)
|
||||
services = append(services, adapter.NewLifecycleService(timeService, "ntp service"))
|
||||
}
|
||||
return &Box{
|
||||
network: networkManager,
|
||||
endpoint: endpointManager,
|
||||
inbound: inboundManager,
|
||||
outbound: outboundManager,
|
||||
connection: connectionManager,
|
||||
router: router,
|
||||
createdAt: createdAt,
|
||||
logFactory: logFactory,
|
||||
logger: logFactory.Logger(),
|
||||
services: services,
|
||||
connTracker: connTracker,
|
||||
done: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Box) PreStart() error {
|
||||
err := s.preStart()
|
||||
if err != nil {
|
||||
// TODO: remove catch error
|
||||
defer func() {
|
||||
v := recover()
|
||||
if v != nil {
|
||||
s.logger.Error(err.Error())
|
||||
s.logger.Error("panic on early close: " + fmt.Sprint(v))
|
||||
}
|
||||
}()
|
||||
s.Close()
|
||||
return err
|
||||
}
|
||||
s.logger.Info("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Box) Start() error {
|
||||
err := s.start()
|
||||
if err != nil {
|
||||
// TODO: remove catch error
|
||||
defer func() {
|
||||
v := recover()
|
||||
if v != nil {
|
||||
s.logger.Debug(err.Error())
|
||||
s.logger.Error("panic on early start: " + fmt.Sprint(v))
|
||||
}
|
||||
}()
|
||||
s.Close()
|
||||
return err
|
||||
}
|
||||
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Box) preStart() error {
|
||||
monitor := taskmonitor.New(s.logger, C.StartTimeout)
|
||||
monitor.Start("start logger")
|
||||
err := s.logFactory.Start()
|
||||
monitor.Finish()
|
||||
if err != nil {
|
||||
return common.NewError(err, "start logger")
|
||||
}
|
||||
err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStateInitialize, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.connection, s.router)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Box) start() error {
|
||||
err := s.preStart()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.StartNamed(adapter.StartStateStart, s.services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.inbound.Start(adapter.StartStateStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStateStart, s.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.connection, s.router, s.inbound, s.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.StartNamed(adapter.StartStatePostStart, s.services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStateStarted, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.StartNamed(adapter.StartStateStarted, s.services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Box) Close() error {
|
||||
select {
|
||||
case <-s.done:
|
||||
return os.ErrClosed
|
||||
default:
|
||||
close(s.done)
|
||||
}
|
||||
err := sbCommon.Close(
|
||||
s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.network,
|
||||
)
|
||||
for _, lifecycleService := range s.services {
|
||||
err1 := lifecycleService.Close()
|
||||
if err1 != nil {
|
||||
s.logger.Debug(lifecycleService.Name(), " close error: ", err1)
|
||||
}
|
||||
}
|
||||
err1 := s.logFactory.Close()
|
||||
if err1 != nil {
|
||||
s.logger.Debug("logger close error: ", err1)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Box) Uptime() uint32 {
|
||||
return uint32(time.Now().Sub(s.createdAt).Seconds())
|
||||
}
|
||||
|
||||
func (s *Box) Network() adapter.NetworkManager {
|
||||
return s.network
|
||||
}
|
||||
|
||||
func (s *Box) Router() adapter.Router {
|
||||
return s.router
|
||||
}
|
||||
|
||||
func (s *Box) Inbound() adapter.InboundManager {
|
||||
return s.inbound
|
||||
}
|
||||
|
||||
func (s *Box) Outbound() adapter.OutboundManager {
|
||||
return s.outbound
|
||||
}
|
||||
|
||||
func (s *Box) Endpoint() adapter.EndpointManager {
|
||||
return s.endpoint
|
||||
}
|
||||
|
||||
func (s *Box) ConnTracker() *ConnTracker {
|
||||
return s.connTracker
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"s-ui/database/model"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
"github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type Counter struct {
|
||||
read *atomic.Int64
|
||||
write *atomic.Int64
|
||||
}
|
||||
|
||||
type ConnTracker struct {
|
||||
access sync.Mutex
|
||||
createdAt time.Time
|
||||
inbounds map[string]Counter
|
||||
outbounds map[string]Counter
|
||||
users map[string]Counter
|
||||
}
|
||||
|
||||
func NewConnTracker() *ConnTracker {
|
||||
return &ConnTracker{
|
||||
createdAt: time.Now(),
|
||||
inbounds: make(map[string]Counter),
|
||||
outbounds: make(map[string]Counter),
|
||||
users: make(map[string]Counter),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ConnTracker) getReadCounters(inbound string, outbound string, user string) ([]*atomic.Int64, []*atomic.Int64) {
|
||||
var readCounter []*atomic.Int64
|
||||
var writeCounter []*atomic.Int64
|
||||
c.access.Lock()
|
||||
if inbound != "" {
|
||||
readCounter = append(readCounter, c.loadOrCreateCounter(&c.inbounds, inbound).read)
|
||||
writeCounter = append(writeCounter, c.inbounds[inbound].write)
|
||||
}
|
||||
if outbound != "" {
|
||||
readCounter = append(readCounter, c.loadOrCreateCounter(&c.outbounds, outbound).read)
|
||||
writeCounter = append(writeCounter, c.outbounds[outbound].write)
|
||||
}
|
||||
if user != "" {
|
||||
readCounter = append(readCounter, c.loadOrCreateCounter(&c.users, user).read)
|
||||
writeCounter = append(writeCounter, c.users[user].write)
|
||||
}
|
||||
c.access.Unlock()
|
||||
return readCounter, writeCounter
|
||||
}
|
||||
|
||||
func (c *ConnTracker) loadOrCreateCounter(obj *map[string]Counter, name string) Counter {
|
||||
counter, loaded := (*obj)[name]
|
||||
if loaded {
|
||||
return counter
|
||||
}
|
||||
counter = Counter{read: &atomic.Int64{}, write: &atomic.Int64{}}
|
||||
(*obj)[name] = counter
|
||||
return counter
|
||||
}
|
||||
|
||||
func (c *ConnTracker) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn {
|
||||
readCounter, writeCounter := c.getReadCounters(metadata.Inbound, matchOutbound.Tag(), metadata.User)
|
||||
return bufio.NewInt64CounterConn(conn, readCounter, writeCounter)
|
||||
}
|
||||
|
||||
func (c *ConnTracker) RoutedPacketConnection(ctx context.Context, conn network.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) network.PacketConn {
|
||||
readCounter, writeCounter := c.getReadCounters(metadata.Inbound, matchOutbound.Tag(), metadata.User)
|
||||
return bufio.NewInt64CounterPacketConn(conn, readCounter, writeCounter)
|
||||
}
|
||||
|
||||
func (c *ConnTracker) GetStats() *[]model.Stats {
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
|
||||
dt := time.Now().Unix()
|
||||
|
||||
s := []model.Stats{}
|
||||
for inbound, counter := range c.inbounds {
|
||||
down := counter.write.Swap(0)
|
||||
up := counter.read.Swap(0)
|
||||
if down > 0 || up > 0 {
|
||||
s = append(s, model.Stats{
|
||||
DateTime: dt,
|
||||
Resource: "inbound",
|
||||
Tag: inbound,
|
||||
Direction: false,
|
||||
Traffic: down,
|
||||
}, model.Stats{
|
||||
DateTime: dt,
|
||||
Resource: "inbound",
|
||||
Tag: inbound,
|
||||
Direction: true,
|
||||
Traffic: up,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for outbound, counter := range c.outbounds {
|
||||
down := counter.write.Swap(0)
|
||||
up := counter.read.Swap(0)
|
||||
if down > 0 || up > 0 {
|
||||
s = append(s, model.Stats{
|
||||
DateTime: dt,
|
||||
Resource: "outbound",
|
||||
Tag: outbound,
|
||||
Direction: false,
|
||||
Traffic: down,
|
||||
}, model.Stats{
|
||||
DateTime: dt,
|
||||
Resource: "outbound",
|
||||
Tag: outbound,
|
||||
Direction: true,
|
||||
Traffic: up,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for user, counter := range c.users {
|
||||
down := counter.write.Swap(0)
|
||||
up := counter.read.Swap(0)
|
||||
if down > 0 || up > 0 {
|
||||
s = append(s, model.Stats{
|
||||
DateTime: dt,
|
||||
Resource: "user",
|
||||
Tag: user,
|
||||
Direction: false,
|
||||
Traffic: down,
|
||||
}, model.Stats{
|
||||
DateTime: dt,
|
||||
Resource: "user",
|
||||
Tag: user,
|
||||
Direction: true,
|
||||
Traffic: up,
|
||||
})
|
||||
}
|
||||
}
|
||||
return &s
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"s-ui/logger"
|
||||
"s-ui/util/common"
|
||||
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
func (c *Core) AddInbound(config []byte) error {
|
||||
if !c.isRunning {
|
||||
return common.NewError("sing-box is not running")
|
||||
}
|
||||
var err error
|
||||
var inbound_config option.Inbound
|
||||
err = inbound_config.UnmarshalJSONContext(globalCtx, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = inbound_manager.Create(
|
||||
globalCtx,
|
||||
router,
|
||||
factory.NewLogger("inbound/"+inbound_config.Type+"["+inbound_config.Tag+"]"),
|
||||
inbound_config.Tag,
|
||||
inbound_config.Type,
|
||||
inbound_config.Options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) RemoveInbound(tag string) error {
|
||||
if !c.isRunning {
|
||||
return common.NewError("sing-box is not running")
|
||||
}
|
||||
logger.Info("remove inbound: ", tag)
|
||||
return inbound_manager.Remove(tag)
|
||||
}
|
||||
|
||||
func (c *Core) AddOutbound(config []byte) error {
|
||||
if !c.isRunning {
|
||||
return common.NewError("sing-box is not running")
|
||||
}
|
||||
var err error
|
||||
var outbound_config option.Outbound
|
||||
|
||||
err = outbound_config.UnmarshalJSONContext(globalCtx, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = outbound_manager.Create(
|
||||
globalCtx,
|
||||
router,
|
||||
factory.NewLogger("outbound/"+outbound_config.Type+"["+outbound_config.Tag+"]"),
|
||||
outbound_config.Tag,
|
||||
outbound_config.Type,
|
||||
outbound_config.Options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) RemoveOutbound(tag string) error {
|
||||
if !c.isRunning {
|
||||
return common.NewError("sing-box is not running")
|
||||
}
|
||||
logger.Info("remove outbound: ", tag)
|
||||
return outbound_manager.Remove(tag)
|
||||
}
|
||||
|
||||
func (c *Core) AddEndpoint(config []byte) error {
|
||||
if !c.isRunning {
|
||||
return common.NewError("sing-box is not running")
|
||||
}
|
||||
var err error
|
||||
var endpoint_config option.Endpoint
|
||||
|
||||
err = endpoint_config.UnmarshalJSONContext(globalCtx, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = endpoint_manager.Create(
|
||||
globalCtx,
|
||||
router,
|
||||
factory.NewLogger("endpoint/"+endpoint_config.Type+"["+endpoint_config.Tag+"]"),
|
||||
endpoint_config.Tag,
|
||||
endpoint_config.Type,
|
||||
endpoint_config.Options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) RemoveEndpoint(tag string) error {
|
||||
if !c.isRunning {
|
||||
return common.NewError("sing-box is not running")
|
||||
}
|
||||
logger.Info("remove endpoint: ", tag)
|
||||
return endpoint_manager.Remove(tag)
|
||||
}
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
suiLog "s-ui/logger"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/observable"
|
||||
"github.com/sagernet/sing/service/filemanager"
|
||||
)
|
||||
|
||||
type PlatformWriter struct{}
|
||||
|
||||
func (p PlatformWriter) DisableColors() bool {
|
||||
return true
|
||||
}
|
||||
func (p PlatformWriter) WriteMessage(level log.Level, message string) {
|
||||
switch level {
|
||||
case log.LevelInfo:
|
||||
suiLog.Info(message)
|
||||
case log.LevelWarn:
|
||||
suiLog.Warning(message)
|
||||
case log.LevelPanic:
|
||||
case log.LevelFatal:
|
||||
case log.LevelError:
|
||||
suiLog.Error(message)
|
||||
default:
|
||||
suiLog.Debug(message)
|
||||
}
|
||||
}
|
||||
|
||||
func NewFactory(options log.Options) (log.Factory, error) {
|
||||
logOptions := options.Options
|
||||
|
||||
if logOptions.Disabled {
|
||||
return log.NewNOPFactory(), nil
|
||||
}
|
||||
|
||||
var logWriter io.Writer
|
||||
var logFilePath string
|
||||
|
||||
switch logOptions.Output {
|
||||
case "":
|
||||
logWriter = options.DefaultWriter
|
||||
if logWriter == nil {
|
||||
logWriter = os.Stderr
|
||||
}
|
||||
case "stderr":
|
||||
logWriter = os.Stderr
|
||||
case "stdout":
|
||||
logWriter = os.Stdout
|
||||
default:
|
||||
logFilePath = logOptions.Output
|
||||
}
|
||||
logFormatter := log.Formatter{
|
||||
BaseTime: options.BaseTime,
|
||||
DisableColors: logOptions.DisableColor || logFilePath != "",
|
||||
DisableTimestamp: !logOptions.Timestamp && logFilePath != "",
|
||||
FullTimestamp: logOptions.Timestamp,
|
||||
TimestampFormat: "-0700 2006-01-02 15:04:05",
|
||||
}
|
||||
factory := NewDefaultFactory(
|
||||
options.Context,
|
||||
logFormatter,
|
||||
logWriter,
|
||||
logFilePath,
|
||||
)
|
||||
if logOptions.Level != "" {
|
||||
logLevel, err := log.ParseLevel(logOptions.Level)
|
||||
if err != nil {
|
||||
return nil, common.Error("parse log level", err)
|
||||
}
|
||||
factory.SetLevel(logLevel)
|
||||
} else {
|
||||
factory.SetLevel(log.LevelTrace)
|
||||
}
|
||||
return factory, nil
|
||||
}
|
||||
|
||||
var _ log.Factory = (*defaultFactory)(nil)
|
||||
|
||||
type defaultFactory struct {
|
||||
ctx context.Context
|
||||
formatter log.Formatter
|
||||
writer io.Writer
|
||||
file *os.File
|
||||
filePath string
|
||||
level log.Level
|
||||
subscriber *observable.Subscriber[log.Entry]
|
||||
observer *observable.Observer[log.Entry]
|
||||
}
|
||||
|
||||
func NewDefaultFactory(
|
||||
ctx context.Context,
|
||||
formatter log.Formatter,
|
||||
writer io.Writer,
|
||||
filePath string,
|
||||
) log.ObservableFactory {
|
||||
factory := &defaultFactory{
|
||||
ctx: ctx,
|
||||
formatter: formatter,
|
||||
writer: writer,
|
||||
filePath: filePath,
|
||||
level: log.LevelTrace,
|
||||
subscriber: observable.NewSubscriber[log.Entry](128),
|
||||
}
|
||||
return factory
|
||||
}
|
||||
|
||||
func (f *defaultFactory) Start() error {
|
||||
if f.filePath != "" {
|
||||
logFile, err := filemanager.OpenFile(f.ctx, f.filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.writer = logFile
|
||||
f.file = logFile
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *defaultFactory) Close() error {
|
||||
return common.Close(
|
||||
common.PtrOrNil(f.file),
|
||||
f.subscriber,
|
||||
)
|
||||
}
|
||||
|
||||
func (f *defaultFactory) Level() log.Level {
|
||||
return f.level
|
||||
}
|
||||
|
||||
func (f *defaultFactory) SetLevel(level log.Level) {
|
||||
f.level = level
|
||||
}
|
||||
|
||||
func (f *defaultFactory) Logger() log.ContextLogger {
|
||||
return f.NewLogger("")
|
||||
}
|
||||
|
||||
func (f *defaultFactory) NewLogger(tag string) log.ContextLogger {
|
||||
return &observableLogger{f, tag}
|
||||
}
|
||||
|
||||
func (f *defaultFactory) Subscribe() (subscription observable.Subscription[log.Entry], done <-chan struct{}, err error) {
|
||||
return f.observer.Subscribe()
|
||||
}
|
||||
|
||||
func (f *defaultFactory) UnSubscribe(sub observable.Subscription[log.Entry]) {
|
||||
f.observer.UnSubscribe(sub)
|
||||
}
|
||||
|
||||
type observableLogger struct {
|
||||
*defaultFactory
|
||||
tag string
|
||||
}
|
||||
|
||||
func (l *observableLogger) Log(ctx context.Context, level log.Level, args []any) {
|
||||
level = log.OverrideLevelFromContext(level, ctx)
|
||||
if level > l.level {
|
||||
return
|
||||
}
|
||||
msg := F.ToString(args...)
|
||||
switch level {
|
||||
case log.LevelInfo:
|
||||
suiLog.Info(l.tag, msg)
|
||||
case log.LevelWarn:
|
||||
suiLog.Warning(l.tag, msg)
|
||||
case log.LevelPanic:
|
||||
case log.LevelFatal:
|
||||
case log.LevelError:
|
||||
suiLog.Error(l.tag, msg)
|
||||
default:
|
||||
suiLog.Debug(l.tag, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *observableLogger) Trace(args ...any) {
|
||||
l.TraceContext(context.Background(), args...)
|
||||
}
|
||||
|
||||
func (l *observableLogger) Debug(args ...any) {
|
||||
l.DebugContext(context.Background(), args...)
|
||||
}
|
||||
|
||||
func (l *observableLogger) Info(args ...any) {
|
||||
l.InfoContext(context.Background(), args...)
|
||||
}
|
||||
|
||||
func (l *observableLogger) Warn(args ...any) {
|
||||
l.WarnContext(context.Background(), args...)
|
||||
}
|
||||
|
||||
func (l *observableLogger) Error(args ...any) {
|
||||
l.ErrorContext(context.Background(), args...)
|
||||
}
|
||||
|
||||
func (l *observableLogger) Fatal(args ...any) {
|
||||
l.FatalContext(context.Background(), args...)
|
||||
}
|
||||
|
||||
func (l *observableLogger) Panic(args ...any) {
|
||||
l.PanicContext(context.Background(), args...)
|
||||
}
|
||||
|
||||
func (l *observableLogger) TraceContext(ctx context.Context, args ...any) {
|
||||
l.Log(ctx, log.LevelTrace, args)
|
||||
}
|
||||
|
||||
func (l *observableLogger) DebugContext(ctx context.Context, args ...any) {
|
||||
l.Log(ctx, log.LevelDebug, args)
|
||||
}
|
||||
|
||||
func (l *observableLogger) InfoContext(ctx context.Context, args ...any) {
|
||||
l.Log(ctx, log.LevelInfo, args)
|
||||
}
|
||||
|
||||
func (l *observableLogger) WarnContext(ctx context.Context, args ...any) {
|
||||
l.Log(ctx, log.LevelWarn, args)
|
||||
}
|
||||
|
||||
func (l *observableLogger) ErrorContext(ctx context.Context, args ...any) {
|
||||
l.Log(ctx, log.LevelError, args)
|
||||
}
|
||||
|
||||
func (l *observableLogger) FatalContext(ctx context.Context, args ...any) {
|
||||
l.Log(ctx, log.LevelFatal, args)
|
||||
}
|
||||
|
||||
func (l *observableLogger) PanicContext(ctx context.Context, args ...any) {
|
||||
l.Log(ctx, log.LevelPanic, args)
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"s-ui/logger"
|
||||
|
||||
sb "github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
_ "github.com/sagernet/sing-box/experimental/clashapi"
|
||||
_ "github.com/sagernet/sing-box/experimental/v2rayapi"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
_ "github.com/sagernet/sing-box/transport/v2rayquic"
|
||||
_ "github.com/sagernet/sing-dns/quic"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
var (
|
||||
globalCtx context.Context
|
||||
inbound_manager adapter.InboundManager
|
||||
outbound_manager adapter.OutboundManager
|
||||
endpoint_manager adapter.EndpointManager
|
||||
router adapter.Router
|
||||
connTracker *ConnTracker
|
||||
factory log.Factory
|
||||
)
|
||||
|
||||
type Core struct {
|
||||
isRunning bool
|
||||
instance *Box
|
||||
}
|
||||
|
||||
func NewCore() *Core {
|
||||
globalCtx = context.Background()
|
||||
globalCtx = sb.Context(globalCtx, inboundRegistry(), outboundRegistry(), EndpointRegistry())
|
||||
return &Core{
|
||||
isRunning: false,
|
||||
instance: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Core) GetCtx() context.Context {
|
||||
return globalCtx
|
||||
}
|
||||
|
||||
func (c *Core) GetInstance() *Box {
|
||||
return c.instance
|
||||
}
|
||||
|
||||
func (c *Core) Start(sbConfig []byte) error {
|
||||
var opt option.Options
|
||||
err := opt.UnmarshalJSONContext(globalCtx, sbConfig)
|
||||
if err != nil {
|
||||
logger.Error("Unmarshal config err:", err.Error())
|
||||
}
|
||||
|
||||
c.instance, err = NewBox(Options{
|
||||
Context: globalCtx,
|
||||
Options: opt,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.instance.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
globalCtx = service.ContextWith(globalCtx, c)
|
||||
inbound_manager = service.FromContext[adapter.InboundManager](globalCtx)
|
||||
outbound_manager = service.FromContext[adapter.OutboundManager](globalCtx)
|
||||
endpoint_manager = service.FromContext[adapter.EndpointManager](globalCtx)
|
||||
router = service.FromContext[adapter.Router](globalCtx)
|
||||
|
||||
c.isRunning = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) Stop() error {
|
||||
if c.isRunning {
|
||||
c.isRunning = false
|
||||
return c.instance.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) IsRunning() bool {
|
||||
return c.isRunning
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||
"github.com/sagernet/sing-box/adapter/inbound"
|
||||
"github.com/sagernet/sing-box/adapter/outbound"
|
||||
"github.com/sagernet/sing-box/protocol/block"
|
||||
"github.com/sagernet/sing-box/protocol/direct"
|
||||
"github.com/sagernet/sing-box/protocol/dns"
|
||||
"github.com/sagernet/sing-box/protocol/group"
|
||||
"github.com/sagernet/sing-box/protocol/http"
|
||||
"github.com/sagernet/sing-box/protocol/hysteria"
|
||||
"github.com/sagernet/sing-box/protocol/hysteria2"
|
||||
"github.com/sagernet/sing-box/protocol/mixed"
|
||||
"github.com/sagernet/sing-box/protocol/naive"
|
||||
_ "github.com/sagernet/sing-box/protocol/naive/quic"
|
||||
"github.com/sagernet/sing-box/protocol/redirect"
|
||||
"github.com/sagernet/sing-box/protocol/shadowsocks"
|
||||
"github.com/sagernet/sing-box/protocol/shadowtls"
|
||||
"github.com/sagernet/sing-box/protocol/socks"
|
||||
"github.com/sagernet/sing-box/protocol/ssh"
|
||||
"github.com/sagernet/sing-box/protocol/tor"
|
||||
"github.com/sagernet/sing-box/protocol/trojan"
|
||||
"github.com/sagernet/sing-box/protocol/tuic"
|
||||
"github.com/sagernet/sing-box/protocol/tun"
|
||||
"github.com/sagernet/sing-box/protocol/vless"
|
||||
"github.com/sagernet/sing-box/protocol/vmess"
|
||||
"github.com/sagernet/sing-box/protocol/wireguard"
|
||||
_ "github.com/sagernet/sing-box/transport/v2rayquic"
|
||||
_ "github.com/sagernet/sing-dns/quic"
|
||||
)
|
||||
|
||||
func inboundRegistry() *inbound.Registry {
|
||||
registry := inbound.NewRegistry()
|
||||
|
||||
tun.RegisterInbound(registry)
|
||||
redirect.RegisterRedirect(registry)
|
||||
redirect.RegisterTProxy(registry)
|
||||
direct.RegisterInbound(registry)
|
||||
|
||||
socks.RegisterInbound(registry)
|
||||
http.RegisterInbound(registry)
|
||||
mixed.RegisterInbound(registry)
|
||||
|
||||
shadowsocks.RegisterInbound(registry)
|
||||
vmess.RegisterInbound(registry)
|
||||
trojan.RegisterInbound(registry)
|
||||
naive.RegisterInbound(registry)
|
||||
shadowtls.RegisterInbound(registry)
|
||||
vless.RegisterInbound(registry)
|
||||
|
||||
hysteria.RegisterInbound(registry)
|
||||
tuic.RegisterInbound(registry)
|
||||
hysteria2.RegisterInbound(registry)
|
||||
|
||||
return registry
|
||||
}
|
||||
|
||||
func outboundRegistry() *outbound.Registry {
|
||||
registry := outbound.NewRegistry()
|
||||
|
||||
direct.RegisterOutbound(registry)
|
||||
|
||||
block.RegisterOutbound(registry)
|
||||
dns.RegisterOutbound(registry)
|
||||
|
||||
group.RegisterSelector(registry)
|
||||
group.RegisterURLTest(registry)
|
||||
|
||||
socks.RegisterOutbound(registry)
|
||||
http.RegisterOutbound(registry)
|
||||
shadowsocks.RegisterOutbound(registry)
|
||||
vmess.RegisterOutbound(registry)
|
||||
trojan.RegisterOutbound(registry)
|
||||
tor.RegisterOutbound(registry)
|
||||
ssh.RegisterOutbound(registry)
|
||||
shadowtls.RegisterOutbound(registry)
|
||||
vless.RegisterOutbound(registry)
|
||||
|
||||
hysteria.RegisterOutbound(registry)
|
||||
tuic.RegisterOutbound(registry)
|
||||
hysteria2.RegisterOutbound(registry)
|
||||
wireguard.RegisterOutbound(registry)
|
||||
|
||||
return registry
|
||||
}
|
||||
|
||||
func EndpointRegistry() *endpoint.Registry {
|
||||
registry := endpoint.NewRegistry()
|
||||
|
||||
wireguard.RegisterEndpoint(registry)
|
||||
|
||||
return registry
|
||||
}
|
||||
Reference in New Issue
Block a user