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 }