command_group.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. package libbox
  2. import (
  3. "bufio"
  4. "encoding/binary"
  5. "io"
  6. "net"
  7. "time"
  8. "github.com/sagernet/sing-box/adapter"
  9. "github.com/sagernet/sing-box/common/urltest"
  10. "github.com/sagernet/sing-box/protocol/group"
  11. E "github.com/sagernet/sing/common/exceptions"
  12. "github.com/sagernet/sing/common/varbin"
  13. "github.com/sagernet/sing/service"
  14. )
  15. func (c *CommandClient) handleGroupConn(conn net.Conn) {
  16. defer conn.Close()
  17. for {
  18. groups, err := readGroups(conn)
  19. if err != nil {
  20. c.handler.Disconnected(err.Error())
  21. return
  22. }
  23. c.handler.WriteGroups(groups)
  24. }
  25. }
  26. func (s *CommandServer) handleGroupConn(conn net.Conn) error {
  27. var interval int64
  28. err := binary.Read(conn, binary.BigEndian, &interval)
  29. if err != nil {
  30. return E.Cause(err, "read interval")
  31. }
  32. ticker := time.NewTicker(time.Duration(interval))
  33. defer ticker.Stop()
  34. ctx := connKeepAlive(conn)
  35. writer := bufio.NewWriter(conn)
  36. for {
  37. service := s.service
  38. if service != nil {
  39. err = writeGroups(writer, service)
  40. if err != nil {
  41. return err
  42. }
  43. } else {
  44. err = binary.Write(writer, binary.BigEndian, uint16(0))
  45. if err != nil {
  46. return err
  47. }
  48. }
  49. err = writer.Flush()
  50. if err != nil {
  51. return err
  52. }
  53. select {
  54. case <-ctx.Done():
  55. return ctx.Err()
  56. case <-ticker.C:
  57. }
  58. select {
  59. case <-ctx.Done():
  60. return ctx.Err()
  61. case <-s.urlTestUpdate:
  62. }
  63. }
  64. }
  65. type OutboundGroup struct {
  66. Tag string
  67. Type string
  68. Selectable bool
  69. Selected string
  70. IsExpand bool
  71. ItemList []*OutboundGroupItem
  72. }
  73. func (g *OutboundGroup) GetItems() OutboundGroupItemIterator {
  74. return newIterator(g.ItemList)
  75. }
  76. type OutboundGroupIterator interface {
  77. Next() *OutboundGroup
  78. HasNext() bool
  79. }
  80. type OutboundGroupItem struct {
  81. Tag string
  82. Type string
  83. URLTestTime int64
  84. URLTestDelay int32
  85. }
  86. type OutboundGroupItemIterator interface {
  87. Next() *OutboundGroupItem
  88. HasNext() bool
  89. }
  90. func readGroups(reader io.Reader) (OutboundGroupIterator, error) {
  91. groups, err := varbin.ReadValue[[]*OutboundGroup](reader, binary.BigEndian)
  92. if err != nil {
  93. return nil, err
  94. }
  95. return newIterator(groups), nil
  96. }
  97. func writeGroups(writer io.Writer, boxService *BoxService) error {
  98. historyStorage := service.PtrFromContext[urltest.HistoryStorage](boxService.ctx)
  99. cacheFile := service.FromContext[adapter.CacheFile](boxService.ctx)
  100. outbounds := boxService.instance.Outbound().Outbounds()
  101. var iGroups []adapter.OutboundGroup
  102. for _, it := range outbounds {
  103. if group, isGroup := it.(adapter.OutboundGroup); isGroup {
  104. iGroups = append(iGroups, group)
  105. }
  106. }
  107. var groups []OutboundGroup
  108. for _, iGroup := range iGroups {
  109. var outboundGroup OutboundGroup
  110. outboundGroup.Tag = iGroup.Tag()
  111. outboundGroup.Type = iGroup.Type()
  112. _, outboundGroup.Selectable = iGroup.(*group.Selector)
  113. outboundGroup.Selected = iGroup.Now()
  114. if cacheFile != nil {
  115. if isExpand, loaded := cacheFile.LoadGroupExpand(outboundGroup.Tag); loaded {
  116. outboundGroup.IsExpand = isExpand
  117. }
  118. }
  119. for _, itemTag := range iGroup.All() {
  120. itemOutbound, isLoaded := boxService.instance.Outbound().Outbound(itemTag)
  121. if !isLoaded {
  122. continue
  123. }
  124. var item OutboundGroupItem
  125. item.Tag = itemTag
  126. item.Type = itemOutbound.Type()
  127. if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(itemOutbound)); history != nil {
  128. item.URLTestTime = history.Time.Unix()
  129. item.URLTestDelay = int32(history.Delay)
  130. }
  131. outboundGroup.ItemList = append(outboundGroup.ItemList, &item)
  132. }
  133. if len(outboundGroup.ItemList) < 2 {
  134. continue
  135. }
  136. groups = append(groups, outboundGroup)
  137. }
  138. return varbin.Write(writer, binary.BigEndian, groups)
  139. }
  140. func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error {
  141. conn, err := c.directConnect()
  142. if err != nil {
  143. return err
  144. }
  145. defer conn.Close()
  146. err = binary.Write(conn, binary.BigEndian, uint8(CommandGroupExpand))
  147. if err != nil {
  148. return err
  149. }
  150. err = varbin.Write(conn, binary.BigEndian, groupTag)
  151. if err != nil {
  152. return err
  153. }
  154. err = binary.Write(conn, binary.BigEndian, isExpand)
  155. if err != nil {
  156. return err
  157. }
  158. return readError(conn)
  159. }
  160. func (s *CommandServer) handleSetGroupExpand(conn net.Conn) error {
  161. groupTag, err := varbin.ReadValue[string](conn, binary.BigEndian)
  162. if err != nil {
  163. return err
  164. }
  165. var isExpand bool
  166. err = binary.Read(conn, binary.BigEndian, &isExpand)
  167. if err != nil {
  168. return err
  169. }
  170. serviceNow := s.service
  171. if serviceNow == nil {
  172. return writeError(conn, E.New("service not ready"))
  173. }
  174. cacheFile := service.FromContext[adapter.CacheFile](serviceNow.ctx)
  175. if cacheFile != nil {
  176. err = cacheFile.StoreGroupExpand(groupTag, isExpand)
  177. if err != nil {
  178. return writeError(conn, err)
  179. }
  180. }
  181. return writeError(conn, nil)
  182. }