feat: context-based tracing to record delayed DNS responses (#870)

See https://github.com/ooni/probe/issues/2221

Co-authored-by: decfox <decfox@github.com>
Co-authored-by: Simone Basso <bassosimone@gmail.com>
This commit is contained in:
DecFox 2022-08-22 17:51:32 +05:30 committed by GitHub
commit 2301a30630
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 498 additions and 317 deletions

View file

@ -6,6 +6,7 @@ package measurexlite
import (
"context"
"errors"
"log"
"net"
"time"
@ -97,9 +98,20 @@ func (tx *Trace) OnDNSRoundTripForLookupHost(started time.Time, reso model.Resol
}
}
// DNSNetworkAddresser is the type of something we just used to perform a DNS
// round trip (e.g., model.DNSTransport, model.Resolver) that allows us to get
// the network and the address of the underlying resolver/transport.
type DNSNetworkAddresser interface {
// Address is like model.DNSTransport.Address
Address() string
// Network is like model.DNSTransport.Network
Network() string
}
// NewArchivalDNSLookupResultFromRoundTrip generates a model.ArchivalDNSLookupResultFromRoundTrip
// from the available information right after the DNS RoundTrip
func NewArchivalDNSLookupResultFromRoundTrip(index int64, started time.Duration, reso model.Resolver, query model.DNSQuery,
func NewArchivalDNSLookupResultFromRoundTrip(index int64, started time.Duration, reso DNSNetworkAddresser, query model.DNSQuery,
response model.DNSResponse, addrs []string, err error, finished time.Duration) *model.ArchivalDNSLookupResult {
return &model.ArchivalDNSLookupResult{
Answers: archivalAnswersFromAddrs(addrs),
@ -167,3 +179,53 @@ func (tx *Trace) FirstDNSLookup() *model.ArchivalDNSLookupResult {
}
return ev[0]
}
// ErrDelayedDNSResponseBufferFull indicates that the delayedDNSResponse buffer is full.
var ErrDelayedDNSResponseBufferFull = errors.New("buffer full")
// OnDelayedDNSResponse implements model.Trace.OnDelayedDNSResponse
func (tx *Trace) OnDelayedDNSResponse(started time.Time, txp model.DNSTransport, query model.DNSQuery,
response model.DNSResponse, addrs []string, err error, finished time.Time) error {
t := finished.Sub(tx.ZeroTime)
select {
case tx.delayedDNSResponse <- NewArchivalDNSLookupResultFromRoundTrip(
tx.Index,
started.Sub(tx.ZeroTime),
txp,
query,
response,
addrs,
err,
t,
):
return nil
default:
return ErrDelayedDNSResponseBufferFull
}
}
// DelayedDNSResponseWithTimeout drains the network events buffered inside
// the delayedDNSResponse channel. We construct a child context based on [ctx]
// and the given [timeout] and we stop reading when original [ctx] has been
// cancelled or the given [timeout] expires, whatever happens first. Once the
// timeout expired, we drain the chan as much as possible before returning.
func (tx *Trace) DelayedDNSResponseWithTimeout(ctx context.Context,
timeout time.Duration) (out []*model.ArchivalDNSLookupResult) {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
for {
select {
case <-ctx.Done():
for { // once the context is done enter in channel draining mode
select {
case ev := <-tx.delayedDNSResponse:
out = append(out, ev)
default:
return
}
}
case ev := <-tx.delayedDNSResponse:
out = append(out, ev)
}
}
}