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 }