Fix FakeIP storage consistency with buffered cache file writes

This commit is contained in:
世界 2026-09-21 22:33:56 +08:00
parent ab5be37361
commit b1dbabc49e
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
4 changed files with 54 additions and 46 deletions

View file

@ -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)

View file

@ -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()

View file

@ -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
}

View file

@ -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