github.com/sagernet/sing-box@v1.9.0-rc.20/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/sagernet/sing-box/adapter"
    10  	"github.com/sagernet/sing-box/common/urltest"
    11  	"github.com/sagernet/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  	var interval int64
    62  	err := binary.Read(conn, binary.BigEndian, &interval)
    63  	if err != nil {
    64  		return E.Cause(err, "read interval")
    65  	}
    66  	ticker := time.NewTicker(time.Duration(interval))
    67  	defer ticker.Stop()
    68  	ctx := connKeepAlive(conn)
    69  	for {
    70  		service := s.service
    71  		if service != nil {
    72  			err := writeGroups(conn, service)
    73  			if err != nil {
    74  				return err
    75  			}
    76  		} else {
    77  			err := binary.Write(conn, binary.BigEndian, uint16(0))
    78  			if err != nil {
    79  				return err
    80  			}
    81  		}
    82  		select {
    83  		case <-ctx.Done():
    84  			return ctx.Err()
    85  		case <-ticker.C:
    86  		}
    87  		select {
    88  		case <-ctx.Done():
    89  			return ctx.Err()
    90  		case <-s.urlTestUpdate:
    91  		}
    92  	}
    93  }
    94  
    95  func readGroups(reader io.Reader) (OutboundGroupIterator, error) {
    96  	var groupLength uint16
    97  	err := binary.Read(reader, binary.BigEndian, &groupLength)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	groups := make([]*OutboundGroup, 0, groupLength)
   103  	for i := 0; i < int(groupLength); i++ {
   104  		var group OutboundGroup
   105  		group.Tag, err = rw.ReadVString(reader)
   106  		if err != nil {
   107  			return nil, err
   108  		}
   109  
   110  		group.Type, err = rw.ReadVString(reader)
   111  		if err != nil {
   112  			return nil, err
   113  		}
   114  
   115  		err = binary.Read(reader, binary.BigEndian, &group.Selectable)
   116  		if err != nil {
   117  			return nil, err
   118  		}
   119  
   120  		group.Selected, err = rw.ReadVString(reader)
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  
   125  		err = binary.Read(reader, binary.BigEndian, &group.IsExpand)
   126  		if err != nil {
   127  			return nil, err
   128  		}
   129  
   130  		var itemLength uint16
   131  		err = binary.Read(reader, binary.BigEndian, &itemLength)
   132  		if err != nil {
   133  			return nil, err
   134  		}
   135  
   136  		group.items = make([]*OutboundGroupItem, itemLength)
   137  		for j := 0; j < int(itemLength); j++ {
   138  			var item OutboundGroupItem
   139  			item.Tag, err = rw.ReadVString(reader)
   140  			if err != nil {
   141  				return nil, err
   142  			}
   143  
   144  			item.Type, err = rw.ReadVString(reader)
   145  			if err != nil {
   146  				return nil, err
   147  			}
   148  
   149  			err = binary.Read(reader, binary.BigEndian, &item.URLTestTime)
   150  			if err != nil {
   151  				return nil, err
   152  			}
   153  
   154  			err = binary.Read(reader, binary.BigEndian, &item.URLTestDelay)
   155  			if err != nil {
   156  				return nil, err
   157  			}
   158  
   159  			group.items[j] = &item
   160  		}
   161  		groups = append(groups, &group)
   162  	}
   163  	return newIterator(groups), nil
   164  }
   165  
   166  func writeGroups(writer io.Writer, boxService *BoxService) error {
   167  	historyStorage := service.PtrFromContext[urltest.HistoryStorage](boxService.ctx)
   168  	cacheFile := service.FromContext[adapter.CacheFile](boxService.ctx)
   169  	outbounds := boxService.instance.Router().Outbounds()
   170  	var iGroups []adapter.OutboundGroup
   171  	for _, it := range outbounds {
   172  		if group, isGroup := it.(adapter.OutboundGroup); isGroup {
   173  			iGroups = append(iGroups, group)
   174  		}
   175  	}
   176  	var groups []OutboundGroup
   177  	for _, iGroup := range iGroups {
   178  		var group OutboundGroup
   179  		group.Tag = iGroup.Tag()
   180  		group.Type = iGroup.Type()
   181  		_, group.Selectable = iGroup.(*outbound.Selector)
   182  		group.Selected = iGroup.Now()
   183  		if cacheFile != nil {
   184  			if isExpand, loaded := cacheFile.LoadGroupExpand(group.Tag); loaded {
   185  				group.IsExpand = isExpand
   186  			}
   187  		}
   188  
   189  		for _, itemTag := range iGroup.All() {
   190  			itemOutbound, isLoaded := boxService.instance.Router().Outbound(itemTag)
   191  			if !isLoaded {
   192  				continue
   193  			}
   194  
   195  			var item OutboundGroupItem
   196  			item.Tag = itemTag
   197  			item.Type = itemOutbound.Type()
   198  			if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(itemOutbound)); history != nil {
   199  				item.URLTestTime = history.Time.Unix()
   200  				item.URLTestDelay = int32(history.Delay)
   201  			}
   202  			group.items = append(group.items, &item)
   203  		}
   204  		if len(group.items) < 2 {
   205  			continue
   206  		}
   207  		groups = append(groups, group)
   208  	}
   209  
   210  	err := binary.Write(writer, binary.BigEndian, uint16(len(groups)))
   211  	if err != nil {
   212  		return err
   213  	}
   214  	for _, group := range groups {
   215  		err = rw.WriteVString(writer, group.Tag)
   216  		if err != nil {
   217  			return err
   218  		}
   219  		err = rw.WriteVString(writer, group.Type)
   220  		if err != nil {
   221  			return err
   222  		}
   223  		err = binary.Write(writer, binary.BigEndian, group.Selectable)
   224  		if err != nil {
   225  			return err
   226  		}
   227  		err = rw.WriteVString(writer, group.Selected)
   228  		if err != nil {
   229  			return err
   230  		}
   231  		err = binary.Write(writer, binary.BigEndian, group.IsExpand)
   232  		if err != nil {
   233  			return err
   234  		}
   235  		err = binary.Write(writer, binary.BigEndian, uint16(len(group.items)))
   236  		if err != nil {
   237  			return err
   238  		}
   239  		for _, item := range group.items {
   240  			err = rw.WriteVString(writer, item.Tag)
   241  			if err != nil {
   242  				return err
   243  			}
   244  			err = rw.WriteVString(writer, item.Type)
   245  			if err != nil {
   246  				return err
   247  			}
   248  			err = binary.Write(writer, binary.BigEndian, item.URLTestTime)
   249  			if err != nil {
   250  				return err
   251  			}
   252  			err = binary.Write(writer, binary.BigEndian, item.URLTestDelay)
   253  			if err != nil {
   254  				return err
   255  			}
   256  		}
   257  	}
   258  	return nil
   259  }
   260  
   261  func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error {
   262  	conn, err := c.directConnect()
   263  	if err != nil {
   264  		return err
   265  	}
   266  	defer conn.Close()
   267  	err = binary.Write(conn, binary.BigEndian, uint8(CommandGroupExpand))
   268  	if err != nil {
   269  		return err
   270  	}
   271  	err = rw.WriteVString(conn, groupTag)
   272  	if err != nil {
   273  		return err
   274  	}
   275  	err = binary.Write(conn, binary.BigEndian, isExpand)
   276  	if err != nil {
   277  		return err
   278  	}
   279  	return readError(conn)
   280  }
   281  
   282  func (s *CommandServer) handleSetGroupExpand(conn net.Conn) error {
   283  	groupTag, err := rw.ReadVString(conn)
   284  	if err != nil {
   285  		return err
   286  	}
   287  	var isExpand bool
   288  	err = binary.Read(conn, binary.BigEndian, &isExpand)
   289  	if err != nil {
   290  		return err
   291  	}
   292  	serviceNow := s.service
   293  	if serviceNow == nil {
   294  		return writeError(conn, E.New("service not ready"))
   295  	}
   296  	cacheFile := service.FromContext[adapter.CacheFile](serviceNow.ctx)
   297  	if cacheFile != nil {
   298  		err = cacheFile.StoreGroupExpand(groupTag, isExpand)
   299  		if err != nil {
   300  			return writeError(conn, err)
   301  		}
   302  	}
   303  	return writeError(conn, nil)
   304  }