mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-10-20 00:04:24 +00:00
Merge tag 'v1.14.0'
This commit is contained in:
+164
-51
@@ -12,9 +12,11 @@ import (
|
||||
"github.com/sagernet/bbolt"
|
||||
bboltErrors "github.com/sagernet/bbolt/errors"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
"github.com/sagernet/sing/service/filemanager"
|
||||
)
|
||||
|
||||
@@ -30,6 +32,7 @@ var (
|
||||
string(bucketMode),
|
||||
string(bucketRuleSet),
|
||||
string(bucketRDRC),
|
||||
string(bucketDNSCache),
|
||||
}
|
||||
|
||||
cacheIDDefault = []byte("default")
|
||||
@@ -38,32 +41,48 @@ var (
|
||||
var _ adapter.CacheFile = (*CacheFile)(nil)
|
||||
|
||||
type CacheFile struct {
|
||||
ctx context.Context
|
||||
path string
|
||||
cacheID []byte
|
||||
storeFakeIP bool
|
||||
storeRDRC bool
|
||||
storeWARPConfig bool
|
||||
storeMASQUEConfig bool
|
||||
rdrcTimeout time.Duration
|
||||
DB *bbolt.DB
|
||||
resetAccess sync.Mutex
|
||||
saveMetadataTimer *time.Timer
|
||||
saveFakeIPAccess sync.RWMutex
|
||||
saveDomain map[netip.Addr]string
|
||||
saveAddress4 map[string]netip.Addr
|
||||
saveAddress6 map[string]netip.Addr
|
||||
saveRDRCAccess sync.RWMutex
|
||||
saveRDRC map[saveRDRCCacheKey]bool
|
||||
ctx context.Context
|
||||
logger logger.Logger
|
||||
path string
|
||||
cacheID []byte
|
||||
cacheIDText string
|
||||
storeFakeIP bool
|
||||
storeRDRC bool
|
||||
storeDNS bool
|
||||
storeWARPConfig bool
|
||||
storeMASQUEConfig bool
|
||||
disableExpire bool
|
||||
rdrcTimeout time.Duration
|
||||
optimisticTimeout time.Duration
|
||||
DB *bbolt.DB
|
||||
dbAccess sync.RWMutex
|
||||
saveMetadataAccess sync.Mutex
|
||||
saveMetadata *adapter.FakeIPMetadata
|
||||
saveMetadataTimer *time.Timer
|
||||
saveFakeIPAccess sync.RWMutex
|
||||
saveDomain map[netip.Addr]string
|
||||
saveAddress4 map[string]netip.Addr
|
||||
saveAddress6 map[string]netip.Addr
|
||||
saveRDRCAccess sync.RWMutex
|
||||
saveRDRC map[saveCacheKey]bool
|
||||
saveDNSCacheAccess sync.RWMutex
|
||||
saveDNSCache map[saveCacheKey]saveDNSCacheEntry
|
||||
}
|
||||
|
||||
type saveRDRCCacheKey struct {
|
||||
type saveCacheKey struct {
|
||||
TransportName string
|
||||
QuestionName string
|
||||
QType uint16
|
||||
}
|
||||
|
||||
func New(ctx context.Context, options option.CacheFileOptions) *CacheFile {
|
||||
type saveDNSCacheEntry struct {
|
||||
rawMessage []byte
|
||||
expireAt time.Time
|
||||
sequence uint64
|
||||
saving bool
|
||||
}
|
||||
|
||||
func New(ctx context.Context, logger logger.Logger, options option.CacheFileOptions) *CacheFile {
|
||||
var path string
|
||||
if options.Path != "" {
|
||||
path = options.Path
|
||||
@@ -74,6 +93,9 @@ func New(ctx context.Context, options option.CacheFileOptions) *CacheFile {
|
||||
if options.CacheID != "" {
|
||||
cacheIDBytes = append([]byte{0}, []byte(options.CacheID)...)
|
||||
}
|
||||
if options.StoreRDRC {
|
||||
deprecated.Report(ctx, deprecated.OptionStoreRDRC)
|
||||
}
|
||||
var rdrcTimeout time.Duration
|
||||
if options.StoreRDRC {
|
||||
if options.RDRCTimeout > 0 {
|
||||
@@ -84,17 +106,21 @@ func New(ctx context.Context, options option.CacheFileOptions) *CacheFile {
|
||||
}
|
||||
return &CacheFile{
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
path: filemanager.BasePath(ctx, path),
|
||||
cacheID: cacheIDBytes,
|
||||
cacheIDText: options.CacheID,
|
||||
storeFakeIP: options.StoreFakeIP,
|
||||
storeRDRC: options.StoreRDRC,
|
||||
storeDNS: options.StoreDNS,
|
||||
storeWARPConfig: options.StoreWARPConfig,
|
||||
storeMASQUEConfig: options.StoreMASQUEConfig,
|
||||
rdrcTimeout: rdrcTimeout,
|
||||
saveDomain: make(map[netip.Addr]string),
|
||||
saveAddress4: make(map[string]netip.Addr),
|
||||
saveAddress6: make(map[string]netip.Addr),
|
||||
saveRDRC: make(map[saveRDRCCacheKey]bool),
|
||||
saveRDRC: make(map[saveCacheKey]bool),
|
||||
saveDNSCache: make(map[saveCacheKey]saveDNSCacheEntry),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,31 +132,81 @@ func (c *CacheFile) Dependencies() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CacheFile) CacheID() string {
|
||||
return c.cacheIDText
|
||||
}
|
||||
|
||||
func (c *CacheFile) SetOptimisticTimeout(timeout time.Duration) {
|
||||
c.optimisticTimeout = timeout
|
||||
}
|
||||
|
||||
func (c *CacheFile) SetDisableExpire(disableExpire bool) {
|
||||
c.disableExpire = disableExpire
|
||||
}
|
||||
|
||||
func (c *CacheFile) Start(stage adapter.StartStage) error {
|
||||
if stage != adapter.StartStateInitialize {
|
||||
return nil
|
||||
switch stage {
|
||||
case adapter.StartStateInitialize:
|
||||
return c.start()
|
||||
case adapter.StartStateStart:
|
||||
c.startCacheCleanup()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CacheFile) startCacheCleanup() {
|
||||
if c.storeDNS {
|
||||
c.clearRDRC()
|
||||
c.cleanupDNSCache()
|
||||
interval := c.optimisticTimeout / 2
|
||||
if interval <= 0 {
|
||||
interval = time.Hour
|
||||
}
|
||||
go c.loopCacheCleanup(interval, c.cleanupDNSCache)
|
||||
} else if c.storeRDRC {
|
||||
c.cleanupRDRC()
|
||||
interval := c.rdrcTimeout / 2
|
||||
if interval <= 0 {
|
||||
interval = time.Hour
|
||||
}
|
||||
go c.loopCacheCleanup(interval, c.cleanupRDRC)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CacheFile) start() error {
|
||||
const fileMode = 0o666
|
||||
cacheFile, err := filemanager.OpenFile(c.ctx, c.path, os.O_RDWR|os.O_CREATE, fileMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cacheFile.Close()
|
||||
options := bbolt.Options{Timeout: time.Second}
|
||||
var (
|
||||
db *bbolt.DB
|
||||
err error
|
||||
)
|
||||
var db *bbolt.DB
|
||||
for range 10 {
|
||||
db, err = bbolt.Open(c.path, fileMode, &options)
|
||||
if err != nil {
|
||||
if errors.Is(err, bboltErrors.ErrTimeout) {
|
||||
continue
|
||||
}
|
||||
if E.IsMulti(err, bboltErrors.ErrInvalid, bboltErrors.ErrChecksum, bboltErrors.ErrVersionMismatch) {
|
||||
rmErr := filemanager.Remove(c.ctx, c.path)
|
||||
if rmErr != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
err = checkDatabase(db)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if errors.Is(err, bboltErrors.ErrTimeout) {
|
||||
continue
|
||||
c.logger.Error("database corrupted: ", err, ": resetting")
|
||||
db.Close()
|
||||
rmErr := filemanager.Remove(c.ctx, c.path)
|
||||
if rmErr != nil {
|
||||
return err
|
||||
}
|
||||
if E.IsMulti(err, bboltErrors.ErrInvalid, bboltErrors.ErrChecksum, bboltErrors.ErrVersionMismatch) {
|
||||
rmErr := os.Remove(c.path)
|
||||
if rmErr != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -168,47 +244,84 @@ func (c *CacheFile) Start(stage adapter.StartStage) error {
|
||||
}
|
||||
|
||||
func (c *CacheFile) Close() error {
|
||||
if c.DB == nil {
|
||||
c.dbAccess.RLock()
|
||||
db := c.DB
|
||||
c.dbAccess.RUnlock()
|
||||
if db == nil {
|
||||
return nil
|
||||
}
|
||||
return c.DB.Close()
|
||||
return db.Close()
|
||||
}
|
||||
|
||||
func checkDatabase(db *bbolt.DB) error {
|
||||
return db.View(func(tx *bbolt.Tx) error {
|
||||
var checkErr error
|
||||
for txErr := range tx.Check() {
|
||||
if checkErr == nil {
|
||||
checkErr = txErr
|
||||
}
|
||||
}
|
||||
return checkErr
|
||||
})
|
||||
}
|
||||
|
||||
func (c *CacheFile) database() *bbolt.DB {
|
||||
c.dbAccess.RLock()
|
||||
defer c.dbAccess.RUnlock()
|
||||
return c.DB
|
||||
}
|
||||
|
||||
func (c *CacheFile) view(fn func(tx *bbolt.Tx) error) (err error) {
|
||||
db := c.database()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
c.resetDB()
|
||||
r := recover()
|
||||
if r != nil {
|
||||
c.resetDB(db, r)
|
||||
err = E.New("database corrupted: ", r)
|
||||
}
|
||||
}()
|
||||
return c.DB.View(fn)
|
||||
return db.View(fn)
|
||||
}
|
||||
|
||||
func (c *CacheFile) batch(fn func(tx *bbolt.Tx) error) (err error) {
|
||||
db := c.database()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
c.resetDB()
|
||||
r := recover()
|
||||
if r != nil {
|
||||
c.resetDB(db, r)
|
||||
err = E.New("database corrupted: ", r)
|
||||
}
|
||||
}()
|
||||
return c.DB.Batch(fn)
|
||||
err = db.Batch(fn)
|
||||
var panicErr bbolt.PanickedError
|
||||
if errors.As(err, &panicErr) {
|
||||
c.resetDB(db, panicErr.Reason)
|
||||
return E.New("database corrupted: ", panicErr.Reason)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CacheFile) update(fn func(tx *bbolt.Tx) error) (err error) {
|
||||
db := c.database()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
c.resetDB()
|
||||
r := recover()
|
||||
if r != nil {
|
||||
c.resetDB(db, r)
|
||||
err = E.New("database corrupted: ", r)
|
||||
}
|
||||
}()
|
||||
return c.DB.Update(fn)
|
||||
return db.Update(fn)
|
||||
}
|
||||
|
||||
func (c *CacheFile) resetDB() {
|
||||
c.resetAccess.Lock()
|
||||
defer c.resetAccess.Unlock()
|
||||
c.DB.Close()
|
||||
os.Remove(c.path)
|
||||
func (c *CacheFile) resetDB(failedDB *bbolt.DB, reason any) {
|
||||
c.dbAccess.Lock()
|
||||
defer c.dbAccess.Unlock()
|
||||
if c.DB != failedDB {
|
||||
return
|
||||
}
|
||||
c.logger.Error("database corrupted: ", reason, ": resetting")
|
||||
failedDB.Close()
|
||||
filemanager.Remove(c.ctx, c.path)
|
||||
db, err := bbolt.Open(c.path, 0o666, &bbolt.Options{Timeout: time.Second})
|
||||
if err == nil {
|
||||
_ = filemanager.Chown(c.ctx, c.path)
|
||||
|
||||
Reference in New Issue
Block a user