github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/experimental/libbox/command_group.go (about)

     1  package libbox
     2  
     3  import (
     4  	"encoding/binary"
     5  	"io"
     6  	"net"
     7  	"time"
     8  
     9  	"github.com/inazumav/sing-box/adapter"
    10  	"github.com/inazumav/sing-box/common/urltest"
    11  	"github.com/inazumav/sing-box/outbound"
    12  	E "github.com/sagernet/sing/common/exceptions"
    13  	"github.com/sagernet/sing/common/rw"
    14  	"github.com/sagernet/sing/service"
    15  )
    16  
    17  type OutboundGroup struct {
    18  	Tag        string
    19  	Type       string
    20  	Selectable bool
    21  	Selected   string
    22  	IsExpand   bool
    23  	items      []*OutboundGroupItem
    24  }
    25  
    26  func (g *OutboundGroup) GetItems() OutboundGroupItemIterator {
    27  	return newIterator(g.items)
    28  }
    29  
    30  type OutboundGroupIterator interface {
    31  	Next() *OutboundGroup
    32  	HasNext() bool
    33  }
    34  
    35  type OutboundGroupItem struct {
    36  	Tag          string
    37  	Type         string
    38  	URLTestTime  int64
    39  	URLTestDelay int32
    40  }
    41  
    42  type OutboundGroupItemIterator interface {
    43  	Next() *OutboundGroupItem
    44  	HasNext() bool
    45  }
    46  
    47  func (c *CommandClient) handleGroupConn(conn net.Conn) {
    48  	defer conn.Close()
    49  
    50  	for {
    51  		groups, err := readGroups(conn)
    52  		if err != nil {
    53  			c.handler.Disconnected(err.Error())
    54  			return
    55  		}
    56  		c.handler.WriteGroups(groups)
    57  	}
    58  }
    59  
    60  func (s *CommandServer) handleGroupConn(conn net.Conn) error {
    61  	defer conn.Close()
    62  	ctx := connKeepAlive(conn)
    63  	for {
    64  		service := s.service
    65  		if service != nil {
    66  			err := writeGroups(conn, service)
    67  			if err != nil {
    68  				return err
    69  			}
    70  		} else {
    71  			err := binary.Write(conn, binary.BigEndian, uint16(0))
    72  			if err != nil {
    73  				return err
    74  			}
    75  		}
    76  		select {
    77  		case <-ctx.Done():
    78  			return ctx.Err()
    79  		case <-time.After(2 * time.Second):
    80  		}
    81  		select {
    82  		case <-ctx.Done():
    83  			return ctx.Err()
    84  		case <-s.urlTestUpdate:
    85  		}
    86  	}
    87  }
    88  
    89  func readGroups(reader io.Reader) (OutboundGroupIterator, error) {
    90  	var groupLength uint16
    91  	err := binary.Read(reader, binary.BigEndian, &groupLength)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	groups := make([]*OutboundGroup, 0, groupLength)
    97  	for i := 0; i < int(groupLength); i++ {
    98  		var group OutboundGroup
    99  		group.Tag, err = rw.ReadVString(reader)
   100  		if err != nil {
   101  			return nil, err
   102  		}
   103  
   104  		group.Type, err = rw.ReadVString(reader)
   105  		if err != nil {
   106  			return nil, err
   107  		}
   108  
   109  		err = binary.Read(reader, binary.BigEndian, &group.Selectable)
   110  		if err != nil {
   111  			return nil, err
   112  		}
   113  
   114  		group.Selected, err = rw.ReadVString(reader)
   115  		if err != nil {
   116  			return nil, err
   117  		}
   118  
   119  		err = binary.Read(reader, binary.BigEndian, &group.IsExpand)
   120  		if err != nil {
   121  			return nil, err
   122  		}
   123  
   124  		var itemLength uint16
   125  		err = binary.Read(reader, binary.BigEndian, &itemLength)
   126  		if err != nil {
   127  			return nil, err
   128  		}
   129  
   130  		group.items = make([]*OutboundGroupItem, itemLength)
   131  		for j := 0; j < int(itemLength); j++ {
   132  			var item OutboundGroupItem
   133  			item.Tag, err = rw.ReadVString(reader)
   134  			if err != nil {
   135  				return nil, err
   136  			}
   137  
   138  			item.Type, err = rw.ReadVString(reader)
   139  			if err != nil {
   140  				return nil, err
   141  			}
   142  
   143  			err = binary.Read(reader, binary.BigEndian, &item.URLTestTime)
   144  			if err != nil {
   145  				return nil, err
   146  			}
   147  
   148  			err = binary.Read(reader, binary.BigEndian, &item.URLTestDelay)
   149  			if err != nil {
   150  				return nil, err
   151  			}
   152  
   153  			group.items[j] = &item
   154  		}
   155  		groups = append(groups, &group)
   156  	}
   157  	return newIterator(groups), nil
   158  }
   159  
   160  func writeGroups(writer io.Writer, boxService *BoxService) error {
   161  	historyStorage := service.PtrFromContext[urltest.HistoryStorage](boxService.ctx)
   162  	var cacheFile adapter.ClashCacheFile
   163  	if clashServer := boxService.instance.Router().ClashServer(); clashServer != nil {
   164  		cacheFile = clashServer.CacheFile()
   165  	}
   166  
   167  	outbounds := boxService.instance.Router().Outbounds()
   168  	var iGroups []adapter.OutboundGroup
   169  	for _, it := range outbounds {
   170  		if group, isGroup := it.(adapter.OutboundGroup); isGroup {
   171  			iGroups = append(iGroups, group)
   172  		}
   173  	}
   174  	var groups []OutboundGroup
   175  	for _, iGroup := range iGroups {
   176  		var group OutboundGroup
   177  		group.Tag = iGroup.Tag()
   178  		group.Type = iGroup.Type()
   179  		_, group.Selectable = iGroup.(*outbound.Selector)
   180  		group.Selected = iGroup.Now()
   181  		if cacheFile != nil {
   182  			if isExpand, loaded := cacheFile.LoadGroupExpand(group.Tag); loaded {
   183  				group.IsExpand = isExpand
   184  			}
   185  		}
   186  
   187  		for _, itemTag := range iGroup.All() {
   188  			itemOutbound, isLoaded := boxService.instance.Router().Outbound(itemTag)
   189  			if !isLoaded {
   190  				continue
   191  			}
   192  
   193  			var item OutboundGroupItem
   194  			item.Tag = itemTag
   195  			item.Type = itemOutbound.Type()
   196  			if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(itemOutbound)); history != nil {
   197  				item.URLTestTime = history.Time.Unix()
   198  				item.URLTestDelay = int32(history.Delay)
   199  			}
   200  			group.items = append(group.items, &item)
   201  		}
   202  		if len(group.items) < 2 {
   203  			continue
   204  		}
   205  		groups = append(groups, group)
   206  	}
   207  
   208  	err := binary.Write(writer, binary.BigEndian, uint16(len(groups)))
   209  	if err != nil {
   210  		return err
   211  	}
   212  	for _, group := range groups {
   213  		err = rw.WriteVString(writer, group.Tag)
   214  		if err != nil {
   215  			return err
   216  		}
   217  		err = rw.WriteVString(writer, group.Type)
   218  		if err != nil {
   219  			return err
   220  		}
   221  		err = binary.Write(writer, binary.BigEndian, group.Selectable)
   222  		if err != nil {
   223  			return err
   224  		}
   225  		err = rw.WriteVString(writer, group.Selected)
   226  		if err != nil {
   227  			return err
   228  		}
   229  		err = binary.Write(writer, binary.BigEndian, group.IsExpand)
   230  		if err != nil {
   231  			return err
   232  		}
   233  		err = binary.Write(writer, binary.BigEndian, uint16(len(group.items)))
   234  		if err != nil {
   235  			return err
   236  		}
   237  		for _, item := range group.items {
   238  			err = rw.WriteVString(writer, item.Tag)
   239  			if err != nil {
   240  				return err
   241  			}
   242  			err = rw.WriteVString(writer, item.Type)
   243  			if err != nil {
   244  				return err
   245  			}
   246  			err = binary.Write(writer, binary.BigEndian, item.URLTestTime)
   247  			if err != nil {
   248  				return err
   249  			}
   250  			err = binary.Write(writer, binary.BigEndian, item.URLTestDelay)
   251  			if err != nil {
   252  				return err
   253  			}
   254  		}
   255  	}
   256  	return nil
   257  }
   258  
   259  func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error {
   260  	conn, err := c.directConnect()
   261  	if err != nil {
   262  		return err
   263  	}
   264  	defer conn.Close()
   265  	err = binary.Write(conn, binary.BigEndian, uint8(CommandGroupExpand))
   266  	if err != nil {
   267  		return err
   268  	}
   269  	err = rw.WriteVString(conn, groupTag)
   270  	if err != nil {
   271  		return err
   272  	}
   273  	err = binary.Write(conn, binary.BigEndian, isExpand)
   274  	if err != nil {
   275  		return err
   276  	}
   277  	return readError(conn)
   278  }
   279  
   280  func (s *CommandServer) handleSetGroupExpand(conn net.Conn) error {
   281  	defer conn.Close()
   282  	groupTag, err := rw.ReadVString(conn)
   283  	if err != nil {
   284  		return err
   285  	}
   286  	var isExpand bool
   287  	err = binary.Read(conn, binary.BigEndian, &isExpand)
   288  	if err != nil {
   289  		return err
   290  	}
   291  	service := s.service
   292  	if service == nil {
   293  		return writeError(conn, E.New("service not ready"))
   294  	}
   295  	if clashServer := service.instance.Router().ClashServer(); clashServer != nil {
   296  		if cacheFile := clashServer.CacheFile(); cacheFile != nil {
   297  			err = cacheFile.StoreGroupExpand(groupTag, isExpand)
   298  			if err != nil {
   299  				return writeError(conn, err)
   300  			}
   301  		}
   302  	}
   303  	return writeError(conn, nil)
   304  }