mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-22 00:21:30 +00:00
platform: Add group interface
This commit is contained in:
parent
5ad0ea2b5a
commit
9c8565cf21
4
Makefile
4
Makefile
|
@ -89,8 +89,8 @@ lib:
|
||||||
|
|
||||||
lib_install:
|
lib_install:
|
||||||
go get -v -d
|
go get -v -d
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20230413023804-244d7ff07035
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20230701084532-493ee2e45182
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230413023804-244d7ff07035
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230701084532-493ee2e45182
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin dist sing-box
|
rm -rf bin dist sing-box
|
||||||
|
|
|
@ -31,6 +31,7 @@ type Tracker interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundGroup interface {
|
type OutboundGroup interface {
|
||||||
|
Outbound
|
||||||
Now() string
|
Now() string
|
||||||
All() []string
|
All() []string
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,6 @@ type PreStarter interface {
|
||||||
PreStart() error
|
PreStart() error
|
||||||
}
|
}
|
||||||
|
|
||||||
func PreStart(starter any) error {
|
type PostStarter interface {
|
||||||
if preService, ok := starter.(PreStarter); ok {
|
PostStart() error
|
||||||
err := preService.PreStart()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
25
box.go
25
box.go
|
@ -211,10 +211,12 @@ func (s *Box) Start() error {
|
||||||
|
|
||||||
func (s *Box) preStart() error {
|
func (s *Box) preStart() error {
|
||||||
for serviceName, service := range s.preServices {
|
for serviceName, service := range s.preServices {
|
||||||
s.logger.Trace("pre-start ", serviceName)
|
if preService, isPreService := service.(adapter.PreStarter); isPreService {
|
||||||
err := adapter.PreStart(service)
|
s.logger.Trace("pre-start ", serviceName)
|
||||||
if err != nil {
|
err := preService.PreStart()
|
||||||
return E.Cause(err, "pre-starting ", serviceName)
|
if err != nil {
|
||||||
|
return E.Cause(err, "pre-starting ", serviceName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err := s.startOutbounds()
|
err := s.startOutbounds()
|
||||||
|
@ -249,13 +251,26 @@ func (s *Box) start() error {
|
||||||
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) postStart() error {
|
||||||
for serviceName, service := range s.postServices {
|
for serviceName, service := range s.postServices {
|
||||||
s.logger.Trace("starting ", service)
|
s.logger.Trace("starting ", service)
|
||||||
err = service.Start()
|
err := service.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start ", serviceName)
|
return E.Cause(err, "start ", serviceName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for serviceName, service := range s.outbounds {
|
||||||
|
if lateService, isLateService := service.(adapter.PostStarter); isLateService {
|
||||||
|
s.logger.Trace("post-starting ", service)
|
||||||
|
err := lateService.PostStart()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "post-start ", serviceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -133,7 +133,7 @@ func buildiOS() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
copyPath := filepath.Join("..", "sing-box-for-ios")
|
copyPath := filepath.Join("..", "sing-box-for-apple")
|
||||||
if rw.FileExists(copyPath) {
|
if rw.FileExists(copyPath) {
|
||||||
targetDir := filepath.Join(copyPath, "Libbox.xcframework")
|
targetDir := filepath.Join(copyPath, "Libbox.xcframework")
|
||||||
targetDir, _ = filepath.Abs(targetDir)
|
targetDir, _ = filepath.Abs(targetDir)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
type History struct {
|
type History struct {
|
||||||
|
@ -20,6 +21,7 @@ type History struct {
|
||||||
type HistoryStorage struct {
|
type HistoryStorage struct {
|
||||||
access sync.RWMutex
|
access sync.RWMutex
|
||||||
delayHistory map[string]*History
|
delayHistory map[string]*History
|
||||||
|
callbacks list.List[func()]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHistoryStorage() *HistoryStorage {
|
func NewHistoryStorage() *HistoryStorage {
|
||||||
|
@ -28,6 +30,18 @@ func NewHistoryStorage() *HistoryStorage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *HistoryStorage) AddListener(listener func()) *list.Element[func()] {
|
||||||
|
s.access.Lock()
|
||||||
|
defer s.access.Unlock()
|
||||||
|
return s.callbacks.PushBack(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HistoryStorage) RemoveListener(element *list.Element[func()]) {
|
||||||
|
s.access.Lock()
|
||||||
|
defer s.access.Unlock()
|
||||||
|
s.callbacks.Remove(element)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *HistoryStorage) LoadURLTestHistory(tag string) *History {
|
func (s *HistoryStorage) LoadURLTestHistory(tag string) *History {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -39,14 +53,24 @@ func (s *HistoryStorage) LoadURLTestHistory(tag string) *History {
|
||||||
|
|
||||||
func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
|
func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
|
||||||
s.access.Lock()
|
s.access.Lock()
|
||||||
defer s.access.Unlock()
|
|
||||||
delete(s.delayHistory, tag)
|
delete(s.delayHistory, tag)
|
||||||
|
s.access.Unlock()
|
||||||
|
s.notifyUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) {
|
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) {
|
||||||
s.access.Lock()
|
s.access.Lock()
|
||||||
defer s.access.Unlock()
|
|
||||||
s.delayHistory[tag] = history
|
s.delayHistory[tag] = history
|
||||||
|
s.access.Unlock()
|
||||||
|
s.notifyUpdated()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HistoryStorage) notifyUpdated() {
|
||||||
|
s.access.RLock()
|
||||||
|
defer s.access.RUnlock()
|
||||||
|
for element := s.callbacks.Front(); element != nil; element = element.Next() {
|
||||||
|
element.Value()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
|
func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
"github.com/sagernet/sing/service/filemanager"
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
"github.com/sagernet/websocket"
|
"github.com/sagernet/websocket"
|
||||||
|
|
||||||
|
@ -68,13 +69,16 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
|
||||||
Handler: chiRouter,
|
Handler: chiRouter,
|
||||||
},
|
},
|
||||||
trafficManager: trafficManager,
|
trafficManager: trafficManager,
|
||||||
urlTestHistory: urltest.NewHistoryStorage(),
|
|
||||||
mode: strings.ToLower(options.DefaultMode),
|
mode: strings.ToLower(options.DefaultMode),
|
||||||
storeSelected: options.StoreSelected,
|
storeSelected: options.StoreSelected,
|
||||||
storeFakeIP: options.StoreFakeIP,
|
storeFakeIP: options.StoreFakeIP,
|
||||||
externalUIDownloadURL: options.ExternalUIDownloadURL,
|
externalUIDownloadURL: options.ExternalUIDownloadURL,
|
||||||
externalUIDownloadDetour: options.ExternalUIDownloadDetour,
|
externalUIDownloadDetour: options.ExternalUIDownloadDetour,
|
||||||
}
|
}
|
||||||
|
server.urlTestHistory = service.PtrFromContext[urltest.HistoryStorage](ctx)
|
||||||
|
if server.urlTestHistory == nil {
|
||||||
|
server.urlTestHistory = urltest.NewHistoryStorage()
|
||||||
|
}
|
||||||
if server.mode == "" {
|
if server.mode == "" {
|
||||||
server.mode = "rule"
|
server.mode = "rule"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,9 @@ package libbox
|
||||||
const (
|
const (
|
||||||
CommandLog int32 = iota
|
CommandLog int32 = iota
|
||||||
CommandStatus
|
CommandStatus
|
||||||
CommandServiceStop
|
|
||||||
CommandServiceReload
|
CommandServiceReload
|
||||||
CommandCloseConnections
|
CommandCloseConnections
|
||||||
|
CommandGroup
|
||||||
|
CommandSelectOutbound
|
||||||
|
CommandURLTest
|
||||||
)
|
)
|
||||||
|
|
|
@ -26,6 +26,13 @@ type CommandClientHandler interface {
|
||||||
Disconnected(message string)
|
Disconnected(message string)
|
||||||
WriteLog(message string)
|
WriteLog(message string)
|
||||||
WriteStatus(message *StatusMessage)
|
WriteStatus(message *StatusMessage)
|
||||||
|
WriteGroups(message OutboundGroupIterator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStandaloneCommandClient(sharedDirectory string) *CommandClient {
|
||||||
|
return &CommandClient{
|
||||||
|
sharedDirectory: sharedDirectory,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCommandClient(sharedDirectory string, handler CommandClientHandler, options *CommandClientOptions) *CommandClient {
|
func NewCommandClient(sharedDirectory string, handler CommandClientHandler, options *CommandClientOptions) *CommandClient {
|
||||||
|
@ -36,16 +43,16 @@ func NewCommandClient(sharedDirectory string, handler CommandClientHandler, opti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func clientConnect(sharedDirectory string) (net.Conn, error) {
|
func (c *CommandClient) directConnect() (net.Conn, error) {
|
||||||
return net.DialUnix("unix", nil, &net.UnixAddr{
|
return net.DialUnix("unix", nil, &net.UnixAddr{
|
||||||
Name: filepath.Join(sharedDirectory, "command.sock"),
|
Name: filepath.Join(c.sharedDirectory, "command.sock"),
|
||||||
Net: "unix",
|
Net: "unix",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CommandClient) Connect() error {
|
func (c *CommandClient) Connect() error {
|
||||||
common.Close(c.conn)
|
common.Close(c.conn)
|
||||||
conn, err := clientConnect(c.sharedDirectory)
|
conn, err := c.directConnect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -65,6 +72,13 @@ func (c *CommandClient) Connect() error {
|
||||||
}
|
}
|
||||||
c.handler.Connected()
|
c.handler.Connected()
|
||||||
go c.handleStatusConn(conn)
|
go c.handleStatusConn(conn)
|
||||||
|
case CommandGroup:
|
||||||
|
err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "write interval")
|
||||||
|
}
|
||||||
|
c.handler.Connected()
|
||||||
|
go c.handleGroupConn(conn)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,8 @@ import (
|
||||||
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ClientCloseConnections(sharedDirectory string) error {
|
func (c *CommandClient) CloseConnections() error {
|
||||||
conn, err := clientConnect(sharedDirectory)
|
conn, err := c.directConnect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
228
experimental/libbox/command_group.go
Normal file
228
experimental/libbox/command_group.go
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
|
"github.com/sagernet/sing-box/outbound"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OutboundGroup struct {
|
||||||
|
Tag string
|
||||||
|
Type string
|
||||||
|
Selectable bool
|
||||||
|
Selected string
|
||||||
|
items []*OutboundGroupItem
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *OutboundGroup) GetItems() OutboundGroupItemIterator {
|
||||||
|
return newIterator(g.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutboundGroupIterator interface {
|
||||||
|
Next() *OutboundGroup
|
||||||
|
HasNext() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutboundGroupItem struct {
|
||||||
|
Tag string
|
||||||
|
Type string
|
||||||
|
URLTestTime int64
|
||||||
|
URLTestDelay int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutboundGroupItemIterator interface {
|
||||||
|
Next() *OutboundGroupItem
|
||||||
|
HasNext() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandClient) handleGroupConn(conn net.Conn) {
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
groups, err := readGroups(conn)
|
||||||
|
if err != nil {
|
||||||
|
c.handler.Disconnected(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.handler.WriteGroups(groups)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CommandServer) handleGroupConn(conn net.Conn) error {
|
||||||
|
defer conn.Close()
|
||||||
|
ctx := connKeepAlive(conn)
|
||||||
|
for {
|
||||||
|
service := s.service
|
||||||
|
if service != nil {
|
||||||
|
err := writeGroups(conn, service)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := binary.Write(conn, binary.BigEndian, uint16(0))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-s.urlTestUpdate:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readGroups(reader io.Reader) (OutboundGroupIterator, error) {
|
||||||
|
var groupLength uint16
|
||||||
|
err := binary.Read(reader, binary.BigEndian, &groupLength)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
groups := make([]*OutboundGroup, 0, groupLength)
|
||||||
|
for i := 0; i < int(groupLength); i++ {
|
||||||
|
var group OutboundGroup
|
||||||
|
group.Tag, err = rw.ReadVString(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
group.Type, err = rw.ReadVString(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &group.Selectable)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
group.Selected, err = rw.ReadVString(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemLength uint16
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &itemLength)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
group.items = make([]*OutboundGroupItem, itemLength)
|
||||||
|
for j := 0; j < int(itemLength); j++ {
|
||||||
|
var item OutboundGroupItem
|
||||||
|
item.Tag, err = rw.ReadVString(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
item.Type, err = rw.ReadVString(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &item.URLTestTime)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &item.URLTestDelay)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
group.items[j] = &item
|
||||||
|
}
|
||||||
|
groups = append(groups, &group)
|
||||||
|
}
|
||||||
|
return newIterator(groups), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeGroups(writer io.Writer, boxService *BoxService) error {
|
||||||
|
historyStorage := service.PtrFromContext[urltest.HistoryStorage](boxService.ctx)
|
||||||
|
|
||||||
|
outbounds := boxService.instance.Router().Outbounds()
|
||||||
|
var iGroups []adapter.OutboundGroup
|
||||||
|
for _, it := range outbounds {
|
||||||
|
if group, isGroup := it.(adapter.OutboundGroup); isGroup {
|
||||||
|
iGroups = append(iGroups, group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var groups []OutboundGroup
|
||||||
|
for _, iGroup := range iGroups {
|
||||||
|
var group OutboundGroup
|
||||||
|
group.Tag = iGroup.Tag()
|
||||||
|
group.Type = iGroup.Type()
|
||||||
|
_, group.Selectable = iGroup.(*outbound.Selector)
|
||||||
|
group.Selected = iGroup.Now()
|
||||||
|
|
||||||
|
for _, itemTag := range iGroup.All() {
|
||||||
|
itemOutbound, isLoaded := boxService.instance.Router().Outbound(itemTag)
|
||||||
|
if !isLoaded {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var item OutboundGroupItem
|
||||||
|
item.Tag = itemTag
|
||||||
|
item.Type = itemOutbound.Type()
|
||||||
|
if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(itemOutbound)); history != nil {
|
||||||
|
item.URLTestTime = history.Time.Unix()
|
||||||
|
item.URLTestDelay = int32(history.Delay)
|
||||||
|
}
|
||||||
|
group.items = append(group.items, &item)
|
||||||
|
}
|
||||||
|
groups = append(groups, group)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := binary.Write(writer, binary.BigEndian, uint16(len(groups)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, group := range groups {
|
||||||
|
err = rw.WriteVString(writer, group.Tag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = rw.WriteVString(writer, group.Type)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.BigEndian, group.Selectable)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = rw.WriteVString(writer, group.Selected)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.BigEndian, uint16(len(group.items)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, item := range group.items {
|
||||||
|
err = rw.WriteVString(writer, item.Tag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = rw.WriteVString(writer, item.Type)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.BigEndian, item.URLTestTime)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.BigEndian, item.URLTestDelay)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ func (s *CommandServer) WriteMessage(message string) {
|
||||||
s.subscriber.Emit(message)
|
s.subscriber.Emit(message)
|
||||||
s.access.Lock()
|
s.access.Lock()
|
||||||
s.savedLines.PushBack(message)
|
s.savedLines.PushBack(message)
|
||||||
if s.savedLines.Len() > 100 {
|
if s.savedLines.Len() > s.maxLines {
|
||||||
s.savedLines.Remove(s.savedLines.Front())
|
s.savedLines.Remove(s.savedLines.Front())
|
||||||
}
|
}
|
||||||
s.access.Unlock()
|
s.access.Unlock()
|
||||||
|
|
|
@ -8,8 +8,8 @@ import (
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ClientServiceReload(sharedDirectory string) error {
|
func (c *CommandClient) ServiceReload() error {
|
||||||
conn, err := clientConnect(sharedDirectory)
|
conn, err := c.directConnect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
59
experimental/libbox/command_select.go
Normal file
59
experimental/libbox/command_select.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/outbound"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *CommandClient) SelectOutbound(groupTag string, outboundTag string) error {
|
||||||
|
conn, err := c.directConnect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
err = binary.Write(conn, binary.BigEndian, uint8(CommandSelectOutbound))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = rw.WriteVString(conn, groupTag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = rw.WriteVString(conn, outboundTag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return readError(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CommandServer) handleSelectOutbound(conn net.Conn) error {
|
||||||
|
defer conn.Close()
|
||||||
|
groupTag, err := rw.ReadVString(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outboundTag, err := rw.ReadVString(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
service := s.service
|
||||||
|
if service == nil {
|
||||||
|
return writeError(conn, E.New("service not ready"))
|
||||||
|
}
|
||||||
|
outboundGroup, isLoaded := service.instance.Router().Outbound(groupTag)
|
||||||
|
if !isLoaded {
|
||||||
|
return writeError(conn, E.New("selector not found: ", groupTag))
|
||||||
|
}
|
||||||
|
selector, isSelector := outboundGroup.(*outbound.Selector)
|
||||||
|
if !isSelector {
|
||||||
|
return writeError(conn, E.New("outbound is not a selector: ", groupTag))
|
||||||
|
}
|
||||||
|
if !selector.SelectOutbound(outboundTag) {
|
||||||
|
return writeError(conn, E.New("outbound not found in selector: ", outboundTag))
|
||||||
|
}
|
||||||
|
return writeError(conn, nil)
|
||||||
|
}
|
|
@ -7,12 +7,14 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/debug"
|
"github.com/sagernet/sing/common/debug"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/observable"
|
"github.com/sagernet/sing/common/observable"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CommandServer struct {
|
type CommandServer struct {
|
||||||
|
@ -22,26 +24,51 @@ type CommandServer struct {
|
||||||
|
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
savedLines *list.List[string]
|
savedLines *list.List[string]
|
||||||
|
maxLines int
|
||||||
subscriber *observable.Subscriber[string]
|
subscriber *observable.Subscriber[string]
|
||||||
observer *observable.Observer[string]
|
observer *observable.Observer[string]
|
||||||
|
service *BoxService
|
||||||
|
|
||||||
|
urlTestListener *list.Element[func()]
|
||||||
|
urlTestUpdate chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommandServerHandler interface {
|
type CommandServerHandler interface {
|
||||||
ServiceStop() error
|
|
||||||
ServiceReload() error
|
ServiceReload() error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCommandServer(sharedDirectory string, handler CommandServerHandler) *CommandServer {
|
func NewCommandServer(sharedDirectory string, handler CommandServerHandler, maxLines int32) *CommandServer {
|
||||||
server := &CommandServer{
|
server := &CommandServer{
|
||||||
sockPath: filepath.Join(sharedDirectory, "command.sock"),
|
sockPath: filepath.Join(sharedDirectory, "command.sock"),
|
||||||
handler: handler,
|
handler: handler,
|
||||||
savedLines: new(list.List[string]),
|
savedLines: new(list.List[string]),
|
||||||
subscriber: observable.NewSubscriber[string](128),
|
maxLines: int(maxLines),
|
||||||
|
subscriber: observable.NewSubscriber[string](128),
|
||||||
|
urlTestUpdate: make(chan struct{}, 1),
|
||||||
}
|
}
|
||||||
server.observer = observable.NewObserver[string](server.subscriber, 64)
|
server.observer = observable.NewObserver[string](server.subscriber, 64)
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *CommandServer) SetService(newService *BoxService) {
|
||||||
|
if s.service != nil && s.listener != nil {
|
||||||
|
service.PtrFromContext[urltest.HistoryStorage](s.service.ctx).RemoveListener(s.urlTestListener)
|
||||||
|
s.urlTestListener = nil
|
||||||
|
}
|
||||||
|
s.service = newService
|
||||||
|
if newService != nil {
|
||||||
|
s.urlTestListener = service.PtrFromContext[urltest.HistoryStorage](newService.ctx).AddListener(s.notifyURLTestUpdate)
|
||||||
|
}
|
||||||
|
s.notifyURLTestUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CommandServer) notifyURLTestUpdate() {
|
||||||
|
select {
|
||||||
|
case s.urlTestUpdate <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *CommandServer) Start() error {
|
func (s *CommandServer) Start() error {
|
||||||
os.Remove(s.sockPath)
|
os.Remove(s.sockPath)
|
||||||
listener, err := net.ListenUnix("unix", &net.UnixAddr{
|
listener, err := net.ListenUnix("unix", &net.UnixAddr{
|
||||||
|
@ -92,12 +119,16 @@ func (s *CommandServer) handleConnection(conn net.Conn) error {
|
||||||
return s.handleLogConn(conn)
|
return s.handleLogConn(conn)
|
||||||
case CommandStatus:
|
case CommandStatus:
|
||||||
return s.handleStatusConn(conn)
|
return s.handleStatusConn(conn)
|
||||||
case CommandServiceStop:
|
|
||||||
return s.handleServiceStop(conn)
|
|
||||||
case CommandServiceReload:
|
case CommandServiceReload:
|
||||||
return s.handleServiceReload(conn)
|
return s.handleServiceReload(conn)
|
||||||
case CommandCloseConnections:
|
case CommandCloseConnections:
|
||||||
return s.handleCloseConnections(conn)
|
return s.handleCloseConnections(conn)
|
||||||
|
case CommandGroup:
|
||||||
|
return s.handleGroupConn(conn)
|
||||||
|
case CommandSelectOutbound:
|
||||||
|
return s.handleSelectOutbound(conn)
|
||||||
|
case CommandURLTest:
|
||||||
|
return s.handleURLTest(conn)
|
||||||
default:
|
default:
|
||||||
return E.New("unknown command: ", command)
|
return E.New("unknown command: ", command)
|
||||||
}
|
}
|
||||||
|
|
39
experimental/libbox/command_shared.go
Normal file
39
experimental/libbox/command_shared.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readError(reader io.Reader) error {
|
||||||
|
var hasError bool
|
||||||
|
err := binary.Read(reader, binary.BigEndian, &hasError)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if hasError {
|
||||||
|
errorMessage, err := rw.ReadVString(reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return E.New(errorMessage)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeError(writer io.Writer, wErr error) error {
|
||||||
|
err := binary.Write(writer, binary.BigEndian, wErr != nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if wErr != nil {
|
||||||
|
err = rw.WriteVString(writer, wErr.Error())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,48 +0,0 @@
|
||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"net"
|
|
||||||
"runtime/debug"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/rw"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ClientServiceStop(sharedDirectory string) error {
|
|
||||||
conn, err := clientConnect(sharedDirectory)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
err = binary.Write(conn, binary.BigEndian, uint8(CommandServiceStop))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var hasError bool
|
|
||||||
err = binary.Read(conn, binary.BigEndian, &hasError)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if hasError {
|
|
||||||
errorMessage, err := rw.ReadVString(conn)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return E.New(errorMessage)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CommandServer) handleServiceStop(conn net.Conn) error {
|
|
||||||
rErr := s.handler.ServiceStop()
|
|
||||||
err := binary.Write(conn, binary.BigEndian, rErr != nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if rErr != nil {
|
|
||||||
return rw.WriteVString(conn, rErr.Error())
|
|
||||||
}
|
|
||||||
debug.FreeOSMemory()
|
|
||||||
return nil
|
|
||||||
}
|
|
95
experimental/libbox/command_urltest.go
Normal file
95
experimental/libbox/command_urltest.go
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
|
"github.com/sagernet/sing-box/outbound"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/batch"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *CommandClient) URLTest(groupTag string) error {
|
||||||
|
conn, err := c.directConnect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
err = binary.Write(conn, binary.BigEndian, uint8(CommandURLTest))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = rw.WriteVString(conn, groupTag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return readError(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CommandServer) handleURLTest(conn net.Conn) error {
|
||||||
|
defer conn.Close()
|
||||||
|
groupTag, err := rw.ReadVString(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
service := s.service
|
||||||
|
if service == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
abstractOutboundGroup, isLoaded := service.instance.Router().Outbound(groupTag)
|
||||||
|
if !isLoaded {
|
||||||
|
return writeError(conn, E.New("outbound group not found: ", groupTag))
|
||||||
|
}
|
||||||
|
outboundGroup, isOutboundGroup := abstractOutboundGroup.(adapter.OutboundGroup)
|
||||||
|
if !isOutboundGroup {
|
||||||
|
return writeError(conn, E.New("outbound is not a group: ", groupTag))
|
||||||
|
}
|
||||||
|
urlTest, isURLTest := abstractOutboundGroup.(*outbound.URLTest)
|
||||||
|
if isURLTest {
|
||||||
|
go urlTest.CheckOutbounds()
|
||||||
|
} else {
|
||||||
|
var historyStorage *urltest.HistoryStorage
|
||||||
|
if clashServer := service.instance.Router().ClashServer(); clashServer != nil {
|
||||||
|
historyStorage = clashServer.HistoryStorage()
|
||||||
|
} else {
|
||||||
|
return writeError(conn, E.New("Clash API is required for URLTest on non-URLTest group"))
|
||||||
|
}
|
||||||
|
|
||||||
|
outbounds := common.Filter(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {
|
||||||
|
itOutbound, _ := service.instance.Router().Outbound(it)
|
||||||
|
return itOutbound
|
||||||
|
}), func(it adapter.Outbound) bool {
|
||||||
|
if it == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, isGroup := it.(adapter.OutboundGroup)
|
||||||
|
if isGroup {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
b, _ := batch.New(service.ctx, batch.WithConcurrencyNum[any](10))
|
||||||
|
for _, detour := range outbounds {
|
||||||
|
outboundToTest := detour
|
||||||
|
outboundTag := outboundToTest.Tag()
|
||||||
|
b.Go(outboundTag, func() (any, error) {
|
||||||
|
t, err := urltest.URLTest(service.ctx, "", outboundToTest)
|
||||||
|
if err != nil {
|
||||||
|
historyStorage.DeleteURLTestHistory(outboundTag)
|
||||||
|
} else {
|
||||||
|
historyStorage.StoreURLTestHistory(outboundTag, &urltest.History{
|
||||||
|
Time: time.Now(),
|
||||||
|
Delay: t,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return writeError(conn, nil)
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/process"
|
"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/internal/procfs"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
@ -16,6 +17,7 @@ import (
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
"github.com/sagernet/sing/service/filemanager"
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,6 +34,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
ctx = filemanager.WithDefault(ctx, sBasePath, sTempPath, sUserID, sGroupID)
|
ctx = filemanager.WithDefault(ctx, sBasePath, sTempPath, sUserID, sGroupID)
|
||||||
|
ctx = service.ContextWithPtr(ctx, urltest.NewHistoryStorage())
|
||||||
instance, err := box.New(box.Options{
|
instance, err := box.New(box.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Options: options,
|
Options: options,
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -21,7 +21,7 @@ require (
|
||||||
github.com/oschwald/maxminddb-golang v1.11.0
|
github.com/oschwald/maxminddb-golang v1.11.0
|
||||||
github.com/pires/go-proxyproto v0.7.0
|
github.com/pires/go-proxyproto v0.7.0
|
||||||
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0
|
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0
|
||||||
github.com/sagernet/gomobile v0.0.0-20230413023804-244d7ff07035
|
github.com/sagernet/gomobile v0.0.0-20230701084532-493ee2e45182
|
||||||
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2
|
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2
|
||||||
github.com/sagernet/quic-go v0.0.0-20230615020047-10f05c797c02
|
github.com/sagernet/quic-go v0.0.0-20230615020047-10f05c797c02
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||||
|
|
5
go.sum
5
go.sum
|
@ -104,8 +104,8 @@ github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 h1:KyhtFFt
|
||||||
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0/go.mod h1:D4SFEOkJK+4W1v86ZhX0jPM0rAL498fyQAChqMtes/I=
|
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0/go.mod h1:D4SFEOkJK+4W1v86ZhX0jPM0rAL498fyQAChqMtes/I=
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
||||||
github.com/sagernet/gomobile v0.0.0-20230413023804-244d7ff07035 h1:KttYh6bBhIw8Y6/Ljn7CGwC3CKZn788rzMJmeAKjY+8=
|
github.com/sagernet/gomobile v0.0.0-20230701084532-493ee2e45182 h1:sD5g92IO15RAX2DvA4Cq3Uc7tcgqNWVi8K3VTCI6sEo=
|
||||||
github.com/sagernet/gomobile v0.0.0-20230413023804-244d7ff07035/go.mod h1:5YE39YkJkCcMsfq1jMKkjsrM2GfBoF9JVWnvU89hmvU=
|
github.com/sagernet/gomobile v0.0.0-20230701084532-493ee2e45182/go.mod h1:5YE39YkJkCcMsfq1jMKkjsrM2GfBoF9JVWnvU89hmvU=
|
||||||
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 h1:dnkKrzapqtAwjTSWt6hdPrARORfoYvuUczynvRLrueo=
|
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 h1:dnkKrzapqtAwjTSWt6hdPrARORfoYvuUczynvRLrueo=
|
||||||
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2/go.mod h1:1JUiV7nGuf++YFm9eWZ8q2lrwHmhcUGzptMl/vL1+LA=
|
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2/go.mod h1:1JUiV7nGuf++YFm9eWZ8q2lrwHmhcUGzptMl/vL1+LA=
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||||
|
@ -149,6 +149,7 @@ github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRM
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
|
|
@ -24,8 +24,9 @@ func NewFactory(formatter Formatter, writer io.Writer, platformWriter io.Writer)
|
||||||
return &simpleFactory{
|
return &simpleFactory{
|
||||||
formatter: formatter,
|
formatter: formatter,
|
||||||
platformFormatter: Formatter{
|
platformFormatter: Formatter{
|
||||||
BaseTime: formatter.BaseTime,
|
BaseTime: formatter.BaseTime,
|
||||||
DisableColors: C.IsDarwin || C.IsIos,
|
DisableColors: C.IsDarwin || C.IsIos,
|
||||||
|
DisableLineBreak: true,
|
||||||
},
|
},
|
||||||
writer: writer,
|
writer: writer,
|
||||||
platformWriter: platformWriter,
|
platformWriter: platformWriter,
|
||||||
|
|
|
@ -17,6 +17,7 @@ type Formatter struct {
|
||||||
DisableTimestamp bool
|
DisableTimestamp bool
|
||||||
FullTimestamp bool
|
FullTimestamp bool
|
||||||
TimestampFormat string
|
TimestampFormat string
|
||||||
|
DisableLineBreak bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Formatter) Format(ctx context.Context, level Level, tag string, message string, timestamp time.Time) string {
|
func (f Formatter) Format(ctx context.Context, level Level, tag string, message string, timestamp time.Time) string {
|
||||||
|
@ -76,8 +77,14 @@ func (f Formatter) Format(ctx context.Context, level Level, tag string, message
|
||||||
default:
|
default:
|
||||||
message = levelString + "[" + xd(int(timestamp.Sub(f.BaseTime)/time.Second), 4) + "] " + message
|
message = levelString + "[" + xd(int(timestamp.Sub(f.BaseTime)/time.Second), 4) + "] " + message
|
||||||
}
|
}
|
||||||
if message[len(message)-1] != '\n' {
|
if f.DisableLineBreak {
|
||||||
message += "\n"
|
if message[len(message)-1] != '\n' {
|
||||||
|
message = message[:len(message)-1]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if message[len(message)-1] != '\n' {
|
||||||
|
message += "\n"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,9 @@ func NewObservableFactory(formatter Formatter, writer io.Writer, platformWriter
|
||||||
factory := &observableFactory{
|
factory := &observableFactory{
|
||||||
formatter: formatter,
|
formatter: formatter,
|
||||||
platformFormatter: Formatter{
|
platformFormatter: Formatter{
|
||||||
BaseTime: formatter.BaseTime,
|
BaseTime: formatter.BaseTime,
|
||||||
DisableColors: C.IsDarwin || C.IsIos,
|
DisableColors: C.IsDarwin || C.IsIos,
|
||||||
|
DisableLineBreak: true,
|
||||||
},
|
},
|
||||||
writer: writer,
|
writer: writer,
|
||||||
platformWriter: platformWriter,
|
platformWriter: platformWriter,
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -74,7 +75,11 @@ func (s *URLTest) Start() error {
|
||||||
outbounds = append(outbounds, detour)
|
outbounds = append(outbounds, detour)
|
||||||
}
|
}
|
||||||
s.group = NewURLTestGroup(s.ctx, s.router, s.logger, outbounds, s.link, s.interval, s.tolerance)
|
s.group = NewURLTestGroup(s.ctx, s.router, s.logger, outbounds, s.link, s.interval, s.tolerance)
|
||||||
go s.group.CheckOutbounds(false)
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *URLTest) PostStart() error {
|
||||||
|
go s.CheckOutbounds()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +101,10 @@ func (s *URLTest) URLTest(ctx context.Context, link string) (map[string]uint16,
|
||||||
return s.group.URLTest(ctx, link)
|
return s.group.URLTest(ctx, link)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *URLTest) CheckOutbounds() {
|
||||||
|
s.group.CheckOutbounds(true)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *URLTest) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (s *URLTest) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
s.group.Start()
|
s.group.Start()
|
||||||
outbound := s.group.Select(network)
|
outbound := s.group.Select(network)
|
||||||
|
@ -157,7 +166,8 @@ func NewURLTestGroup(ctx context.Context, router adapter.Router, logger log.Logg
|
||||||
tolerance = 50
|
tolerance = 50
|
||||||
}
|
}
|
||||||
var history *urltest.HistoryStorage
|
var history *urltest.HistoryStorage
|
||||||
if clashServer := router.ClashServer(); clashServer != nil {
|
if history = service.PtrFromContext[urltest.HistoryStorage](ctx); history != nil {
|
||||||
|
} else if clashServer := router.ClashServer(); clashServer != nil {
|
||||||
history = clashServer.HistoryStorage()
|
history = clashServer.HistoryStorage()
|
||||||
} else {
|
} else {
|
||||||
history = urltest.NewHistoryStorage()
|
history = urltest.NewHistoryStorage()
|
||||||
|
|
Loading…
Reference in a new issue