123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- 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)
- }
|