Skip to content

Commit bdd2a7e

Browse files
committed
all: imp optimistic cache
1 parent 8c5f841 commit bdd2a7e

File tree

10 files changed

+230
-111
lines changed

10 files changed

+230
-111
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ Usage of ./dnsproxy:
9999
Listening addresses.
100100
--max-go-routines=uint
101101
Set the maximum number of go routines. A zero value will not not set a maximum.
102+
--optimistic-answers-ttl
103+
Default TTL value for expired DNS entries in optimistic cache. Default: 30s
104+
--optimistic-max-age
105+
Period of time after which entries are removed from optimistic cache in human-readable form. Default: 12h.
102106
--output=path/-o path
103107
Path to the log file.
104108
--pending-requests-enabled

internal/cmd/args.go

Lines changed: 65 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ const (
4242
timeoutIdx
4343
cacheMinTTLIdx
4444
cacheMaxTTLIdx
45+
cacheOptimisticAnswerTTLIdx
46+
cacheOptimisticMaxAgeIdx
4547
cacheSizeBytesIdx
4648
ratelimitIdx
4749
ratelimitSubnetLenIPv4Idx
@@ -247,6 +249,18 @@ var commandLineOptions = []*commandLineOption{
247249
short: "",
248250
valueType: "uint32",
249251
},
252+
cacheOptimisticAnswerTTLIdx: {
253+
description: "Default TTL value for expired answers from optimistic cache",
254+
long: "optimistic-answers-ttl",
255+
short: "",
256+
valueType: "duration",
257+
},
258+
cacheOptimisticMaxAgeIdx: {
259+
description: "Period of time after which entries are removed from the optimistic cache",
260+
long: "optimistic-max-age",
261+
short: "",
262+
valueType: "duration",
263+
},
250264
cacheSizeBytesIdx: {
251265
description: "Cache size (in bytes). Default: 64k.",
252266
long: "cache-size",
@@ -399,55 +413,57 @@ func parseCmdLineOptions(conf *configuration) (err error) {
399413

400414
flags := flag.NewFlagSet(cmdName, flag.ContinueOnError)
401415
for i, fieldPtr := range []any{
402-
configPathIdx: &conf.ConfigPath,
403-
logOutputIdx: &conf.LogOutput,
404-
tlsCertPathIdx: &conf.TLSCertPath,
405-
tlsKeyPathIdx: &conf.TLSKeyPath,
406-
httpsServerNameIdx: &conf.HTTPSServerName,
407-
httpsUserinfoIdx: &conf.HTTPSUserinfo,
408-
dnsCryptConfigPathIdx: &conf.DNSCryptConfigPath,
409-
ednsAddrIdx: &conf.EDNSAddr,
410-
upstreamModeIdx: &conf.UpstreamMode,
411-
listenAddrsIdx: &conf.ListenAddrs,
412-
listenPortsIdx: &conf.ListenPorts,
413-
httpsListenPortsIdx: &conf.HTTPSListenPorts,
414-
tlsListenPortsIdx: &conf.TLSListenPorts,
415-
quicListenPortsIdx: &conf.QUICListenPorts,
416-
dnsCryptListenPortsIdx: &conf.DNSCryptListenPorts,
417-
upstreamsIdx: &conf.Upstreams,
418-
bootstrapDNSIdx: &conf.BootstrapDNS,
419-
fallbacksIdx: &conf.Fallbacks,
420-
privateRDNSUpstreamsIdx: &conf.PrivateRDNSUpstreams,
421-
dns64PrefixIdx: &conf.DNS64Prefix,
422-
privateSubnetsIdx: &conf.PrivateSubnets,
423-
bogusNXDomainIdx: &conf.BogusNXDomain,
424-
hostsFilesIdx: &conf.HostsFiles,
425-
timeoutIdx: &conf.Timeout,
426-
cacheMinTTLIdx: &conf.CacheMinTTL,
427-
cacheMaxTTLIdx: &conf.CacheMaxTTL,
428-
cacheSizeBytesIdx: &conf.CacheSizeBytes,
429-
ratelimitIdx: &conf.Ratelimit,
430-
ratelimitSubnetLenIPv4Idx: &conf.RatelimitSubnetLenIPv4,
431-
ratelimitSubnetLenIPv6Idx: &conf.RatelimitSubnetLenIPv6,
432-
udpBufferSizeIdx: &conf.UDPBufferSize,
433-
maxGoRoutinesIdx: &conf.MaxGoRoutines,
434-
tlsMinVersionIdx: &conf.TLSMinVersion,
435-
tlsMaxVersionIdx: &conf.TLSMaxVersion,
436-
helpIdx: &conf.help,
437-
hostsFileEnabledIdx: &conf.HostsFileEnabled,
438-
pprofIdx: &conf.Pprof,
439-
versionIdx: &conf.Version,
440-
verboseIdx: &conf.Verbose,
441-
insecureIdx: &conf.Insecure,
442-
ipv6DisabledIdx: &conf.IPv6Disabled,
443-
http3Idx: &conf.HTTP3,
444-
cacheOptimisticIdx: &conf.CacheOptimistic,
445-
cacheIdx: &conf.Cache,
446-
refuseAnyIdx: &conf.RefuseAny,
447-
enableEDNSSubnetIdx: &conf.EnableEDNSSubnet,
448-
pendingRequestsEnabledIdx: &conf.PendingRequestsEnabled,
449-
dns64Idx: &conf.DNS64,
450-
usePrivateRDNSIdx: &conf.UsePrivateRDNS,
416+
configPathIdx: &conf.ConfigPath,
417+
logOutputIdx: &conf.LogOutput,
418+
tlsCertPathIdx: &conf.TLSCertPath,
419+
tlsKeyPathIdx: &conf.TLSKeyPath,
420+
httpsServerNameIdx: &conf.HTTPSServerName,
421+
httpsUserinfoIdx: &conf.HTTPSUserinfo,
422+
dnsCryptConfigPathIdx: &conf.DNSCryptConfigPath,
423+
ednsAddrIdx: &conf.EDNSAddr,
424+
upstreamModeIdx: &conf.UpstreamMode,
425+
listenAddrsIdx: &conf.ListenAddrs,
426+
listenPortsIdx: &conf.ListenPorts,
427+
httpsListenPortsIdx: &conf.HTTPSListenPorts,
428+
tlsListenPortsIdx: &conf.TLSListenPorts,
429+
quicListenPortsIdx: &conf.QUICListenPorts,
430+
dnsCryptListenPortsIdx: &conf.DNSCryptListenPorts,
431+
upstreamsIdx: &conf.Upstreams,
432+
bootstrapDNSIdx: &conf.BootstrapDNS,
433+
fallbacksIdx: &conf.Fallbacks,
434+
privateRDNSUpstreamsIdx: &conf.PrivateRDNSUpstreams,
435+
dns64PrefixIdx: &conf.DNS64Prefix,
436+
privateSubnetsIdx: &conf.PrivateSubnets,
437+
bogusNXDomainIdx: &conf.BogusNXDomain,
438+
hostsFilesIdx: &conf.HostsFiles,
439+
timeoutIdx: &conf.Timeout,
440+
cacheMinTTLIdx: &conf.CacheMinTTL,
441+
cacheMaxTTLIdx: &conf.CacheMaxTTL,
442+
cacheOptimisticAnswerTTLIdx: &conf.CacheOptimisticAnswerTTL,
443+
cacheOptimisticMaxAgeIdx: &conf.CacheOptimisticMaxAge,
444+
cacheSizeBytesIdx: &conf.CacheSizeBytes,
445+
ratelimitIdx: &conf.Ratelimit,
446+
ratelimitSubnetLenIPv4Idx: &conf.RatelimitSubnetLenIPv4,
447+
ratelimitSubnetLenIPv6Idx: &conf.RatelimitSubnetLenIPv6,
448+
udpBufferSizeIdx: &conf.UDPBufferSize,
449+
maxGoRoutinesIdx: &conf.MaxGoRoutines,
450+
tlsMinVersionIdx: &conf.TLSMinVersion,
451+
tlsMaxVersionIdx: &conf.TLSMaxVersion,
452+
helpIdx: &conf.help,
453+
hostsFileEnabledIdx: &conf.HostsFileEnabled,
454+
pprofIdx: &conf.Pprof,
455+
versionIdx: &conf.Version,
456+
verboseIdx: &conf.Verbose,
457+
insecureIdx: &conf.Insecure,
458+
ipv6DisabledIdx: &conf.IPv6Disabled,
459+
http3Idx: &conf.HTTP3,
460+
cacheOptimisticIdx: &conf.CacheOptimistic,
461+
cacheIdx: &conf.Cache,
462+
refuseAnyIdx: &conf.RefuseAny,
463+
enableEDNSSubnetIdx: &conf.EnableEDNSSubnet,
464+
pendingRequestsEnabledIdx: &conf.PendingRequestsEnabled,
465+
dns64Idx: &conf.DNS64,
466+
usePrivateRDNSIdx: &conf.UsePrivateRDNS,
451467
} {
452468
addOption(flags, fieldPtr, commandLineOptions[i])
453469
}

internal/cmd/config.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,14 @@ type configuration struct {
107107
// greater.
108108
CacheMaxTTL uint32 `yaml:"cache-max-ttl"`
109109

110+
// CacheOptimisticAnswerTTL is the default TTL for expired cached responses
111+
// in seconds.
112+
CacheOptimisticAnswerTTL timeutil.Duration `yaml:"optimistic-answers-ttl"`
113+
114+
// CacheOptimisticMaxAge is the maximum time entries remain in the cache
115+
// when cache is optimistic.
116+
CacheOptimisticMaxAge timeutil.Duration `yaml:"optimistic-max-age"`
117+
110118
// CacheSizeBytes is the cache size in bytes. Default is 64k.
111119
CacheSizeBytes int `yaml:"cache-size"`
112120

@@ -198,14 +206,16 @@ type configuration struct {
198206
// no options have been parsed, it returns a suitable exit code and an error.
199207
func parseConfig() (conf *configuration, exitCode int, err error) {
200208
conf = &configuration{
201-
HTTPSServerName: "dnsproxy",
202-
UpstreamMode: string(proxy.UpstreamModeLoadBalance),
203-
CacheSizeBytes: 64 * 1024,
204-
Timeout: timeutil.Duration(10 * time.Second),
205-
RatelimitSubnetLenIPv4: 24,
206-
RatelimitSubnetLenIPv6: 56,
207-
HostsFileEnabled: true,
208-
PendingRequestsEnabled: true,
209+
HTTPSServerName: "dnsproxy",
210+
UpstreamMode: string(proxy.UpstreamModeLoadBalance),
211+
CacheSizeBytes: 64 * 1024,
212+
Timeout: timeutil.Duration(10 * time.Second),
213+
CacheOptimisticAnswerTTL: timeutil.Duration(30 * time.Second),
214+
CacheOptimisticMaxAge: timeutil.Duration(12 * time.Hour),
215+
RatelimitSubnetLenIPv4: 24,
216+
RatelimitSubnetLenIPv6: 56,
217+
HostsFileEnabled: true,
218+
PendingRequestsEnabled: true,
209219
}
210220

211221
err = parseCmdLineOptions(conf)

internal/cmd/proxy.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,16 @@ func createProxyConfig(
5858
RatelimitSubnetLenIPv4: conf.RatelimitSubnetLenIPv4,
5959
RatelimitSubnetLenIPv6: conf.RatelimitSubnetLenIPv6,
6060

61-
Ratelimit: conf.Ratelimit,
62-
CacheEnabled: conf.Cache,
63-
CacheSizeBytes: conf.CacheSizeBytes,
64-
CacheMinTTL: conf.CacheMinTTL,
65-
CacheMaxTTL: conf.CacheMaxTTL,
66-
CacheOptimistic: conf.CacheOptimistic,
67-
RefuseAny: conf.RefuseAny,
68-
HTTP3: conf.HTTP3,
61+
Ratelimit: conf.Ratelimit,
62+
CacheEnabled: conf.Cache,
63+
CacheSizeBytes: conf.CacheSizeBytes,
64+
CacheMinTTL: conf.CacheMinTTL,
65+
CacheMaxTTL: conf.CacheMaxTTL,
66+
CacheOptimisticAnswerTTL: uint32(time.Duration(conf.CacheOptimisticAnswerTTL).Seconds()),
67+
CacheOptimisticMaxAge: uint32(time.Duration(conf.CacheOptimisticMaxAge).Seconds()),
68+
CacheOptimistic: conf.CacheOptimistic,
69+
RefuseAny: conf.RefuseAny,
70+
HTTP3: conf.HTTP3,
6971
// TODO(e.burkov): The following CIDRs are aimed to match any address.
7072
// This is not quite proper approach to be used by default so think
7173
// about configuring it.

proxy/cache.go

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,22 @@ type cache struct {
3939
// optimistic defines if the cache should return expired items and resolve
4040
// those again.
4141
optimistic bool
42+
43+
// optimisticTTL is the default TTL for expired cached responses in seconds.
44+
optimisticTTL uint32
45+
46+
// optimisticMaxAge is the maximum time entries remain in the cache when
47+
// cache is optimistic.
48+
optimisticMaxAge uint32
4249
}
4350

4451
// cacheItem is a single cache entry. It's a helper type to aggregate the
4552
// item-specific logic.
4653
type cacheItem struct {
47-
// m contains the cached response.
48-
m *dns.Msg
49-
50-
// u contains an address of the upstream which resolved m.
51-
u string
52-
53-
// ttl is the time-to-live value for the item. Should be set before calling
54-
// [cacheItem.pack].
55-
ttl uint32
54+
createdTime time.Time
55+
m *dns.Msg
56+
u string
57+
ttl uint32
5658
}
5759

5860
// respToItem converts the pair of the response and upstream resolved the one
@@ -69,9 +71,10 @@ func (c *cache) respToItem(m *dns.Msg, u upstream.Upstream, l *slog.Logger) (ite
6971
}
7072

7173
return &cacheItem{
72-
m: m,
73-
u: upsAddr,
74-
ttl: ttl,
74+
m: m,
75+
u: upsAddr,
76+
ttl: ttl,
77+
createdTime: time.Now(),
7578
}
7679
}
7780

@@ -82,9 +85,12 @@ const (
8285
// expTimeSz is the exact length of byte slice capable to store the
8386
// expiration time the response. It's essentially the size of a uint32.
8487
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
8591

8692
// minPackedLen is the minimum length of the packed cacheItem.
87-
minPackedLen = expTimeSz + packedMsgLenSz
93+
minPackedLen = expTimeSz + createdTimeSz + packedMsgLenSz
8894
)
8995

9096
// pack converts the ci into bytes slice.
@@ -96,8 +102,11 @@ func (ci *cacheItem) pack() (packed []byte) {
96102
// Put expiration time.
97103
binary.BigEndian.PutUint32(packed, uint32(time.Now().Unix())+ci.ttl)
98104

105+
// Put creation time.
106+
binary.BigEndian.PutUint32(packed[expTimeSz:], uint32(ci.createdTime.Unix()))
107+
99108
// Put the length of the packed message.
100-
binary.BigEndian.PutUint16(packed[expTimeSz:], uint16(pmLen))
109+
binary.BigEndian.PutUint16(packed[expTimeSz+createdTimeSz:], uint16(pmLen))
101110

102111
// Put the packed message itself.
103112
packed = append(packed, pm...)
@@ -108,9 +117,6 @@ func (ci *cacheItem) pack() (packed []byte) {
108117
return packed
109118
}
110119

111-
// optimisticTTL is the default TTL for expired cached responses in seconds.
112-
const optimisticTTL = 10
113-
114120
// unpackItem converts the data into cacheItem using req as a request message.
115121
// expired is true if the item exists but expired. The expired cached items are
116122
// only returned if c is optimistic. req must not be nil.
@@ -121,14 +127,15 @@ func (c *cache) unpackItem(data []byte, req *dns.Msg) (ci *cacheItem, expired bo
121127

122128
b := bytes.NewBuffer(data)
123129
expire := int64(binary.BigEndian.Uint32(b.Next(expTimeSz)))
130+
createdTime := time.Unix(int64(binary.BigEndian.Uint32(b.Next(createdTimeSz))), 0)
124131
now := time.Now().Unix()
125132
var ttl uint32
126133
if expired = expire <= now; expired {
127134
if !c.optimistic {
128135
return nil, expired
129136
}
130137

131-
ttl = optimisticTTL
138+
ttl = c.optimisticTTL
132139
} else {
133140
ttl = uint32(expire - now)
134141
}
@@ -158,8 +165,9 @@ func (c *cache) unpackItem(data []byte, req *dns.Msg) (ci *cacheItem, expired bo
158165
filterMsg(res, m, req.AuthenticatedData, doBit, ttl)
159166

160167
return &cacheItem{
161-
m: res,
162-
u: string(b.Next(b.Len())),
168+
m: res,
169+
u: string(b.Next(b.Len())),
170+
createdTime: createdTime,
163171
}, expired
164172
}
165173

@@ -173,18 +181,31 @@ func (p *Proxy) initCache() {
173181

174182
size := p.CacheSizeBytes
175183
p.logger.Info("cache enabled", "size", size)
176-
177-
p.cache = newCache(size, p.EnableEDNSClientSubnet, p.CacheOptimistic)
184+
p.cache = newCache(
185+
size,
186+
p.CacheOptimisticAnswerTTL,
187+
p.CacheOptimisticMaxAge,
188+
p.EnableEDNSClientSubnet,
189+
p.CacheOptimistic,
190+
)
178191
p.shortFlighter = newOptimisticResolver(p)
179192
}
180193

181194
// newCache returns a properly initialized cache. logger must not be nil.
182-
func newCache(size int, withECS, optimistic bool) (c *cache) {
195+
func newCache(
196+
size int,
197+
optimisticTTL,
198+
optimisticMaxAge uint32,
199+
withECS,
200+
optimistic bool,
201+
) (c *cache) {
183202
c = &cache{
184203
itemsLock: &sync.RWMutex{},
185204
itemsWithSubnetLock: &sync.RWMutex{},
186205
items: createCache(size),
187206
optimistic: optimistic,
207+
optimisticTTL: optimisticTTL,
208+
optimisticMaxAge: optimisticMaxAge,
188209
}
189210

190211
if withECS {
@@ -215,9 +236,19 @@ func (c *cache) get(req *dns.Msg) (ci *cacheItem, expired bool, key []byte) {
215236
c.items.Del(key)
216237
}
217238

239+
if c.isOptimisticExpired(ci) {
240+
c.items.Del(key)
241+
242+
return nil, false, key
243+
}
244+
218245
return ci, expired, key
219246
}
220247

248+
func (c *cache) isOptimisticExpired(ci *cacheItem) (expired bool) {
249+
return c.optimistic && uint32(time.Since(ci.createdTime).Seconds()) > ci.ttl+c.optimisticMaxAge
250+
}
251+
221252
// getWithSubnet returns cached item for the req if it's found by n. expired
222253
// is true if the item's TTL is expired. k is the resulting key for req. It's
223254
// returned to avoid recalculating it afterwards.

0 commit comments

Comments
 (0)