refactor(tracex): convert to unit testing (#781)
The exercise already allowed me to notice issues such as fields not being properly initialized by savers. This is one of the last steps before moving tracex away from the internal/netx package and into the internal package. See https://github.com/ooni/probe/issues/2121
This commit is contained in:
parent
6212daa54a
commit
d397036073
17 changed files with 1674 additions and 1111 deletions
|
|
@ -3,291 +3,250 @@ package tracex
|
|||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"reflect"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/ooni/probe-cli/v3/internal/model/mocks"
|
||||
)
|
||||
|
||||
func TestSaverTLSHandshakerSuccessWithReadWrite(t *testing.T) {
|
||||
// This is the most common use case for collecting reads, writes
|
||||
if testing.Short() {
|
||||
t.Skip("skip test in short mode")
|
||||
}
|
||||
nextprotos := []string{"h2"}
|
||||
saver := &Saver{}
|
||||
tlsdlr := &netxlite.TLSDialerLegacy{
|
||||
Config: &tls.Config{NextProtos: nextprotos},
|
||||
Dialer: netxlite.NewDialerWithResolver(
|
||||
model.DiscardLogger,
|
||||
netxlite.NewResolverStdlib(model.DiscardLogger),
|
||||
saver.NewReadWriteObserver(),
|
||||
),
|
||||
TLSHandshaker: saver.WrapTLSHandshaker(&netxlite.TLSHandshakerConfigurable{}),
|
||||
}
|
||||
// Implementation note: we don't close the connection here because it is
|
||||
// very handy to have the last event being the end of the handshake
|
||||
_, err := tlsdlr.DialTLSContext(context.Background(), "tcp", "www.google.com:443")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ev := saver.Read()
|
||||
if len(ev) < 4 {
|
||||
// it's a bit tricky to be sure about the right number of
|
||||
// events because network conditions may influence that
|
||||
t.Fatal("unexpected number of events")
|
||||
}
|
||||
if ev[0].Name() != "tls_handshake_start" {
|
||||
t.Fatal("unexpected Name")
|
||||
}
|
||||
if ev[0].Value().TLSServerName != "www.google.com" {
|
||||
t.Fatal("unexpected TLSServerName")
|
||||
}
|
||||
if !reflect.DeepEqual(ev[0].Value().TLSNextProtos, nextprotos) {
|
||||
t.Fatal("unexpected TLSNextProtos")
|
||||
}
|
||||
if ev[0].Value().Time.After(time.Now()) {
|
||||
t.Fatal("unexpected Time")
|
||||
}
|
||||
last := len(ev) - 1
|
||||
for idx := 1; idx < last; idx++ {
|
||||
if ev[idx].Value().Data == nil {
|
||||
t.Fatal("unexpected Data")
|
||||
func TestTLSHandshakerSaver(t *testing.T) {
|
||||
|
||||
t.Run("Handshake", func(t *testing.T) {
|
||||
checkStartEventFields := func(t *testing.T, value *EventValue) {
|
||||
if value.Address != "8.8.8.8:443" {
|
||||
t.Fatal("invalid Address")
|
||||
}
|
||||
if !value.NoTLSVerify {
|
||||
t.Fatal("expected NoTLSVerify to be true")
|
||||
}
|
||||
if value.Proto != "tcp" {
|
||||
t.Fatal("wrong protocol")
|
||||
}
|
||||
if diff := cmp.Diff(value.TLSNextProtos, []string{"h2"}); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
if value.TLSServerName != "dns.google" {
|
||||
t.Fatal("invalid TLSServerName")
|
||||
}
|
||||
if value.Time.IsZero() {
|
||||
t.Fatal("expected non zero time")
|
||||
}
|
||||
}
|
||||
if ev[idx].Value().Duration <= 0 {
|
||||
t.Fatal("unexpected Duration")
|
||||
|
||||
checkStartedEvent := func(t *testing.T, ev Event) {
|
||||
if _, good := ev.(*EventTLSHandshakeStart); !good {
|
||||
t.Fatal("invalid event type")
|
||||
}
|
||||
value := ev.Value()
|
||||
checkStartEventFields(t, value)
|
||||
}
|
||||
if ev[idx].Value().Err != nil {
|
||||
t.Fatal("unexpected Err")
|
||||
|
||||
checkDoneEventFieldsSuccess := func(t *testing.T, value *EventValue) {
|
||||
if value.Duration <= 0 {
|
||||
t.Fatal("expected non-zero duration")
|
||||
}
|
||||
if value.Err != nil {
|
||||
t.Fatal("expected no error here")
|
||||
}
|
||||
if value.TLSCipherSuite != "TLS_RSA_WITH_RC4_128_SHA" {
|
||||
t.Fatal("invalid cipher suite")
|
||||
}
|
||||
if value.TLSNegotiatedProto != "h2" {
|
||||
t.Fatal("invalid negotiated protocol")
|
||||
}
|
||||
if diff := cmp.Diff(value.TLSPeerCerts, []*x509.Certificate{}); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
if value.TLSVersion != "TLSv1.3" {
|
||||
t.Fatal("invalid TLS version")
|
||||
}
|
||||
}
|
||||
if ev[idx].Value().NumBytes <= 0 {
|
||||
t.Fatal("unexpected NumBytes")
|
||||
|
||||
checkDoneEvent := func(t *testing.T, ev Event, fun func(t *testing.T, value *EventValue)) {
|
||||
if _, good := ev.(*EventTLSHandshakeDone); !good {
|
||||
t.Fatal("invalid event type")
|
||||
}
|
||||
value := ev.Value()
|
||||
checkStartEventFields(t, value)
|
||||
fun(t, value)
|
||||
}
|
||||
switch ev[idx].Name() {
|
||||
case netxlite.ReadOperation, netxlite.WriteOperation:
|
||||
default:
|
||||
t.Fatal("unexpected Name")
|
||||
|
||||
t.Run("on success", func(t *testing.T) {
|
||||
saver := &Saver{}
|
||||
returnedConnState := tls.ConnectionState{
|
||||
CipherSuite: tls.TLS_RSA_WITH_RC4_128_SHA,
|
||||
NegotiatedProtocol: "h2",
|
||||
PeerCertificates: []*x509.Certificate{},
|
||||
Version: tls.VersionTLS13,
|
||||
}
|
||||
returnedConn := &mocks.TLSConn{
|
||||
MockConnectionState: func() tls.ConnectionState {
|
||||
return returnedConnState
|
||||
},
|
||||
}
|
||||
thx := saver.WrapTLSHandshaker(&mocks.TLSHandshaker{
|
||||
MockHandshake: func(ctx context.Context, conn net.Conn,
|
||||
config *tls.Config) (net.Conn, tls.ConnectionState, error) {
|
||||
return returnedConn, returnedConnState, nil
|
||||
},
|
||||
})
|
||||
ctx := context.Background()
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{"h2"},
|
||||
ServerName: "dns.google",
|
||||
}
|
||||
tcpConn := &mocks.Conn{
|
||||
MockRemoteAddr: func() net.Addr {
|
||||
return &mocks.Addr{
|
||||
MockString: func() string {
|
||||
return "8.8.8.8:443"
|
||||
},
|
||||
MockNetwork: func() string {
|
||||
return "tcp"
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
conn, _, err := thx.Handshake(ctx, tcpConn, tlsConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if conn == nil {
|
||||
t.Fatal("expected non-nil conn")
|
||||
}
|
||||
events := saver.Read()
|
||||
if len(events) != 2 {
|
||||
t.Fatal("expected two events")
|
||||
}
|
||||
checkStartedEvent(t, events[0])
|
||||
checkDoneEvent(t, events[1], checkDoneEventFieldsSuccess)
|
||||
})
|
||||
|
||||
checkDoneEventFieldsFailure := func(t *testing.T, value *EventValue) {
|
||||
if value.Duration <= 0 {
|
||||
t.Fatal("expected non-zero duration")
|
||||
}
|
||||
if value.Err == nil {
|
||||
t.Fatal("expected non-nil error here")
|
||||
}
|
||||
if value.TLSCipherSuite != "" {
|
||||
t.Fatal("invalid TLS cipher suite")
|
||||
}
|
||||
if value.TLSNegotiatedProto != "" {
|
||||
t.Fatal("invalid negotiated proto")
|
||||
}
|
||||
if len(value.TLSPeerCerts) > 0 {
|
||||
t.Fatal("expected no peer certs")
|
||||
}
|
||||
if value.TLSVersion != "" {
|
||||
t.Fatal("invalid TLS version")
|
||||
}
|
||||
}
|
||||
if ev[idx].Value().Time.Before(ev[idx-1].Value().Time) {
|
||||
t.Fatal("unexpected Time")
|
||||
}
|
||||
}
|
||||
if ev[last].Value().Duration <= 0 {
|
||||
t.Fatal("unexpected Duration")
|
||||
}
|
||||
if ev[last].Value().Err != nil {
|
||||
t.Fatal("unexpected Err")
|
||||
}
|
||||
if ev[last].Name() != "tls_handshake_done" {
|
||||
t.Fatal("unexpected Name")
|
||||
}
|
||||
if ev[last].Value().TLSCipherSuite == "" {
|
||||
t.Fatal("unexpected TLSCipherSuite")
|
||||
}
|
||||
if ev[last].Value().TLSNegotiatedProto != "h2" {
|
||||
t.Fatal("unexpected TLSNegotiatedProto")
|
||||
}
|
||||
if !reflect.DeepEqual(ev[last].Value().TLSNextProtos, nextprotos) {
|
||||
t.Fatal("unexpected TLSNextProtos")
|
||||
}
|
||||
if ev[last].Value().TLSPeerCerts == nil {
|
||||
t.Fatal("unexpected TLSPeerCerts")
|
||||
}
|
||||
if ev[last].Value().TLSServerName != "www.google.com" {
|
||||
t.Fatal("unexpected TLSServerName")
|
||||
}
|
||||
if ev[last].Value().TLSVersion == "" {
|
||||
t.Fatal("unexpected TLSVersion")
|
||||
}
|
||||
if ev[last].Value().Time.Before(ev[last-1].Value().Time) {
|
||||
t.Fatal("unexpected Time")
|
||||
}
|
||||
|
||||
t.Run("on failure", func(t *testing.T) {
|
||||
expected := errors.New("mocked error")
|
||||
saver := &Saver{}
|
||||
thx := saver.WrapTLSHandshaker(&mocks.TLSHandshaker{
|
||||
MockHandshake: func(ctx context.Context, conn net.Conn,
|
||||
config *tls.Config) (net.Conn, tls.ConnectionState, error) {
|
||||
return nil, tls.ConnectionState{}, expected
|
||||
},
|
||||
})
|
||||
ctx := context.Background()
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{"h2"},
|
||||
ServerName: "dns.google",
|
||||
}
|
||||
tcpConn := &mocks.Conn{
|
||||
MockRemoteAddr: func() net.Addr {
|
||||
return &mocks.Addr{
|
||||
MockString: func() string {
|
||||
return "8.8.8.8:443"
|
||||
},
|
||||
MockNetwork: func() string {
|
||||
return "tcp"
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
conn, _, err := thx.Handshake(ctx, tcpConn, tlsConfig)
|
||||
if !errors.Is(err, expected) {
|
||||
t.Fatal("unexpected err", err)
|
||||
}
|
||||
if conn != nil {
|
||||
t.Fatal("expected nil conn")
|
||||
}
|
||||
events := saver.Read()
|
||||
if len(events) != 2 {
|
||||
t.Fatal("expected two events")
|
||||
}
|
||||
checkStartedEvent(t, events[0])
|
||||
checkDoneEvent(t, events[1], checkDoneEventFieldsFailure)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSaverTLSHandshakerSuccess(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skip test in short mode")
|
||||
func Test_tlsPeerCerts(t *testing.T) {
|
||||
cert0 := &x509.Certificate{Raw: []byte{1, 2, 3, 4}}
|
||||
type args struct {
|
||||
state tls.ConnectionState
|
||||
err error
|
||||
}
|
||||
nextprotos := []string{"h2"}
|
||||
saver := &Saver{}
|
||||
tlsdlr := &netxlite.TLSDialerLegacy{
|
||||
Config: &tls.Config{NextProtos: nextprotos},
|
||||
Dialer: &netxlite.DialerSystem{},
|
||||
TLSHandshaker: saver.WrapTLSHandshaker(&netxlite.TLSHandshakerConfigurable{}),
|
||||
}
|
||||
conn, err := tlsdlr.DialTLSContext(context.Background(), "tcp", "www.google.com:443")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
conn.Close()
|
||||
ev := saver.Read()
|
||||
if len(ev) != 2 {
|
||||
t.Fatal("unexpected number of events")
|
||||
}
|
||||
if ev[0].Name() != "tls_handshake_start" {
|
||||
t.Fatal("unexpected Name")
|
||||
}
|
||||
if ev[0].Value().TLSServerName != "www.google.com" {
|
||||
t.Fatal("unexpected TLSServerName")
|
||||
}
|
||||
if !reflect.DeepEqual(ev[0].Value().TLSNextProtos, nextprotos) {
|
||||
t.Fatal("unexpected TLSNextProtos")
|
||||
}
|
||||
if ev[0].Value().Time.After(time.Now()) {
|
||||
t.Fatal("unexpected Time")
|
||||
}
|
||||
if ev[1].Value().Duration <= 0 {
|
||||
t.Fatal("unexpected Duration")
|
||||
}
|
||||
if ev[1].Value().Err != nil {
|
||||
t.Fatal("unexpected Err")
|
||||
}
|
||||
if ev[1].Name() != "tls_handshake_done" {
|
||||
t.Fatal("unexpected Name")
|
||||
}
|
||||
if ev[1].Value().TLSCipherSuite == "" {
|
||||
t.Fatal("unexpected TLSCipherSuite")
|
||||
}
|
||||
if ev[1].Value().TLSNegotiatedProto != "h2" {
|
||||
t.Fatal("unexpected TLSNegotiatedProto")
|
||||
}
|
||||
if !reflect.DeepEqual(ev[1].Value().TLSNextProtos, nextprotos) {
|
||||
t.Fatal("unexpected TLSNextProtos")
|
||||
}
|
||||
if ev[1].Value().TLSPeerCerts == nil {
|
||||
t.Fatal("unexpected TLSPeerCerts")
|
||||
}
|
||||
if ev[1].Value().TLSServerName != "www.google.com" {
|
||||
t.Fatal("unexpected TLSServerName")
|
||||
}
|
||||
if ev[1].Value().TLSVersion == "" {
|
||||
t.Fatal("unexpected TLSVersion")
|
||||
}
|
||||
if ev[1].Value().Time.Before(ev[0].Value().Time) {
|
||||
t.Fatal("unexpected Time")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaverTLSHandshakerHostnameError(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skip test in short mode")
|
||||
}
|
||||
saver := &Saver{}
|
||||
tlsdlr := &netxlite.TLSDialerLegacy{
|
||||
Dialer: &netxlite.DialerSystem{},
|
||||
TLSHandshaker: saver.WrapTLSHandshaker(&netxlite.TLSHandshakerConfigurable{}),
|
||||
}
|
||||
conn, err := tlsdlr.DialTLSContext(
|
||||
context.Background(), "tcp", "wrong.host.badssl.com:443")
|
||||
if err == nil {
|
||||
t.Fatal("expected an error here")
|
||||
}
|
||||
if conn != nil {
|
||||
t.Fatal("expected nil conn here")
|
||||
}
|
||||
for _, ev := range saver.Read() {
|
||||
if ev.Name() != "tls_handshake_done" {
|
||||
continue
|
||||
}
|
||||
if ev.Value().NoTLSVerify == true {
|
||||
t.Fatal("expected NoTLSVerify to be false")
|
||||
}
|
||||
if len(ev.Value().TLSPeerCerts) < 1 {
|
||||
t.Fatal("expected at least a certificate here")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaverTLSHandshakerInvalidCertError(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skip test in short mode")
|
||||
}
|
||||
saver := &Saver{}
|
||||
tlsdlr := &netxlite.TLSDialerLegacy{
|
||||
Dialer: &netxlite.DialerSystem{},
|
||||
TLSHandshaker: saver.WrapTLSHandshaker(&netxlite.TLSHandshakerConfigurable{}),
|
||||
}
|
||||
conn, err := tlsdlr.DialTLSContext(
|
||||
context.Background(), "tcp", "expired.badssl.com:443")
|
||||
if err == nil {
|
||||
t.Fatal("expected an error here")
|
||||
}
|
||||
if conn != nil {
|
||||
t.Fatal("expected nil conn here")
|
||||
}
|
||||
for _, ev := range saver.Read() {
|
||||
if ev.Name() != "tls_handshake_done" {
|
||||
continue
|
||||
}
|
||||
if ev.Value().NoTLSVerify == true {
|
||||
t.Fatal("expected NoTLSVerify to be false")
|
||||
}
|
||||
if len(ev.Value().TLSPeerCerts) < 1 {
|
||||
t.Fatal("expected at least a certificate here")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaverTLSHandshakerAuthorityError(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skip test in short mode")
|
||||
}
|
||||
saver := &Saver{}
|
||||
tlsdlr := &netxlite.TLSDialerLegacy{
|
||||
Dialer: &netxlite.DialerSystem{},
|
||||
TLSHandshaker: saver.WrapTLSHandshaker(&netxlite.TLSHandshakerConfigurable{}),
|
||||
}
|
||||
conn, err := tlsdlr.DialTLSContext(
|
||||
context.Background(), "tcp", "self-signed.badssl.com:443")
|
||||
if err == nil {
|
||||
t.Fatal("expected an error here")
|
||||
}
|
||||
if conn != nil {
|
||||
t.Fatal("expected nil conn here")
|
||||
}
|
||||
for _, ev := range saver.Read() {
|
||||
if ev.Name() != "tls_handshake_done" {
|
||||
continue
|
||||
}
|
||||
if ev.Value().NoTLSVerify == true {
|
||||
t.Fatal("expected NoTLSVerify to be false")
|
||||
}
|
||||
if len(ev.Value().TLSPeerCerts) < 1 {
|
||||
t.Fatal("expected at least a certificate here")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaverTLSHandshakerNoTLSVerify(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skip test in short mode")
|
||||
}
|
||||
saver := &Saver{}
|
||||
tlsdlr := &netxlite.TLSDialerLegacy{
|
||||
Config: &tls.Config{InsecureSkipVerify: true},
|
||||
Dialer: &netxlite.DialerSystem{},
|
||||
TLSHandshaker: saver.WrapTLSHandshaker(&netxlite.TLSHandshakerConfigurable{}),
|
||||
}
|
||||
conn, err := tlsdlr.DialTLSContext(
|
||||
context.Background(), "tcp", "self-signed.badssl.com:443")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if conn == nil {
|
||||
t.Fatal("expected non-nil conn here")
|
||||
}
|
||||
conn.Close()
|
||||
for _, ev := range saver.Read() {
|
||||
if ev.Name() != "tls_handshake_done" {
|
||||
continue
|
||||
}
|
||||
if ev.Value().NoTLSVerify != true {
|
||||
t.Fatal("expected NoTLSVerify to be true")
|
||||
}
|
||||
if len(ev.Value().TLSPeerCerts) < 1 {
|
||||
t.Fatal("expected at least a certificate here")
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*x509.Certificate
|
||||
}{{
|
||||
name: "no error",
|
||||
args: args{
|
||||
state: tls.ConnectionState{
|
||||
PeerCertificates: []*x509.Certificate{cert0},
|
||||
},
|
||||
},
|
||||
want: []*x509.Certificate{cert0},
|
||||
}, {
|
||||
name: "all empty",
|
||||
args: args{},
|
||||
want: nil,
|
||||
}, {
|
||||
name: "x509.HostnameError",
|
||||
args: args{
|
||||
state: tls.ConnectionState{},
|
||||
err: x509.HostnameError{
|
||||
Certificate: cert0,
|
||||
},
|
||||
},
|
||||
want: []*x509.Certificate{cert0},
|
||||
}, {
|
||||
name: "x509.UnknownAuthorityError",
|
||||
args: args{
|
||||
state: tls.ConnectionState{},
|
||||
err: x509.UnknownAuthorityError{
|
||||
Cert: cert0,
|
||||
},
|
||||
},
|
||||
want: []*x509.Certificate{cert0},
|
||||
}, {
|
||||
name: "x509.CertificateInvalidError",
|
||||
args: args{
|
||||
state: tls.ConnectionState{},
|
||||
err: x509.CertificateInvalidError{
|
||||
Cert: cert0,
|
||||
},
|
||||
},
|
||||
want: []*x509.Certificate{cert0},
|
||||
}}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tlsPeerCerts(tt.args.state, tt.args.err)
|
||||
if diff := cmp.Diff(tt.want, got); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue