feat: add uTLS support in measurexlite (#918)
Closes https://github.com/ooni/probe/issues/2253 Co-authored-by: decfox <decfox@github.com>
This commit is contained in:
parent
9e8ad551aa
commit
59d8b6ecef
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/ooni/probe-cli/v3/internal/model"
|
"github.com/ooni/probe-cli/v3/internal/model"
|
||||||
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
||||||
|
utls "gitlab.com/yawning/utls.git"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Trace implements model.Trace.
|
// Trace implements model.Trace.
|
||||||
|
@ -57,6 +58,10 @@ type Trace struct {
|
||||||
// calls to the netxlite.NewTLSHandshakerStdlib factory.
|
// calls to the netxlite.NewTLSHandshakerStdlib factory.
|
||||||
NewTLSHandshakerStdlibFn func(dl model.DebugLogger) model.TLSHandshaker
|
NewTLSHandshakerStdlibFn func(dl model.DebugLogger) model.TLSHandshaker
|
||||||
|
|
||||||
|
// NewTLSHandshakerUTLSFn is OPTIONAL and can be used to overide
|
||||||
|
// calls to the netxlite.NewTLSHandshakerUTLS factory.
|
||||||
|
NewTLSHandshakerUTLSFn func(dl model.DebugLogger, id *utls.ClientHelloID) model.TLSHandshaker
|
||||||
|
|
||||||
// NewDialerWithoutResolverFn is OPTIONAL and can be used to override
|
// NewDialerWithoutResolverFn is OPTIONAL and can be used to override
|
||||||
// calls to the netxlite.NewQUICDialerWithoutResolver factory.
|
// calls to the netxlite.NewQUICDialerWithoutResolver factory.
|
||||||
NewQUICDialerWithoutResolverFn func(listener model.QUICListener, dl model.DebugLogger) model.QUICDialer
|
NewQUICDialerWithoutResolverFn func(listener model.QUICListener, dl model.DebugLogger) model.QUICDialer
|
||||||
|
@ -200,7 +205,16 @@ func (tx *Trace) newTLSHandshakerStdlib(dl model.DebugLogger) model.TLSHandshake
|
||||||
return netxlite.NewTLSHandshakerStdlib(dl)
|
return netxlite.NewTLSHandshakerStdlib(dl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// newWUICDialerWithoutResolver indirectly calls netxlite.NewQUICDialerWithoutResolver
|
// newTLSHandshakerUTLS indirectly calls netxlite.NewTLSHandshakerUTLS
|
||||||
|
// thus allowing us to mock this func for testing.
|
||||||
|
func (tx *Trace) newTLSHandshakerUTLS(dl model.DebugLogger, id *utls.ClientHelloID) model.TLSHandshaker {
|
||||||
|
if tx.NewTLSHandshakerUTLSFn != nil {
|
||||||
|
return tx.NewTLSHandshakerUTLSFn(dl, id)
|
||||||
|
}
|
||||||
|
return netxlite.NewTLSHandshakerUTLS(dl, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newQUICDialerWithoutResolver indirectly calls netxlite.NewQUICDialerWithoutResolver
|
||||||
// thus allowing us to mock this func for testing.
|
// thus allowing us to mock this func for testing.
|
||||||
func (tx *Trace) newQUICDialerWithoutResolver(listener model.QUICListener, dl model.DebugLogger) model.QUICDialer {
|
func (tx *Trace) newQUICDialerWithoutResolver(listener model.QUICListener, dl model.DebugLogger) model.QUICDialer {
|
||||||
if tx.NewQUICDialerWithoutResolverFn != nil {
|
if tx.NewQUICDialerWithoutResolverFn != nil {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/ooni/probe-cli/v3/internal/model/mocks"
|
"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"
|
||||||
"github.com/ooni/probe-cli/v3/internal/testingx"
|
"github.com/ooni/probe-cli/v3/internal/testingx"
|
||||||
|
utls "gitlab.com/yawning/utls.git"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewTrace(t *testing.T) {
|
func TestNewTrace(t *testing.T) {
|
||||||
|
@ -78,6 +79,12 @@ func TestNewTrace(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("newTLShandshakerUTLSFn is nil", func(t *testing.T) {
|
||||||
|
if trace.NewTLSHandshakerUTLSFn != nil {
|
||||||
|
t.Fatal("expected nil NewTLSHandshakerUTLSfn")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("NewQUICDialerWithoutResolverFn is nil", func(t *testing.T) {
|
t.Run("NewQUICDialerWithoutResolverFn is nil", func(t *testing.T) {
|
||||||
if trace.NewQUICDialerWithoutResolverFn != nil {
|
if trace.NewQUICDialerWithoutResolverFn != nil {
|
||||||
t.Fatal("expected nil NewQUICDialerQithoutResolverFn")
|
t.Fatal("expected nil NewQUICDialerQithoutResolverFn")
|
||||||
|
@ -426,6 +433,76 @@ func TestTrace(t *testing.T) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("NewTLSHandshakerUTLSFn works as intended", func(t *testing.T) {
|
||||||
|
t.Run("when not nil", func(t *testing.T) {
|
||||||
|
mockedErr := errors.New("mocked")
|
||||||
|
tx := &Trace{
|
||||||
|
NewTLSHandshakerUTLSFn: func(dl model.DebugLogger, id *utls.ClientHelloID) model.TLSHandshaker {
|
||||||
|
return &mocks.TLSHandshaker{
|
||||||
|
MockHandshake: func(ctx context.Context, conn net.Conn, config *tls.Config) (net.Conn, tls.ConnectionState, error) {
|
||||||
|
return nil, tls.ConnectionState{}, mockedErr
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
thx := tx.NewTLSHandshakerUTLS(model.DiscardLogger, &utls.HelloGolang)
|
||||||
|
ctx := context.Background()
|
||||||
|
conn, state, err := thx.Handshake(ctx, &mocks.Conn{}, &tls.Config{})
|
||||||
|
if !errors.Is(err, mockedErr) {
|
||||||
|
t.Fatal("unexpected err", err)
|
||||||
|
}
|
||||||
|
if !reflect.ValueOf(state).IsZero() {
|
||||||
|
t.Fatal("state is not a zero value")
|
||||||
|
}
|
||||||
|
if conn != nil {
|
||||||
|
t.Fatal("expected nil conn")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when nil", func(t *testing.T) {
|
||||||
|
mockedErr := errors.New("mocked")
|
||||||
|
tx := &Trace{
|
||||||
|
NewTLSHandshakerStdlibFn: nil,
|
||||||
|
}
|
||||||
|
thx := tx.newTLSHandshakerUTLS(model.DiscardLogger, &utls.HelloGolang)
|
||||||
|
tcpConn := &mocks.Conn{
|
||||||
|
MockSetDeadline: func(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
MockRemoteAddr: func() net.Addr {
|
||||||
|
return &mocks.Addr{
|
||||||
|
MockNetwork: func() string {
|
||||||
|
return "tcp"
|
||||||
|
},
|
||||||
|
MockString: func() string {
|
||||||
|
return "1.1.1.1:443"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MockWrite: func(b []byte) (int, error) {
|
||||||
|
return 0, mockedErr
|
||||||
|
},
|
||||||
|
MockClose: func() error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
conn, state, err := thx.Handshake(ctx, tcpConn, tlsConfig)
|
||||||
|
if !errors.Is(err, mockedErr) {
|
||||||
|
t.Fatal("unexpected err", err)
|
||||||
|
}
|
||||||
|
if !reflect.ValueOf(state).IsZero() {
|
||||||
|
t.Fatal("state is not a zero value")
|
||||||
|
}
|
||||||
|
if conn != nil {
|
||||||
|
t.Fatal("expected nil conn")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("NewQUICDialerWithoutResolverFn works as intended", func(t *testing.T) {
|
t.Run("NewQUICDialerWithoutResolverFn works as intended", func(t *testing.T) {
|
||||||
t.Run("when not nil", func(t *testing.T) {
|
t.Run("when not nil", func(t *testing.T) {
|
||||||
mockedErr := errors.New("mocked")
|
mockedErr := errors.New("mocked")
|
||||||
|
|
15
internal/measurexlite/utls.go
Normal file
15
internal/measurexlite/utls.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package measurexlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ooni/probe-cli/v3/internal/model"
|
||||||
|
utls "gitlab.com/yawning/utls.git"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewTLSHandshakerUTLS is equivalent to netxlite.NewTLSHandshakerUTLS
|
||||||
|
// except that it returns a model.TLSHandshaker that uses this trace.
|
||||||
|
func (tx *Trace) NewTLSHandshakerUTLS(dl model.DebugLogger, id *utls.ClientHelloID) model.TLSHandshaker {
|
||||||
|
return &tlsHandshakerTrace{
|
||||||
|
thx: tx.newTLSHandshakerUTLS(dl, id),
|
||||||
|
tx: tx,
|
||||||
|
}
|
||||||
|
}
|
29
internal/measurexlite/utls_test.go
Normal file
29
internal/measurexlite/utls_test.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package measurexlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ooni/probe-cli/v3/internal/model"
|
||||||
|
"github.com/ooni/probe-cli/v3/internal/model/mocks"
|
||||||
|
utls "gitlab.com/yawning/utls.git"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewTLSHandshakerUTLS(t *testing.T) {
|
||||||
|
t.Run("NewTLSHandshakerUTLS creates a wrapped TLSHandshaker", func(t *testing.T) {
|
||||||
|
underlying := &mocks.TLSHandshaker{}
|
||||||
|
zeroTime := time.Now()
|
||||||
|
trace := NewTrace(0, zeroTime)
|
||||||
|
trace.NewTLSHandshakerUTLSFn = func(dl model.DebugLogger, id *utls.ClientHelloID) model.TLSHandshaker {
|
||||||
|
return underlying
|
||||||
|
}
|
||||||
|
thx := trace.NewTLSHandshakerUTLS(model.DiscardLogger, &utls.HelloGolang)
|
||||||
|
thxt := thx.(*tlsHandshakerTrace)
|
||||||
|
if thxt.thx != underlying {
|
||||||
|
t.Fatal("invalid TLS handshaker")
|
||||||
|
}
|
||||||
|
if thxt.tx != trace {
|
||||||
|
t.Fatal("invalid trace")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user