ooni-probe-cli/internal/engine/experiment/webconnectivity/dnsanalysis.go
Simone Basso 83440cf110
refactor: split errorsx in good and legacy (#477)
The legacy part for now is internal/errorsx. It will stay there until
I figure out whether it also needs some extra bug fixing.

The good part is now in internal/netxlite/errorsx and contains all the
logic for mapping errors. We need to further improve upon this logic
by writing more thorough integration tests for QUIC.

We also need to copy the various dialer, conn, etc adapters that set
errors. We will put them inside netxlite and we will generate errors in
a way that is less crazy with respect to the major operation. (The
idea is to always wrap, given that now we measure in an incremental way
and we don't measure every operation together.)

Part of https://github.com/ooni/probe/issues/1591
2021-09-07 17:09:30 +02:00

100 lines
3.0 KiB
Go

package webconnectivity
import (
"net"
"net/url"
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
)
// DNSAnalysisResult contains the results of analysing comparing
// the measurement and the control DNS results.
type DNSAnalysisResult struct {
DNSConsistency *string `json:"dns_consistency"`
}
// DNSNameError is the error returned by the control on NXDOMAIN
const DNSNameError = "dns_name_error"
var (
// DNSConsistent indicates that the measurement and the
// control have consistent DNS results.
DNSConsistent = "consistent"
// DNSInconsistent indicates that the measurement and the
// control have inconsistent DNS results.
DNSInconsistent = "inconsistent"
)
// DNSAnalysis compares the measurement and the control DNS results. This
// implementation is a simplified version of the implementation of the same
// check implemented in Measurement Kit v0.10.11.
func DNSAnalysis(URL *url.URL, measurement DNSLookupResult,
control ControlResponse) (out DNSAnalysisResult) {
// 0. start assuming it's not consistent
out.DNSConsistency = &DNSInconsistent
// 1. flip to consistent if we're targeting an IP address because the
// control will actually return dns_name_error in this case.
if net.ParseIP(URL.Hostname()) != nil {
out.DNSConsistency = &DNSConsistent
return
}
// 2. flip to consistent if the failures are compatible
if measurement.Failure != nil && control.DNS.Failure != nil {
switch *control.DNS.Failure {
case DNSNameError: // the control returns this on NXDOMAIN error
switch *measurement.Failure {
case errorsx.FailureDNSNXDOMAINError:
out.DNSConsistency = &DNSConsistent
}
}
return
}
// 3. flip to consistent if measurement and control returned IP addresses
// that belong to the same Autonomous System(s).
//
// This specific check is present in MK's implementation.
//
// Note that this covers also the cases where the measurement contains only
// bogons while the control does not contain bogons.
//
// Note that this also covers the cases where results are equal.
const (
inMeasurement = 1 << 0
inControl = 1 << 1
inBoth = inMeasurement | inControl
)
asnmap := make(map[int64]int)
for _, asn := range measurement.Addrs {
asnmap[asn] |= inMeasurement
}
for _, asn := range control.DNS.ASNs {
asnmap[asn] |= inControl
}
for key, value := range asnmap {
// zero means that ASN lookup failed
if key != 0 && (value&inBoth) == inBoth {
out.DNSConsistency = &DNSConsistent
return
}
}
// 4. when ASN lookup failed (unlikely), check whether
// there is overlap in the returned IP addresses
ipmap := make(map[string]int)
for ip := range measurement.Addrs {
ipmap[ip] |= inMeasurement
}
for _, ip := range control.DNS.Addrs {
ipmap[ip] |= inControl
}
for key, value := range ipmap {
// just in case an empty string slipped through
if key != "" && (value&inBoth) == inBoth {
out.DNSConsistency = &DNSConsistent
return
}
}
// 5. conclude that measurement and control are inconsistent
return
}