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 }