github.com/igoogolx/clash@v1.19.8/component/fakeip/pool.go (about) 1 package fakeip 2 3 import ( 4 "errors" 5 "net" 6 "strings" 7 "sync" 8 9 "github.com/igoogolx/clash/common/cache" 10 "github.com/igoogolx/clash/component/profile/cachefile" 11 "github.com/igoogolx/clash/component/trie" 12 ) 13 14 type store interface { 15 GetByHost(host string) (net.IP, bool) 16 PutByHost(host string, ip net.IP) 17 GetByIP(ip net.IP) (string, bool) 18 PutByIP(ip net.IP, host string) 19 DelByIP(ip net.IP) 20 Exist(ip net.IP) bool 21 CloneTo(store) 22 } 23 24 // Pool is an implementation about fake ip generator without storage 25 type Pool struct { 26 max uint32 27 min uint32 28 gateway uint32 29 offset uint32 30 mux sync.Mutex 31 host *trie.DomainTrie 32 ipnet *net.IPNet 33 store store 34 } 35 36 // Lookup return a fake ip with host 37 func (p *Pool) Lookup(host string) net.IP { 38 p.mux.Lock() 39 defer p.mux.Unlock() 40 41 // RFC4343: DNS Case Insensitive, we SHOULD return result with all cases. 42 host = strings.ToLower(host) 43 if ip, exist := p.store.GetByHost(host); exist { 44 return ip 45 } 46 47 ip := p.get(host) 48 p.store.PutByHost(host, ip) 49 return ip 50 } 51 52 // LookBack return host with the fake ip 53 func (p *Pool) LookBack(ip net.IP) (string, bool) { 54 p.mux.Lock() 55 defer p.mux.Unlock() 56 57 if ip = ip.To4(); ip == nil { 58 return "", false 59 } 60 61 return p.store.GetByIP(ip) 62 } 63 64 // ShouldSkipped return if domain should be skipped 65 func (p *Pool) ShouldSkipped(domain string) bool { 66 if p.host == nil { 67 return false 68 } 69 return p.host.Search(domain) != nil 70 } 71 72 // Exist returns if given ip exists in fake-ip pool 73 func (p *Pool) Exist(ip net.IP) bool { 74 p.mux.Lock() 75 defer p.mux.Unlock() 76 77 if ip = ip.To4(); ip == nil { 78 return false 79 } 80 81 return p.store.Exist(ip) 82 } 83 84 // Gateway return gateway ip 85 func (p *Pool) Gateway() net.IP { 86 return uintToIP(p.gateway) 87 } 88 89 // IPNet return raw ipnet 90 func (p *Pool) IPNet() *net.IPNet { 91 return p.ipnet 92 } 93 94 // CloneFrom clone cache from old pool 95 func (p *Pool) CloneFrom(o *Pool) { 96 o.store.CloneTo(p.store) 97 } 98 99 func (p *Pool) get(host string) net.IP { 100 current := p.offset 101 for { 102 ip := uintToIP(p.min + p.offset) 103 if !p.store.Exist(ip) { 104 break 105 } 106 107 p.offset = (p.offset + 1) % (p.max - p.min) 108 // Avoid infinite loops 109 if p.offset == current { 110 p.offset = (p.offset + 1) % (p.max - p.min) 111 ip := uintToIP(p.min + p.offset) 112 p.store.DelByIP(ip) 113 break 114 } 115 } 116 ip := uintToIP(p.min + p.offset) 117 p.store.PutByIP(ip, host) 118 return ip 119 } 120 121 func ipToUint(ip net.IP) uint32 { 122 v := uint32(ip[0]) << 24 123 v += uint32(ip[1]) << 16 124 v += uint32(ip[2]) << 8 125 v += uint32(ip[3]) 126 return v 127 } 128 129 func uintToIP(v uint32) net.IP { 130 return net.IP{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)} 131 } 132 133 type Options struct { 134 IPNet *net.IPNet 135 Host *trie.DomainTrie 136 137 // Size sets the maximum number of entries in memory 138 // and does not work if Persistence is true 139 Size int 140 141 // Persistence will save the data to disk. 142 // Size will not work and record will be fully stored. 143 Persistence bool 144 } 145 146 // New return Pool instance 147 func New(options Options) (*Pool, error) { 148 min := ipToUint(options.IPNet.IP) + 2 149 150 ones, bits := options.IPNet.Mask.Size() 151 total := 1<<uint(bits-ones) - 2 152 153 if total <= 0 { 154 return nil, errors.New("ipnet don't have valid ip") 155 } 156 157 max := min + uint32(total) - 1 158 pool := &Pool{ 159 min: min, 160 max: max, 161 gateway: min - 1, 162 host: options.Host, 163 ipnet: options.IPNet, 164 } 165 if options.Persistence { 166 pool.store = &cachefileStore{ 167 cache: cachefile.Cache(), 168 } 169 } else { 170 pool.store = &memoryStore{ 171 cache: cache.New(cache.WithSize(options.Size * 2)), 172 } 173 } 174 175 return pool, nil 176 }