github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/p2p/dns/cache.go (about)

     1  package dns
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"time"
     7  
     8  	"github.com/rs/zerolog"
     9  
    10  	"github.com/onflow/flow-go/module/mempool"
    11  )
    12  
    13  // DefaultTimeToLive is the default duration a dns result is cached.
    14  const (
    15  	// DefaultTimeToLive is the default duration a dns result is cached.
    16  	DefaultTimeToLive = 5 * time.Minute
    17  	DefaultCacheSize  = 10e3
    18  	cacheEntryExists  = true
    19  	cacheEntryFresh   = true // TTL yet has not reached
    20  )
    21  
    22  // ipResolutionResult encapsulates result of resolving an ip address within dns cache.
    23  type ipResolutionResult struct {
    24  	addresses []net.IPAddr
    25  	exists    bool // determines whether the domain exists in the cache.
    26  	fresh     bool // determines whether the domain cache is fresh, i.e., TTL has not yet reached.
    27  	locked    bool // determines whether the domain record is currently being resolved by another thread.
    28  }
    29  
    30  // txtResolutionResult encapsulates result of resolving a txt record within dns cache.
    31  type txtResolutionResult struct {
    32  	records []string
    33  	exists  bool // determines whether the domain exists in the cache.
    34  	fresh   bool // determines whether the domain cache is fresh, i.e., TTL has not yet reached.
    35  	locked  bool // determines whether the domain record is currently being resolved by another thread.
    36  }
    37  
    38  // cache is a ttl-based cache for dns entries
    39  type cache struct {
    40  	ttl    time.Duration // time-to-live for cache entry
    41  	dCache mempool.DNSCache
    42  	logger zerolog.Logger
    43  }
    44  
    45  func newCache(logger zerolog.Logger, dnsCache mempool.DNSCache) *cache {
    46  	return &cache{
    47  		ttl:    DefaultTimeToLive,
    48  		dCache: dnsCache,
    49  		logger: logger.With().Str("component", "dns-cache").Logger(),
    50  	}
    51  }
    52  
    53  // resolveIPCache resolves the domain through the cache if it is available.
    54  func (c *cache) resolveIPCache(domain string) *ipResolutionResult {
    55  	record, ok := c.dCache.GetDomainIp(domain)
    56  	currentTimeStamp := runtimeNano()
    57  	if !ok {
    58  		// does not exist
    59  		return &ipResolutionResult{
    60  			addresses: nil,
    61  			exists:    !cacheEntryExists,
    62  			fresh:     !cacheEntryFresh,
    63  			locked:    false,
    64  		}
    65  	}
    66  	c.logger.Trace().
    67  		Str("domain", domain).
    68  		Str("address", fmt.Sprintf("%v", record.Addresses)).
    69  		Int64("record_timestamp", record.Timestamp).
    70  		Int64("current_timestamp", currentTimeStamp).
    71  		Bool("record_locked", record.Locked).
    72  		Msg("dns record retrieved")
    73  
    74  	if time.Duration(currentTimeStamp-record.Timestamp) > c.ttl {
    75  		// exists but fresh
    76  		return &ipResolutionResult{
    77  			addresses: record.Addresses,
    78  			exists:    cacheEntryExists,
    79  			fresh:     !cacheEntryFresh,
    80  			locked:    record.Locked,
    81  		}
    82  	}
    83  
    84  	// exists and fresh
    85  	return &ipResolutionResult{
    86  		addresses: record.Addresses,
    87  		exists:    cacheEntryExists,
    88  		fresh:     cacheEntryFresh,
    89  		locked:    record.Locked,
    90  	}
    91  }
    92  
    93  // resolveIPCache resolves the txt through the cache if it is available.
    94  func (c *cache) resolveTXTCache(txt string) *txtResolutionResult {
    95  	record, ok := c.dCache.GetTxtRecord(txt)
    96  	currentTimeStamp := runtimeNano()
    97  	if !ok {
    98  		// does not exist
    99  		return &txtResolutionResult{
   100  			records: nil,
   101  			exists:  !cacheEntryExists,
   102  			fresh:   !cacheEntryFresh,
   103  			locked:  false,
   104  		}
   105  	}
   106  	c.logger.Trace().
   107  		Str("txt", txt).
   108  		Str("address", fmt.Sprintf("%v", record.Records)).
   109  		Int64("record_timestamp", record.Timestamp).
   110  		Int64("current_timestamp", currentTimeStamp).
   111  		Bool("record_locked", record.Locked).
   112  		Msg("dns record retrieved")
   113  
   114  	if time.Duration(currentTimeStamp-record.Timestamp) > c.ttl {
   115  		// exists but fresh
   116  		return &txtResolutionResult{
   117  			records: record.Records,
   118  			exists:  cacheEntryExists,
   119  			fresh:   !cacheEntryFresh,
   120  			locked:  record.Locked,
   121  		}
   122  	}
   123  
   124  	// exists and fresh
   125  	return &txtResolutionResult{
   126  		records: record.Records,
   127  		exists:  cacheEntryExists,
   128  		fresh:   cacheEntryFresh,
   129  		locked:  record.Locked,
   130  	}
   131  }
   132  
   133  // updateIPCache updates the cache entry for the domain.
   134  func (c *cache) updateIPCache(domain string, addr []net.IPAddr) {
   135  	lg := c.logger.With().
   136  		Str("domain", domain).
   137  		Str("address", fmt.Sprintf("%v", addr)).Logger()
   138  
   139  	timestamp := runtimeNano()
   140  
   141  	err := c.dCache.UpdateIPDomain(domain, addr, runtimeNano())
   142  	if err != nil {
   143  		lg.Error().Err(err).Msg("could not update ip record")
   144  		return
   145  	}
   146  
   147  	ipSize, txtSize := c.dCache.Size()
   148  	lg.Trace().
   149  		Int64("timestamp", timestamp).
   150  		Uint("ip_size", ipSize).
   151  		Uint("txt_size", txtSize).
   152  		Msg("dns cache updated")
   153  }
   154  
   155  // updateTXTCache updates the cache entry for the txt record.
   156  func (c *cache) updateTXTCache(txt string, record []string) {
   157  	lg := c.logger.With().
   158  		Str("txt", txt).
   159  		Strs("record", record).Logger()
   160  
   161  	timestamp := runtimeNano()
   162  	err := c.dCache.UpdateTxtRecord(txt, record, runtimeNano())
   163  	if err != nil {
   164  		lg.Error().Err(err).Msg("could not update txt record")
   165  		return
   166  	}
   167  
   168  	ipSize, txtSize := c.dCache.Size()
   169  	lg.Trace().
   170  		Int64("timestamp", timestamp).
   171  		Uint("ip_size", ipSize).
   172  		Uint("txt_size", txtSize).
   173  		Msg("dns cache updated")
   174  }
   175  
   176  // shouldResolveIP returns true if there is no other concurrent attempt ongoing for resolving the domain.
   177  func (c *cache) shouldResolveIP(domain string) bool {
   178  	lg := c.logger.With().
   179  		Str("domain", domain).Logger()
   180  
   181  	locked, err := c.dCache.LockIPDomain(domain)
   182  	if err != nil {
   183  		lg.Error().Err(err).Msg("cannot lock ip domain")
   184  		return false
   185  	}
   186  
   187  	lg.Trace().Bool("locked", locked).Msg("attempt on locking ip domain")
   188  	return locked
   189  }
   190  
   191  // shouldResolveTxt returns true if there is no other concurrent attempt ongoing for resolving the txt.
   192  func (c *cache) shouldResolveTXT(txt string) bool {
   193  	lg := c.logger.With().
   194  		Str("txt", txt).Logger()
   195  
   196  	locked, err := c.dCache.LockTxtRecord(txt)
   197  	if err != nil {
   198  		lg.Error().Err(err).Msg("cannot lock txt domain")
   199  		return false
   200  	}
   201  
   202  	lg.Trace().Bool("locked", locked).Msg("attempt on locking txt domain")
   203  	return locked
   204  }
   205  
   206  // invalidateIPCacheEntry atomically invalidates ip cache entry. Boolean variable determines whether invalidation
   207  // is successful.
   208  func (c *cache) invalidateIPCacheEntry(domain string) bool {
   209  	return c.dCache.RemoveIp(domain)
   210  }
   211  
   212  // invalidateTXTCacheEntry atomically invalidates txt cache entry. Boolean variable determines whether invalidation
   213  // is successful.
   214  func (c *cache) invalidateTXTCacheEntry(txt string) bool {
   215  	return c.dCache.RemoveTxt(txt)
   216  }