github.com/sagernet/sing-box@v1.9.0-rc.20/experimental/cachefile/cache.go (about) 1 package cachefile 2 3 import ( 4 "context" 5 "errors" 6 "net/netip" 7 "os" 8 "strings" 9 "sync" 10 "time" 11 12 "github.com/sagernet/bbolt" 13 bboltErrors "github.com/sagernet/bbolt/errors" 14 "github.com/sagernet/sing-box/adapter" 15 "github.com/sagernet/sing-box/option" 16 "github.com/sagernet/sing/common" 17 E "github.com/sagernet/sing/common/exceptions" 18 "github.com/sagernet/sing/service/filemanager" 19 ) 20 21 var ( 22 bucketSelected = []byte("selected") 23 bucketExpand = []byte("group_expand") 24 bucketMode = []byte("clash_mode") 25 bucketRuleSet = []byte("rule_set") 26 27 bucketNameList = []string{ 28 string(bucketSelected), 29 string(bucketExpand), 30 string(bucketMode), 31 string(bucketRuleSet), 32 string(bucketRDRC), 33 } 34 35 cacheIDDefault = []byte("default") 36 ) 37 38 var _ adapter.CacheFile = (*CacheFile)(nil) 39 40 type CacheFile struct { 41 ctx context.Context 42 path string 43 cacheID []byte 44 storeFakeIP bool 45 storeRDRC bool 46 rdrcTimeout time.Duration 47 DB *bbolt.DB 48 saveMetadataTimer *time.Timer 49 saveFakeIPAccess sync.RWMutex 50 saveDomain map[netip.Addr]string 51 saveAddress4 map[string]netip.Addr 52 saveAddress6 map[string]netip.Addr 53 saveRDRCAccess sync.RWMutex 54 saveRDRC map[saveRDRCCacheKey]bool 55 } 56 57 type saveRDRCCacheKey struct { 58 TransportName string 59 QuestionName string 60 QType uint16 61 } 62 63 func New(ctx context.Context, options option.CacheFileOptions) *CacheFile { 64 var path string 65 if options.Path != "" { 66 path = options.Path 67 } else { 68 path = "cache.db" 69 } 70 var cacheIDBytes []byte 71 if options.CacheID != "" { 72 cacheIDBytes = append([]byte{0}, []byte(options.CacheID)...) 73 } 74 var rdrcTimeout time.Duration 75 if options.StoreRDRC { 76 if options.RDRCTimeout > 0 { 77 rdrcTimeout = time.Duration(options.RDRCTimeout) 78 } else { 79 rdrcTimeout = 7 * 24 * time.Hour 80 } 81 } 82 return &CacheFile{ 83 ctx: ctx, 84 path: filemanager.BasePath(ctx, path), 85 cacheID: cacheIDBytes, 86 storeFakeIP: options.StoreFakeIP, 87 storeRDRC: options.StoreRDRC, 88 rdrcTimeout: rdrcTimeout, 89 saveDomain: make(map[netip.Addr]string), 90 saveAddress4: make(map[string]netip.Addr), 91 saveAddress6: make(map[string]netip.Addr), 92 saveRDRC: make(map[saveRDRCCacheKey]bool), 93 } 94 } 95 96 func (c *CacheFile) start() error { 97 const fileMode = 0o666 98 options := bbolt.Options{Timeout: time.Second} 99 var ( 100 db *bbolt.DB 101 err error 102 ) 103 for i := 0; i < 10; i++ { 104 db, err = bbolt.Open(c.path, fileMode, &options) 105 if err == nil { 106 break 107 } 108 if errors.Is(err, bboltErrors.ErrTimeout) { 109 continue 110 } 111 if E.IsMulti(err, bboltErrors.ErrInvalid, bboltErrors.ErrChecksum, bboltErrors.ErrVersionMismatch) { 112 rmErr := os.Remove(c.path) 113 if rmErr != nil { 114 return err 115 } 116 } 117 time.Sleep(100 * time.Millisecond) 118 } 119 if err != nil { 120 return err 121 } 122 err = filemanager.Chown(c.ctx, c.path) 123 if err != nil { 124 db.Close() 125 return E.Cause(err, "platform chown") 126 } 127 err = db.Batch(func(tx *bbolt.Tx) error { 128 return tx.ForEach(func(name []byte, b *bbolt.Bucket) error { 129 if name[0] == 0 { 130 return b.ForEachBucket(func(k []byte) error { 131 bucketName := string(k) 132 if !(common.Contains(bucketNameList, bucketName)) { 133 _ = b.DeleteBucket(name) 134 } 135 return nil 136 }) 137 } else { 138 bucketName := string(name) 139 if !(common.Contains(bucketNameList, bucketName) || strings.HasPrefix(bucketName, fakeipBucketPrefix)) { 140 _ = tx.DeleteBucket(name) 141 } 142 } 143 return nil 144 }) 145 }) 146 if err != nil { 147 db.Close() 148 return err 149 } 150 c.DB = db 151 return nil 152 } 153 154 func (c *CacheFile) PreStart() error { 155 return c.start() 156 } 157 158 func (c *CacheFile) Start() error { 159 return nil 160 } 161 162 func (c *CacheFile) Close() error { 163 if c.DB == nil { 164 return nil 165 } 166 return c.DB.Close() 167 } 168 169 func (c *CacheFile) StoreFakeIP() bool { 170 return c.storeFakeIP 171 } 172 173 func (c *CacheFile) LoadMode() string { 174 var mode string 175 c.DB.View(func(t *bbolt.Tx) error { 176 bucket := t.Bucket(bucketMode) 177 if bucket == nil { 178 return nil 179 } 180 var modeBytes []byte 181 if len(c.cacheID) > 0 { 182 modeBytes = bucket.Get(c.cacheID) 183 } else { 184 modeBytes = bucket.Get(cacheIDDefault) 185 } 186 mode = string(modeBytes) 187 return nil 188 }) 189 return mode 190 } 191 192 func (c *CacheFile) StoreMode(mode string) error { 193 return c.DB.Batch(func(t *bbolt.Tx) error { 194 bucket, err := t.CreateBucketIfNotExists(bucketMode) 195 if err != nil { 196 return err 197 } 198 if len(c.cacheID) > 0 { 199 return bucket.Put(c.cacheID, []byte(mode)) 200 } else { 201 return bucket.Put(cacheIDDefault, []byte(mode)) 202 } 203 }) 204 } 205 206 func (c *CacheFile) bucket(t *bbolt.Tx, key []byte) *bbolt.Bucket { 207 if c.cacheID == nil { 208 return t.Bucket(key) 209 } 210 bucket := t.Bucket(c.cacheID) 211 if bucket == nil { 212 return nil 213 } 214 return bucket.Bucket(key) 215 } 216 217 func (c *CacheFile) createBucket(t *bbolt.Tx, key []byte) (*bbolt.Bucket, error) { 218 if c.cacheID == nil { 219 return t.CreateBucketIfNotExists(key) 220 } 221 bucket, err := t.CreateBucketIfNotExists(c.cacheID) 222 if bucket == nil { 223 return nil, err 224 } 225 return bucket.CreateBucketIfNotExists(key) 226 } 227 228 func (c *CacheFile) LoadSelected(group string) string { 229 var selected string 230 c.DB.View(func(t *bbolt.Tx) error { 231 bucket := c.bucket(t, bucketSelected) 232 if bucket == nil { 233 return nil 234 } 235 selectedBytes := bucket.Get([]byte(group)) 236 if len(selectedBytes) > 0 { 237 selected = string(selectedBytes) 238 } 239 return nil 240 }) 241 return selected 242 } 243 244 func (c *CacheFile) StoreSelected(group, selected string) error { 245 return c.DB.Batch(func(t *bbolt.Tx) error { 246 bucket, err := c.createBucket(t, bucketSelected) 247 if err != nil { 248 return err 249 } 250 return bucket.Put([]byte(group), []byte(selected)) 251 }) 252 } 253 254 func (c *CacheFile) LoadGroupExpand(group string) (isExpand bool, loaded bool) { 255 c.DB.View(func(t *bbolt.Tx) error { 256 bucket := c.bucket(t, bucketExpand) 257 if bucket == nil { 258 return nil 259 } 260 expandBytes := bucket.Get([]byte(group)) 261 if len(expandBytes) == 1 { 262 isExpand = expandBytes[0] == 1 263 loaded = true 264 } 265 return nil 266 }) 267 return 268 } 269 270 func (c *CacheFile) StoreGroupExpand(group string, isExpand bool) error { 271 return c.DB.Batch(func(t *bbolt.Tx) error { 272 bucket, err := c.createBucket(t, bucketExpand) 273 if err != nil { 274 return err 275 } 276 if isExpand { 277 return bucket.Put([]byte(group), []byte{1}) 278 } else { 279 return bucket.Put([]byte(group), []byte{0}) 280 } 281 }) 282 } 283 284 func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedRuleSet { 285 var savedSet adapter.SavedRuleSet 286 err := c.DB.View(func(t *bbolt.Tx) error { 287 bucket := c.bucket(t, bucketRuleSet) 288 if bucket == nil { 289 return os.ErrNotExist 290 } 291 setBinary := bucket.Get([]byte(tag)) 292 if len(setBinary) == 0 { 293 return os.ErrInvalid 294 } 295 return savedSet.UnmarshalBinary(setBinary) 296 }) 297 if err != nil { 298 return nil 299 } 300 return &savedSet 301 } 302 303 func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedRuleSet) error { 304 return c.DB.Batch(func(t *bbolt.Tx) error { 305 bucket, err := c.createBucket(t, bucketRuleSet) 306 if err != nil { 307 return err 308 } 309 setBinary, err := set.MarshalBinary() 310 if err != nil { 311 return err 312 } 313 return bucket.Put([]byte(tag), setBinary) 314 }) 315 }