feat(netxlite): support extracting the CNAME (#875)
* feat(netxlite): support extracting the CNAME Closes https://github.com/ooni/probe/issues/2225 * fix(netxlite): attempt to increase coverage and improve tests 1. dnsovergetaddrinfo: specify the behavior of a DNSResponse returned by this file to make it line with normal responses and write unit tests to make sure we adhere to expectations; 2. dnsoverudp: make sure we wait to deferred responses also w/o a custom context and post on a private channel and test that; 3. utls: recognize that we can actually write a test for NetConn and what needs to change when we'll use go1.19 by default will just be a cast that at that point can be removed.
This commit is contained in:
parent
da1c13e312
commit
cc24f28b9d
10 changed files with 390 additions and 39 deletions
|
|
@ -44,7 +44,7 @@ func TestDNSDecoderMiekg(t *testing.T) {
|
|||
queryID = 17
|
||||
unrelatedID = 14
|
||||
)
|
||||
reply := dnsGenLookupHostReplySuccess(dnsGenQuery(dns.TypeA, queryID))
|
||||
reply := dnsGenLookupHostReplySuccess(dnsGenQuery(dns.TypeA, queryID), nil)
|
||||
resp, err := d.DecodeResponse(reply, &mocks.DNSQuery{
|
||||
MockID: func() uint16 {
|
||||
return unrelatedID
|
||||
|
|
@ -62,7 +62,7 @@ func TestDNSDecoderMiekg(t *testing.T) {
|
|||
d := &DNSDecoderMiekg{}
|
||||
queryID := dns.Id()
|
||||
rawQuery := dnsGenQuery(dns.TypeA, queryID)
|
||||
rawResponse := dnsGenLookupHostReplySuccess(rawQuery)
|
||||
rawResponse := dnsGenLookupHostReplySuccess(rawQuery, nil)
|
||||
query := &mocks.DNSQuery{
|
||||
MockID: func() uint16 {
|
||||
return queryID
|
||||
|
|
@ -81,7 +81,7 @@ func TestDNSDecoderMiekg(t *testing.T) {
|
|||
d := &DNSDecoderMiekg{}
|
||||
queryID := dns.Id()
|
||||
rawQuery := dnsGenQuery(dns.TypeA, queryID)
|
||||
rawResponse := dnsGenLookupHostReplySuccess(rawQuery)
|
||||
rawResponse := dnsGenLookupHostReplySuccess(rawQuery, nil)
|
||||
query := &mocks.DNSQuery{
|
||||
MockID: func() uint16 {
|
||||
return queryID
|
||||
|
|
@ -323,7 +323,7 @@ func TestDNSDecoderMiekg(t *testing.T) {
|
|||
})
|
||||
})
|
||||
|
||||
t.Run("dnsResponse.LookupHost", func(t *testing.T) {
|
||||
t.Run("dnsResponse.DecodeLookupHost", func(t *testing.T) {
|
||||
t.Run("with failure", func(t *testing.T) {
|
||||
// Ensure that we're not trying to decode if rcode != 0
|
||||
d := &DNSDecoderMiekg{}
|
||||
|
|
@ -352,7 +352,7 @@ func TestDNSDecoderMiekg(t *testing.T) {
|
|||
d := &DNSDecoderMiekg{}
|
||||
queryID := dns.Id()
|
||||
rawQuery := dnsGenQuery(dns.TypeA, queryID)
|
||||
rawResponse := dnsGenLookupHostReplySuccess(rawQuery)
|
||||
rawResponse := dnsGenLookupHostReplySuccess(rawQuery, nil)
|
||||
query := &mocks.DNSQuery{
|
||||
MockID: func() uint16 {
|
||||
return queryID
|
||||
|
|
@ -375,7 +375,7 @@ func TestDNSDecoderMiekg(t *testing.T) {
|
|||
d := &DNSDecoderMiekg{}
|
||||
queryID := dns.Id()
|
||||
rawQuery := dnsGenQuery(dns.TypeA, queryID)
|
||||
rawResponse := dnsGenLookupHostReplySuccess(rawQuery, "1.1.1.1", "8.8.8.8")
|
||||
rawResponse := dnsGenLookupHostReplySuccess(rawQuery, nil, "1.1.1.1", "8.8.8.8")
|
||||
query := &mocks.DNSQuery{
|
||||
MockID: func() uint16 {
|
||||
return queryID
|
||||
|
|
@ -407,7 +407,7 @@ func TestDNSDecoderMiekg(t *testing.T) {
|
|||
d := &DNSDecoderMiekg{}
|
||||
queryID := dns.Id()
|
||||
rawQuery := dnsGenQuery(dns.TypeAAAA, queryID)
|
||||
rawResponse := dnsGenLookupHostReplySuccess(rawQuery, "::1", "fe80::1")
|
||||
rawResponse := dnsGenLookupHostReplySuccess(rawQuery, nil, "::1", "fe80::1")
|
||||
query := &mocks.DNSQuery{
|
||||
MockID: func() uint16 {
|
||||
return queryID
|
||||
|
|
@ -439,7 +439,7 @@ func TestDNSDecoderMiekg(t *testing.T) {
|
|||
d := &DNSDecoderMiekg{}
|
||||
queryID := dns.Id()
|
||||
rawQuery := dnsGenQuery(dns.TypeAAAA, queryID)
|
||||
rawResponse := dnsGenLookupHostReplySuccess(rawQuery, "1.1.1.1", "8.8.8.8")
|
||||
rawResponse := dnsGenLookupHostReplySuccess(rawQuery, nil, "1.1.1.1", "8.8.8.8")
|
||||
query := &mocks.DNSQuery{
|
||||
MockID: func() uint16 {
|
||||
return queryID
|
||||
|
|
@ -465,7 +465,7 @@ func TestDNSDecoderMiekg(t *testing.T) {
|
|||
d := &DNSDecoderMiekg{}
|
||||
queryID := dns.Id()
|
||||
rawQuery := dnsGenQuery(dns.TypeA, queryID)
|
||||
rawResponse := dnsGenLookupHostReplySuccess(rawQuery, "::1", "fe80::1")
|
||||
rawResponse := dnsGenLookupHostReplySuccess(rawQuery, nil, "::1", "fe80::1")
|
||||
query := &mocks.DNSQuery{
|
||||
MockID: func() uint16 {
|
||||
return queryID
|
||||
|
|
@ -487,6 +487,82 @@ func TestDNSDecoderMiekg(t *testing.T) {
|
|||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("dnsResponse.DecodeCNAME", func(t *testing.T) {
|
||||
t.Run("with failure", func(t *testing.T) {
|
||||
// Ensure that we're not trying to decode if rcode != 0
|
||||
d := &DNSDecoderMiekg{}
|
||||
queryID := dns.Id()
|
||||
rawQuery := dnsGenQuery(dns.TypeA, queryID)
|
||||
rawResponse := dnsGenReplyWithError(rawQuery, dns.RcodeRefused)
|
||||
query := &mocks.DNSQuery{
|
||||
MockID: func() uint16 {
|
||||
return queryID
|
||||
},
|
||||
}
|
||||
resp, err := d.DecodeResponse(rawResponse, query)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cname, err := resp.DecodeCNAME()
|
||||
if !errors.Is(err, ErrOODNSRefused) {
|
||||
t.Fatal("unexpected err", err)
|
||||
}
|
||||
if cname != "" {
|
||||
t.Fatal("expected empty cname result")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("with empty answer", func(t *testing.T) {
|
||||
d := &DNSDecoderMiekg{}
|
||||
queryID := dns.Id()
|
||||
rawQuery := dnsGenQuery(dns.TypeA, queryID)
|
||||
var expectedCNAME *dnsCNAMEAnswer = nil // explicity not set
|
||||
rawResponse := dnsGenLookupHostReplySuccess(rawQuery, expectedCNAME, "8.8.8.8")
|
||||
query := &mocks.DNSQuery{
|
||||
MockID: func() uint16 {
|
||||
return queryID
|
||||
},
|
||||
}
|
||||
resp, err := d.DecodeResponse(rawResponse, query)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cname, err := resp.DecodeCNAME()
|
||||
if !errors.Is(err, ErrOODNSNoAnswer) {
|
||||
t.Fatal("unexpected err", err)
|
||||
}
|
||||
if cname != "" {
|
||||
t.Fatal("expected empty cname result")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("with full answer", func(t *testing.T) {
|
||||
expectedCNAME := &dnsCNAMEAnswer{
|
||||
CNAME: "dns.google.",
|
||||
}
|
||||
d := &DNSDecoderMiekg{}
|
||||
queryID := dns.Id()
|
||||
rawQuery := dnsGenQuery(dns.TypeA, queryID)
|
||||
rawResponse := dnsGenLookupHostReplySuccess(rawQuery, expectedCNAME, "8.8.8.8")
|
||||
query := &mocks.DNSQuery{
|
||||
MockID: func() uint16 {
|
||||
return queryID
|
||||
},
|
||||
}
|
||||
resp, err := d.DecodeResponse(rawResponse, query)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cname, err := resp.DecodeCNAME()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cname != expectedCNAME.CNAME {
|
||||
t.Fatal("unexpected cname", cname)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -522,9 +598,18 @@ func dnsGenReplyWithError(rawQuery []byte, code int) []byte {
|
|||
return data
|
||||
}
|
||||
|
||||
// ImplementationNote: dnsCNAMEAnswer could have been a string but then
|
||||
// dnsGenLookupHostReplySuccess invocations would have been confusing to read,
|
||||
// because they would not have had a boundary between CNAME and addrs.
|
||||
|
||||
// dnsCNAMEAnswer is the DNS cname answer to include into a response.
|
||||
type dnsCNAMEAnswer struct {
|
||||
CNAME string
|
||||
}
|
||||
|
||||
// dnsGenLookupHostReplySuccess generates a successful DNS reply containing the given ips...
|
||||
// in the answers where each answer's type depends on the IP's type (A/AAAA).
|
||||
func dnsGenLookupHostReplySuccess(rawQuery []byte, ips ...string) []byte {
|
||||
func dnsGenLookupHostReplySuccess(rawQuery []byte, cname *dnsCNAMEAnswer, ips ...string) []byte {
|
||||
query := new(dns.Msg)
|
||||
err := query.Unpack(rawQuery)
|
||||
runtimex.PanicOnError(err, "query.Unpack failed")
|
||||
|
|
@ -562,6 +647,17 @@ func dnsGenLookupHostReplySuccess(rawQuery []byte, ips ...string) []byte {
|
|||
})
|
||||
}
|
||||
}
|
||||
if cname != nil {
|
||||
reply.Answer = append(reply.Answer, &dns.CNAME{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: question.Name,
|
||||
Rrtype: dns.TypeCNAME,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 0,
|
||||
},
|
||||
Target: cname.CNAME,
|
||||
})
|
||||
}
|
||||
data, err := reply.Pack()
|
||||
runtimex.PanicOnError(err, "reply.Pack failed")
|
||||
return data
|
||||
|
|
|
|||
Loading…
Reference in a new issue