feat(dns): expose more low-level fields (#873)

This pull request started as a draft to enable users to see CNAME answers. It contained several patches which we merged separately (see https://github.com/ooni/probe-cli/pull/873#issuecomment-1222406732 and 2301a30630...60b7d1f87b for details on what has actually changed, which is based on patches originally part of this PR). In its final form, however, this PR only deals with exposing more low-level DNS fields to the archival data format.

Closes: https://github.com/ooni/probe/issues/2228

Related PR spec: https://github.com/ooni/spec/pull/256
This commit is contained in:
Simone Basso 2022-08-23 16:12:04 +02:00 committed by GitHub
parent 60b7d1f87b
commit c9943dff38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 68 additions and 3 deletions

View File

@ -117,14 +117,35 @@ func NewArchivalDNSLookupResultFromRoundTrip(index int64, started time.Duration,
Answers: newArchivalDNSAnswers(addrs, response), Answers: newArchivalDNSAnswers(addrs, response),
Engine: reso.Network(), Engine: reso.Network(),
Failure: tracex.NewFailure(err), Failure: tracex.NewFailure(err),
GetaddrinfoError: netxlite.ErrorToGetaddrinfoRetvalOrZero(err),
Hostname: query.Domain(), Hostname: query.Domain(),
QueryType: dns.TypeToString[query.Type()], QueryType: dns.TypeToString[query.Type()],
RawResponse: maybeRawResponse(response),
Rcode: maybeResponseRcode(response),
ResolverHostname: nil, ResolverHostname: nil,
ResolverPort: nil,
ResolverAddress: reso.Address(), ResolverAddress: reso.Address(),
T0: started.Seconds(),
T: finished.Seconds(), T: finished.Seconds(),
} }
} }
// maybeResponseRcode returns the response rcode (when available)
func maybeResponseRcode(resp model.DNSResponse) (out int64) {
if resp != nil {
out = int64(resp.Rcode())
}
return
}
// maybeRawResponse returns either the raw response (when available) or nil.
func maybeRawResponse(resp model.DNSResponse) (out []byte) {
if resp != nil {
out = resp.Bytes()
}
return
}
// newArchivalDNSAnswers generates []model.ArchivalDNSAnswer from [addrs] and [resp]. // newArchivalDNSAnswers generates []model.ArchivalDNSAnswer from [addrs] and [resp].
func newArchivalDNSAnswers(addrs []string, resp model.DNSResponse) (out []model.ArchivalDNSAnswer) { func newArchivalDNSAnswers(addrs []string, resp model.DNSResponse) (out []model.ArchivalDNSAnswer) {
// Design note: in principle we might want to extract everything from the // Design note: in principle we might want to extract everything from the

View File

@ -138,6 +138,12 @@ func TestNewResolver(t *testing.T) {
MockDecodeCNAME: func() (string, error) { MockDecodeCNAME: func() (string, error) {
return "dns.google.", nil return "dns.google.", nil
}, },
MockRcode: func() int {
return 0
},
MockBytes: func() []byte {
return []byte{}
},
} }
return response, nil return response, nil
}, },
@ -214,6 +220,12 @@ func TestNewResolver(t *testing.T) {
MockDecodeCNAME: func() (string, error) { MockDecodeCNAME: func() (string, error) {
return "dns.google.", nil return "dns.google.", nil
}, },
MockRcode: func() int {
return 0
},
MockBytes: func() []byte {
return []byte{}
},
} }
return response, nil return response, nil
}, },
@ -363,6 +375,12 @@ func TestDelayedDNSResponseWithTimeout(t *testing.T) {
MockDecodeCNAME: func() (string, error) { MockDecodeCNAME: func() (string, error) {
return "", netxlite.ErrOODNSNoAnswer return "", netxlite.ErrOODNSNoAnswer
}, },
MockRcode: func() int {
return 0
},
MockBytes: func() []byte {
return []byte{}
},
} }
err := trace.OnDelayedDNSResponse(started, txp, query, dnsResponse, addrs, nil, finished) err := trace.OnDelayedDNSResponse(started, txp, query, dnsResponse, addrs, nil, finished)
// 2. read the trace // 2. read the trace
@ -405,6 +423,12 @@ func TestDelayedDNSResponseWithTimeout(t *testing.T) {
MockDecodeCNAME: func() (string, error) { MockDecodeCNAME: func() (string, error) {
return "", netxlite.ErrOODNSNoAnswer return "", netxlite.ErrOODNSNoAnswer
}, },
MockRcode: func() int {
return 0
},
MockBytes: func() []byte {
return []byte{}
},
} }
err := trace.OnDelayedDNSResponse(started, txp, query, dnsResponse, addrs, nil, finished) err := trace.OnDelayedDNSResponse(started, txp, query, dnsResponse, addrs, nil, finished)
if !errors.Is(err, ErrDelayedDNSResponseBufferFull) { if !errors.Is(err, ErrDelayedDNSResponseBufferFull) {
@ -448,6 +472,12 @@ func TestDelayedDNSResponseWithTimeout(t *testing.T) {
MockDecodeCNAME: func() (string, error) { MockDecodeCNAME: func() (string, error) {
return "", netxlite.ErrOODNSNoAnswer return "", netxlite.ErrOODNSNoAnswer
}, },
MockRcode: func() int {
return 0
},
MockBytes: func() []byte {
return []byte{}
},
} }
for i := 0; i < events; i++ { for i := 0; i < events; i++ {
// fill the trace // fill the trace
@ -491,6 +521,12 @@ func TestDelayedDNSResponseWithTimeout(t *testing.T) {
MockDecodeCNAME: func() (string, error) { MockDecodeCNAME: func() (string, error) {
return "", netxlite.ErrOODNSNoAnswer return "", netxlite.ErrOODNSNoAnswer
}, },
MockRcode: func() int {
return 0
},
MockBytes: func() []byte {
return []byte{}
},
} }
trace.delayedDNSResponse <- NewArchivalDNSLookupResultFromRoundTrip(trace.Index, started.Sub(trace.ZeroTime), trace.delayedDNSResponse <- NewArchivalDNSLookupResultFromRoundTrip(trace.Index, started.Sub(trace.ZeroTime),
txp, query, dnsResponse, addrs, nil, finished.Sub(trace.ZeroTime)) txp, query, dnsResponse, addrs, nil, finished.Sub(trace.ZeroTime))

View File

@ -115,11 +115,15 @@ type ArchivalDNSLookupResult struct {
Answers []ArchivalDNSAnswer `json:"answers"` Answers []ArchivalDNSAnswer `json:"answers"`
Engine string `json:"engine"` Engine string `json:"engine"`
Failure *string `json:"failure"` Failure *string `json:"failure"`
GetaddrinfoError int64 `json:"getaddrinfo_error,omitempty"`
Hostname string `json:"hostname"` Hostname string `json:"hostname"`
QueryType string `json:"query_type"` QueryType string `json:"query_type"`
RawResponse []byte `json:"raw_response,omitempty"`
Rcode int64 `json:"rcode,omitempty"`
ResolverHostname *string `json:"resolver_hostname"` ResolverHostname *string `json:"resolver_hostname"`
ResolverPort *string `json:"resolver_port"` ResolverPort *string `json:"resolver_port"`
ResolverAddress string `json:"resolver_address"` ResolverAddress string `json:"resolver_address"`
T0 float64 `json:"t0"`
T float64 `json:"t"` T float64 `json:"t"`
} }

View File

@ -29,10 +29,10 @@ func (err *ErrGetaddrinfo) Unwrap() error {
return err.Underlying return err.Underlying
} }
// ErrorToGetaddrinfoRetval converts an arbitrary error to // ErrorToGetaddrinfoRetvalOrZero converts an arbitrary error to
// the return value of getaddrinfo. If err is nil or is not // the return value of getaddrinfo. If err is nil or is not
// an instance of ErrGetaddrinfo, we just return zero. // an instance of ErrGetaddrinfo, we just return zero.
func ErrorToGetaddrinfoRetval(err error) int64 { func ErrorToGetaddrinfoRetvalOrZero(err error) int64 {
var aierr *ErrGetaddrinfo var aierr *ErrGetaddrinfo
if err != nil && errors.As(err, &aierr) { if err != nil && errors.As(err, &aierr) {
return aierr.Code return aierr.Code

View File

@ -35,10 +35,14 @@ func TestErrorToGetaddrinfoRetval(t *testing.T) {
name: "with another kind of error", name: "with another kind of error",
args: args{io.EOF}, args: args{io.EOF},
want: 0, want: 0,
}, {
name: "with nil error",
args: args{nil},
want: 0,
}} }}
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if got := ErrorToGetaddrinfoRetval(tt.args.err); got != tt.want { if got := ErrorToGetaddrinfoRetvalOrZero(tt.args.err); got != tt.want {
t.Errorf("ErrorToGetaddrinfoRetval() = %v, want %v", got, tt.want) t.Errorf("ErrorToGetaddrinfoRetval() = %v, want %v", got, tt.want)
} }
}) })