ooni-probe-cli/internal/netxlite/tls_test.go

868 lines
24 KiB
Go
Raw Permalink Normal View History

refactor(netxlite): hide details without breaking the rest of the tree (#454) ## Description This PR continues the refactoring of `netx` under the following principles: 1. do not break the rest of the tree and do not engage in extensive tree-wide refactoring yet 2. move under `netxlite` clearly related subpackages (e.g., `iox`, `netxmocks`) 3. move into `internal/netxlite/internal` stuff that is clearly private of `netxlite` 4. hide implementation details in `netxlite` pending new factories 5. refactor `tls` code in `netxlite` to clearly separate `crypto/tls` code from `utls` code After each commit, I run `go test -short -race ./...` locally. Each individual commit explains what it does. I will squash, but this operation will preserve the original commit titles, so this will give further insight on each step. ## Commits * refactor: rename netxmocks -> netxlite/mocks Part of https://github.com/ooni/probe/issues/1591 * refactor: rename quicx -> netxlite/quicx See https://github.com/ooni/probe/issues/1591 * refactor: rename iox -> netxlite/iox Regenerate sources and make sure the tests pass. See https://github.com/ooni/probe/issues/1591. * refactor(iox): move MockableReader to netxlite/mocks See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): generator is an implementation detail See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): separate tls and utls code See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): hide most types but keep old names as legacy With this change we avoid breaking the rest of the tree, but we start hiding some implementation details a bit. Factories will follow. See https://github.com/ooni/probe/issues/1591
2021-09-05 14:49:38 +02:00
package netxlite
import (
"context"
"crypto/tls"
"errors"
"io"
"net"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"strings"
"testing"
"time"
"github.com/apex/log"
"github.com/google/go-cmp/cmp"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/model/mocks"
"github.com/ooni/probe-cli/v3/internal/netxlite/filtering"
"github.com/ooni/probe-cli/v3/internal/testingx"
refactor(netxlite): hide details without breaking the rest of the tree (#454) ## Description This PR continues the refactoring of `netx` under the following principles: 1. do not break the rest of the tree and do not engage in extensive tree-wide refactoring yet 2. move under `netxlite` clearly related subpackages (e.g., `iox`, `netxmocks`) 3. move into `internal/netxlite/internal` stuff that is clearly private of `netxlite` 4. hide implementation details in `netxlite` pending new factories 5. refactor `tls` code in `netxlite` to clearly separate `crypto/tls` code from `utls` code After each commit, I run `go test -short -race ./...` locally. Each individual commit explains what it does. I will squash, but this operation will preserve the original commit titles, so this will give further insight on each step. ## Commits * refactor: rename netxmocks -> netxlite/mocks Part of https://github.com/ooni/probe/issues/1591 * refactor: rename quicx -> netxlite/quicx See https://github.com/ooni/probe/issues/1591 * refactor: rename iox -> netxlite/iox Regenerate sources and make sure the tests pass. See https://github.com/ooni/probe/issues/1591. * refactor(iox): move MockableReader to netxlite/mocks See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): generator is an implementation detail See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): separate tls and utls code See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): hide most types but keep old names as legacy With this change we avoid breaking the rest of the tree, but we start hiding some implementation details a bit. Factories will follow. See https://github.com/ooni/probe/issues/1591
2021-09-05 14:49:38 +02:00
)
func TestVersionString(t *testing.T) {
if TLSVersionString(tls.VersionTLS13) != "TLSv1.3" {
t.Fatal("not working for existing version")
}
if TLSVersionString(1) != "TLS_VERSION_UNKNOWN_1" {
t.Fatal("not working for nonexisting version")
}
if TLSVersionString(0) != "" {
t.Fatal("not working for zero version")
}
}
func TestCipherSuite(t *testing.T) {
if TLSCipherSuiteString(tls.TLS_AES_128_GCM_SHA256) != "TLS_AES_128_GCM_SHA256" {
t.Fatal("not working for existing cipher suite")
}
if TLSCipherSuiteString(1) != "TLS_CIPHER_SUITE_UNKNOWN_1" {
t.Fatal("not working for nonexisting cipher suite")
}
if TLSCipherSuiteString(0) != "" {
t.Fatal("not working for zero cipher suite")
}
}
func TestNewDefaultCertPoolWorks(t *testing.T) {
pool := NewDefaultCertPool()
if pool == nil {
t.Fatal("expected non-nil value here")
}
}
func TestConfigureTLSVersion(t *testing.T) {
tests := []struct {
name string
version string
wantErr error
versionMin int
versionMax int
}{{
name: "with TLSv1.3",
version: "TLSv1.3",
wantErr: nil,
versionMin: tls.VersionTLS13,
versionMax: tls.VersionTLS13,
}, {
name: "with TLSv1.2",
version: "TLSv1.2",
wantErr: nil,
versionMin: tls.VersionTLS12,
versionMax: tls.VersionTLS12,
}, {
name: "with TLSv1.1",
version: "TLSv1.1",
wantErr: nil,
versionMin: tls.VersionTLS11,
versionMax: tls.VersionTLS11,
}, {
name: "with TLSv1.0",
version: "TLSv1.0",
wantErr: nil,
versionMin: tls.VersionTLS10,
versionMax: tls.VersionTLS10,
}, {
name: "with TLSv1",
version: "TLSv1",
wantErr: nil,
versionMin: tls.VersionTLS10,
versionMax: tls.VersionTLS10,
}, {
name: "with default",
version: "",
wantErr: nil,
versionMin: 0,
versionMax: 0,
}, {
name: "with invalid version",
version: "TLSv999",
wantErr: ErrInvalidTLSVersion,
versionMin: 0,
versionMax: 0,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
conf := new(tls.Config)
err := ConfigureTLSVersion(conf, tt.version)
if !errors.Is(err, tt.wantErr) {
t.Fatalf("not the error we expected: %+v", err)
}
if conf.MinVersion != uint16(tt.versionMin) {
t.Fatalf("not the min version we expected: %+v", conf.MinVersion)
}
if conf.MaxVersion != uint16(tt.versionMax) {
t.Fatalf("not the max version we expected: %+v", conf.MaxVersion)
}
})
}
}
func TestNewTLSHandshakerStdlib(t *testing.T) {
th := NewTLSHandshakerStdlib(log.Log)
logger := th.(*tlsHandshakerLogger)
if logger.DebugLogger != log.Log {
t.Fatal("invalid logger")
}
configurable := logger.TLSHandshaker.(*tlsHandshakerConfigurable)
if configurable.NewConn != nil {
t.Fatal("expected nil NewConn")
}
}
func TestTLSHandshakerConfigurable(t *testing.T) {
t.Run("Handshake", func(t *testing.T) {
t.Run("with handshake I/O error", func(t *testing.T) {
var times []time.Time
h := &tlsHandshakerConfigurable{}
tcpConn := &mocks.Conn{
MockWrite: func(b []byte) (int, error) {
return 0, io.EOF
},
MockSetDeadline: func(t time.Time) error {
times = append(times, t)
return nil
},
MockRemoteAddr: func() net.Addr {
return &mocks.Addr{
MockString: func() string {
return "1.1.1.1:443"
},
MockNetwork: func() string {
return "tcp"
},
}
},
}
ctx := context.Background()
conn, state, err := h.Handshake(ctx, tcpConn, &tls.Config{
ServerName: "x.org",
})
if !errors.Is(err, io.EOF) {
t.Fatal("not the error that we expected")
}
var errWrapper *ErrWrapper
if !errors.As(err, &errWrapper) {
t.Fatal("the error has not been wrapped")
}
if errWrapper.Failure != FailureEOFError {
t.Fatal("invalid wrapped error's failure")
}
if errWrapper.Operation != TLSHandshakeOperation {
t.Fatal("invalid wrapped error's operation")
}
if !errors.Is(errWrapper.WrappedErr, io.EOF) {
t.Fatal("invalid wrapped error's underlying error")
}
if conn != nil {
t.Fatal("expected nil con here")
}
if len(times) != 2 {
t.Fatal("expected two time entries")
}
if !times[0].After(time.Now()) {
t.Fatal("timeout not in the future")
}
if !times[1].IsZero() {
t.Fatal("did not clear timeout on exit")
}
if !reflect.ValueOf(state).IsZero() {
t.Fatal("the returned connection state is not a zero value")
}
})
refactor(netxlite): hide details without breaking the rest of the tree (#454) ## Description This PR continues the refactoring of `netx` under the following principles: 1. do not break the rest of the tree and do not engage in extensive tree-wide refactoring yet 2. move under `netxlite` clearly related subpackages (e.g., `iox`, `netxmocks`) 3. move into `internal/netxlite/internal` stuff that is clearly private of `netxlite` 4. hide implementation details in `netxlite` pending new factories 5. refactor `tls` code in `netxlite` to clearly separate `crypto/tls` code from `utls` code After each commit, I run `go test -short -race ./...` locally. Each individual commit explains what it does. I will squash, but this operation will preserve the original commit titles, so this will give further insight on each step. ## Commits * refactor: rename netxmocks -> netxlite/mocks Part of https://github.com/ooni/probe/issues/1591 * refactor: rename quicx -> netxlite/quicx See https://github.com/ooni/probe/issues/1591 * refactor: rename iox -> netxlite/iox Regenerate sources and make sure the tests pass. See https://github.com/ooni/probe/issues/1591. * refactor(iox): move MockableReader to netxlite/mocks See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): generator is an implementation detail See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): separate tls and utls code See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): hide most types but keep old names as legacy With this change we avoid breaking the rest of the tree, but we start hiding some implementation details a bit. Factories will follow. See https://github.com/ooni/probe/issues/1591
2021-09-05 14:49:38 +02:00
t.Run("with success", func(t *testing.T) {
handler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(200)
})
srvr := httptest.NewTLSServer(handler)
defer srvr.Close()
URL, err := url.Parse(srvr.URL)
if err != nil {
t.Fatal(err)
}
conn, err := net.Dial("tcp", URL.Host)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
handshaker := &tlsHandshakerConfigurable{}
ctx := context.Background()
config := &tls.Config{
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS13,
MaxVersion: tls.VersionTLS13,
ServerName: URL.Hostname(),
}
tlsConn, connState, err := handshaker.Handshake(ctx, conn, config)
if err != nil {
t.Fatal(err)
}
defer tlsConn.Close()
if connState.Version != tls.VersionTLS13 {
t.Fatal("unexpected TLS version")
}
})
t.Run("sets default root CA", func(t *testing.T) {
expected := errors.New("mocked error")
var gotTLSConfig *tls.Config
handshaker := &tlsHandshakerConfigurable{
NewConn: func(conn net.Conn, config *tls.Config) (TLSConn, error) {
gotTLSConfig = config
return &mocks.TLSConn{
MockHandshakeContext: func(ctx context.Context) error {
return expected
},
}, nil
refactor(netxlite): hide details without breaking the rest of the tree (#454) ## Description This PR continues the refactoring of `netx` under the following principles: 1. do not break the rest of the tree and do not engage in extensive tree-wide refactoring yet 2. move under `netxlite` clearly related subpackages (e.g., `iox`, `netxmocks`) 3. move into `internal/netxlite/internal` stuff that is clearly private of `netxlite` 4. hide implementation details in `netxlite` pending new factories 5. refactor `tls` code in `netxlite` to clearly separate `crypto/tls` code from `utls` code After each commit, I run `go test -short -race ./...` locally. Each individual commit explains what it does. I will squash, but this operation will preserve the original commit titles, so this will give further insight on each step. ## Commits * refactor: rename netxmocks -> netxlite/mocks Part of https://github.com/ooni/probe/issues/1591 * refactor: rename quicx -> netxlite/quicx See https://github.com/ooni/probe/issues/1591 * refactor: rename iox -> netxlite/iox Regenerate sources and make sure the tests pass. See https://github.com/ooni/probe/issues/1591. * refactor(iox): move MockableReader to netxlite/mocks See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): generator is an implementation detail See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): separate tls and utls code See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): hide most types but keep old names as legacy With this change we avoid breaking the rest of the tree, but we start hiding some implementation details a bit. Factories will follow. See https://github.com/ooni/probe/issues/1591
2021-09-05 14:49:38 +02:00
},
}
ctx := context.Background()
config := &tls.Config{}
conn := &mocks.Conn{
MockSetDeadline: func(t time.Time) error {
return nil
},
MockRemoteAddr: func() net.Addr {
return &mocks.Addr{
MockString: func() string {
return "1.1.1.1:443"
},
MockNetwork: func() string {
return "tcp"
},
}
},
}
tlsConn, connState, err := handshaker.Handshake(ctx, conn, config)
if !errors.Is(err, expected) {
t.Fatal("not the error we expected", err)
}
if !reflect.ValueOf(connState).IsZero() {
t.Fatal("expected zero connState here")
}
if tlsConn != nil {
t.Fatal("expected nil tlsConn here")
}
if config.RootCAs != nil {
t.Fatal("config.RootCAs should still be nil")
}
if gotTLSConfig.RootCAs != defaultCertPool {
t.Fatal("gotTLSConfig.RootCAs has not been correctly set")
}
})
t.Run("h.newConn fails", func(t *testing.T) {
expected := errors.New("mocked error")
handshaker := &tlsHandshakerConfigurable{
NewConn: func(conn net.Conn, config *tls.Config) (TLSConn, error) {
return nil, expected
},
}
ctx := context.Background()
config := &tls.Config{}
conn := &mocks.Conn{
MockSetDeadline: func(t time.Time) error {
return nil
},
}
tlsConn, connState, err := handshaker.Handshake(ctx, conn, config)
if !errors.Is(err, expected) {
t.Fatal("not the error we expected", err)
}
if !reflect.ValueOf(connState).IsZero() {
t.Fatal("expected zero connState here")
}
if tlsConn != nil {
t.Fatal("expected nil tlsConn here")
}
})
t.Run("uses a context-injected custom trace (success case)", func(t *testing.T) {
var (
expectedSNI = "dns.google"
goodStartStartTime bool
goodStartInsecureSkipVerify bool
goodDoneInsecureSkipVerify bool
goodStartServerName bool
goodDoneServerName bool
goodDoneStartTime bool
goodDoneDoneTime bool
goodStartRemoteAddr bool
goodDoneRemoteAddr bool
goodDoneError bool
goodConnectionState bool
startCalled bool
doneCalled bool
)
server := filtering.NewTLSServer(filtering.TLSActionBlockText)
defer server.Close()
zeroTime := time.Now()
deterministicTime := testingx.NewTimeDeterministic(zeroTime)
tx := &mocks.Trace{
MockTimeNow: deterministicTime.Now,
MockOnTLSHandshakeStart: func(now time.Time, remoteAddr string, config *tls.Config) {
startCalled = true
goodStartInsecureSkipVerify = (config.InsecureSkipVerify == true)
goodStartServerName = (config.ServerName == expectedSNI)
goodStartStartTime = (now.Sub(zeroTime) == 0)
goodStartRemoteAddr = (remoteAddr == server.Endpoint())
},
MockOnTLSHandshakeDone: func(started time.Time, remoteAddr string, config *tls.Config, state tls.ConnectionState, err error, finished time.Time) {
doneCalled = true
goodDoneInsecureSkipVerify = (config.InsecureSkipVerify == true)
goodDoneServerName = (config.ServerName == expectedSNI)
goodDoneStartTime = (started.Sub(zeroTime) == 0)
goodDoneDoneTime = (finished.Sub(zeroTime) == time.Second)
goodDoneRemoteAddr = (remoteAddr == server.Endpoint())
goodDoneError = (err == nil)
goodConnectionState = (!reflect.ValueOf(state).IsZero())
},
}
ctx := ContextWithTrace(context.Background(), tx)
tcpConn, err := net.Dial("tcp", server.Endpoint())
if err != nil {
t.Fatal(err)
}
thx := NewTLSHandshakerStdlib(model.DiscardLogger)
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
ServerName: expectedSNI,
}
tlsConn, connState, err := thx.Handshake(ctx, tcpConn, tlsConfig)
if err != nil {
t.Fatal(err)
}
tlsConn.Close()
if reflect.ValueOf(connState).IsZero() {
t.Fatal("expected nonzero connState")
}
if !startCalled {
t.Fatal("start not called")
}
if !doneCalled {
t.Fatal("done not called")
}
if !goodStartInsecureSkipVerify {
t.Fatal("invalid start-event's InsecureSkipVerify")
}
if !goodDoneInsecureSkipVerify {
t.Fatal("invalid done-event's InsecureSkipVerify")
}
if !goodStartServerName {
t.Fatal("invalid start-event's ServerName")
}
if !goodDoneServerName {
t.Fatal("invalid done-event's ServerName")
}
if !goodStartStartTime {
t.Fatal("invalid start-event's start time")
}
if !goodDoneStartTime {
t.Fatal("invalid done-event's start time")
}
if !goodDoneDoneTime {
t.Fatal("invalid done-event's done time")
}
if !goodStartRemoteAddr {
t.Fatal("invalid start-event's remoteAddr")
}
if !goodDoneRemoteAddr {
t.Fatal("invalid done-event's remoteAddr")
}
if !goodDoneError {
t.Fatal("invalid done-event's error")
}
if !goodConnectionState {
t.Fatal("invalid done-event's connState")
}
})
t.Run("uses a context-injected custom trace (failure case)", func(t *testing.T) {
var (
expectedEndpoint = "8.8.8.8:443"
expectedSNI = "dns.google"
goodStartStartTime bool
goodStartInsecureSkipVerify bool
goodDoneInsecureSkipVerify bool
goodStartServerName bool
goodDoneServerName bool
goodDoneStartTime bool
goodDoneDoneTime bool
goodStartRemoteAddr bool
goodDoneRemoteAddr bool
goodDoneError bool
goodConnectionState bool
startCalled bool
doneCalled bool
)
zeroTime := time.Now()
deterministicTime := testingx.NewTimeDeterministic(zeroTime)
tx := &mocks.Trace{
MockTimeNow: deterministicTime.Now,
MockOnTLSHandshakeStart: func(now time.Time, remoteAddr string, config *tls.Config) {
startCalled = true
goodStartInsecureSkipVerify = (config.InsecureSkipVerify == true)
goodStartServerName = (config.ServerName == expectedSNI)
goodStartStartTime = (now.Sub(zeroTime) == 0)
goodStartRemoteAddr = (remoteAddr == expectedEndpoint)
},
MockOnTLSHandshakeDone: func(started time.Time, remoteAddr string, config *tls.Config, state tls.ConnectionState, err error, finished time.Time) {
doneCalled = true
goodDoneInsecureSkipVerify = (config.InsecureSkipVerify == true)
goodDoneServerName = (config.ServerName == expectedSNI)
goodDoneStartTime = (started.Sub(zeroTime) == 0)
goodDoneDoneTime = (finished.Sub(zeroTime) == time.Second)
goodDoneRemoteAddr = (remoteAddr == expectedEndpoint)
var ew *ErrWrapper
goodDoneError = (errors.As(err, &ew) && ew.Error() == FailureEOFError)
goodConnectionState = (reflect.ValueOf(state).IsZero())
},
}
ctx := ContextWithTrace(context.Background(), tx)
tcpConn := &mocks.Conn{
MockSetDeadline: func(t time.Time) error {
return nil
},
MockWrite: func(b []byte) (int, error) {
return 0, io.EOF
},
MockRemoteAddr: func() net.Addr {
return &mocks.Addr{
MockString: func() string {
return expectedEndpoint
},
MockNetwork: func() string {
return "tcp"
},
}
},
}
thx := NewTLSHandshakerStdlib(model.DiscardLogger)
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
ServerName: expectedSNI,
}
tlsConn, connState, err := thx.Handshake(ctx, tcpConn, tlsConfig)
if !errors.Is(err, io.EOF) {
t.Fatal("unexpected err", err)
}
if tlsConn != nil {
t.Fatal("expected nil tlsConn")
}
if !reflect.ValueOf(connState).IsZero() {
t.Fatal("expected zero connState")
}
if !startCalled {
t.Fatal("start not called")
}
if !doneCalled {
t.Fatal("done not called")
}
if !goodStartInsecureSkipVerify {
t.Fatal("invalid start-event's InsecureSkipVerify")
}
if !goodDoneInsecureSkipVerify {
t.Fatal("invalid done-event's InsecureSkipVerify")
}
if !goodStartServerName {
t.Fatal("invalid start-event's ServerName")
}
if !goodDoneServerName {
t.Fatal("invalid done-event's ServerName")
}
if !goodStartStartTime {
t.Fatal("invalid start-event's start time")
}
if !goodDoneStartTime {
t.Fatal("invalid done-event's start time")
}
if !goodDoneDoneTime {
t.Fatal("invalid done-event's done time")
}
if !goodStartRemoteAddr {
t.Fatal("invalid start-event's remoteAddr")
}
if !goodDoneRemoteAddr {
t.Fatal("invalid done-event's remoteAddr")
}
if !goodDoneError {
t.Fatal("invalid done-event's error")
}
if !goodConnectionState {
t.Fatal("invalid done-event's connState")
}
})
})
refactor(netxlite): hide details without breaking the rest of the tree (#454) ## Description This PR continues the refactoring of `netx` under the following principles: 1. do not break the rest of the tree and do not engage in extensive tree-wide refactoring yet 2. move under `netxlite` clearly related subpackages (e.g., `iox`, `netxmocks`) 3. move into `internal/netxlite/internal` stuff that is clearly private of `netxlite` 4. hide implementation details in `netxlite` pending new factories 5. refactor `tls` code in `netxlite` to clearly separate `crypto/tls` code from `utls` code After each commit, I run `go test -short -race ./...` locally. Each individual commit explains what it does. I will squash, but this operation will preserve the original commit titles, so this will give further insight on each step. ## Commits * refactor: rename netxmocks -> netxlite/mocks Part of https://github.com/ooni/probe/issues/1591 * refactor: rename quicx -> netxlite/quicx See https://github.com/ooni/probe/issues/1591 * refactor: rename iox -> netxlite/iox Regenerate sources and make sure the tests pass. See https://github.com/ooni/probe/issues/1591. * refactor(iox): move MockableReader to netxlite/mocks See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): generator is an implementation detail See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): separate tls and utls code See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): hide most types but keep old names as legacy With this change we avoid breaking the rest of the tree, but we start hiding some implementation details a bit. Factories will follow. See https://github.com/ooni/probe/issues/1591
2021-09-05 14:49:38 +02:00
}
func TestTLSHandshakerLogger(t *testing.T) {
t.Run("Handshake", func(t *testing.T) {
t.Run("on success", func(t *testing.T) {
var count int
lo := &mocks.Logger{
MockDebugf: func(format string, v ...interface{}) {
count++
},
}
th := &tlsHandshakerLogger{
TLSHandshaker: &mocks.TLSHandshaker{
MockHandshake: func(ctx context.Context, conn net.Conn, config *tls.Config) (net.Conn, tls.ConnectionState, error) {
return tls.Client(conn, config), tls.ConnectionState{}, nil
},
},
DebugLogger: lo,
}
conn := &mocks.Conn{
MockClose: func() error {
return nil
},
}
config := &tls.Config{}
ctx := context.Background()
tlsConn, connState, err := th.Handshake(ctx, conn, config)
if err != nil {
t.Fatal(err)
}
if err := tlsConn.Close(); err != nil {
t.Fatal(err)
}
if !reflect.ValueOf(connState).IsZero() {
t.Fatal("expected zero ConnectionState here")
}
if count != 2 {
t.Fatal("invalid count")
}
})
refactor(netxlite): hide details without breaking the rest of the tree (#454) ## Description This PR continues the refactoring of `netx` under the following principles: 1. do not break the rest of the tree and do not engage in extensive tree-wide refactoring yet 2. move under `netxlite` clearly related subpackages (e.g., `iox`, `netxmocks`) 3. move into `internal/netxlite/internal` stuff that is clearly private of `netxlite` 4. hide implementation details in `netxlite` pending new factories 5. refactor `tls` code in `netxlite` to clearly separate `crypto/tls` code from `utls` code After each commit, I run `go test -short -race ./...` locally. Each individual commit explains what it does. I will squash, but this operation will preserve the original commit titles, so this will give further insight on each step. ## Commits * refactor: rename netxmocks -> netxlite/mocks Part of https://github.com/ooni/probe/issues/1591 * refactor: rename quicx -> netxlite/quicx See https://github.com/ooni/probe/issues/1591 * refactor: rename iox -> netxlite/iox Regenerate sources and make sure the tests pass. See https://github.com/ooni/probe/issues/1591. * refactor(iox): move MockableReader to netxlite/mocks See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): generator is an implementation detail See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): separate tls and utls code See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): hide most types but keep old names as legacy With this change we avoid breaking the rest of the tree, but we start hiding some implementation details a bit. Factories will follow. See https://github.com/ooni/probe/issues/1591
2021-09-05 14:49:38 +02:00
t.Run("on failure", func(t *testing.T) {
var count int
lo := &mocks.Logger{
MockDebugf: func(format string, v ...interface{}) {
count++
},
}
expected := errors.New("mocked error")
th := &tlsHandshakerLogger{
TLSHandshaker: &mocks.TLSHandshaker{
MockHandshake: func(ctx context.Context, conn net.Conn, config *tls.Config) (net.Conn, tls.ConnectionState, error) {
return nil, tls.ConnectionState{}, expected
},
},
DebugLogger: lo,
}
conn := &mocks.Conn{
MockClose: func() error {
return nil
},
}
config := &tls.Config{}
ctx := context.Background()
tlsConn, connState, err := th.Handshake(ctx, conn, config)
if !errors.Is(err, expected) {
t.Fatal("not the error we expected", err)
}
if tlsConn != nil {
t.Fatal("expected nil conn here")
}
if !reflect.ValueOf(connState).IsZero() {
t.Fatal("expected zero ConnectionState here")
}
if count != 2 {
t.Fatal("invalid count")
}
})
})
refactor(netxlite): hide details without breaking the rest of the tree (#454) ## Description This PR continues the refactoring of `netx` under the following principles: 1. do not break the rest of the tree and do not engage in extensive tree-wide refactoring yet 2. move under `netxlite` clearly related subpackages (e.g., `iox`, `netxmocks`) 3. move into `internal/netxlite/internal` stuff that is clearly private of `netxlite` 4. hide implementation details in `netxlite` pending new factories 5. refactor `tls` code in `netxlite` to clearly separate `crypto/tls` code from `utls` code After each commit, I run `go test -short -race ./...` locally. Each individual commit explains what it does. I will squash, but this operation will preserve the original commit titles, so this will give further insight on each step. ## Commits * refactor: rename netxmocks -> netxlite/mocks Part of https://github.com/ooni/probe/issues/1591 * refactor: rename quicx -> netxlite/quicx See https://github.com/ooni/probe/issues/1591 * refactor: rename iox -> netxlite/iox Regenerate sources and make sure the tests pass. See https://github.com/ooni/probe/issues/1591. * refactor(iox): move MockableReader to netxlite/mocks See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): generator is an implementation detail See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): separate tls and utls code See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): hide most types but keep old names as legacy With this change we avoid breaking the rest of the tree, but we start hiding some implementation details a bit. Factories will follow. See https://github.com/ooni/probe/issues/1591
2021-09-05 14:49:38 +02:00
}
func TestNewTLSDialer(t *testing.T) {
d := &mocks.Dialer{}
th := &mocks.TLSHandshaker{}
dialer := NewTLSDialer(d, th)
tlsd := dialer.(*tlsDialer)
if tlsd.Config == nil {
t.Fatal("unexpected config")
}
if tlsd.Dialer != d {
t.Fatal("unexpected dialer")
}
if tlsd.TLSHandshaker != th {
t.Fatal("invalid handshaker")
}
}
func TestTLSDialer(t *testing.T) {
t.Run("CloseIdleConnections", func(t *testing.T) {
var called bool
dialer := &tlsDialer{
Dialer: &mocks.Dialer{
MockCloseIdleConnections: func() {
called = true
},
},
}
dialer.CloseIdleConnections()
if !called {
t.Fatal("not called")
}
})
t.Run("DialTLSContext", func(t *testing.T) {
t.Run("failure to split host and port", func(t *testing.T) {
dialer := &tlsDialer{}
ctx := context.Background()
const address = "www.google.com" // missing port
conn, err := dialer.DialTLSContext(ctx, "tcp", address)
if err == nil || !strings.HasSuffix(err.Error(), "missing port in address") {
t.Fatal("not the error we expected", err)
}
if conn != nil {
t.Fatal("connection is not nil")
}
})
refactor(netxlite): hide details without breaking the rest of the tree (#454) ## Description This PR continues the refactoring of `netx` under the following principles: 1. do not break the rest of the tree and do not engage in extensive tree-wide refactoring yet 2. move under `netxlite` clearly related subpackages (e.g., `iox`, `netxmocks`) 3. move into `internal/netxlite/internal` stuff that is clearly private of `netxlite` 4. hide implementation details in `netxlite` pending new factories 5. refactor `tls` code in `netxlite` to clearly separate `crypto/tls` code from `utls` code After each commit, I run `go test -short -race ./...` locally. Each individual commit explains what it does. I will squash, but this operation will preserve the original commit titles, so this will give further insight on each step. ## Commits * refactor: rename netxmocks -> netxlite/mocks Part of https://github.com/ooni/probe/issues/1591 * refactor: rename quicx -> netxlite/quicx See https://github.com/ooni/probe/issues/1591 * refactor: rename iox -> netxlite/iox Regenerate sources and make sure the tests pass. See https://github.com/ooni/probe/issues/1591. * refactor(iox): move MockableReader to netxlite/mocks See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): generator is an implementation detail See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): separate tls and utls code See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): hide most types but keep old names as legacy With this change we avoid breaking the rest of the tree, but we start hiding some implementation details a bit. Factories will follow. See https://github.com/ooni/probe/issues/1591
2021-09-05 14:49:38 +02:00
t.Run("failure dialing", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel() // immediately fail
dialer := tlsDialer{Dialer: &DialerSystem{}}
conn, err := dialer.DialTLSContext(ctx, "tcp", "www.google.com:443")
if err == nil || !strings.HasSuffix(err.Error(), "operation was canceled") {
t.Fatal("not the error we expected", err)
}
if conn != nil {
t.Fatal("connection is not nil")
}
})
refactor(netxlite): hide details without breaking the rest of the tree (#454) ## Description This PR continues the refactoring of `netx` under the following principles: 1. do not break the rest of the tree and do not engage in extensive tree-wide refactoring yet 2. move under `netxlite` clearly related subpackages (e.g., `iox`, `netxmocks`) 3. move into `internal/netxlite/internal` stuff that is clearly private of `netxlite` 4. hide implementation details in `netxlite` pending new factories 5. refactor `tls` code in `netxlite` to clearly separate `crypto/tls` code from `utls` code After each commit, I run `go test -short -race ./...` locally. Each individual commit explains what it does. I will squash, but this operation will preserve the original commit titles, so this will give further insight on each step. ## Commits * refactor: rename netxmocks -> netxlite/mocks Part of https://github.com/ooni/probe/issues/1591 * refactor: rename quicx -> netxlite/quicx See https://github.com/ooni/probe/issues/1591 * refactor: rename iox -> netxlite/iox Regenerate sources and make sure the tests pass. See https://github.com/ooni/probe/issues/1591. * refactor(iox): move MockableReader to netxlite/mocks See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): generator is an implementation detail See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): separate tls and utls code See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): hide most types but keep old names as legacy With this change we avoid breaking the rest of the tree, but we start hiding some implementation details a bit. Factories will follow. See https://github.com/ooni/probe/issues/1591
2021-09-05 14:49:38 +02:00
t.Run("failure handshaking", func(t *testing.T) {
ctx := context.Background()
dialer := tlsDialer{
Config: &tls.Config{},
Dialer: &mocks.Dialer{MockDialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
return &mocks.Conn{MockWrite: func(b []byte) (int, error) {
return 0, io.EOF
}, MockClose: func() error {
return nil
}, MockSetDeadline: func(t time.Time) error {
return nil
}, MockRemoteAddr: func() net.Addr {
return &mocks.Addr{
MockNetwork: func() string {
return "1.1.1.1:443"
},
MockString: func() string {
return "tcp"
},
}
}}, nil
}},
TLSHandshaker: &tlsHandshakerConfigurable{},
}
conn, err := dialer.DialTLSContext(ctx, "tcp", "www.google.com:443")
if !errors.Is(err, io.EOF) {
t.Fatal("not the error we expected", err)
}
if conn != nil {
t.Fatal("connection is not nil")
}
})
refactor(netxlite): hide details without breaking the rest of the tree (#454) ## Description This PR continues the refactoring of `netx` under the following principles: 1. do not break the rest of the tree and do not engage in extensive tree-wide refactoring yet 2. move under `netxlite` clearly related subpackages (e.g., `iox`, `netxmocks`) 3. move into `internal/netxlite/internal` stuff that is clearly private of `netxlite` 4. hide implementation details in `netxlite` pending new factories 5. refactor `tls` code in `netxlite` to clearly separate `crypto/tls` code from `utls` code After each commit, I run `go test -short -race ./...` locally. Each individual commit explains what it does. I will squash, but this operation will preserve the original commit titles, so this will give further insight on each step. ## Commits * refactor: rename netxmocks -> netxlite/mocks Part of https://github.com/ooni/probe/issues/1591 * refactor: rename quicx -> netxlite/quicx See https://github.com/ooni/probe/issues/1591 * refactor: rename iox -> netxlite/iox Regenerate sources and make sure the tests pass. See https://github.com/ooni/probe/issues/1591. * refactor(iox): move MockableReader to netxlite/mocks See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): generator is an implementation detail See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): separate tls and utls code See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): hide most types but keep old names as legacy With this change we avoid breaking the rest of the tree, but we start hiding some implementation details a bit. Factories will follow. See https://github.com/ooni/probe/issues/1591
2021-09-05 14:49:38 +02:00
t.Run("success handshaking", func(t *testing.T) {
ctx := context.Background()
dialer := tlsDialer{
Dialer: &mocks.Dialer{MockDialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
return &mocks.Conn{MockWrite: func(b []byte) (int, error) {
return 0, io.EOF
}, MockClose: func() error {
return nil
}, MockSetDeadline: func(t time.Time) error {
return nil
}}, nil
}},
TLSHandshaker: &mocks.TLSHandshaker{
MockHandshake: func(ctx context.Context, conn net.Conn, config *tls.Config) (net.Conn, tls.ConnectionState, error) {
return tls.Client(conn, config), tls.ConnectionState{}, nil
},
},
}
conn, err := dialer.DialTLSContext(ctx, "tcp", "www.google.com:443")
if err != nil {
t.Fatal(err)
}
if conn == nil {
t.Fatal("connection is nil")
}
conn.Close()
})
})
refactor(netxlite): hide details without breaking the rest of the tree (#454) ## Description This PR continues the refactoring of `netx` under the following principles: 1. do not break the rest of the tree and do not engage in extensive tree-wide refactoring yet 2. move under `netxlite` clearly related subpackages (e.g., `iox`, `netxmocks`) 3. move into `internal/netxlite/internal` stuff that is clearly private of `netxlite` 4. hide implementation details in `netxlite` pending new factories 5. refactor `tls` code in `netxlite` to clearly separate `crypto/tls` code from `utls` code After each commit, I run `go test -short -race ./...` locally. Each individual commit explains what it does. I will squash, but this operation will preserve the original commit titles, so this will give further insight on each step. ## Commits * refactor: rename netxmocks -> netxlite/mocks Part of https://github.com/ooni/probe/issues/1591 * refactor: rename quicx -> netxlite/quicx See https://github.com/ooni/probe/issues/1591 * refactor: rename iox -> netxlite/iox Regenerate sources and make sure the tests pass. See https://github.com/ooni/probe/issues/1591. * refactor(iox): move MockableReader to netxlite/mocks See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): generator is an implementation detail See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): separate tls and utls code See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): hide most types but keep old names as legacy With this change we avoid breaking the rest of the tree, but we start hiding some implementation details a bit. Factories will follow. See https://github.com/ooni/probe/issues/1591
2021-09-05 14:49:38 +02:00
t.Run("config", func(t *testing.T) {
t.Run("from empty config for web", func(t *testing.T) {
d := &tlsDialer{}
config := d.config("www.google.com", "443")
if config.ServerName != "www.google.com" {
t.Fatal("invalid server name")
}
if diff := cmp.Diff(config.NextProtos, []string{"h2", "http/1.1"}); diff != "" {
t.Fatal(diff)
}
})
refactor(netxlite): hide details without breaking the rest of the tree (#454) ## Description This PR continues the refactoring of `netx` under the following principles: 1. do not break the rest of the tree and do not engage in extensive tree-wide refactoring yet 2. move under `netxlite` clearly related subpackages (e.g., `iox`, `netxmocks`) 3. move into `internal/netxlite/internal` stuff that is clearly private of `netxlite` 4. hide implementation details in `netxlite` pending new factories 5. refactor `tls` code in `netxlite` to clearly separate `crypto/tls` code from `utls` code After each commit, I run `go test -short -race ./...` locally. Each individual commit explains what it does. I will squash, but this operation will preserve the original commit titles, so this will give further insight on each step. ## Commits * refactor: rename netxmocks -> netxlite/mocks Part of https://github.com/ooni/probe/issues/1591 * refactor: rename quicx -> netxlite/quicx See https://github.com/ooni/probe/issues/1591 * refactor: rename iox -> netxlite/iox Regenerate sources and make sure the tests pass. See https://github.com/ooni/probe/issues/1591. * refactor(iox): move MockableReader to netxlite/mocks See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): generator is an implementation detail See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): separate tls and utls code See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): hide most types but keep old names as legacy With this change we avoid breaking the rest of the tree, but we start hiding some implementation details a bit. Factories will follow. See https://github.com/ooni/probe/issues/1591
2021-09-05 14:49:38 +02:00
t.Run("from empty config for dot", func(t *testing.T) {
d := &tlsDialer{}
config := d.config("dns.google", "853")
if config.ServerName != "dns.google" {
t.Fatal("invalid server name")
}
if diff := cmp.Diff(config.NextProtos, []string{"dot"}); diff != "" {
t.Fatal(diff)
}
})
refactor(netxlite): hide details without breaking the rest of the tree (#454) ## Description This PR continues the refactoring of `netx` under the following principles: 1. do not break the rest of the tree and do not engage in extensive tree-wide refactoring yet 2. move under `netxlite` clearly related subpackages (e.g., `iox`, `netxmocks`) 3. move into `internal/netxlite/internal` stuff that is clearly private of `netxlite` 4. hide implementation details in `netxlite` pending new factories 5. refactor `tls` code in `netxlite` to clearly separate `crypto/tls` code from `utls` code After each commit, I run `go test -short -race ./...` locally. Each individual commit explains what it does. I will squash, but this operation will preserve the original commit titles, so this will give further insight on each step. ## Commits * refactor: rename netxmocks -> netxlite/mocks Part of https://github.com/ooni/probe/issues/1591 * refactor: rename quicx -> netxlite/quicx See https://github.com/ooni/probe/issues/1591 * refactor: rename iox -> netxlite/iox Regenerate sources and make sure the tests pass. See https://github.com/ooni/probe/issues/1591. * refactor(iox): move MockableReader to netxlite/mocks See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): generator is an implementation detail See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): separate tls and utls code See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): hide most types but keep old names as legacy With this change we avoid breaking the rest of the tree, but we start hiding some implementation details a bit. Factories will follow. See https://github.com/ooni/probe/issues/1591
2021-09-05 14:49:38 +02:00
t.Run("with server name", func(t *testing.T) {
d := &tlsDialer{
Config: &tls.Config{
ServerName: "example.com",
},
}
config := d.config("dns.google", "853")
if config.ServerName != "example.com" {
t.Fatal("invalid server name")
}
if diff := cmp.Diff(config.NextProtos, []string{"dot"}); diff != "" {
t.Fatal(diff)
}
})
refactor(netxlite): hide details without breaking the rest of the tree (#454) ## Description This PR continues the refactoring of `netx` under the following principles: 1. do not break the rest of the tree and do not engage in extensive tree-wide refactoring yet 2. move under `netxlite` clearly related subpackages (e.g., `iox`, `netxmocks`) 3. move into `internal/netxlite/internal` stuff that is clearly private of `netxlite` 4. hide implementation details in `netxlite` pending new factories 5. refactor `tls` code in `netxlite` to clearly separate `crypto/tls` code from `utls` code After each commit, I run `go test -short -race ./...` locally. Each individual commit explains what it does. I will squash, but this operation will preserve the original commit titles, so this will give further insight on each step. ## Commits * refactor: rename netxmocks -> netxlite/mocks Part of https://github.com/ooni/probe/issues/1591 * refactor: rename quicx -> netxlite/quicx See https://github.com/ooni/probe/issues/1591 * refactor: rename iox -> netxlite/iox Regenerate sources and make sure the tests pass. See https://github.com/ooni/probe/issues/1591. * refactor(iox): move MockableReader to netxlite/mocks See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): generator is an implementation detail See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): separate tls and utls code See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): hide most types but keep old names as legacy With this change we avoid breaking the rest of the tree, but we start hiding some implementation details a bit. Factories will follow. See https://github.com/ooni/probe/issues/1591
2021-09-05 14:49:38 +02:00
t.Run("with alpn", func(t *testing.T) {
d := &tlsDialer{
Config: &tls.Config{
NextProtos: []string{"h2"},
},
}
config := d.config("dns.google", "853")
if config.ServerName != "dns.google" {
t.Fatal("invalid server name")
}
if diff := cmp.Diff(config.NextProtos, []string{"h2"}); diff != "" {
t.Fatal(diff)
}
})
})
refactor(netxlite): hide details without breaking the rest of the tree (#454) ## Description This PR continues the refactoring of `netx` under the following principles: 1. do not break the rest of the tree and do not engage in extensive tree-wide refactoring yet 2. move under `netxlite` clearly related subpackages (e.g., `iox`, `netxmocks`) 3. move into `internal/netxlite/internal` stuff that is clearly private of `netxlite` 4. hide implementation details in `netxlite` pending new factories 5. refactor `tls` code in `netxlite` to clearly separate `crypto/tls` code from `utls` code After each commit, I run `go test -short -race ./...` locally. Each individual commit explains what it does. I will squash, but this operation will preserve the original commit titles, so this will give further insight on each step. ## Commits * refactor: rename netxmocks -> netxlite/mocks Part of https://github.com/ooni/probe/issues/1591 * refactor: rename quicx -> netxlite/quicx See https://github.com/ooni/probe/issues/1591 * refactor: rename iox -> netxlite/iox Regenerate sources and make sure the tests pass. See https://github.com/ooni/probe/issues/1591. * refactor(iox): move MockableReader to netxlite/mocks See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): generator is an implementation detail See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): separate tls and utls code See https://github.com/ooni/probe/issues/1591 * refactor(netxlite): hide most types but keep old names as legacy With this change we avoid breaking the rest of the tree, but we start hiding some implementation details a bit. Factories will follow. See https://github.com/ooni/probe/issues/1591
2021-09-05 14:49:38 +02:00
}
func TestNewSingleUseTLSDialer(t *testing.T) {
conn := &mocks.TLSConn{}
d := NewSingleUseTLSDialer(conn)
defer d.CloseIdleConnections()
outconn, err := d.DialTLSContext(context.Background(), "", "")
if err != nil {
t.Fatal(err)
}
if conn != outconn {
t.Fatal("invalid outconn")
}
for i := 0; i < 4; i++ {
outconn, err = d.DialTLSContext(context.Background(), "", "")
if !errors.Is(err, ErrNoConnReuse) {
t.Fatal("not the error we expected", err)
}
if outconn != nil {
t.Fatal("expected nil outconn here")
}
}
}
func TestNewNullTLSDialer(t *testing.T) {
dialer := NewNullTLSDialer()
conn, err := dialer.DialTLSContext(context.Background(), "", "")
if !errors.Is(err, ErrNoTLSDialer) {
t.Fatal("unexpected err", err)
}
if conn != nil {
t.Fatal("expected nil conn")
}
dialer.CloseIdleConnections() // does not crash
}
func TestClonedTLSConfigOrNewEmptyConfig(t *testing.T) {
t.Run("with nil config", func(t *testing.T) {
var input *tls.Config
output := ClonedTLSConfigOrNewEmptyConfig(input)
if output == nil {
t.Fatal("expected non-nil result")
}
v := reflect.ValueOf(*output)
if !v.IsZero() {
t.Fatal("expected zero config")
}
})
t.Run("", func(t *testing.T) {
input := &tls.Config{
ServerName: "dns.google",
}
output := ClonedTLSConfigOrNewEmptyConfig(input)
if output == input {
t.Fatal("expected two distinct objects")
}
if !reflect.DeepEqual(input, output) {
t.Fatal("apparently the two objects have different values")
}
})
}
func TestMaybeConnectionState(t *testing.T) {
t.Run("with an error", func(t *testing.T) {
returned := tls.ConnectionState{
CipherSuite: tls.TLS_AES_128_GCM_SHA256,
}
conn := &mocks.TLSConn{
MockConnectionState: func() tls.ConnectionState {
return returned
},
}
state := tlsMaybeConnectionState(conn, errors.New("mocked error"))
if !reflect.ValueOf(state).IsZero() {
t.Fatal("expected to see a zero connection state")
}
})
t.Run("without an error", func(t *testing.T) {
returned := tls.ConnectionState{
CipherSuite: tls.TLS_AES_128_GCM_SHA256,
}
conn := &mocks.TLSConn{
MockConnectionState: func() tls.ConnectionState {
return returned
},
}
state := tlsMaybeConnectionState(conn, nil)
if reflect.ValueOf(state).IsZero() {
t.Fatal("expected to see a nonzero connection state")
}
})
}