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  }