/
dns.go
124 lines (100 loc) · 3.4 KB
/
dns.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package oohelperd
//
// DNS measurements
//
import (
"context"
"sync"
"time"
"github.com/ooni/probe-cli/v3/internal/legacy/tracex"
"github.com/ooni/probe-cli/v3/internal/logx"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/netxlite"
)
// newfailure is a convenience shortcut to save typing.
var newfailure = tracex.NewFailure
// ctrlDNSResult is the result returned by the [dnsDo] function and
// included into the response sent to the client.
type ctrlDNSResult = model.THDNSResult
// dnsConfig contains configuration for the [dnsDo] function.
type dnsConfig struct {
// Domain is the MANDATORY domain to resolve.
Domain string
// Logger is the MANDATORY logger to use.
Logger model.Logger
// NewResolver is the MANDATORY factory to create a new resolver.
NewResolver func(model.Logger) model.Resolver
// Out is the MANDATORY channel where we publish the results.
Out chan ctrlDNSResult
// Wg is MANDATORY and allows [dnsDo] to synchronize with the caller.
Wg *sync.WaitGroup
}
// dnsDo performs a DNS micro-measurement using the given [dnsConfig].
func dnsDo(ctx context.Context, config *dnsConfig) {
// make sure this micro-measurement is bounded in time
const timeout = 4 * time.Second
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
// make sure the caller knows when we're done
defer config.Wg.Done()
// create a temporary resolver for this micro-measurement
reso := config.NewResolver(config.Logger)
defer reso.CloseIdleConnections()
// take the time before running this micro-measurement
started := time.Now()
// perform and log the actual DNS lookup
ol := logx.NewOperationLogger(config.Logger, "DNSLookup %s", config.Domain)
addrs, err := reso.LookupHost(ctx, config.Domain)
ol.Stop(err)
// publish the time required for running this micro-measurement
elapsed := time.Since(started)
metricDNSTaskDurationSeconds.Observe(elapsed.Seconds())
// make sure we return an empty slice on failure because this
// is what the legacy TH would have done.
if addrs == nil {
addrs = []string{}
}
// map the OONI failure to the failure string that the legacy
// TH would have returned to us in this case.
failure := dnsMapFailure(newfailure(err))
// emit the result; note that the ASNs field is unused by
// the TH and is not serialized to JSON.
config.Out <- ctrlDNSResult{
Failure: failure,
Addrs: addrs,
ASNs: []int64{},
}
}
// dnsMapFailure attempts to map netxlite failures to the strings
// used by the original OONI test helper.
//
// See https://github.com/ooni/backend/blob/6ec4fda5b18/oonib/testhelpers/http_helpers.py#L430
func dnsMapFailure(failure *string) *string {
switch failure {
case nil:
return nil
default:
switch *failure {
case netxlite.FailureDNSNXDOMAINError:
// We have a name for this string because dnsanalysis.go is
// already checking for this specific error string.
s := model.THDNSNameError
return &s
case netxlite.FailureDNSNoAnswer:
// In this case the legacy TH would produce an empty
// reply that is not attached to any error.
//
// See https://github.com/ooni/probe/issues/1707#issuecomment-944322725
return nil
case netxlite.FailureDNSNonRecoverableFailure,
netxlite.FailureDNSRefusedError,
netxlite.FailureDNSServerMisbehaving,
netxlite.FailureDNSTemporaryFailure:
s := "dns_server_failure"
return &s
default:
s := "unknown_error"
return &s
}
}
}