github.com/kelleygo/clashcore@v1.0.2/component/fakeip/pool.go (about)

     1  package fakeip
     2  
     3  import (
     4  	"errors"
     5  	"net/netip"
     6  	"strings"
     7  	"sync"
     8  
     9  	"github.com/kelleygo/clashcore/common/nnip"
    10  	"github.com/kelleygo/clashcore/component/profile/cachefile"
    11  	"github.com/kelleygo/clashcore/component/trie"
    12  )
    13  
    14  const (
    15  	offsetKey = "key-offset-fake-ip"
    16  	cycleKey  = "key-cycle-fake-ip"
    17  )
    18  
    19  type store interface {
    20  	GetByHost(host string) (netip.Addr, bool)
    21  	PutByHost(host string, ip netip.Addr)
    22  	GetByIP(ip netip.Addr) (string, bool)
    23  	PutByIP(ip netip.Addr, host string)
    24  	DelByIP(ip netip.Addr)
    25  	Exist(ip netip.Addr) bool
    26  	CloneTo(store)
    27  	FlushFakeIP() error
    28  }
    29  
    30  // Pool is an implementation about fake ip generator without storage
    31  type Pool struct {
    32  	gateway netip.Addr
    33  	first   netip.Addr
    34  	last    netip.Addr
    35  	offset  netip.Addr
    36  	cycle   bool
    37  	mux     sync.Mutex
    38  	host    *trie.DomainTrie[struct{}]
    39  	ipnet   netip.Prefix
    40  	store   store
    41  }
    42  
    43  // Lookup return a fake ip with host
    44  func (p *Pool) Lookup(host string) netip.Addr {
    45  	p.mux.Lock()
    46  	defer p.mux.Unlock()
    47  
    48  	// RFC4343: DNS Case Insensitive, we SHOULD return result with all cases.
    49  	host = strings.ToLower(host)
    50  	if ip, exist := p.store.GetByHost(host); exist {
    51  		return ip
    52  	}
    53  
    54  	ip := p.get(host)
    55  	p.store.PutByHost(host, ip)
    56  	return ip
    57  }
    58  
    59  // LookBack return host with the fake ip
    60  func (p *Pool) LookBack(ip netip.Addr) (string, bool) {
    61  	p.mux.Lock()
    62  	defer p.mux.Unlock()
    63  
    64  	return p.store.GetByIP(ip)
    65  }
    66  
    67  // ShouldSkipped return if domain should be skipped
    68  func (p *Pool) ShouldSkipped(domain string) bool {
    69  	if p.host == nil {
    70  		return false
    71  	}
    72  	return p.host.Search(domain) != nil
    73  }
    74  
    75  // Exist returns if given ip exists in fake-ip pool
    76  func (p *Pool) Exist(ip netip.Addr) bool {
    77  	p.mux.Lock()
    78  	defer p.mux.Unlock()
    79  
    80  	return p.store.Exist(ip)
    81  }
    82  
    83  // Gateway return gateway ip
    84  func (p *Pool) Gateway() netip.Addr {
    85  	return p.gateway
    86  }
    87  
    88  // Broadcast return the last ip
    89  func (p *Pool) Broadcast() netip.Addr {
    90  	return p.last
    91  }
    92  
    93  // IPNet return raw ipnet
    94  func (p *Pool) IPNet() netip.Prefix {
    95  	return p.ipnet
    96  }
    97  
    98  // CloneFrom clone cache from old pool
    99  func (p *Pool) CloneFrom(o *Pool) {
   100  	o.store.CloneTo(p.store)
   101  }
   102  
   103  func (p *Pool) get(host string) netip.Addr {
   104  	p.offset = p.offset.Next()
   105  
   106  	if !p.offset.Less(p.last) {
   107  		p.cycle = true
   108  		p.offset = p.first
   109  	}
   110  
   111  	if p.cycle || p.store.Exist(p.offset) {
   112  		p.store.DelByIP(p.offset)
   113  	}
   114  
   115  	p.store.PutByIP(p.offset, host)
   116  	return p.offset
   117  }
   118  
   119  func (p *Pool) FlushFakeIP() error {
   120  	err := p.store.FlushFakeIP()
   121  	if err == nil {
   122  		p.cycle = false
   123  		p.offset = p.first.Prev()
   124  	}
   125  	return err
   126  }
   127  
   128  func (p *Pool) StoreState() {
   129  	if s, ok := p.store.(*cachefileStore); ok {
   130  		s.PutByHost(offsetKey, p.offset)
   131  		if p.cycle {
   132  			s.PutByHost(cycleKey, p.offset)
   133  		}
   134  	}
   135  }
   136  
   137  func (p *Pool) restoreState() {
   138  	if s, ok := p.store.(*cachefileStore); ok {
   139  		if _, exist := s.GetByHost(cycleKey); exist {
   140  			p.cycle = true
   141  		}
   142  
   143  		if offset, exist := s.GetByHost(offsetKey); exist {
   144  			if p.ipnet.Contains(offset) {
   145  				p.offset = offset
   146  			} else {
   147  				_ = p.FlushFakeIP()
   148  			}
   149  		} else if s.Exist(p.first) {
   150  			_ = p.FlushFakeIP()
   151  		}
   152  	}
   153  }
   154  
   155  type Options struct {
   156  	IPNet netip.Prefix
   157  	Host  *trie.DomainTrie[struct{}]
   158  
   159  	// Size sets the maximum number of entries in memory
   160  	// and does not work if Persistence is true
   161  	Size int
   162  
   163  	// Persistence will save the data to disk.
   164  	// Size will not work and record will be fully stored.
   165  	Persistence bool
   166  }
   167  
   168  // New return Pool instance
   169  func New(options Options) (*Pool, error) {
   170  	var (
   171  		hostAddr = options.IPNet.Masked().Addr()
   172  		gateway  = hostAddr.Next()
   173  		first    = gateway.Next().Next().Next() // default start with 198.18.0.4
   174  		last     = nnip.UnMasked(options.IPNet)
   175  	)
   176  
   177  	if !options.IPNet.IsValid() || !first.IsValid() || !first.Less(last) {
   178  		return nil, errors.New("ipnet don't have valid ip")
   179  	}
   180  
   181  	pool := &Pool{
   182  		gateway: gateway,
   183  		first:   first,
   184  		last:    last,
   185  		offset:  first.Prev(),
   186  		cycle:   false,
   187  		host:    options.Host,
   188  		ipnet:   options.IPNet,
   189  	}
   190  	if options.Persistence {
   191  		pool.store = &cachefileStore{
   192  			cache: cachefile.Cache(),
   193  		}
   194  	} else {
   195  		pool.store = newMemoryStore(options.Size)
   196  	}
   197  
   198  	pool.restoreState()
   199  
   200  	return pool, nil
   201  }