From b1dbabc49e2aae2f1a1fc71203fef4df26a01674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 21 Sep 2026 22:33:56 +0800 Subject: [PATCH] Fix FakeIP storage consistency with buffered cache file writes --- adapter/fakeip.go | 1 - dns/transport/fakeip/memory.go | 3 -- dns/transport/fakeip/store.go | 66 ++++++++++++++++++++------------ experimental/cachefile/fakeip.go | 30 ++++++--------- 4 files changed, 54 insertions(+), 46 deletions(-) diff --git a/adapter/fakeip.go b/adapter/fakeip.go index 0787c1464..a17ab63e0 100644 --- a/adapter/fakeip.go +++ b/adapter/fakeip.go @@ -17,7 +17,6 @@ type FakeIPStore interface { type FakeIPStorage interface { FakeIPMetadata() *FakeIPMetadata FakeIPSaveMetadata(metadata *FakeIPMetadata) error - FakeIPSaveMetadataAsync(metadata *FakeIPMetadata) FakeIPStore(address netip.Addr, domain string) error FakeIPStoreAsync(address netip.Addr, domain string, logger logger.Logger) FakeIPLoad(address netip.Addr) (string, bool) diff --git a/dns/transport/fakeip/memory.go b/dns/transport/fakeip/memory.go index 0cf8ecc7d..71ad4a8f6 100644 --- a/dns/transport/fakeip/memory.go +++ b/dns/transport/fakeip/memory.go @@ -34,9 +34,6 @@ func (s *MemoryStorage) FakeIPSaveMetadata(metadata *adapter.FakeIPMetadata) err return nil } -func (s *MemoryStorage) FakeIPSaveMetadataAsync(metadata *adapter.FakeIPMetadata) { -} - func (s *MemoryStorage) FakeIPStore(address netip.Addr, domain string) error { s.addressAccess.Lock() s.domainAccess.Lock() diff --git a/dns/transport/fakeip/store.go b/dns/transport/fakeip/store.go index 90d27732f..aa948a162 100644 --- a/dns/transport/fakeip/store.go +++ b/dns/transport/fakeip/store.go @@ -11,6 +11,8 @@ import ( "github.com/sagernet/sing/service" ) +const reservedAddressCount = 1024 + var _ adapter.FakeIPStore = (*Store)(nil) type Store struct { @@ -25,6 +27,7 @@ type Store struct { addressAccess sync.Mutex inet4Current netip.Addr inet6Current netip.Addr + reservedCount int } func NewStore(ctx context.Context, logger logger.Logger, inet4Range netip.Prefix, inet6Range netip.Prefix) *Store { @@ -59,6 +62,14 @@ func broadcastAddress(prefix netip.Prefix) netip.Addr { return netip.AddrFrom16(raw) } +func nextAddress(addressRange netip.Prefix, last netip.Addr, current netip.Addr) netip.Addr { + address := current.Next() + if address == last || !addressRange.Contains(address) { + address = addressRange.Addr().Next().Next() + } + return address +} + func (s *Store) Start() error { var storage adapter.FakeIPStorage cacheFile := service.FromContext[adapter.CacheFile](s.ctx) @@ -117,35 +128,42 @@ func (s *Store) Create(domain string, isIPv6 bool) (netip.Addr, error) { return address, nil } + if !isIPv6 && !s.inet4Current.IsValid() { + return netip.Addr{}, E.New("missing IPv4 fakeip address range") + } else if isIPv6 && !s.inet6Current.IsValid() { + return netip.Addr{}, E.New("missing IPv6 fakeip address range") + } + if s.reservedCount == 0 { + metadata := &adapter.FakeIPMetadata{ + Inet4Range: s.inet4Range, + Inet6Range: s.inet6Range, + Inet4Current: s.inet4Current, + Inet6Current: s.inet6Current, + } + for range reservedAddressCount { + if metadata.Inet4Current.IsValid() { + metadata.Inet4Current = nextAddress(s.inet4Range, s.inet4Last, metadata.Inet4Current) + } + if metadata.Inet6Current.IsValid() { + metadata.Inet6Current = nextAddress(s.inet6Range, s.inet6Last, metadata.Inet6Current) + } + } + err := s.storage.FakeIPSaveMetadata(metadata) + if err != nil { + return netip.Addr{}, E.Cause(err, "save fakeip metadata") + } + s.reservedCount = reservedAddressCount + } + s.reservedCount-- var address netip.Addr if !isIPv6 { - if !s.inet4Current.IsValid() { - return netip.Addr{}, E.New("missing IPv4 fakeip address range") - } - nextAddress := s.inet4Current.Next() - if nextAddress == s.inet4Last || !s.inet4Range.Contains(nextAddress) { - nextAddress = s.inet4Range.Addr().Next().Next() - } - s.inet4Current = nextAddress - address = nextAddress + s.inet4Current = nextAddress(s.inet4Range, s.inet4Last, s.inet4Current) + address = s.inet4Current } else { - if !s.inet6Current.IsValid() { - return netip.Addr{}, E.New("missing IPv6 fakeip address range") - } - nextAddress := s.inet6Current.Next() - if nextAddress == s.inet6Last || !s.inet6Range.Contains(nextAddress) { - nextAddress = s.inet6Range.Addr().Next().Next() - } - s.inet6Current = nextAddress - address = nextAddress + s.inet6Current = nextAddress(s.inet6Range, s.inet6Last, s.inet6Current) + address = s.inet6Current } s.storage.FakeIPStoreAsync(address, domain, s.logger) - s.storage.FakeIPSaveMetadataAsync(&adapter.FakeIPMetadata{ - Inet4Range: s.inet4Range, - Inet6Range: s.inet6Range, - Inet4Current: s.inet4Current, - Inet6Current: s.inet6Current, - }) return address, nil } diff --git a/experimental/cachefile/fakeip.go b/experimental/cachefile/fakeip.go index 307f8ff0d..ba8764dc0 100644 --- a/experimental/cachefile/fakeip.go +++ b/experimental/cachefile/fakeip.go @@ -1,6 +1,7 @@ package cachefile import ( + "bytes" "net/netip" "os" @@ -21,7 +22,7 @@ var ( func (c *CacheFile) FakeIPMetadata() *adapter.FakeIPMetadata { var metadata adapter.FakeIPMetadata - err := c.batch(func(tx *bbolt.Tx) error { + err := c.view(func(tx *bbolt.Tx) error { bucket := tx.Bucket(bucketFakeIP) if bucket == nil { return os.ErrNotExist @@ -30,10 +31,6 @@ func (c *CacheFile) FakeIPMetadata() *adapter.FakeIPMetadata { if len(metadataBinary) == 0 { return os.ErrInvalid } - err := bucket.Delete(keyMetadata) - if err != nil { - return err - } return metadata.UnmarshalBinary(metadataBinary) }) if err != nil { @@ -43,17 +40,13 @@ func (c *CacheFile) FakeIPMetadata() *adapter.FakeIPMetadata { } func (c *CacheFile) FakeIPSaveMetadata(metadata *adapter.FakeIPMetadata) error { - c.FakeIPSaveMetadataAsync(metadata) - c.Flush() - return nil -} - -func (c *CacheFile) FakeIPSaveMetadataAsync(metadata *adapter.FakeIPMetadata) { c.pendingAccess.Lock() - defer c.pendingAccess.Unlock() added := c.pending.fakeIPMetadata == nil c.pending.fakeIPMetadata = metadata c.enqueueLocked(added, 0) + c.pendingAccess.Unlock() + c.Flush() + return nil } func putFakeIPMetadata(tx *bbolt.Tx, metadata *adapter.FakeIPMetadata) error { @@ -79,23 +72,24 @@ func (c *CacheFile) FakeIPStoreAsync(address netip.Addr, domain string, logger l } func (c *CacheFile) queueFakeIP(address netip.Addr, domain string) { + oldDomain, loaded := c.FakeIPLoad(address) c.pendingAccess.Lock() defer c.pendingAccess.Unlock() - oldDomain, loaded := c.pending.fakeIPDomain[address] if loaded { if address.Is4() { - delete(c.pending.fakeIPAddress4, oldDomain) + c.pending.fakeIPAddress4[oldDomain] = netip.Addr{} } else { - delete(c.pending.fakeIPAddress6, oldDomain) + c.pending.fakeIPAddress6[oldDomain] = netip.Addr{} } } + pendingDomain, pendingLoaded := c.pending.fakeIPDomain[address] c.pending.fakeIPDomain[address] = domain if address.Is4() { c.pending.fakeIPAddress4[domain] = address } else { c.pending.fakeIPAddress6[domain] = address } - c.enqueueLocked(!loaded, len(domain)-len(oldDomain)) + c.enqueueLocked(!pendingLoaded, len(domain)-len(pendingDomain)) } func putFakeIP(tx *bbolt.Tx, address netip.Addr, domain string) error { @@ -117,7 +111,7 @@ func putFakeIP(tx *bbolt.Tx, address netip.Addr, domain string) error { if err != nil { return err } - if oldDomain != nil { + if oldDomain != nil && bytes.Equal(bucket.Get(oldDomain), addressBytes) { err = bucket.Delete(oldDomain) if err != nil { return err @@ -164,7 +158,7 @@ func (c *CacheFile) FakeIPLoadDomain(domain string, isIPv6 bool) (netip.Addr, bo } c.pendingAccess.RUnlock() if cached { - return address, true + return address, address.IsValid() } _ = c.view(func(tx *bbolt.Tx) error { var bucket *bbolt.Bucket