mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-30 12:31:31 +00:00
300 lines
6.5 KiB
Go
300 lines
6.5 KiB
Go
package libbox
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"io"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/sagernet/sing-box/adapter"
|
|
"github.com/sagernet/sing-box/common/urltest"
|
|
"github.com/sagernet/sing-box/outbound"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
"github.com/sagernet/sing/common/rw"
|
|
"github.com/sagernet/sing/service"
|
|
)
|
|
|
|
type OutboundGroup struct {
|
|
Tag string
|
|
Type string
|
|
Selectable bool
|
|
Selected string
|
|
IsExpand bool
|
|
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 <-time.After(2 * time.Second):
|
|
}
|
|
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
|
|
}
|
|
|
|
err = binary.Read(reader, binary.BigEndian, &group.IsExpand)
|
|
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)
|
|
cacheFile := service.FromContext[adapter.CacheFile](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()
|
|
if cacheFile != nil {
|
|
if isExpand, loaded := cacheFile.LoadGroupExpand(group.Tag); loaded {
|
|
group.IsExpand = isExpand
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
if len(group.items) < 2 {
|
|
continue
|
|
}
|
|
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, group.IsExpand)
|
|
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
|
|
}
|
|
|
|
func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error {
|
|
conn, err := c.directConnect()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer conn.Close()
|
|
err = binary.Write(conn, binary.BigEndian, uint8(CommandGroupExpand))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = rw.WriteVString(conn, groupTag)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = binary.Write(conn, binary.BigEndian, isExpand)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return readError(conn)
|
|
}
|
|
|
|
func (s *CommandServer) handleSetGroupExpand(conn net.Conn) error {
|
|
defer conn.Close()
|
|
groupTag, err := rw.ReadVString(conn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var isExpand bool
|
|
err = binary.Read(conn, binary.BigEndian, &isExpand)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
serviceNow := s.service
|
|
if serviceNow == nil {
|
|
return writeError(conn, E.New("service not ready"))
|
|
}
|
|
cacheFile := service.FromContext[adapter.CacheFile](serviceNow.ctx)
|
|
if cacheFile != nil {
|
|
err = cacheFile.StoreGroupExpand(groupTag, isExpand)
|
|
if err != nil {
|
|
return writeError(conn, err)
|
|
}
|
|
}
|
|
return writeError(conn, nil)
|
|
}
|