mirror of
https://github.com/AdguardTeam/dnsproxy.git
synced 2026-10-12 02:01:26 +00:00
74 lines
2.0 KiB
Go
74 lines
2.0 KiB
Go
package proxy
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"log/slog"
|
|
|
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
|
"github.com/AdguardTeam/golibs/syncutil"
|
|
)
|
|
|
|
// cachingResolver is the DNS resolver that is also able to cache responses.
|
|
type cachingResolver interface {
|
|
// replyFromUpstream returns true if the request from dctx is successfully
|
|
// resolved and the response may be cached.
|
|
//
|
|
// TODO(e.burkov): Find out when ok can be false with nil err.
|
|
replyFromUpstream(ctx context.Context, dctx *DNSContext) (ok bool, err error)
|
|
|
|
// cacheResp caches the response from dctx.
|
|
cacheResp(dctx *DNSContext)
|
|
}
|
|
|
|
// type check
|
|
var _ cachingResolver = (*Proxy)(nil)
|
|
|
|
// unit is a convenient alias for struct{}.
|
|
type unit = struct{}
|
|
|
|
// optimisticResolver is used to eventually resolve expired cached requests.
|
|
type optimisticResolver struct {
|
|
reqs *syncutil.Map[string, unit]
|
|
cr cachingResolver
|
|
}
|
|
|
|
// newOptimisticResolver returns the new resolver for expired cached requests.
|
|
// cr must not be nil.
|
|
func newOptimisticResolver(cr cachingResolver) (s *optimisticResolver) {
|
|
return &optimisticResolver{
|
|
reqs: syncutil.NewMap[string, unit](),
|
|
cr: cr,
|
|
}
|
|
}
|
|
|
|
// resolveOnce tries to resolve the request from dctx but only a single request
|
|
// with the same key at the same period of time. It runs in a separate
|
|
// goroutine. Do not pass the *DNSContext which is used elsewhere since it
|
|
// isn't intended to be used concurrently.
|
|
func (s *optimisticResolver) resolveOnce(
|
|
ctx context.Context,
|
|
dctx *DNSContext,
|
|
key []byte,
|
|
l *slog.Logger,
|
|
) {
|
|
defer slogutil.RecoverAndLog(ctx, l)
|
|
|
|
keyHexed := hex.EncodeToString(key)
|
|
if _, ok := s.reqs.LoadOrStore(keyHexed, unit{}); ok {
|
|
return
|
|
}
|
|
defer s.reqs.Delete(keyHexed)
|
|
|
|
// NOTE: Discard the context deadline to prevent cancellation during
|
|
// refresh.
|
|
ok, err := s.cr.replyFromUpstream(context.WithoutCancel(ctx), dctx)
|
|
if err != nil {
|
|
l.DebugContext(ctx, "resolving request for optimistic cache", slogutil.KeyError, err)
|
|
}
|
|
|
|
if ok {
|
|
s.cr.cacheResp(dctx)
|
|
}
|
|
}
|