922 lines
25 KiB
Go
922 lines
25 KiB
Go
|
package archival
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"io"
|
||
|
"net/http"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/google/go-cmp/cmp"
|
||
|
"github.com/ooni/probe-cli/v3/internal/model"
|
||
|
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
||
|
)
|
||
|
|
||
|
// traceTime generates a time that is based off a fixed beginning
|
||
|
// in time, so we can easily compare times.
|
||
|
func traceTime(d int64) time.Time {
|
||
|
t := time.Date(2021, 01, 13, 14, 21, 59, 0, time.UTC)
|
||
|
return t.Add(time.Duration(d) * time.Millisecond)
|
||
|
}
|
||
|
|
||
|
// deltaSinceTraceTime computes the delta since the original
|
||
|
// trace time expressed in floating point seconds.
|
||
|
func deltaSinceTraceTime(d int64) float64 {
|
||
|
return (time.Duration(d) * time.Millisecond).Seconds()
|
||
|
}
|
||
|
|
||
|
// failureFromString converts a string to a failure.
|
||
|
func failureFromString(failure string) *string {
|
||
|
return &failure
|
||
|
}
|
||
|
|
||
|
func TestTraceNewArchivalTCPConnectResultList(t *testing.T) {
|
||
|
type fields struct {
|
||
|
DNSLookupHTTPS []*DNSLookupEvent
|
||
|
DNSLookupHost []*DNSLookupEvent
|
||
|
DNSRoundTrip []*DNSRoundTripEvent
|
||
|
HTTPRoundTrip []*HTTPRoundTripEvent
|
||
|
Network []*NetworkEvent
|
||
|
QUICHandshake []*QUICTLSHandshakeEvent
|
||
|
TLSHandshake []*QUICTLSHandshakeEvent
|
||
|
}
|
||
|
type args struct {
|
||
|
begin time.Time
|
||
|
}
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
fields fields
|
||
|
args args
|
||
|
wantOut []model.ArchivalTCPConnectResult
|
||
|
}{{
|
||
|
name: "with empty trace",
|
||
|
fields: fields{
|
||
|
DNSLookupHTTPS: []*DNSLookupEvent{},
|
||
|
DNSLookupHost: []*DNSLookupEvent{},
|
||
|
DNSRoundTrip: []*DNSRoundTripEvent{},
|
||
|
HTTPRoundTrip: []*HTTPRoundTripEvent{},
|
||
|
Network: []*NetworkEvent{},
|
||
|
QUICHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
TLSHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
},
|
||
|
args: args{
|
||
|
begin: traceTime(0),
|
||
|
},
|
||
|
wantOut: nil,
|
||
|
}, {
|
||
|
name: "we ignore I/O operations",
|
||
|
fields: fields{
|
||
|
DNSLookupHTTPS: []*DNSLookupEvent{},
|
||
|
DNSLookupHost: []*DNSLookupEvent{},
|
||
|
DNSRoundTrip: []*DNSRoundTripEvent{},
|
||
|
HTTPRoundTrip: []*HTTPRoundTripEvent{},
|
||
|
Network: []*NetworkEvent{{
|
||
|
Count: 1024,
|
||
|
Failure: nil,
|
||
|
Finished: traceTime(2),
|
||
|
Network: "tcp",
|
||
|
Operation: netxlite.WriteOperation,
|
||
|
RemoteAddr: "8.8.8.8:443",
|
||
|
Started: traceTime(1),
|
||
|
}, {
|
||
|
Count: 4096,
|
||
|
Failure: nil,
|
||
|
Finished: traceTime(4),
|
||
|
Network: "tcp",
|
||
|
Operation: netxlite.ReadOperation,
|
||
|
RemoteAddr: "8.8.8.8:443",
|
||
|
Started: traceTime(3),
|
||
|
}},
|
||
|
QUICHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
TLSHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
},
|
||
|
args: args{
|
||
|
begin: traceTime(0),
|
||
|
},
|
||
|
wantOut: nil,
|
||
|
}, {
|
||
|
name: "we ignore UDP connect",
|
||
|
fields: fields{
|
||
|
DNSLookupHTTPS: []*DNSLookupEvent{},
|
||
|
DNSLookupHost: []*DNSLookupEvent{},
|
||
|
DNSRoundTrip: []*DNSRoundTripEvent{},
|
||
|
HTTPRoundTrip: []*HTTPRoundTripEvent{},
|
||
|
Network: []*NetworkEvent{{
|
||
|
Count: 0,
|
||
|
Failure: nil,
|
||
|
Finished: traceTime(2),
|
||
|
Network: "udp",
|
||
|
Operation: netxlite.ConnectOperation,
|
||
|
RemoteAddr: "8.8.8.8:53",
|
||
|
Started: traceTime(1),
|
||
|
}},
|
||
|
QUICHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
TLSHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
},
|
||
|
args: args{
|
||
|
begin: traceTime(0),
|
||
|
},
|
||
|
wantOut: nil,
|
||
|
}, {
|
||
|
name: "with TCP connect success",
|
||
|
fields: fields{
|
||
|
DNSLookupHTTPS: []*DNSLookupEvent{},
|
||
|
DNSLookupHost: []*DNSLookupEvent{},
|
||
|
DNSRoundTrip: []*DNSRoundTripEvent{},
|
||
|
HTTPRoundTrip: []*HTTPRoundTripEvent{},
|
||
|
Network: []*NetworkEvent{{
|
||
|
Count: 0,
|
||
|
Failure: nil,
|
||
|
Finished: traceTime(2),
|
||
|
Network: "tcp",
|
||
|
Operation: netxlite.ConnectOperation,
|
||
|
RemoteAddr: "8.8.8.8:443",
|
||
|
Started: traceTime(1),
|
||
|
}},
|
||
|
QUICHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
TLSHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
},
|
||
|
args: args{
|
||
|
begin: traceTime(0),
|
||
|
},
|
||
|
wantOut: []model.ArchivalTCPConnectResult{{
|
||
|
IP: "8.8.8.8",
|
||
|
Port: 443,
|
||
|
Status: model.ArchivalTCPConnectStatus{
|
||
|
Blocked: nil,
|
||
|
Failure: nil,
|
||
|
Success: true,
|
||
|
},
|
||
|
T: deltaSinceTraceTime(2),
|
||
|
}},
|
||
|
}, {
|
||
|
name: "with TCP connect failure",
|
||
|
fields: fields{
|
||
|
DNSLookupHTTPS: []*DNSLookupEvent{},
|
||
|
DNSLookupHost: []*DNSLookupEvent{},
|
||
|
DNSRoundTrip: []*DNSRoundTripEvent{},
|
||
|
HTTPRoundTrip: []*HTTPRoundTripEvent{},
|
||
|
Network: []*NetworkEvent{{
|
||
|
Count: 0,
|
||
|
Failure: netxlite.NewTopLevelGenericErrWrapper(netxlite.ECONNREFUSED),
|
||
|
Finished: traceTime(2),
|
||
|
Network: "tcp",
|
||
|
Operation: netxlite.ConnectOperation,
|
||
|
RemoteAddr: "8.8.8.8:443",
|
||
|
Started: traceTime(1),
|
||
|
}},
|
||
|
QUICHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
TLSHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
},
|
||
|
args: args{
|
||
|
begin: traceTime(0),
|
||
|
},
|
||
|
wantOut: []model.ArchivalTCPConnectResult{{
|
||
|
IP: "8.8.8.8",
|
||
|
Port: 443,
|
||
|
Status: model.ArchivalTCPConnectStatus{
|
||
|
Blocked: nil,
|
||
|
Failure: failureFromString(netxlite.FailureConnectionRefused),
|
||
|
Success: false,
|
||
|
},
|
||
|
T: deltaSinceTraceTime(2),
|
||
|
}},
|
||
|
}}
|
||
|
for _, tt := range tests {
|
||
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
tr := &Trace{
|
||
|
DNSLookupHTTPS: tt.fields.DNSLookupHTTPS,
|
||
|
DNSLookupHost: tt.fields.DNSLookupHost,
|
||
|
DNSRoundTrip: tt.fields.DNSRoundTrip,
|
||
|
HTTPRoundTrip: tt.fields.HTTPRoundTrip,
|
||
|
Network: tt.fields.Network,
|
||
|
QUICHandshake: tt.fields.QUICHandshake,
|
||
|
TLSHandshake: tt.fields.TLSHandshake,
|
||
|
}
|
||
|
gotOut := tr.NewArchivalTCPConnectResultList(tt.args.begin)
|
||
|
if diff := cmp.Diff(tt.wantOut, gotOut); diff != "" {
|
||
|
t.Fatal(diff)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestTraceNewArchivalHTTPRequestResultList(t *testing.T) {
|
||
|
type fields struct {
|
||
|
DNSLookupHTTPS []*DNSLookupEvent
|
||
|
DNSLookupHost []*DNSLookupEvent
|
||
|
DNSRoundTrip []*DNSRoundTripEvent
|
||
|
HTTPRoundTrip []*HTTPRoundTripEvent
|
||
|
Network []*NetworkEvent
|
||
|
QUICHandshake []*QUICTLSHandshakeEvent
|
||
|
TLSHandshake []*QUICTLSHandshakeEvent
|
||
|
}
|
||
|
type args struct {
|
||
|
begin time.Time
|
||
|
}
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
fields fields
|
||
|
args args
|
||
|
wantOut []model.ArchivalHTTPRequestResult
|
||
|
}{{
|
||
|
name: "with empty trace",
|
||
|
fields: fields{
|
||
|
DNSLookupHTTPS: []*DNSLookupEvent{},
|
||
|
DNSLookupHost: []*DNSLookupEvent{},
|
||
|
DNSRoundTrip: []*DNSRoundTripEvent{},
|
||
|
HTTPRoundTrip: []*HTTPRoundTripEvent{},
|
||
|
Network: []*NetworkEvent{},
|
||
|
QUICHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
TLSHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
},
|
||
|
args: args{
|
||
|
begin: traceTime(0),
|
||
|
},
|
||
|
wantOut: nil,
|
||
|
}, {
|
||
|
name: "with failure",
|
||
|
fields: fields{
|
||
|
DNSLookupHTTPS: []*DNSLookupEvent{},
|
||
|
DNSLookupHost: []*DNSLookupEvent{},
|
||
|
DNSRoundTrip: []*DNSRoundTripEvent{},
|
||
|
HTTPRoundTrip: []*HTTPRoundTripEvent{{
|
||
|
Failure: netxlite.NewTopLevelGenericErrWrapper(netxlite.ECONNRESET),
|
||
|
Finished: traceTime(2),
|
||
|
Method: "GET",
|
||
|
RequestHeaders: http.Header{
|
||
|
"Accept": {"*/*"},
|
||
|
"X-Cookie": {"A", "B", "C"},
|
||
|
},
|
||
|
ResponseBody: nil,
|
||
|
ResponseBodyIsTruncated: false,
|
||
|
ResponseBodyLength: 0,
|
||
|
ResponseHeaders: nil,
|
||
|
Started: traceTime(1),
|
||
|
StatusCode: 0,
|
||
|
Transport: "tcp",
|
||
|
URL: "http://x.org/",
|
||
|
}},
|
||
|
Network: []*NetworkEvent{},
|
||
|
QUICHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
TLSHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
},
|
||
|
args: args{
|
||
|
begin: traceTime(0),
|
||
|
},
|
||
|
wantOut: []model.ArchivalHTTPRequestResult{{
|
||
|
Failure: failureFromString(netxlite.FailureConnectionReset),
|
||
|
Request: model.ArchivalHTTPRequest{
|
||
|
Body: model.ArchivalMaybeBinaryData{},
|
||
|
BodyIsTruncated: false,
|
||
|
HeadersList: []model.ArchivalHTTPHeader{{
|
||
|
Key: "Accept",
|
||
|
Value: model.ArchivalMaybeBinaryData{
|
||
|
Value: "*/*",
|
||
|
},
|
||
|
}, {
|
||
|
Key: "X-Cookie",
|
||
|
Value: model.ArchivalMaybeBinaryData{
|
||
|
Value: "A",
|
||
|
},
|
||
|
}, {
|
||
|
Key: "X-Cookie",
|
||
|
Value: model.ArchivalMaybeBinaryData{
|
||
|
Value: "B",
|
||
|
},
|
||
|
}, {
|
||
|
Key: "X-Cookie",
|
||
|
Value: model.ArchivalMaybeBinaryData{
|
||
|
Value: "C",
|
||
|
},
|
||
|
}},
|
||
|
Headers: map[string]model.ArchivalMaybeBinaryData{
|
||
|
"Accept": {Value: "*/*"},
|
||
|
"X-Cookie": {Value: "A"},
|
||
|
},
|
||
|
Method: "GET",
|
||
|
Tor: model.ArchivalHTTPTor{},
|
||
|
Transport: "tcp",
|
||
|
URL: "http://x.org/",
|
||
|
},
|
||
|
Response: model.ArchivalHTTPResponse{
|
||
|
Body: model.ArchivalMaybeBinaryData{},
|
||
|
BodyIsTruncated: false,
|
||
|
Code: 0,
|
||
|
HeadersList: nil,
|
||
|
Headers: nil,
|
||
|
Locations: nil,
|
||
|
},
|
||
|
T: deltaSinceTraceTime(2),
|
||
|
}},
|
||
|
}, {
|
||
|
name: "with success",
|
||
|
fields: fields{
|
||
|
DNSLookupHTTPS: []*DNSLookupEvent{},
|
||
|
DNSLookupHost: []*DNSLookupEvent{},
|
||
|
DNSRoundTrip: []*DNSRoundTripEvent{},
|
||
|
HTTPRoundTrip: []*HTTPRoundTripEvent{{
|
||
|
Failure: nil,
|
||
|
Finished: traceTime(2),
|
||
|
Method: "GET",
|
||
|
RequestHeaders: http.Header{
|
||
|
"Accept": {"*/*"},
|
||
|
"X-Cookie": {"A", "B", "C"},
|
||
|
},
|
||
|
ResponseBody: []byte("0xdeadbeef"),
|
||
|
ResponseBodyIsTruncated: true,
|
||
|
ResponseBodyLength: 10,
|
||
|
ResponseHeaders: http.Header{
|
||
|
"Server": {"antani/1.0"},
|
||
|
"X-Cookie-Reply": {"C", "D", "F"},
|
||
|
"Location": {"https://x.org/", "https://x.org/robots.txt"},
|
||
|
},
|
||
|
Started: traceTime(1),
|
||
|
StatusCode: 302,
|
||
|
Transport: "tcp",
|
||
|
URL: "http://x.org/",
|
||
|
}},
|
||
|
Network: []*NetworkEvent{},
|
||
|
QUICHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
TLSHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
},
|
||
|
args: args{
|
||
|
begin: traceTime(0),
|
||
|
},
|
||
|
wantOut: []model.ArchivalHTTPRequestResult{{
|
||
|
Failure: nil,
|
||
|
Request: model.ArchivalHTTPRequest{
|
||
|
Body: model.ArchivalMaybeBinaryData{},
|
||
|
BodyIsTruncated: false,
|
||
|
HeadersList: []model.ArchivalHTTPHeader{{
|
||
|
Key: "Accept",
|
||
|
Value: model.ArchivalMaybeBinaryData{
|
||
|
Value: "*/*",
|
||
|
},
|
||
|
}, {
|
||
|
Key: "X-Cookie",
|
||
|
Value: model.ArchivalMaybeBinaryData{
|
||
|
Value: "A",
|
||
|
},
|
||
|
}, {
|
||
|
Key: "X-Cookie",
|
||
|
Value: model.ArchivalMaybeBinaryData{
|
||
|
Value: "B",
|
||
|
},
|
||
|
}, {
|
||
|
Key: "X-Cookie",
|
||
|
Value: model.ArchivalMaybeBinaryData{
|
||
|
Value: "C",
|
||
|
},
|
||
|
}},
|
||
|
Headers: map[string]model.ArchivalMaybeBinaryData{
|
||
|
"Accept": {Value: "*/*"},
|
||
|
"X-Cookie": {Value: "A"},
|
||
|
},
|
||
|
Method: "GET",
|
||
|
Tor: model.ArchivalHTTPTor{},
|
||
|
Transport: "tcp",
|
||
|
URL: "http://x.org/",
|
||
|
},
|
||
|
Response: model.ArchivalHTTPResponse{
|
||
|
Body: model.ArchivalMaybeBinaryData{
|
||
|
Value: "0xdeadbeef",
|
||
|
},
|
||
|
BodyIsTruncated: true,
|
||
|
Code: 302,
|
||
|
HeadersList: []model.ArchivalHTTPHeader{{
|
||
|
Key: "Location",
|
||
|
Value: model.ArchivalMaybeBinaryData{
|
||
|
Value: "https://x.org/",
|
||
|
},
|
||
|
}, {
|
||
|
Key: "Location",
|
||
|
Value: model.ArchivalMaybeBinaryData{
|
||
|
Value: "https://x.org/robots.txt",
|
||
|
},
|
||
|
}, {
|
||
|
Key: "Server",
|
||
|
Value: model.ArchivalMaybeBinaryData{
|
||
|
Value: "antani/1.0",
|
||
|
},
|
||
|
}, {
|
||
|
Key: "X-Cookie-Reply",
|
||
|
Value: model.ArchivalMaybeBinaryData{
|
||
|
Value: "C",
|
||
|
},
|
||
|
}, {
|
||
|
Key: "X-Cookie-Reply",
|
||
|
Value: model.ArchivalMaybeBinaryData{
|
||
|
Value: "D",
|
||
|
},
|
||
|
}, {
|
||
|
Key: "X-Cookie-Reply",
|
||
|
Value: model.ArchivalMaybeBinaryData{
|
||
|
Value: "F",
|
||
|
},
|
||
|
}},
|
||
|
Headers: map[string]model.ArchivalMaybeBinaryData{
|
||
|
"Server": {Value: "antani/1.0"},
|
||
|
"X-Cookie-Reply": {Value: "C"},
|
||
|
"Location": {Value: "https://x.org/"},
|
||
|
},
|
||
|
Locations: []string{
|
||
|
"https://x.org/",
|
||
|
"https://x.org/robots.txt",
|
||
|
},
|
||
|
},
|
||
|
T: deltaSinceTraceTime(2),
|
||
|
}},
|
||
|
}, {
|
||
|
name: "The result is sorted by the value of T",
|
||
|
fields: fields{
|
||
|
DNSLookupHTTPS: []*DNSLookupEvent{},
|
||
|
DNSLookupHost: []*DNSLookupEvent{},
|
||
|
DNSRoundTrip: []*DNSRoundTripEvent{},
|
||
|
HTTPRoundTrip: []*HTTPRoundTripEvent{{
|
||
|
Failure: nil,
|
||
|
Finished: traceTime(3),
|
||
|
Method: "",
|
||
|
RequestHeaders: map[string][]string{},
|
||
|
ResponseBody: []byte{},
|
||
|
ResponseBodyIsTruncated: false,
|
||
|
ResponseBodyLength: 0,
|
||
|
ResponseHeaders: map[string][]string{},
|
||
|
Started: time.Time{},
|
||
|
StatusCode: 0,
|
||
|
Transport: "",
|
||
|
URL: "",
|
||
|
}, {
|
||
|
Failure: nil,
|
||
|
Finished: traceTime(2),
|
||
|
Method: "",
|
||
|
RequestHeaders: map[string][]string{},
|
||
|
ResponseBody: []byte{},
|
||
|
ResponseBodyIsTruncated: false,
|
||
|
ResponseBodyLength: 0,
|
||
|
ResponseHeaders: map[string][]string{},
|
||
|
Started: time.Time{},
|
||
|
StatusCode: 0,
|
||
|
Transport: "",
|
||
|
URL: "",
|
||
|
}, {
|
||
|
Failure: nil,
|
||
|
Finished: traceTime(5),
|
||
|
Method: "",
|
||
|
RequestHeaders: map[string][]string{},
|
||
|
ResponseBody: []byte{},
|
||
|
ResponseBodyIsTruncated: false,
|
||
|
ResponseBodyLength: 0,
|
||
|
ResponseHeaders: map[string][]string{},
|
||
|
Started: time.Time{},
|
||
|
StatusCode: 0,
|
||
|
Transport: "",
|
||
|
URL: "",
|
||
|
}},
|
||
|
Network: []*NetworkEvent{},
|
||
|
QUICHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
TLSHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
},
|
||
|
args: args{
|
||
|
begin: traceTime(0),
|
||
|
},
|
||
|
wantOut: []model.ArchivalHTTPRequestResult{{
|
||
|
Failure: nil,
|
||
|
Request: model.ArchivalHTTPRequest{},
|
||
|
Response: model.ArchivalHTTPResponse{},
|
||
|
T: deltaSinceTraceTime(5),
|
||
|
}, {
|
||
|
Failure: nil,
|
||
|
Request: model.ArchivalHTTPRequest{},
|
||
|
Response: model.ArchivalHTTPResponse{},
|
||
|
T: deltaSinceTraceTime(3),
|
||
|
}, {
|
||
|
Failure: nil,
|
||
|
Request: model.ArchivalHTTPRequest{},
|
||
|
Response: model.ArchivalHTTPResponse{},
|
||
|
T: deltaSinceTraceTime(2),
|
||
|
}},
|
||
|
}}
|
||
|
for _, tt := range tests {
|
||
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
tr := &Trace{
|
||
|
DNSLookupHTTPS: tt.fields.DNSLookupHTTPS,
|
||
|
DNSLookupHost: tt.fields.DNSLookupHost,
|
||
|
DNSRoundTrip: tt.fields.DNSRoundTrip,
|
||
|
HTTPRoundTrip: tt.fields.HTTPRoundTrip,
|
||
|
Network: tt.fields.Network,
|
||
|
QUICHandshake: tt.fields.QUICHandshake,
|
||
|
TLSHandshake: tt.fields.TLSHandshake,
|
||
|
}
|
||
|
gotOut := tr.NewArchivalHTTPRequestResultList(tt.args.begin)
|
||
|
if diff := cmp.Diff(tt.wantOut, gotOut); diff != "" {
|
||
|
t.Fatal(diff)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestTraceNewArchivalDNSLookupResultList(t *testing.T) {
|
||
|
type fields struct {
|
||
|
DNSLookupHTTPS []*DNSLookupEvent
|
||
|
DNSLookupHost []*DNSLookupEvent
|
||
|
DNSRoundTrip []*DNSRoundTripEvent
|
||
|
HTTPRoundTrip []*HTTPRoundTripEvent
|
||
|
Network []*NetworkEvent
|
||
|
QUICHandshake []*QUICTLSHandshakeEvent
|
||
|
TLSHandshake []*QUICTLSHandshakeEvent
|
||
|
}
|
||
|
type args struct {
|
||
|
begin time.Time
|
||
|
}
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
fields fields
|
||
|
args args
|
||
|
wantOut []model.ArchivalDNSLookupResult
|
||
|
}{{
|
||
|
name: "with empty trace",
|
||
|
fields: fields{
|
||
|
DNSLookupHTTPS: []*DNSLookupEvent{},
|
||
|
DNSLookupHost: []*DNSLookupEvent{},
|
||
|
DNSRoundTrip: []*DNSRoundTripEvent{},
|
||
|
HTTPRoundTrip: []*HTTPRoundTripEvent{},
|
||
|
Network: []*NetworkEvent{},
|
||
|
QUICHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
TLSHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
},
|
||
|
args: args{
|
||
|
begin: traceTime(0),
|
||
|
},
|
||
|
wantOut: nil,
|
||
|
}, {
|
||
|
name: "with NXDOMAIN failure",
|
||
|
fields: fields{
|
||
|
DNSLookupHTTPS: []*DNSLookupEvent{},
|
||
|
DNSLookupHost: []*DNSLookupEvent{{
|
||
|
ALPNs: nil,
|
||
|
Addresses: nil,
|
||
|
Domain: "example.com",
|
||
|
Failure: netxlite.NewTopLevelGenericErrWrapper(errors.New(netxlite.DNSNoSuchHostSuffix)),
|
||
|
Finished: traceTime(2),
|
||
|
LookupType: "", // not processed
|
||
|
ResolverAddress: "8.8.8.8:53",
|
||
|
ResolverNetwork: "udp",
|
||
|
Started: traceTime(1),
|
||
|
}},
|
||
|
DNSRoundTrip: []*DNSRoundTripEvent{},
|
||
|
HTTPRoundTrip: []*HTTPRoundTripEvent{},
|
||
|
Network: []*NetworkEvent{},
|
||
|
QUICHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
TLSHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
},
|
||
|
args: args{
|
||
|
begin: traceTime(0),
|
||
|
},
|
||
|
wantOut: []model.ArchivalDNSLookupResult{{
|
||
|
Answers: nil,
|
||
|
Engine: "udp",
|
||
|
Failure: failureFromString(netxlite.FailureDNSNXDOMAINError),
|
||
|
Hostname: "example.com",
|
||
|
QueryType: "A",
|
||
|
ResolverHostname: nil,
|
||
|
ResolverPort: nil,
|
||
|
ResolverAddress: "8.8.8.8:53",
|
||
|
T: deltaSinceTraceTime(2),
|
||
|
}, {
|
||
|
Answers: nil,
|
||
|
Engine: "udp",
|
||
|
Failure: failureFromString(netxlite.FailureDNSNXDOMAINError),
|
||
|
Hostname: "example.com",
|
||
|
QueryType: "AAAA",
|
||
|
ResolverHostname: nil,
|
||
|
ResolverPort: nil,
|
||
|
ResolverAddress: "8.8.8.8:53",
|
||
|
T: deltaSinceTraceTime(2),
|
||
|
}},
|
||
|
}, {
|
||
|
name: "with success for A and AAAA",
|
||
|
fields: fields{
|
||
|
DNSLookupHTTPS: []*DNSLookupEvent{},
|
||
|
DNSLookupHost: []*DNSLookupEvent{{
|
||
|
ALPNs: nil,
|
||
|
Addresses: []string{
|
||
|
"8.8.8.8", "8.8.4.4", "2001:4860:4860::8844",
|
||
|
},
|
||
|
Domain: "dns.google",
|
||
|
Failure: nil,
|
||
|
Finished: traceTime(2),
|
||
|
LookupType: "", // not processed
|
||
|
ResolverAddress: "8.8.8.8:53",
|
||
|
ResolverNetwork: "udp",
|
||
|
Started: traceTime(1),
|
||
|
}},
|
||
|
DNSRoundTrip: []*DNSRoundTripEvent{},
|
||
|
HTTPRoundTrip: []*HTTPRoundTripEvent{},
|
||
|
Network: []*NetworkEvent{},
|
||
|
QUICHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
TLSHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
},
|
||
|
args: args{
|
||
|
begin: traceTime(0),
|
||
|
},
|
||
|
wantOut: []model.ArchivalDNSLookupResult{{
|
||
|
Answers: []model.ArchivalDNSAnswer{{
|
||
|
ASN: 15169,
|
||
|
ASOrgName: "Google LLC",
|
||
|
AnswerType: "A",
|
||
|
Hostname: "",
|
||
|
IPv4: "8.8.8.8",
|
||
|
IPv6: "",
|
||
|
TTL: nil,
|
||
|
}, {
|
||
|
ASN: 15169,
|
||
|
ASOrgName: "Google LLC",
|
||
|
AnswerType: "A",
|
||
|
Hostname: "",
|
||
|
IPv4: "8.8.4.4",
|
||
|
IPv6: "",
|
||
|
TTL: nil,
|
||
|
}},
|
||
|
Engine: "udp",
|
||
|
Failure: nil,
|
||
|
Hostname: "dns.google",
|
||
|
QueryType: "A",
|
||
|
ResolverHostname: nil,
|
||
|
ResolverPort: nil,
|
||
|
ResolverAddress: "8.8.8.8:53",
|
||
|
T: deltaSinceTraceTime(2),
|
||
|
}, {
|
||
|
Answers: []model.ArchivalDNSAnswer{{
|
||
|
ASN: 15169,
|
||
|
ASOrgName: "Google LLC",
|
||
|
AnswerType: "AAAA",
|
||
|
Hostname: "",
|
||
|
IPv4: "",
|
||
|
IPv6: "2001:4860:4860::8844",
|
||
|
TTL: nil,
|
||
|
}},
|
||
|
Engine: "udp",
|
||
|
Failure: nil,
|
||
|
Hostname: "dns.google",
|
||
|
QueryType: "AAAA",
|
||
|
ResolverHostname: nil,
|
||
|
ResolverPort: nil,
|
||
|
ResolverAddress: "8.8.8.8:53",
|
||
|
T: deltaSinceTraceTime(2),
|
||
|
}},
|
||
|
}, {
|
||
|
name: "when a domain has no AAAA addresses",
|
||
|
fields: fields{
|
||
|
DNSLookupHTTPS: []*DNSLookupEvent{},
|
||
|
DNSLookupHost: []*DNSLookupEvent{{
|
||
|
ALPNs: nil,
|
||
|
Addresses: []string{
|
||
|
"8.8.8.8", "8.8.4.4",
|
||
|
},
|
||
|
Domain: "dns.google",
|
||
|
Failure: nil,
|
||
|
Finished: traceTime(2),
|
||
|
LookupType: "", // not processed
|
||
|
ResolverAddress: "8.8.8.8:53",
|
||
|
ResolverNetwork: "udp",
|
||
|
Started: traceTime(1),
|
||
|
}},
|
||
|
DNSRoundTrip: []*DNSRoundTripEvent{},
|
||
|
HTTPRoundTrip: []*HTTPRoundTripEvent{},
|
||
|
Network: []*NetworkEvent{},
|
||
|
QUICHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
TLSHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
},
|
||
|
args: args{
|
||
|
begin: traceTime(0),
|
||
|
},
|
||
|
wantOut: []model.ArchivalDNSLookupResult{{
|
||
|
Answers: []model.ArchivalDNSAnswer{{
|
||
|
ASN: 15169,
|
||
|
ASOrgName: "Google LLC",
|
||
|
AnswerType: "A",
|
||
|
Hostname: "",
|
||
|
IPv4: "8.8.8.8",
|
||
|
IPv6: "",
|
||
|
TTL: nil,
|
||
|
}, {
|
||
|
ASN: 15169,
|
||
|
ASOrgName: "Google LLC",
|
||
|
AnswerType: "A",
|
||
|
Hostname: "",
|
||
|
IPv4: "8.8.4.4",
|
||
|
IPv6: "",
|
||
|
TTL: nil,
|
||
|
}},
|
||
|
Engine: "udp",
|
||
|
Failure: nil,
|
||
|
Hostname: "dns.google",
|
||
|
QueryType: "A",
|
||
|
ResolverHostname: nil,
|
||
|
ResolverPort: nil,
|
||
|
ResolverAddress: "8.8.8.8:53",
|
||
|
T: deltaSinceTraceTime(2),
|
||
|
}},
|
||
|
}}
|
||
|
for _, tt := range tests {
|
||
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
tr := &Trace{
|
||
|
DNSLookupHTTPS: tt.fields.DNSLookupHTTPS,
|
||
|
DNSLookupHost: tt.fields.DNSLookupHost,
|
||
|
DNSRoundTrip: tt.fields.DNSRoundTrip,
|
||
|
HTTPRoundTrip: tt.fields.HTTPRoundTrip,
|
||
|
Network: tt.fields.Network,
|
||
|
QUICHandshake: tt.fields.QUICHandshake,
|
||
|
TLSHandshake: tt.fields.TLSHandshake,
|
||
|
}
|
||
|
gotOut := tr.NewArchivalDNSLookupResultList(tt.args.begin)
|
||
|
if diff := cmp.Diff(tt.wantOut, gotOut); diff != "" {
|
||
|
t.Fatal(diff)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestTraceNewArchivalNetworkEventList(t *testing.T) {
|
||
|
type fields struct {
|
||
|
DNSLookupHTTPS []*DNSLookupEvent
|
||
|
DNSLookupHost []*DNSLookupEvent
|
||
|
DNSRoundTrip []*DNSRoundTripEvent
|
||
|
HTTPRoundTrip []*HTTPRoundTripEvent
|
||
|
Network []*NetworkEvent
|
||
|
QUICHandshake []*QUICTLSHandshakeEvent
|
||
|
TLSHandshake []*QUICTLSHandshakeEvent
|
||
|
}
|
||
|
type args struct {
|
||
|
begin time.Time
|
||
|
}
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
fields fields
|
||
|
args args
|
||
|
wantOut []model.ArchivalNetworkEvent
|
||
|
}{{
|
||
|
name: "with empty trace",
|
||
|
fields: fields{
|
||
|
DNSLookupHTTPS: []*DNSLookupEvent{},
|
||
|
DNSLookupHost: []*DNSLookupEvent{},
|
||
|
DNSRoundTrip: []*DNSRoundTripEvent{},
|
||
|
HTTPRoundTrip: []*HTTPRoundTripEvent{},
|
||
|
Network: []*NetworkEvent{},
|
||
|
QUICHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
TLSHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
},
|
||
|
args: args{
|
||
|
begin: traceTime(0),
|
||
|
},
|
||
|
wantOut: nil,
|
||
|
}, {
|
||
|
name: "we fill all the fields we should be filling",
|
||
|
fields: fields{
|
||
|
DNSLookupHTTPS: []*DNSLookupEvent{},
|
||
|
DNSLookupHost: []*DNSLookupEvent{},
|
||
|
DNSRoundTrip: []*DNSRoundTripEvent{},
|
||
|
HTTPRoundTrip: []*HTTPRoundTripEvent{},
|
||
|
Network: []*NetworkEvent{{
|
||
|
Count: 1234,
|
||
|
Failure: netxlite.NewTopLevelGenericErrWrapper(io.EOF),
|
||
|
Finished: traceTime(2),
|
||
|
Network: "tcp",
|
||
|
Operation: netxlite.ReadOperation,
|
||
|
RemoteAddr: "8.8.8.8:443",
|
||
|
Started: traceTime(1),
|
||
|
}},
|
||
|
QUICHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
TLSHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
},
|
||
|
args: args{
|
||
|
begin: traceTime(0),
|
||
|
},
|
||
|
wantOut: []model.ArchivalNetworkEvent{{
|
||
|
Address: "8.8.8.8:443",
|
||
|
Failure: failureFromString(netxlite.FailureEOFError),
|
||
|
NumBytes: 1234,
|
||
|
Operation: netxlite.ReadOperation,
|
||
|
Proto: "tcp",
|
||
|
T: deltaSinceTraceTime(2),
|
||
|
Tags: nil,
|
||
|
}},
|
||
|
}}
|
||
|
for _, tt := range tests {
|
||
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
tr := &Trace{
|
||
|
DNSLookupHTTPS: tt.fields.DNSLookupHTTPS,
|
||
|
DNSLookupHost: tt.fields.DNSLookupHost,
|
||
|
DNSRoundTrip: tt.fields.DNSRoundTrip,
|
||
|
HTTPRoundTrip: tt.fields.HTTPRoundTrip,
|
||
|
Network: tt.fields.Network,
|
||
|
QUICHandshake: tt.fields.QUICHandshake,
|
||
|
TLSHandshake: tt.fields.TLSHandshake,
|
||
|
}
|
||
|
gotOut := tr.NewArchivalNetworkEventList(tt.args.begin)
|
||
|
if diff := cmp.Diff(tt.wantOut, gotOut); diff != "" {
|
||
|
t.Fatal(diff)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestTraceNewArchivalTLSHandshakeResultList(t *testing.T) {
|
||
|
type fields struct {
|
||
|
DNSLookupHTTPS []*DNSLookupEvent
|
||
|
DNSLookupHost []*DNSLookupEvent
|
||
|
DNSRoundTrip []*DNSRoundTripEvent
|
||
|
HTTPRoundTrip []*HTTPRoundTripEvent
|
||
|
Network []*NetworkEvent
|
||
|
QUICHandshake []*QUICTLSHandshakeEvent
|
||
|
TLSHandshake []*QUICTLSHandshakeEvent
|
||
|
}
|
||
|
type args struct {
|
||
|
begin time.Time
|
||
|
}
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
fields fields
|
||
|
args args
|
||
|
wantOut []model.ArchivalTLSOrQUICHandshakeResult
|
||
|
}{{
|
||
|
name: "with empty trace",
|
||
|
fields: fields{
|
||
|
DNSLookupHTTPS: []*DNSLookupEvent{},
|
||
|
DNSLookupHost: []*DNSLookupEvent{},
|
||
|
DNSRoundTrip: []*DNSRoundTripEvent{},
|
||
|
HTTPRoundTrip: []*HTTPRoundTripEvent{},
|
||
|
Network: []*NetworkEvent{},
|
||
|
QUICHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
TLSHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
},
|
||
|
args: args{
|
||
|
begin: traceTime(0),
|
||
|
},
|
||
|
wantOut: nil,
|
||
|
}, {
|
||
|
name: "we fill all the fields we should be filling",
|
||
|
fields: fields{
|
||
|
DNSLookupHTTPS: []*DNSLookupEvent{},
|
||
|
DNSLookupHost: []*DNSLookupEvent{},
|
||
|
DNSRoundTrip: []*DNSRoundTripEvent{},
|
||
|
HTTPRoundTrip: []*HTTPRoundTripEvent{},
|
||
|
Network: []*NetworkEvent{},
|
||
|
QUICHandshake: []*QUICTLSHandshakeEvent{},
|
||
|
TLSHandshake: []*QUICTLSHandshakeEvent{{
|
||
|
ALPN: []string{"h2", "http/1.1"},
|
||
|
CipherSuite: "TLS_AES_128_GCM_SHA256",
|
||
|
Failure: netxlite.NewTopLevelGenericErrWrapper(io.EOF),
|
||
|
Finished: traceTime(2),
|
||
|
NegotiatedProto: "h2",
|
||
|
Network: "tcp",
|
||
|
PeerCerts: [][]byte{
|
||
|
[]byte("deadbeef"),
|
||
|
[]byte("xox"),
|
||
|
},
|
||
|
RemoteAddr: "8.8.8.8:443",
|
||
|
SNI: "dns.google",
|
||
|
SkipVerify: true,
|
||
|
Started: traceTime(1),
|
||
|
TLSVersion: "TLSv1.3",
|
||
|
}},
|
||
|
},
|
||
|
args: args{
|
||
|
begin: traceTime(0),
|
||
|
},
|
||
|
wantOut: []model.ArchivalTLSOrQUICHandshakeResult{{
|
||
|
CipherSuite: "TLS_AES_128_GCM_SHA256",
|
||
|
Failure: failureFromString(netxlite.FailureEOFError),
|
||
|
NegotiatedProtocol: "h2",
|
||
|
NoTLSVerify: true,
|
||
|
PeerCertificates: []model.ArchivalMaybeBinaryData{{
|
||
|
Value: "deadbeef",
|
||
|
}, {
|
||
|
Value: "xox",
|
||
|
}},
|
||
|
ServerName: "dns.google",
|
||
|
T: deltaSinceTraceTime(2),
|
||
|
Tags: nil,
|
||
|
TLSVersion: "TLSv1.3",
|
||
|
}},
|
||
|
}}
|
||
|
for _, tt := range tests {
|
||
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
tr := &Trace{
|
||
|
DNSLookupHTTPS: tt.fields.DNSLookupHTTPS,
|
||
|
DNSLookupHost: tt.fields.DNSLookupHost,
|
||
|
DNSRoundTrip: tt.fields.DNSRoundTrip,
|
||
|
HTTPRoundTrip: tt.fields.HTTPRoundTrip,
|
||
|
Network: tt.fields.Network,
|
||
|
QUICHandshake: tt.fields.QUICHandshake,
|
||
|
TLSHandshake: tt.fields.TLSHandshake,
|
||
|
}
|
||
|
gotOut := tr.NewArchivalTLSHandshakeResultList(tt.args.begin)
|
||
|
if diff := cmp.Diff(tt.wantOut, gotOut); diff != "" {
|
||
|
t.Fatal(diff)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|