github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/mempool/herocache/dns_cache.go (about) 1 package herocache 2 3 import ( 4 "fmt" 5 "net" 6 7 "github.com/rs/zerolog" 8 9 "github.com/onflow/flow-go/model/flow" 10 "github.com/onflow/flow-go/module" 11 "github.com/onflow/flow-go/module/mempool" 12 herocache "github.com/onflow/flow-go/module/mempool/herocache/backdata" 13 "github.com/onflow/flow-go/module/mempool/herocache/backdata/heropool" 14 "github.com/onflow/flow-go/module/mempool/stdmap" 15 ) 16 17 type DNSCache struct { 18 ipCache *stdmap.Backend 19 txtCache *stdmap.Backend 20 } 21 22 func NewDNSCache(sizeLimit uint32, logger zerolog.Logger, ipCollector module.HeroCacheMetrics, txtCollector module.HeroCacheMetrics, 23 ) *DNSCache { 24 return &DNSCache{ 25 txtCache: stdmap.NewBackend( 26 stdmap.WithBackData( 27 herocache.NewCache( 28 sizeLimit, 29 herocache.DefaultOversizeFactor, 30 heropool.LRUEjection, 31 logger.With().Str("mempool", "dns-txt-cache").Logger(), 32 txtCollector))), 33 ipCache: stdmap.NewBackend( 34 stdmap.WithBackData( 35 herocache.NewCache( 36 sizeLimit, 37 herocache.DefaultOversizeFactor, 38 heropool.LRUEjection, 39 logger.With().Str("mempool", "dns-ip-cache").Logger(), 40 ipCollector))), 41 } 42 } 43 44 // PutIpDomain adds the given ip domain into the cache. 45 func (d *DNSCache) PutIpDomain(domain string, addresses []net.IPAddr, timestamp int64) bool { 46 i := ipEntity{ 47 IpRecord: mempool.IpRecord{ 48 Domain: domain, 49 Addresses: addresses, 50 Timestamp: timestamp, 51 Locked: false, 52 }, 53 id: domainToIdentifier(domain), 54 } 55 56 return d.ipCache.Add(i) 57 } 58 59 // PutTxtRecord adds the given txt record into the cache. 60 func (d *DNSCache) PutTxtRecord(domain string, record []string, timestamp int64) bool { 61 t := txtEntity{ 62 TxtRecord: mempool.TxtRecord{ 63 Txt: domain, 64 Records: record, 65 Timestamp: timestamp, 66 Locked: false, 67 }, 68 id: domainToIdentifier(domain), 69 } 70 71 return d.txtCache.Add(t) 72 } 73 74 // GetDomainIp returns the ip domain if exists in the cache. 75 // The boolean return value determines if domain exists in the cache. 76 func (d *DNSCache) GetDomainIp(domain string) (*mempool.IpRecord, bool) { 77 entity, ok := d.ipCache.ByID(domainToIdentifier(domain)) 78 if !ok { 79 return nil, false 80 } 81 82 i, ok := entity.(ipEntity) 83 if !ok { 84 return nil, false 85 } 86 ipRecord := i.IpRecord 87 88 return &ipRecord, true 89 } 90 91 // GetTxtRecord returns the txt record if exists in the cache. 92 // The boolean return value determines if record exists in the cache. 93 func (d *DNSCache) GetTxtRecord(domain string) (*mempool.TxtRecord, bool) { 94 entity, ok := d.txtCache.ByID(domainToIdentifier(domain)) 95 if !ok { 96 return nil, false 97 } 98 99 t, ok := entity.(txtEntity) 100 if !ok { 101 return nil, false 102 } 103 txtRecord := t.TxtRecord 104 105 return &txtRecord, true 106 } 107 108 // RemoveIp removes an ip domain from cache. 109 func (d *DNSCache) RemoveIp(domain string) bool { 110 return d.ipCache.Remove(domainToIdentifier(domain)) 111 } 112 113 // RemoveTxt removes a txt record from cache. 114 func (d *DNSCache) RemoveTxt(domain string) bool { 115 return d.txtCache.Remove(domainToIdentifier(domain)) 116 } 117 118 // LockIPDomain locks an ip address dns record if exists in the cache. 119 // The boolean return value determines whether attempt on locking was successful. 120 // 121 // A locking attempt is successful when the domain record exists in the cache and has not 122 // been locked before. 123 // Once a domain record gets locked the only way to unlock it is through updating that record. 124 // 125 // The locking process is defined to record that a resolving attempt is ongoing for an expired domain. 126 // So the locking happens to avoid any other parallel resolving 127 func (d *DNSCache) LockIPDomain(domain string) (bool, error) { 128 locked := false 129 err := d.ipCache.Run(func(backdata mempool.BackData) error { 130 id := domainToIdentifier(domain) 131 entity, ok := backdata.ByID(id) 132 if !ok { 133 return fmt.Errorf("ip record does not exist in cache for locking: %s", domain) 134 } 135 136 record, ok := entity.(ipEntity) 137 if !ok { 138 return fmt.Errorf("unexpected type retrieved, expected: %T, obtained: %T", ipEntity{}, entity) 139 } 140 141 if record.Locked { 142 return nil // record has already been locked 143 } 144 145 record.Locked = true 146 147 if _, removed := backdata.Remove(id); !removed { 148 return fmt.Errorf("ip record could not be removed from backdata") 149 } 150 151 if added := backdata.Add(id, record); !added { 152 return fmt.Errorf("updated ip record could not be added to back data") 153 } 154 155 locked = record.Locked 156 return nil 157 }) 158 159 return locked, err 160 } 161 162 // UpdateIPDomain updates the dns record for the given ip domain with the new address and timestamp values. 163 func (d *DNSCache) UpdateIPDomain(domain string, addresses []net.IPAddr, timestamp int64) error { 164 return d.ipCache.Run(func(backdata mempool.BackData) error { 165 id := domainToIdentifier(domain) 166 167 // removes old entry if exists. 168 backdata.Remove(id) 169 170 ipRecord := ipEntity{ 171 IpRecord: mempool.IpRecord{ 172 Domain: domain, 173 Addresses: addresses, 174 Timestamp: timestamp, 175 Locked: false, // by default an ip record is unlocked. 176 }, 177 id: id, 178 } 179 180 if added := backdata.Add(id, ipRecord); !added { 181 return fmt.Errorf("updated ip record could not be added to backdata") 182 } 183 184 return nil 185 }) 186 } 187 188 // UpdateTxtRecord updates the dns record for the given txt domain with the new address and timestamp values. 189 func (d *DNSCache) UpdateTxtRecord(txt string, records []string, timestamp int64) error { 190 return d.txtCache.Run(func(backdata mempool.BackData) error { 191 id := domainToIdentifier(txt) 192 193 // removes old entry if exists. 194 backdata.Remove(id) 195 196 txtRecord := txtEntity{ 197 TxtRecord: mempool.TxtRecord{ 198 Txt: txt, 199 Records: records, 200 Timestamp: timestamp, 201 Locked: false, // by default a txt record is unlocked. 202 }, 203 id: id, 204 } 205 206 if added := backdata.Add(id, txtRecord); !added { 207 return fmt.Errorf("updated txt record could not be added to backdata") 208 } 209 210 return nil 211 }) 212 } 213 214 // LockTxtRecord locks a txt address dns record if exists in the cache. 215 // The boolean return value determines whether attempt on locking was successful. 216 // 217 // A locking attempt is successful when the domain record exists in the cache and has not 218 // been locked before. 219 // Once a domain record gets locked the only way to unlock it is through updating that record. 220 // 221 // The locking process is defined to record that a resolving attempt is ongoing for an expired domain. 222 // So the locking happens to avoid any other parallel resolving. 223 func (d *DNSCache) LockTxtRecord(txt string) (bool, error) { 224 locked := false 225 err := d.txtCache.Run(func(backdata mempool.BackData) error { 226 id := domainToIdentifier(txt) 227 entity, ok := backdata.ByID(id) 228 if !ok { 229 return fmt.Errorf("txt record does not exist in cache for locking: %s", txt) 230 } 231 232 record, ok := entity.(txtEntity) 233 if !ok { 234 return fmt.Errorf("unexpected type retrieved, expected: %T, obtained: %T", txtEntity{}, entity) 235 } 236 237 if record.Locked { 238 return nil // record has already been locked 239 } 240 241 record.Locked = true 242 243 if _, removed := backdata.Remove(id); !removed { 244 return fmt.Errorf("txt record could not be removed from backdata") 245 } 246 247 if added := backdata.Add(id, record); !added { 248 return fmt.Errorf("updated txt record could not be added to back data") 249 } 250 251 locked = record.Locked 252 return nil 253 }) 254 255 return locked, err 256 } 257 258 // Size returns total domains maintained into this cache. 259 // The first returned value determines number of ip domains. 260 // The second returned value determines number of txt records. 261 func (d DNSCache) Size() (uint, uint) { 262 return d.ipCache.Size(), d.txtCache.Size() 263 } 264 265 // ipEntity is a dns cache entry for ip records. 266 type ipEntity struct { 267 mempool.IpRecord 268 // caching identifier to avoid cpu overhead 269 // per query. 270 id flow.Identifier 271 } 272 273 func (i ipEntity) ID() flow.Identifier { 274 return i.id 275 } 276 277 func (i ipEntity) Checksum() flow.Identifier { 278 return domainToIdentifier(i.IpRecord.Domain) 279 } 280 281 // txtEntity is a dns cache entry for txt records. 282 type txtEntity struct { 283 mempool.TxtRecord 284 // caching identifier to avoid cpu overhead 285 // per query. 286 id flow.Identifier 287 } 288 289 func (t txtEntity) ID() flow.Identifier { 290 return t.id 291 } 292 293 func (t txtEntity) Checksum() flow.Identifier { 294 return domainToIdentifier(t.TxtRecord.Txt) 295 } 296 297 func domainToIdentifier(domain string) flow.Identifier { 298 return flow.MakeID(domain) 299 }