@@ -40,21 +40,20 @@ type cache struct {
4040 // those again.
4141 optimistic bool
4242
43- // optimisticTTL is the default TTL for expired cached responses in seconds .
44- optimisticTTL uint32
43+ // optimisticTTL is the default TTL for expired cached responses.
44+ optimisticTTL time. Duration
4545
4646 // optimisticMaxAge is the maximum time entries remain in the cache when
4747 // cache is optimistic.
48- optimisticMaxAge uint32
48+ optimisticMaxAge time. Duration
4949}
5050
5151// cacheItem is a single cache entry. It's a helper type to aggregate the
5252// item-specific logic.
5353type cacheItem struct {
54- createdTime time.Time
55- m * dns.Msg
56- u string
57- ttl uint32
54+ m * dns.Msg
55+ u string
56+ ttl uint32
5857}
5958
6059// respToItem converts the pair of the response and upstream resolved the one
@@ -71,10 +70,9 @@ func (c *cache) respToItem(m *dns.Msg, u upstream.Upstream, l *slog.Logger) (ite
7170 }
7271
7372 return & cacheItem {
74- m : m ,
75- u : upsAddr ,
76- ttl : ttl ,
77- createdTime : time .Now (),
73+ m : m ,
74+ u : upsAddr ,
75+ ttl : ttl ,
7876 }
7977}
8078
@@ -85,12 +83,9 @@ const (
8583 // expTimeSz is the exact length of byte slice capable to store the
8684 // expiration time the response. It's essentially the size of a uint32.
8785 expTimeSz = 4
88- // createdTimeSz is the exact length of byte slice capable to store the
89- // creation time. It's essentially the size of a uint32.
90- createdTimeSz = 4
9186
9287 // minPackedLen is the minimum length of the packed cacheItem.
93- minPackedLen = expTimeSz + createdTimeSz + packedMsgLenSz
88+ minPackedLen = expTimeSz + packedMsgLenSz
9489)
9590
9691// pack converts the ci into bytes slice.
@@ -102,11 +97,8 @@ func (ci *cacheItem) pack() (packed []byte) {
10297 // Put expiration time.
10398 binary .BigEndian .PutUint32 (packed , uint32 (time .Now ().Unix ())+ ci .ttl )
10499
105- // Put creation time.
106- binary .BigEndian .PutUint32 (packed [expTimeSz :], uint32 (ci .createdTime .Unix ()))
107-
108100 // Put the length of the packed message.
109- binary .BigEndian .PutUint16 (packed [expTimeSz + createdTimeSz :], uint16 (pmLen ))
101+ binary .BigEndian .PutUint16 (packed [expTimeSz :], uint16 (pmLen ))
110102
111103 // Put the packed message itself.
112104 packed = append (packed , pm ... )
@@ -119,25 +111,26 @@ func (ci *cacheItem) pack() (packed []byte) {
119111
120112// unpackItem converts the data into cacheItem using req as a request message.
121113// expired is true if the item exists but expired. The expired cached items are
122- // only returned if c is optimistic. req must not be nil.
114+ // only returned if c is optimistic and optimistic max age is not exceeded. req
115+ // must not be nil.
123116func (c * cache ) unpackItem (data []byte , req * dns.Msg ) (ci * cacheItem , expired bool ) {
124117 if len (data ) < minPackedLen {
125118 return nil , false
126119 }
127120
128121 b := bytes .NewBuffer (data )
129- expire := int64 (binary .BigEndian .Uint32 (b .Next (expTimeSz )))
130- createdTime := time .Unix (int64 (binary .BigEndian .Uint32 (b .Next (createdTimeSz ))), 0 )
131- now := time .Now ().Unix ()
122+ expire := time .Unix (int64 (binary .BigEndian .Uint32 (b .Next (expTimeSz ))), 0 )
123+ now := time .Now ()
132124 var ttl uint32
133- if expired = expire <= now ; expired {
134- if ! c .optimistic {
125+ if expired = now .After (expire ); expired {
126+ optimisticExpire := expire .Add (c .optimisticMaxAge )
127+ if ! c .optimistic || now .After (optimisticExpire ) {
135128 return nil , expired
136129 }
137130
138- ttl = c .optimisticTTL
131+ ttl = uint32 ( c .optimisticTTL . Seconds ())
139132 } else {
140- ttl = uint32 (expire - now )
133+ ttl = uint32 (expire . Unix () - now . Unix () )
141134 }
142135
143136 l := int (binary .BigEndian .Uint16 (b .Next (packedMsgLenSz )))
@@ -165,9 +158,8 @@ func (c *cache) unpackItem(data []byte, req *dns.Msg) (ci *cacheItem, expired bo
165158 filterMsg (res , m , req .AuthenticatedData , doBit , ttl )
166159
167160 return & cacheItem {
168- m : res ,
169- u : string (b .Next (b .Len ())),
170- createdTime : createdTime ,
161+ m : res ,
162+ u : string (b .Next (b .Len ())),
171163 }, expired
172164}
173165
@@ -181,35 +173,50 @@ func (p *Proxy) initCache() {
181173
182174 size := p .CacheSizeBytes
183175 p .logger .Info ("cache enabled" , "size" , size )
184- p .cache = newCache (
185- size ,
186- p .CacheOptimisticAnswerTTL ,
187- p .CacheOptimisticMaxAge ,
188- p .EnableEDNSClientSubnet ,
189- p .CacheOptimistic ,
190- )
176+ p .cache = newCache (& cacheConfig {
177+ size : size ,
178+ optimisticTTL : p .CacheOptimisticAnswerTTL ,
179+ optimisticMaxAge : p .CacheOptimisticMaxAge ,
180+ withECS : p .EnableEDNSClientSubnet ,
181+ optimistic : p .CacheOptimistic ,
182+ } )
191183 p .shortFlighter = newOptimisticResolver (p )
192184}
193185
186+ // cacheConfig is the configuration structure for [cache].
187+ type cacheConfig struct {
188+ // size is the cache size in bytes.
189+ size int
190+
191+ // optimisticTTL is the default TTL for expired cached responses when
192+ // optimistic is enabled.
193+ optimisticTTL time.Duration
194+
195+ // optimisticMaxAge is the maximum time entries remain in the cache when
196+ // cache is optimistic.
197+ optimisticMaxAge time.Duration
198+
199+ // withECS enables EDNS Client Subnet support for cache.
200+ withECS bool
201+
202+ // optimistic defines if the cache should return expired items and resolve
203+ // those again.
204+ optimistic bool
205+ }
206+
194207// newCache returns a properly initialized cache. logger must not be nil.
195- func newCache (
196- size int ,
197- optimisticTTL ,
198- optimisticMaxAge uint32 ,
199- withECS ,
200- optimistic bool ,
201- ) (c * cache ) {
208+ func newCache (conf * cacheConfig ) (c * cache ) {
202209 c = & cache {
203210 itemsLock : & sync.RWMutex {},
204211 itemsWithSubnetLock : & sync.RWMutex {},
205- items : createCache (size ),
206- optimistic : optimistic ,
207- optimisticTTL : optimisticTTL ,
208- optimisticMaxAge : optimisticMaxAge ,
212+ items : createCache (conf . size ),
213+ optimistic : conf . optimistic ,
214+ optimisticTTL : conf . optimisticTTL ,
215+ optimisticMaxAge : conf . optimisticMaxAge ,
209216 }
210217
211- if withECS {
212- c .itemsWithSubnet = createCache (size )
218+ if conf . withECS {
219+ c .itemsWithSubnet = createCache (conf . size )
213220 }
214221
215222 return c
@@ -236,21 +243,9 @@ func (c *cache) get(req *dns.Msg) (ci *cacheItem, expired bool, key []byte) {
236243 c .items .Del (key )
237244 }
238245
239- if c .isOptimisticExpired (ci ) {
240- c .items .Del (key )
241-
242- return nil , false , key
243- }
244-
245246 return ci , expired , key
246247}
247248
248- // isOptimisticExpired returns true if cache is optimistic and expired item
249- // should be deleted explicitly. ci must not be nil.
250- func (c * cache ) isOptimisticExpired (ci * cacheItem ) (expired bool ) {
251- return c .optimistic && uint32 (time .Since (ci .createdTime ).Seconds ()) > ci .ttl + c .optimisticMaxAge
252- }
253-
254249// getWithSubnet returns cached item for the req if it's found by n. expired
255250// is true if the item's TTL is expired. k is the resulting key for req. It's
256251// returned to avoid recalculating it afterwards.
0 commit comments