refactor(netx): use netxlite to build TLSDialer (#790)

This diff modifies netx to use netxlite to build the TLSDialer.

Building the TLSDialer entails building a TLSHandshaker.

While there, hide netxlite names we don't want to be public
and change netx tests to test for functionality.

To this end, refactor filtering to provide an easier to
use TLS server. We don't need the complexity of proxying
rather we need to provoke specific errors.

Part of https://github.com/ooni/probe/issues/2121
This commit is contained in:
Simone Basso
2022-06-02 17:39:48 +02:00
committed by GitHub
parent ae24ba644c
commit e9ed733f07
9 changed files with 363 additions and 690 deletions
+10 -21
View File
@@ -64,8 +64,6 @@ type Config struct {
TLSSaver *tracex.Saver // default: not saving TLS
}
var defaultCertPool *x509.CertPool = netxlite.NewDefaultCertPool()
// NewResolver creates a new resolver from the specified config
func NewResolver(config Config) model.Resolver {
if config.BaseResolver == nil {
@@ -132,25 +130,16 @@ func NewTLSDialer(config Config) model.TLSDialer {
if config.Dialer == nil {
config.Dialer = NewDialer(config)
}
var h model.TLSHandshaker = &netxlite.TLSHandshakerConfigurable{}
h = &netxlite.ErrorWrapperTLSHandshaker{TLSHandshaker: h}
if config.Logger != nil {
h = &netxlite.TLSHandshakerLogger{DebugLogger: config.Logger, TLSHandshaker: h}
}
h = config.TLSSaver.WrapTLSHandshaker(h) // behaves with nil TLSSaver
if config.TLSConfig == nil {
config.TLSConfig = &tls.Config{NextProtos: []string{"h2", "http/1.1"}}
}
if config.CertPool == nil {
config.CertPool = defaultCertPool
}
config.TLSConfig.RootCAs = config.CertPool
config.TLSConfig.InsecureSkipVerify = config.NoTLSVerify
return &netxlite.TLSDialerLegacy{
Config: config.TLSConfig,
Dialer: config.Dialer,
TLSHandshaker: h,
}
logger := model.ValidLoggerOrDefault(config.Logger)
thx := netxlite.NewTLSHandshakerStdlib(logger)
thx = config.TLSSaver.WrapTLSHandshaker(thx) // WAI when TLSSaver is nil
tlsConfig := netxlite.ClonedTLSConfigOrNewEmptyConfig(config.TLSConfig)
// TODO(bassosimone): we should not provide confusing options and
// so we should drop CertPool and NoTLSVerify in favour of encouraging
// the users of this library to always use a TLSConfig.
tlsConfig.RootCAs = config.CertPool // netxlite uses default cert pool if this is nil
tlsConfig.InsecureSkipVerify = config.NoTLSVerify
return netxlite.NewTLSDialerWithConfig(config.Dialer, thx, tlsConfig)
}
// NewHTTPTransport creates a new HTTPRoundTripper. You can further extend the returned
+89 -222
View File
@@ -13,6 +13,7 @@ import (
"github.com/ooni/probe-cli/v3/internal/bytecounter"
"github.com/ooni/probe-cli/v3/internal/model/mocks"
"github.com/ooni/probe-cli/v3/internal/netxlite"
"github.com/ooni/probe-cli/v3/internal/netxlite/filtering"
"github.com/ooni/probe-cli/v3/internal/tracex"
)
@@ -208,210 +209,103 @@ func TestNewResolverWithPrefilledReadonlyCache(t *testing.T) {
}
}
func TestNewTLSDialerVanilla(t *testing.T) {
td := NewTLSDialer(Config{})
rtd, ok := td.(*netxlite.TLSDialerLegacy)
if !ok {
t.Fatal("not the TLSDialer we expected")
}
if len(rtd.Config.NextProtos) != 2 {
t.Fatal("invalid len(config.NextProtos)")
}
if rtd.Config.NextProtos[0] != "h2" || rtd.Config.NextProtos[1] != "http/1.1" {
t.Fatal("invalid Config.NextProtos")
}
if rtd.Config.RootCAs != defaultCertPool {
t.Fatal("invalid Config.RootCAs")
}
if rtd.Dialer == nil {
t.Fatal("invalid Dialer")
}
if rtd.TLSHandshaker == nil {
t.Fatal("invalid TLSHandshaker")
}
ewth, ok := rtd.TLSHandshaker.(*netxlite.ErrorWrapperTLSHandshaker)
if !ok {
t.Fatal("not the TLSHandshaker we expected")
}
if _, ok := ewth.TLSHandshaker.(*netxlite.TLSHandshakerConfigurable); !ok {
t.Fatal("not the TLSHandshaker we expected")
}
}
func TestNewTLSDialerWithConfig(t *testing.T) {
td := NewTLSDialer(Config{
TLSConfig: new(tls.Config),
func TestNewTLSDialer(t *testing.T) {
t.Run("we always have error wrapping", func(t *testing.T) {
server := filtering.NewTLSServer(filtering.TLSActionReset)
defer server.Close()
tdx := NewTLSDialer(Config{})
conn, err := tdx.DialTLSContext(context.Background(), "tcp", server.Endpoint())
if err == nil || err.Error() != netxlite.FailureConnectionReset {
t.Fatal("unexpected err", err)
}
if conn != nil {
t.Fatal("expected nil conn")
}
})
rtd, ok := td.(*netxlite.TLSDialerLegacy)
if !ok {
t.Fatal("not the TLSDialer we expected")
}
if len(rtd.Config.NextProtos) != 0 {
t.Fatal("invalid len(config.NextProtos)")
}
if rtd.Config.RootCAs != defaultCertPool {
t.Fatal("invalid Config.RootCAs")
}
if rtd.Dialer == nil {
t.Fatal("invalid Dialer")
}
if rtd.TLSHandshaker == nil {
t.Fatal("invalid TLSHandshaker")
}
ewth, ok := rtd.TLSHandshaker.(*netxlite.ErrorWrapperTLSHandshaker)
if !ok {
t.Fatal("not the TLSHandshaker we expected")
}
if _, ok := ewth.TLSHandshaker.(*netxlite.TLSHandshakerConfigurable); !ok {
t.Fatal("not the TLSHandshaker we expected")
}
}
func TestNewTLSDialerWithLogging(t *testing.T) {
td := NewTLSDialer(Config{
Logger: log.Log,
t.Run("we can collect TLS measurements", func(t *testing.T) {
server := filtering.NewTLSServer(filtering.TLSActionReset)
defer server.Close()
saver := &tracex.Saver{}
tdx := NewTLSDialer(Config{
TLSSaver: saver,
})
conn, err := tdx.DialTLSContext(context.Background(), "tcp", server.Endpoint())
if err == nil || err.Error() != netxlite.FailureConnectionReset {
t.Fatal("unexpected err", err)
}
if conn != nil {
t.Fatal("expected nil conn")
}
if len(saver.Read()) <= 0 {
t.Fatal("did not read any event")
}
})
rtd, ok := td.(*netxlite.TLSDialerLegacy)
if !ok {
t.Fatal("not the TLSDialer we expected")
}
if len(rtd.Config.NextProtos) != 2 {
t.Fatal("invalid len(config.NextProtos)")
}
if rtd.Config.NextProtos[0] != "h2" || rtd.Config.NextProtos[1] != "http/1.1" {
t.Fatal("invalid Config.NextProtos")
}
if rtd.Config.RootCAs != defaultCertPool {
t.Fatal("invalid Config.RootCAs")
}
if rtd.Dialer == nil {
t.Fatal("invalid Dialer")
}
if rtd.TLSHandshaker == nil {
t.Fatal("invalid TLSHandshaker")
}
lth, ok := rtd.TLSHandshaker.(*netxlite.TLSHandshakerLogger)
if !ok {
t.Fatal("not the TLSHandshaker we expected")
}
if lth.DebugLogger != log.Log {
t.Fatal("not the Logger we expected")
}
ewth, ok := lth.TLSHandshaker.(*netxlite.ErrorWrapperTLSHandshaker)
if !ok {
t.Fatal("not the TLSHandshaker we expected")
}
if _, ok := ewth.TLSHandshaker.(*netxlite.TLSHandshakerConfigurable); !ok {
t.Fatal("not the TLSHandshaker we expected")
}
}
func TestNewTLSDialerWithSaver(t *testing.T) {
saver := new(tracex.Saver)
td := NewTLSDialer(Config{
TLSSaver: saver,
t.Run("we can collect dial measurements", func(t *testing.T) {
server := filtering.NewTLSServer(filtering.TLSActionReset)
defer server.Close()
saver := &tracex.Saver{}
tdx := NewTLSDialer(Config{
DialSaver: saver,
})
conn, err := tdx.DialTLSContext(context.Background(), "tcp", server.Endpoint())
if err == nil || err.Error() != netxlite.FailureConnectionReset {
t.Fatal("unexpected err", err)
}
if conn != nil {
t.Fatal("expected nil conn")
}
if len(saver.Read()) <= 0 {
t.Fatal("did not read any event")
}
})
rtd, ok := td.(*netxlite.TLSDialerLegacy)
if !ok {
t.Fatal("not the TLSDialer we expected")
}
if len(rtd.Config.NextProtos) != 2 {
t.Fatal("invalid len(config.NextProtos)")
}
if rtd.Config.NextProtos[0] != "h2" || rtd.Config.NextProtos[1] != "http/1.1" {
t.Fatal("invalid Config.NextProtos")
}
if rtd.Config.RootCAs != defaultCertPool {
t.Fatal("invalid Config.RootCAs")
}
if rtd.Dialer == nil {
t.Fatal("invalid Dialer")
}
if rtd.TLSHandshaker == nil {
t.Fatal("invalid TLSHandshaker")
}
sth, ok := rtd.TLSHandshaker.(*tracex.TLSHandshakerSaver)
if !ok {
t.Fatal("not the TLSHandshaker we expected")
}
if sth.Saver != saver {
t.Fatal("not the Logger we expected")
}
ewth, ok := sth.TLSHandshaker.(*netxlite.ErrorWrapperTLSHandshaker)
if !ok {
t.Fatal("not the TLSHandshaker we expected")
}
if _, ok := ewth.TLSHandshaker.(*netxlite.TLSHandshakerConfigurable); !ok {
t.Fatal("not the TLSHandshaker we expected")
}
}
func TestNewTLSDialerWithNoTLSVerifyAndConfig(t *testing.T) {
td := NewTLSDialer(Config{
TLSConfig: new(tls.Config),
NoTLSVerify: true,
t.Run("we can collect I/O measurements", func(t *testing.T) {
server := filtering.NewTLSServer(filtering.TLSActionReset)
defer server.Close()
saver := &tracex.Saver{}
tdx := NewTLSDialer(Config{
ReadWriteSaver: saver,
})
conn, err := tdx.DialTLSContext(context.Background(), "tcp", server.Endpoint())
if err == nil || err.Error() != netxlite.FailureConnectionReset {
t.Fatal("unexpected err", err)
}
if conn != nil {
t.Fatal("expected nil conn")
}
if len(saver.Read()) <= 0 {
t.Fatal("did not read any event")
}
})
rtd, ok := td.(*netxlite.TLSDialerLegacy)
if !ok {
t.Fatal("not the TLSDialer we expected")
}
if len(rtd.Config.NextProtos) != 0 {
t.Fatal("invalid len(config.NextProtos)")
}
if rtd.Config.InsecureSkipVerify != true {
t.Fatal("expected true InsecureSkipVerify")
}
if rtd.Config.RootCAs != defaultCertPool {
t.Fatal("invalid Config.RootCAs")
}
if rtd.Dialer == nil {
t.Fatal("invalid Dialer")
}
if rtd.TLSHandshaker == nil {
t.Fatal("invalid TLSHandshaker")
}
ewth, ok := rtd.TLSHandshaker.(*netxlite.ErrorWrapperTLSHandshaker)
if !ok {
t.Fatal("not the TLSHandshaker we expected")
}
if _, ok := ewth.TLSHandshaker.(*netxlite.TLSHandshakerConfigurable); !ok {
t.Fatal("not the TLSHandshaker we expected")
}
}
func TestNewTLSDialerWithNoTLSVerifyAndNoConfig(t *testing.T) {
td := NewTLSDialer(Config{
NoTLSVerify: true,
t.Run("we can skip TLS verification", func(t *testing.T) {
server := filtering.NewTLSServer(filtering.TLSActionBlockText)
defer server.Close()
tdx := NewTLSDialer(Config{NoTLSVerify: true})
conn, err := tdx.DialTLSContext(context.Background(), "tcp", server.Endpoint())
if err != nil {
t.Fatal(err.(*netxlite.ErrWrapper).WrappedErr)
}
conn.Close()
})
t.Run("we can set the cert pool", func(t *testing.T) {
server := filtering.NewTLSServer(filtering.TLSActionBlockText)
defer server.Close()
tdx := NewTLSDialer(Config{
CertPool: server.CertPool(),
TLSConfig: &tls.Config{
ServerName: "dns.google",
},
})
conn, err := tdx.DialTLSContext(context.Background(), "tcp", server.Endpoint())
if err != nil {
t.Fatal(err)
}
conn.Close()
})
rtd, ok := td.(*netxlite.TLSDialerLegacy)
if !ok {
t.Fatal("not the TLSDialer we expected")
}
if len(rtd.Config.NextProtos) != 2 {
t.Fatal("invalid len(config.NextProtos)")
}
if rtd.Config.NextProtos[0] != "h2" || rtd.Config.NextProtos[1] != "http/1.1" {
t.Fatal("invalid Config.NextProtos")
}
if rtd.Config.InsecureSkipVerify != true {
t.Fatal("expected true InsecureSkipVerify")
}
if rtd.Config.RootCAs != defaultCertPool {
t.Fatal("invalid Config.RootCAs")
}
if rtd.Dialer == nil {
t.Fatal("invalid Dialer")
}
if rtd.TLSHandshaker == nil {
t.Fatal("invalid TLSHandshaker")
}
ewth, ok := rtd.TLSHandshaker.(*netxlite.ErrorWrapperTLSHandshaker)
if !ok {
t.Fatal("not the TLSHandshaker we expected")
}
if _, ok := ewth.TLSHandshaker.(*netxlite.TLSHandshakerConfigurable); !ok {
t.Fatal("not the TLSHandshaker we expected")
}
}
func TestNewVanilla(t *testing.T) {
@@ -441,33 +335,6 @@ func TestNewWithDialer(t *testing.T) {
}
}
func TestNewWithTLSDialer(t *testing.T) {
expected := errors.New("mocked error")
tlsDialer := &netxlite.TLSDialerLegacy{
Config: new(tls.Config),
Dialer: &mocks.Dialer{
MockDialContext: func(ctx context.Context, network string, address string) (net.Conn, error) {
return nil, expected
},
MockCloseIdleConnections: func() {
// nothing
},
},
TLSHandshaker: &netxlite.TLSHandshakerConfigurable{},
}
txp := NewHTTPTransport(Config{
TLSDialer: tlsDialer,
})
client := &http.Client{Transport: txp}
resp, err := client.Get("https://www.google.com")
if !errors.Is(err, expected) {
t.Fatal("not the error we expected")
}
if resp != nil {
t.Fatal("not the response we expected")
}
}
func TestNewWithByteCounter(t *testing.T) {
counter := bytecounter.New()
txp := NewHTTPTransport(Config{