refactor: split errorsx in good and legacy (#477)
The legacy part for now is internal/errorsx. It will stay there until I figure out whether it also needs some extra bug fixing. The good part is now in internal/netxlite/errorsx and contains all the logic for mapping errors. We need to further improve upon this logic by writing more thorough integration tests for QUIC. We also need to copy the various dialer, conn, etc adapters that set errors. We will put them inside netxlite and we will generate errors in a way that is less crazy with respect to the major operation. (The idea is to always wrap, given that now we measure in an incremental way and we don't measure every operation together.) Part of https://github.com/ooni/probe/issues/1591
This commit is contained in:
parent
ccb3a644e1
commit
83440cf110
|
@ -11,7 +11,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/runtimex"
|
||||
)
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
func TestMeasureSuccess(t *testing.T) {
|
||||
|
|
|
@ -17,8 +17,8 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/trace"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/humanize"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/iox"
|
||||
)
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/mockable"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/trace"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
func TestRunnerLoopLocateFailure(t *testing.T) {
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/experiment/urlgetter"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/mockable"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/archival"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
func TestNewExperimentMeasurer(t *testing.T) {
|
||||
|
|
|
@ -19,7 +19,8 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/archival"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/dialer"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
errorsxlegacy "github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/iox"
|
||||
"github.com/ooni/probe-cli/v3/internal/randx"
|
||||
)
|
||||
|
@ -180,7 +181,7 @@ func Transact(txp Transport, req *http.Request,
|
|||
callbacks model.ExperimentCallbacks) (*http.Response, []byte, error) {
|
||||
// make sure that we return a wrapped error here
|
||||
resp, data, err := transact(txp, req, callbacks)
|
||||
err = errorsx.SafeErrWrapperBuilder{
|
||||
err = errorsxlegacy.SafeErrWrapperBuilder{
|
||||
Error: err, Operation: errorsx.TopLevelOperation}.MaybeBuild()
|
||||
return resp, data, err
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/mockable"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/archival"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
func TestNewExperimentMeasurer(t *testing.T) {
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/archival"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/randx"
|
||||
)
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/archival"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
func TestNewExperimentMeasurer(t *testing.T) {
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/mockable"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/archival"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/experiment/urlgetter"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/mockable"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
func TestNewExperimentMeasurer(t *testing.T) {
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/experiment/urlgetter"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/apex/log"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/mockable"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/experiment/stunreachability"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/mockable"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/pion/stun"
|
||||
)
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/experiment/urlgetter"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/experiment/urlgetter"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/mockable"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
func TestNewExperimentMeasurer(t *testing.T) {
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/legacy/oonidatamodel"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/legacy/oonitemplates"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/runtimex"
|
||||
"github.com/ooni/probe-cli/v3/internal/scrubber"
|
||||
)
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/legacy/oonitemplates"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/mockable"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/scrubber"
|
||||
)
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/archival"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/trace"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
legacyerrorsx "github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/tunnel"
|
||||
)
|
||||
|
||||
|
@ -54,7 +55,7 @@ func (g Getter) Get(ctx context.Context) (TestKeys, error) {
|
|||
tk, err := g.get(ctx, saver)
|
||||
// Make sure we have an operation in cases where we fail before
|
||||
// hitting our httptransport that does error wrapping.
|
||||
err = errorsx.SafeErrWrapperBuilder{
|
||||
err = legacyerrorsx.SafeErrWrapperBuilder{
|
||||
Error: err,
|
||||
Operation: errorsx.TopLevelOperation,
|
||||
}.MaybeBuild()
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"github.com/apex/log"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/experiment/urlgetter"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/mockable"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
func TestGetterWithVeryShortTimeout(t *testing.T) {
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/httpheader"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/iox"
|
||||
"github.com/ooni/probe-cli/v3/internal/runtimex"
|
||||
)
|
||||
|
|
|
@ -6,7 +6,8 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/geolocate"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/httpx"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
legacyerrorsx "github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
// ControlRequest is the request that we send to the control
|
||||
|
@ -59,7 +60,7 @@ func Control(
|
|||
}
|
||||
sess.Logger().Infof("control for %s...", creq.HTTPRequest)
|
||||
// make sure error is wrapped
|
||||
err = errorsx.SafeErrWrapperBuilder{
|
||||
err = legacyerrorsx.SafeErrWrapperBuilder{
|
||||
Error: clnt.PostJSON(ctx, "/", creq, &out),
|
||||
Operation: errorsx.TopLevelOperation,
|
||||
}.MaybeBuild()
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"net"
|
||||
"net/url"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
// DNSAnalysisResult contains the results of analysing comparing
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/experiment/webconnectivity"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
func TestDNSAnalysis(t *testing.T) {
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/experiment/webconnectivity/internal"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
// The following set of status flags identifies in a more nuanced way the
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/experiment/webconnectivity"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/archival"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
func TestSummarize(t *testing.T) {
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/experiment/webconnectivity"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/archival"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
func TestNewExperimentMeasurer(t *testing.T) {
|
||||
|
|
|
@ -5,7 +5,8 @@ import (
|
|||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/httpx"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
errorsxlegacy "github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
// Control performs the control request and returns the response.
|
||||
|
@ -18,7 +19,7 @@ func Control(
|
|||
Logger: sess.Logger(),
|
||||
}
|
||||
// make sure error is wrapped
|
||||
err = errorsx.SafeErrWrapperBuilder{
|
||||
err = errorsxlegacy.SafeErrWrapperBuilder{
|
||||
Error: clnt.PostJSON(ctx, resourcePath, creq, &out),
|
||||
Operation: errorsx.TopLevelOperation,
|
||||
}.MaybeBuild()
|
||||
|
|
|
@ -8,7 +8,8 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/legacy/netx/handlers"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/legacy/netx/modelx"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/legacy/netx/oldhttptransport"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
errorsxlegacy "github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
|
@ -82,7 +83,7 @@ func (t *HTTPTransport) RoundTrip(
|
|||
// For safety wrap the error as modelx.HTTPRoundTripOperation but this
|
||||
// will only be used if the error chain does not contain any
|
||||
// other major operation failure. See errorsx.ErrWrapper.
|
||||
err = errorsx.SafeErrWrapperBuilder{
|
||||
err = errorsxlegacy.SafeErrWrapperBuilder{
|
||||
Error: err,
|
||||
Operation: errorsx.HTTPRoundTripOperation,
|
||||
}.MaybeBuild()
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/legacy/netx"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/iox"
|
||||
)
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
func TestNewTLSConnectionState(t *testing.T) {
|
||||
|
|
|
@ -12,7 +12,8 @@ import (
|
|||
|
||||
"github.com/ooni/probe-cli/v3/internal/atomicx"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/legacy/netx/modelx"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
errorsxlegacy "github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/iox"
|
||||
)
|
||||
|
||||
|
@ -116,7 +117,7 @@ func (t *TraceTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||
TLSHandshakeDone: func(state tls.ConnectionState, err error) {
|
||||
// Wrapping the error even if we're not returning it because it may
|
||||
// less confusing to users to see the wrapped name
|
||||
err = errorsx.SafeErrWrapperBuilder{
|
||||
err = errorsxlegacy.SafeErrWrapperBuilder{
|
||||
Error: err,
|
||||
Operation: errorsx.TLSHandshakeOperation,
|
||||
}.MaybeBuild()
|
||||
|
@ -171,7 +172,7 @@ func (t *TraceTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||
WroteRequest: func(info httptrace.WroteRequestInfo) {
|
||||
// Wrapping the error even if we're not returning it because it may
|
||||
// less confusing to users to see the wrapped name
|
||||
err := errorsx.SafeErrWrapperBuilder{
|
||||
err := errorsxlegacy.SafeErrWrapperBuilder{
|
||||
Error: info.Err,
|
||||
Operation: errorsx.HTTPRoundTripOperation,
|
||||
}.MaybeBuild()
|
||||
|
@ -207,7 +208,7 @@ func (t *TraceTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||
}
|
||||
|
||||
resp, err := t.roundTripper.RoundTrip(req)
|
||||
err = errorsx.SafeErrWrapperBuilder{
|
||||
err = errorsxlegacy.SafeErrWrapperBuilder{
|
||||
Error: err,
|
||||
Operation: majorOp,
|
||||
}.MaybeBuild()
|
||||
|
|
|
@ -19,8 +19,8 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/legacy/netx/modelx"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/legacy/oonitemplates"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
// ExtSpec describes a data format extension
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/legacy/netx/modelx"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/legacy/oonitemplates"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
func TestNewTCPConnectListEmpty(t *testing.T) {
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
goptlib "git.torproject.org/pluggable-transports/goptlib.git"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/legacy/netx/modelx"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"gitlab.com/yawning/obfs4.git/transports"
|
||||
obfs4base "gitlab.com/yawning/obfs4.git/transports/base"
|
||||
)
|
||||
|
|
|
@ -19,7 +19,8 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/geolocate"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/trace"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
errorsxlegacy "github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
// ExtSpec describes a data format extension
|
||||
|
@ -110,7 +111,7 @@ func NewFailure(err error) *string {
|
|||
// The following code guarantees that the error is always wrapped even
|
||||
// when we could not actually hit our code that does the wrapping. A case
|
||||
// in which this happen is with context deadline for HTTP.
|
||||
err = errorsx.SafeErrWrapperBuilder{
|
||||
err = errorsxlegacy.SafeErrWrapperBuilder{
|
||||
Error: err,
|
||||
Operation: errorsx.TopLevelOperation,
|
||||
}.MaybeBuild()
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/archival"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/trace"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
func TestNewTCPConnectList(t *testing.T) {
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/trace"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
// saverDialer saves events occurring during the dial
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/trace"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/mocks"
|
||||
)
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/bytecounter"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/trace"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/iox"
|
||||
)
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/trace"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/quicx"
|
||||
)
|
||||
|
||||
|
|
|
@ -10,8 +10,8 @@ import (
|
|||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/quicdialer"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/trace"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/mocks"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/quicx"
|
||||
)
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/runtimex"
|
||||
)
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/resolver"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
func TestResolverIsBogon(t *testing.T) {
|
||||
|
|
|
@ -11,8 +11,8 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/netx/dialer"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/tlsdialer"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/trace"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
func TestSaverTLSHandshakerSuccessWithReadWrite(t *testing.T) {
|
||||
|
|
|
@ -3,6 +3,8 @@ package errorsx
|
|||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
// Dialer establishes network connections.
|
||||
|
@ -22,9 +24,9 @@ type ErrorWrapperDialer struct {
|
|||
func (d *ErrorWrapperDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
conn, err := d.Dialer.DialContext(ctx, network, address)
|
||||
if err != nil {
|
||||
return nil, &ErrWrapper{
|
||||
Failure: ClassifyGenericError(err),
|
||||
Operation: ConnectOperation,
|
||||
return nil, &errorsx.ErrWrapper{
|
||||
Failure: errorsx.ClassifyGenericError(err),
|
||||
Operation: errorsx.ConnectOperation,
|
||||
WrappedErr: err,
|
||||
}
|
||||
}
|
||||
|
@ -41,9 +43,9 @@ type errorWrapperConn struct {
|
|||
func (c *errorWrapperConn) Read(b []byte) (int, error) {
|
||||
count, err := c.Conn.Read(b)
|
||||
if err != nil {
|
||||
return 0, &ErrWrapper{
|
||||
Failure: ClassifyGenericError(err),
|
||||
Operation: ReadOperation,
|
||||
return 0, &errorsx.ErrWrapper{
|
||||
Failure: errorsx.ClassifyGenericError(err),
|
||||
Operation: errorsx.ReadOperation,
|
||||
WrappedErr: err,
|
||||
}
|
||||
}
|
||||
|
@ -54,9 +56,9 @@ func (c *errorWrapperConn) Read(b []byte) (int, error) {
|
|||
func (c *errorWrapperConn) Write(b []byte) (int, error) {
|
||||
count, err := c.Conn.Write(b)
|
||||
if err != nil {
|
||||
return 0, &ErrWrapper{
|
||||
Failure: ClassifyGenericError(err),
|
||||
Operation: WriteOperation,
|
||||
return 0, &errorsx.ErrWrapper{
|
||||
Failure: errorsx.ClassifyGenericError(err),
|
||||
Operation: errorsx.WriteOperation,
|
||||
WrappedErr: err,
|
||||
}
|
||||
}
|
||||
|
@ -67,9 +69,9 @@ func (c *errorWrapperConn) Write(b []byte) (int, error) {
|
|||
func (c *errorWrapperConn) Close() error {
|
||||
err := c.Conn.Close()
|
||||
if err != nil {
|
||||
return &ErrWrapper{
|
||||
Failure: ClassifyGenericError(err),
|
||||
Operation: CloseOperation,
|
||||
return &errorsx.ErrWrapper{
|
||||
Failure: errorsx.ClassifyGenericError(err),
|
||||
Operation: errorsx.CloseOperation,
|
||||
WrappedErr: err,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/mocks"
|
||||
)
|
||||
|
||||
|
@ -18,14 +19,14 @@ func TestErrorWrapperDialerFailure(t *testing.T) {
|
|||
},
|
||||
}}
|
||||
conn, err := d.DialContext(ctx, "tcp", "www.google.com:443")
|
||||
var ew *ErrWrapper
|
||||
var ew *errorsx.ErrWrapper
|
||||
if !errors.As(err, &ew) {
|
||||
t.Fatal("cannot convert to ErrWrapper")
|
||||
}
|
||||
if ew.Operation != ConnectOperation {
|
||||
if ew.Operation != errorsx.ConnectOperation {
|
||||
t.Fatal("unexpected operation", ew.Operation)
|
||||
}
|
||||
if ew.Failure != FailureEOFError {
|
||||
if ew.Failure != errorsx.FailureEOFError {
|
||||
t.Fatal("unexpected failure", ew.Failure)
|
||||
}
|
||||
if !errors.Is(ew.WrappedErr, io.EOF) {
|
||||
|
@ -67,14 +68,14 @@ func TestErrorWrapperConnReadFailure(t *testing.T) {
|
|||
}
|
||||
buf := make([]byte, 1024)
|
||||
cnt, err := c.Read(buf)
|
||||
var ew *ErrWrapper
|
||||
var ew *errorsx.ErrWrapper
|
||||
if !errors.As(err, &ew) {
|
||||
t.Fatal("cannot cast error to ErrWrapper")
|
||||
}
|
||||
if ew.Operation != ReadOperation {
|
||||
if ew.Operation != errorsx.ReadOperation {
|
||||
t.Fatal("invalid operation", ew.Operation)
|
||||
}
|
||||
if ew.Failure != FailureEOFError {
|
||||
if ew.Failure != errorsx.FailureEOFError {
|
||||
t.Fatal("invalid failure", ew.Failure)
|
||||
}
|
||||
if !errors.Is(ew.WrappedErr, io.EOF) {
|
||||
|
@ -113,14 +114,14 @@ func TestErrorWrapperConnWriteFailure(t *testing.T) {
|
|||
}
|
||||
buf := make([]byte, 1024)
|
||||
cnt, err := c.Write(buf)
|
||||
var ew *ErrWrapper
|
||||
var ew *errorsx.ErrWrapper
|
||||
if !errors.As(err, &ew) {
|
||||
t.Fatal("cannot cast error to ErrWrapper")
|
||||
}
|
||||
if ew.Operation != WriteOperation {
|
||||
if ew.Operation != errorsx.WriteOperation {
|
||||
t.Fatal("invalid operation", ew.Operation)
|
||||
}
|
||||
if ew.Failure != FailureEOFError {
|
||||
if ew.Failure != errorsx.FailureEOFError {
|
||||
t.Fatal("invalid failure", ew.Failure)
|
||||
}
|
||||
if !errors.Is(ew.WrappedErr, io.EOF) {
|
||||
|
@ -158,14 +159,14 @@ func TestErrorWrapperConnCloseFailure(t *testing.T) {
|
|||
},
|
||||
}
|
||||
err := c.Close()
|
||||
var ew *ErrWrapper
|
||||
var ew *errorsx.ErrWrapper
|
||||
if !errors.As(err, &ew) {
|
||||
t.Fatal("cannot cast error to ErrWrapper")
|
||||
}
|
||||
if ew.Operation != CloseOperation {
|
||||
if ew.Operation != errorsx.CloseOperation {
|
||||
t.Fatal("invalid operation", ew.Operation)
|
||||
}
|
||||
if ew.Failure != FailureEOFError {
|
||||
if ew.Failure != errorsx.FailureEOFError {
|
||||
t.Fatal("invalid failure", ew.Failure)
|
||||
}
|
||||
if !errors.Is(ew.WrappedErr, io.EOF) {
|
||||
|
|
|
@ -2,69 +2,11 @@
|
|||
package errorsx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/scrubber"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
// ErrDNSBogon indicates that we found a bogon address. This is the
|
||||
// correct value with which to initialize MeasurementRoot.ErrDNSBogon
|
||||
// to tell this library to return an error when a bogon is found.
|
||||
var ErrDNSBogon = errors.New("dns: detected bogon address")
|
||||
|
||||
// ErrWrapper is our error wrapper for Go errors. The key objective of
|
||||
// this structure is to properly set Failure, which is also returned by
|
||||
// the Error() method, so be one of the OONI defined strings.
|
||||
type ErrWrapper struct {
|
||||
// Failure is the OONI failure string. The failure strings are
|
||||
// loosely backward compatible with Measurement Kit.
|
||||
//
|
||||
// This is either one of the FailureXXX strings or any other
|
||||
// string like `unknown_failure ...`. The latter represents an
|
||||
// error that we have not yet mapped to a failure.
|
||||
Failure string
|
||||
|
||||
// Operation is the operation that failed. If possible, it
|
||||
// SHOULD be a _major_ operation. Major operations are:
|
||||
//
|
||||
// - ResolveOperation: resolving a domain name failed
|
||||
// - ConnectOperation: connecting to an IP failed
|
||||
// - TLSHandshakeOperation: TLS handshaking failed
|
||||
// - HTTPRoundTripOperation: other errors during round trip
|
||||
//
|
||||
// Because a network connection doesn't necessarily know
|
||||
// what is the current major operation we also have the
|
||||
// following _minor_ operations:
|
||||
//
|
||||
// - CloseOperation: CLOSE failed
|
||||
// - ReadOperation: READ failed
|
||||
// - WriteOperation: WRITE failed
|
||||
//
|
||||
// If an ErrWrapper referring to a major operation is wrapping
|
||||
// another ErrWrapper and such ErrWrapper already refers to
|
||||
// a major operation, then the new ErrWrapper should use the
|
||||
// child ErrWrapper major operation. Otherwise, it should use
|
||||
// its own major operation. This way, the topmost wrapper is
|
||||
// supposed to refer to the major operation that failed.
|
||||
Operation string
|
||||
|
||||
// WrappedErr is the error that we're wrapping.
|
||||
WrappedErr error
|
||||
}
|
||||
|
||||
// Error returns a description of the error that occurred.
|
||||
func (e *ErrWrapper) Error() string {
|
||||
return e.Failure
|
||||
}
|
||||
|
||||
// Unwrap allows to access the underlying error
|
||||
func (e *ErrWrapper) Unwrap() error {
|
||||
return e.WrappedErr
|
||||
}
|
||||
|
||||
// SafeErrWrapperBuilder contains a builder for ErrWrapper that
|
||||
// is safe, i.e., behaves correctly when the error is nil.
|
||||
type SafeErrWrapperBuilder struct {
|
||||
|
@ -85,9 +27,9 @@ func (b SafeErrWrapperBuilder) MaybeBuild() (err error) {
|
|||
if b.Error != nil {
|
||||
classifier := b.Classifier
|
||||
if classifier == nil {
|
||||
classifier = ClassifyGenericError
|
||||
classifier = errorsx.ClassifyGenericError
|
||||
}
|
||||
err = &ErrWrapper{
|
||||
err = &errorsx.ErrWrapper{
|
||||
Failure: classifier(b.Error),
|
||||
Operation: toOperationString(b.Error, b.Operation),
|
||||
WrappedErr: b.Error,
|
||||
|
@ -96,86 +38,31 @@ func (b SafeErrWrapperBuilder) MaybeBuild() (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// TODO (kelmenhorst, bassosimone):
|
||||
// Use errors.Is / errors.As more often, when possible, in this classifier.
|
||||
// These methods are more robust to library changes than strings.
|
||||
// errors.Is / errors.As can only be used when the error is exported.
|
||||
|
||||
// ClassifyGenericError is the generic classifier mapping an error
|
||||
// occurred during an operation to an OONI failure string.
|
||||
func ClassifyGenericError(err error) string {
|
||||
// The list returned here matches the values used by MK unless
|
||||
// explicitly noted otherwise with a comment.
|
||||
|
||||
// TODO(bassosimone): we need to always apply this rule not only here
|
||||
// when we're making the most generic conversion.
|
||||
var errwrapper *ErrWrapper
|
||||
if errors.As(err, &errwrapper) {
|
||||
return errwrapper.Error() // we've already wrapped it
|
||||
}
|
||||
|
||||
if failure := classifySyscallError(err); failure != "" {
|
||||
return failure
|
||||
}
|
||||
|
||||
if errors.Is(err, context.Canceled) {
|
||||
return FailureInterrupted
|
||||
}
|
||||
s := err.Error()
|
||||
if strings.HasSuffix(s, "operation was canceled") {
|
||||
return FailureInterrupted
|
||||
}
|
||||
if strings.HasSuffix(s, "EOF") {
|
||||
return FailureEOFError
|
||||
}
|
||||
if strings.HasSuffix(s, "context deadline exceeded") {
|
||||
return FailureGenericTimeoutError
|
||||
}
|
||||
if strings.HasSuffix(s, "transaction is timed out") {
|
||||
return FailureGenericTimeoutError
|
||||
}
|
||||
if strings.HasSuffix(s, "i/o timeout") {
|
||||
return FailureGenericTimeoutError
|
||||
}
|
||||
// TODO(kelmenhorst,bassosimone): this can probably be moved since it's TLS specific
|
||||
if strings.HasSuffix(s, "TLS handshake timeout") {
|
||||
return FailureGenericTimeoutError
|
||||
}
|
||||
if strings.HasSuffix(s, "no such host") {
|
||||
// This is dns_lookup_error in MK but such error is used as a
|
||||
// generic "hey, the lookup failed" error. Instead, this error
|
||||
// that we return here is significantly more specific.
|
||||
return FailureDNSNXDOMAINError
|
||||
}
|
||||
formatted := fmt.Sprintf("unknown_failure: %s", s)
|
||||
return scrubber.Scrub(formatted) // scrub IP addresses in the error
|
||||
}
|
||||
|
||||
func toOperationString(err error, operation string) string {
|
||||
var errwrapper *ErrWrapper
|
||||
var errwrapper *errorsx.ErrWrapper
|
||||
if errors.As(err, &errwrapper) {
|
||||
// Basically, as explained in ErrWrapper docs, let's
|
||||
// keep the child major operation, if any.
|
||||
if errwrapper.Operation == ConnectOperation {
|
||||
if errwrapper.Operation == errorsx.ConnectOperation {
|
||||
return errwrapper.Operation
|
||||
}
|
||||
if errwrapper.Operation == HTTPRoundTripOperation {
|
||||
if errwrapper.Operation == errorsx.HTTPRoundTripOperation {
|
||||
return errwrapper.Operation
|
||||
}
|
||||
if errwrapper.Operation == ResolveOperation {
|
||||
if errwrapper.Operation == errorsx.ResolveOperation {
|
||||
return errwrapper.Operation
|
||||
}
|
||||
if errwrapper.Operation == TLSHandshakeOperation {
|
||||
if errwrapper.Operation == errorsx.TLSHandshakeOperation {
|
||||
return errwrapper.Operation
|
||||
}
|
||||
if errwrapper.Operation == QUICHandshakeOperation {
|
||||
if errwrapper.Operation == errorsx.QUICHandshakeOperation {
|
||||
return errwrapper.Operation
|
||||
}
|
||||
if errwrapper.Operation == "quic_handshake_start" {
|
||||
return QUICHandshakeOperation
|
||||
return errorsx.QUICHandshakeOperation
|
||||
}
|
||||
if errwrapper.Operation == "quic_handshake_done" {
|
||||
return QUICHandshakeOperation
|
||||
return errorsx.QUICHandshakeOperation
|
||||
}
|
||||
// FALLTHROUGH
|
||||
}
|
||||
|
|
|
@ -1,25 +1,17 @@
|
|||
package errorsx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/pion/stun"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
func TestMaybeBuildFactory(t *testing.T) {
|
||||
err := SafeErrWrapperBuilder{
|
||||
Error: errors.New("mocked error"),
|
||||
}.MaybeBuild()
|
||||
var target *ErrWrapper
|
||||
var target *errorsx.ErrWrapper
|
||||
if errors.As(err, &target) == false {
|
||||
t.Fatal("not the expected error type")
|
||||
}
|
||||
|
@ -31,242 +23,36 @@ func TestMaybeBuildFactory(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestToFailureString(t *testing.T) {
|
||||
t.Run("for already wrapped error", func(t *testing.T) {
|
||||
err := SafeErrWrapperBuilder{Error: io.EOF}.MaybeBuild()
|
||||
if ClassifyGenericError(err) != FailureEOFError {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
t.Run("for context.Canceled", func(t *testing.T) {
|
||||
if ClassifyGenericError(context.Canceled) != FailureInterrupted {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
t.Run("for operation was canceled error", func(t *testing.T) {
|
||||
if ClassifyGenericError(errors.New("operation was canceled")) != FailureInterrupted {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
t.Run("for EOF", func(t *testing.T) {
|
||||
if ClassifyGenericError(io.EOF) != FailureEOFError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for canceled", func(t *testing.T) {
|
||||
if ClassifyGenericError(syscall.ECANCELED) != FailureOperationCanceled {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for connection_refused", func(t *testing.T) {
|
||||
if ClassifyGenericError(syscall.ECONNREFUSED) != FailureConnectionRefused {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for connection_reset", func(t *testing.T) {
|
||||
if ClassifyGenericError(syscall.ECONNRESET) != FailureConnectionReset {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for host_unreachable", func(t *testing.T) {
|
||||
if ClassifyGenericError(syscall.EHOSTUNREACH) != FailureHostUnreachable {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for system timeout", func(t *testing.T) {
|
||||
if ClassifyGenericError(syscall.ETIMEDOUT) != FailureTimedOut {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for context deadline exceeded", func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1)
|
||||
defer cancel()
|
||||
<-ctx.Done()
|
||||
if ClassifyGenericError(ctx.Err()) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for stun's transaction is timed out", func(t *testing.T) {
|
||||
if ClassifyGenericError(stun.ErrTransactionTimeOut) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for i/o error", func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1)
|
||||
defer cancel() // fail immediately
|
||||
conn, err := (&net.Dialer{}).DialContext(ctx, "tcp", "www.google.com:80")
|
||||
if err == nil {
|
||||
t.Fatal("expected an error here")
|
||||
}
|
||||
if conn != nil {
|
||||
t.Fatal("expected nil connection here")
|
||||
}
|
||||
if ClassifyGenericError(err) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for TLS handshake timeout error", func(t *testing.T) {
|
||||
err := errors.New("net/http: TLS handshake timeout")
|
||||
if ClassifyGenericError(err) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for no such host", func(t *testing.T) {
|
||||
if ClassifyGenericError(&net.DNSError{
|
||||
Err: "no such host",
|
||||
}) != FailureDNSNXDOMAINError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for errors including IPv4 address", func(t *testing.T) {
|
||||
input := errors.New("read tcp 10.0.2.15:56948->93.184.216.34:443: use of closed network connection")
|
||||
expected := "unknown_failure: read tcp [scrubbed]->[scrubbed]: use of closed network connection"
|
||||
out := ClassifyGenericError(input)
|
||||
if out != expected {
|
||||
t.Fatal(cmp.Diff(expected, out))
|
||||
}
|
||||
})
|
||||
t.Run("for errors including IPv6 address", func(t *testing.T) {
|
||||
input := errors.New("read tcp [::1]:56948->[::1]:443: use of closed network connection")
|
||||
expected := "unknown_failure: read tcp [scrubbed]->[scrubbed]: use of closed network connection"
|
||||
out := ClassifyGenericError(input)
|
||||
if out != expected {
|
||||
t.Fatal(cmp.Diff(expected, out))
|
||||
}
|
||||
})
|
||||
t.Run("for i/o error", func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1)
|
||||
defer cancel() // fail immediately
|
||||
udpAddr := &net.UDPAddr{IP: net.ParseIP("216.58.212.164"), Port: 80, Zone: ""}
|
||||
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sess, err := quic.DialEarlyContext(ctx, udpConn, udpAddr, "google.com:80", &tls.Config{}, &quic.Config{})
|
||||
if err == nil {
|
||||
t.Fatal("expected an error here")
|
||||
}
|
||||
if sess != nil {
|
||||
t.Fatal("expected nil session here")
|
||||
}
|
||||
if ClassifyGenericError(err) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestClassifyQUICFailure(t *testing.T) {
|
||||
t.Run("for connection_reset", func(t *testing.T) {
|
||||
if ClassifyQUICHandshakeError(&quic.StatelessResetError{}) != FailureConnectionReset {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for incompatible quic version", func(t *testing.T) {
|
||||
if ClassifyQUICHandshakeError(&quic.VersionNegotiationError{}) != FailureQUICIncompatibleVersion {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for quic connection refused", func(t *testing.T) {
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: quic.ConnectionRefused}) != FailureConnectionRefused {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for quic handshake timeout", func(t *testing.T) {
|
||||
if ClassifyQUICHandshakeError(&quic.HandshakeTimeoutError{}) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for QUIC idle connection timeout", func(t *testing.T) {
|
||||
if ClassifyQUICHandshakeError(&quic.IdleTimeoutError{}) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for QUIC CRYPTO Handshake", func(t *testing.T) {
|
||||
var err quic.TransportErrorCode = quicTLSAlertHandshakeFailure
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLFailedHandshake {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for QUIC CRYPTO Invalid Certificate", func(t *testing.T) {
|
||||
var err quic.TransportErrorCode = quicTLSAlertBadCertificate
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidCertificate {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for QUIC CRYPTO Unknown CA", func(t *testing.T) {
|
||||
var err quic.TransportErrorCode = quicTLSAlertUnknownCA
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLUnknownAuthority {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for QUIC CRYPTO Bad Hostname", func(t *testing.T) {
|
||||
var err quic.TransportErrorCode = quicTLSUnrecognizedName
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidHostname {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestClassifyResolveFailure(t *testing.T) {
|
||||
t.Run("for ErrDNSBogon", func(t *testing.T) {
|
||||
if ClassifyResolverError(ErrDNSBogon) != FailureDNSBogonError {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestClassifyTLSFailure(t *testing.T) {
|
||||
t.Run("for x509.HostnameError", func(t *testing.T) {
|
||||
var err x509.HostnameError
|
||||
if ClassifyTLSHandshakeError(err) != FailureSSLInvalidHostname {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
t.Run("for x509.UnknownAuthorityError", func(t *testing.T) {
|
||||
var err x509.UnknownAuthorityError
|
||||
if ClassifyTLSHandshakeError(err) != FailureSSLUnknownAuthority {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
t.Run("for x509.CertificateInvalidError", func(t *testing.T) {
|
||||
var err x509.CertificateInvalidError
|
||||
if ClassifyTLSHandshakeError(err) != FailureSSLInvalidCertificate {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestToOperationString(t *testing.T) {
|
||||
t.Run("for connect", func(t *testing.T) {
|
||||
// You're doing HTTP and connect fails. You want to know
|
||||
// that connect failed not that HTTP failed.
|
||||
err := &ErrWrapper{Operation: ConnectOperation}
|
||||
if toOperationString(err, HTTPRoundTripOperation) != ConnectOperation {
|
||||
err := &errorsx.ErrWrapper{Operation: errorsx.ConnectOperation}
|
||||
if toOperationString(err, errorsx.HTTPRoundTripOperation) != errorsx.ConnectOperation {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
t.Run("for http_round_trip", func(t *testing.T) {
|
||||
// You're doing DoH and something fails inside HTTP. You want
|
||||
// to know about the internal HTTP error, not resolve.
|
||||
err := &ErrWrapper{Operation: HTTPRoundTripOperation}
|
||||
if toOperationString(err, ResolveOperation) != HTTPRoundTripOperation {
|
||||
err := &errorsx.ErrWrapper{Operation: errorsx.HTTPRoundTripOperation}
|
||||
if toOperationString(err, errorsx.ResolveOperation) != errorsx.HTTPRoundTripOperation {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
t.Run("for resolve", func(t *testing.T) {
|
||||
// You're doing HTTP and the DNS fails. You want to
|
||||
// know that resolve failed.
|
||||
err := &ErrWrapper{Operation: ResolveOperation}
|
||||
if toOperationString(err, HTTPRoundTripOperation) != ResolveOperation {
|
||||
err := &errorsx.ErrWrapper{Operation: errorsx.ResolveOperation}
|
||||
if toOperationString(err, errorsx.HTTPRoundTripOperation) != errorsx.ResolveOperation {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
t.Run("for tls_handshake", func(t *testing.T) {
|
||||
// You're doing HTTP and the TLS handshake fails. You want
|
||||
// to know about a TLS handshake error.
|
||||
err := &ErrWrapper{Operation: TLSHandshakeOperation}
|
||||
if toOperationString(err, HTTPRoundTripOperation) != TLSHandshakeOperation {
|
||||
err := &errorsx.ErrWrapper{Operation: errorsx.TLSHandshakeOperation}
|
||||
if toOperationString(err, errorsx.HTTPRoundTripOperation) != errorsx.TLSHandshakeOperation {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
|
@ -274,16 +60,16 @@ func TestToOperationString(t *testing.T) {
|
|||
// You just noticed that TLS handshake failed and you
|
||||
// have a child error telling you that read failed. Here
|
||||
// you want to know about a TLS handshake error.
|
||||
err := &ErrWrapper{Operation: ReadOperation}
|
||||
if toOperationString(err, TLSHandshakeOperation) != TLSHandshakeOperation {
|
||||
err := &errorsx.ErrWrapper{Operation: errorsx.ReadOperation}
|
||||
if toOperationString(err, errorsx.TLSHandshakeOperation) != errorsx.TLSHandshakeOperation {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
t.Run("for quic_handshake", func(t *testing.T) {
|
||||
// You're doing HTTP and the TLS handshake fails. You want
|
||||
// to know about a TLS handshake error.
|
||||
err := &ErrWrapper{Operation: QUICHandshakeOperation}
|
||||
if toOperationString(err, HTTPRoundTripOperation) != QUICHandshakeOperation {
|
||||
err := &errorsx.ErrWrapper{Operation: errorsx.QUICHandshakeOperation}
|
||||
if toOperationString(err, errorsx.HTTPRoundTripOperation) != errorsx.QUICHandshakeOperation {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
|
|
|
@ -6,8 +6,9 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
errorsxlegacy "github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
func TestErrorWrapperQUICDialerInvalidCertificate(t *testing.T) {
|
||||
|
@ -18,7 +19,7 @@ func TestErrorWrapperQUICDialerInvalidCertificate(t *testing.T) {
|
|||
ServerName: servername,
|
||||
}
|
||||
|
||||
dlr := &errorsx.ErrorWrapperQUICDialer{Dialer: &netxlite.QUICDialerQUICGo{
|
||||
dlr := &errorsxlegacy.ErrorWrapperQUICDialer{Dialer: &netxlite.QUICDialerQUICGo{
|
||||
QUICListener: &netxlite.QUICListenerStdlib{},
|
||||
}}
|
||||
// use Google IP
|
||||
|
@ -41,7 +42,7 @@ func TestErrorWrapperQUICDialerSuccess(t *testing.T) {
|
|||
NextProtos: []string{"h3"},
|
||||
ServerName: "www.google.com",
|
||||
}
|
||||
d := &errorsx.ErrorWrapperQUICDialer{Dialer: &netxlite.QUICDialerQUICGo{
|
||||
d := &errorsxlegacy.ErrorWrapperQUICDialer{Dialer: &netxlite.QUICDialerQUICGo{
|
||||
QUICListener: &netxlite.QUICListenerStdlib{},
|
||||
}}
|
||||
sess, err := d.DialContext(ctx, "udp", "216.58.212.164:443", tlsConf, &quic.Config{})
|
||||
|
|
|
@ -3,10 +3,10 @@ package errorsx
|
|||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/quicx"
|
||||
)
|
||||
|
||||
|
@ -39,7 +39,7 @@ func (qls *ErrorWrapperQUICListener) Listen(addr *net.UDPAddr) (quicx.UDPLikeCon
|
|||
if err != nil {
|
||||
return nil, SafeErrWrapperBuilder{
|
||||
Error: err,
|
||||
Operation: QUICListenOperation,
|
||||
Operation: errorsx.QUICListenOperation,
|
||||
}.MaybeBuild()
|
||||
}
|
||||
return &errorWrapperUDPConn{pconn}, nil
|
||||
|
@ -59,7 +59,7 @@ func (c *errorWrapperUDPConn) WriteTo(p []byte, addr net.Addr) (int, error) {
|
|||
if err != nil {
|
||||
return 0, SafeErrWrapperBuilder{
|
||||
Error: err,
|
||||
Operation: WriteToOperation,
|
||||
Operation: errorsx.WriteToOperation,
|
||||
}.MaybeBuild()
|
||||
}
|
||||
return count, nil
|
||||
|
@ -71,7 +71,7 @@ func (c *errorWrapperUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
|||
if err != nil {
|
||||
return 0, nil, SafeErrWrapperBuilder{
|
||||
Error: err,
|
||||
Operation: ReadFromOperation,
|
||||
Operation: errorsx.ReadFromOperation,
|
||||
}.MaybeBuild()
|
||||
}
|
||||
return n, addr, nil
|
||||
|
@ -88,95 +88,12 @@ func (d *ErrorWrapperQUICDialer) DialContext(
|
|||
tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlySession, error) {
|
||||
sess, err := d.Dialer.DialContext(ctx, network, host, tlsCfg, cfg)
|
||||
err = SafeErrWrapperBuilder{
|
||||
Classifier: ClassifyQUICHandshakeError,
|
||||
Classifier: errorsx.ClassifyQUICHandshakeError,
|
||||
Error: err,
|
||||
Operation: QUICHandshakeOperation,
|
||||
Operation: errorsx.QUICHandshakeOperation,
|
||||
}.MaybeBuild()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
// ClassifyQUICHandshakeError maps an error occurred during the QUIC
|
||||
// handshake to an OONI failure string.
|
||||
func ClassifyQUICHandshakeError(err error) string {
|
||||
var versionNegotiation *quic.VersionNegotiationError
|
||||
var statelessReset *quic.StatelessResetError
|
||||
var handshakeTimeout *quic.HandshakeTimeoutError
|
||||
var idleTimeout *quic.IdleTimeoutError
|
||||
var transportError *quic.TransportError
|
||||
|
||||
if errors.As(err, &versionNegotiation) {
|
||||
return FailureQUICIncompatibleVersion
|
||||
}
|
||||
if errors.As(err, &statelessReset) {
|
||||
return FailureConnectionReset
|
||||
}
|
||||
if errors.As(err, &handshakeTimeout) {
|
||||
return FailureGenericTimeoutError
|
||||
}
|
||||
if errors.As(err, &idleTimeout) {
|
||||
return FailureGenericTimeoutError
|
||||
}
|
||||
if errors.As(err, &transportError) {
|
||||
if transportError.ErrorCode == quic.ConnectionRefused {
|
||||
return FailureConnectionRefused
|
||||
}
|
||||
// the TLS Alert constants are taken from RFC8446
|
||||
errCode := uint8(transportError.ErrorCode)
|
||||
if quicIsCertificateError(errCode) {
|
||||
return FailureSSLInvalidCertificate
|
||||
}
|
||||
// TLSAlertDecryptError and TLSAlertHandshakeFailure are summarized to a FailureSSLHandshake error because both
|
||||
// alerts are caused by a failed or corrupted parameter negotiation during the TLS handshake.
|
||||
if errCode == quicTLSAlertDecryptError || errCode == quicTLSAlertHandshakeFailure {
|
||||
return FailureSSLFailedHandshake
|
||||
}
|
||||
if errCode == quicTLSAlertUnknownCA {
|
||||
return FailureSSLUnknownAuthority
|
||||
}
|
||||
if errCode == quicTLSUnrecognizedName {
|
||||
return FailureSSLInvalidHostname
|
||||
}
|
||||
}
|
||||
return ClassifyGenericError(err)
|
||||
}
|
||||
|
||||
// TLS alert protocol as defined in RFC8446
|
||||
const (
|
||||
// Sender was unable to negotiate an acceptable set of security parameters given the options available.
|
||||
quicTLSAlertHandshakeFailure = 40
|
||||
|
||||
// Certificate was corrupt, contained signatures that did not verify correctly, etc.
|
||||
quicTLSAlertBadCertificate = 42
|
||||
|
||||
// Certificate was of an unsupported type.
|
||||
quicTLSAlertUnsupportedCertificate = 43
|
||||
|
||||
// Certificate was revoked by its signer.
|
||||
quicTLSAlertCertificateRevoked = 44
|
||||
|
||||
// Certificate has expired or is not currently valid.
|
||||
quicTLSAlertCertificateExpired = 45
|
||||
|
||||
// Some unspecified issue arose in processing the certificate, rendering it unacceptable.
|
||||
quicTLSAlertCertificateUnknown = 46
|
||||
|
||||
// Certificate was not accepted because the CA certificate could not be located or could not be matched with a known trust anchor.
|
||||
quicTLSAlertUnknownCA = 48
|
||||
|
||||
// Handshake (not record layer) cryptographic operation failed.
|
||||
quicTLSAlertDecryptError = 51
|
||||
|
||||
// Sent by servers when no server exists identified by the name provided by the client via the "server_name" extension.
|
||||
quicTLSUnrecognizedName = 112
|
||||
)
|
||||
|
||||
func quicIsCertificateError(alert uint8) bool {
|
||||
return (alert == quicTLSAlertBadCertificate ||
|
||||
alert == quicTLSAlertUnsupportedCertificate ||
|
||||
alert == quicTLSAlertCertificateExpired ||
|
||||
alert == quicTLSAlertCertificateRevoked ||
|
||||
alert == quicTLSAlertCertificateUnknown)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/mocks"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/quicx"
|
||||
)
|
||||
|
@ -142,14 +143,14 @@ func TestErrorWrapperQUICDialerFailure(t *testing.T) {
|
|||
if !errors.Is(err, io.EOF) {
|
||||
t.Fatal("expected another error here")
|
||||
}
|
||||
var errWrapper *ErrWrapper
|
||||
var errWrapper *errorsx.ErrWrapper
|
||||
if !errors.As(err, &errWrapper) {
|
||||
t.Fatal("cannot cast to ErrWrapper")
|
||||
}
|
||||
if errWrapper.Operation != QUICHandshakeOperation {
|
||||
if errWrapper.Operation != errorsx.QUICHandshakeOperation {
|
||||
t.Fatal("unexpected Operation")
|
||||
}
|
||||
if errWrapper.Failure != FailureEOFError {
|
||||
if errWrapper.Failure != errorsx.FailureEOFError {
|
||||
t.Fatal("unexpected failure")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@ package errorsx
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
// Resolver is a DNS resolver. The *net.Resolver used by Go implements
|
||||
|
@ -23,22 +24,13 @@ var _ Resolver = &ErrorWrapperResolver{}
|
|||
func (r *ErrorWrapperResolver) LookupHost(ctx context.Context, hostname string) ([]string, error) {
|
||||
addrs, err := r.Resolver.LookupHost(ctx, hostname)
|
||||
err = SafeErrWrapperBuilder{
|
||||
Classifier: ClassifyResolverError,
|
||||
Classifier: errorsx.ClassifyResolverError,
|
||||
Error: err,
|
||||
Operation: ResolveOperation,
|
||||
Operation: errorsx.ResolveOperation,
|
||||
}.MaybeBuild()
|
||||
return addrs, err
|
||||
}
|
||||
|
||||
// ClassifyResolverError maps an error occurred during a domain name
|
||||
// resolution to the corresponding OONI failure string.
|
||||
func ClassifyResolverError(err error) string {
|
||||
if errors.Is(err, ErrDNSBogon) {
|
||||
return FailureDNSBogonError // not in MK
|
||||
}
|
||||
return ClassifyGenericError(err)
|
||||
}
|
||||
|
||||
type resolverNetworker interface {
|
||||
Network() string
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/mocks"
|
||||
)
|
||||
|
||||
|
@ -40,14 +41,14 @@ func TestErrorWrapperResolverFailure(t *testing.T) {
|
|||
if addrs != nil {
|
||||
t.Fatal("expected nil addr here")
|
||||
}
|
||||
var errWrapper *ErrWrapper
|
||||
var errWrapper *errorsx.ErrWrapper
|
||||
if !errors.As(err, &errWrapper) {
|
||||
t.Fatal("cannot properly cast the returned error")
|
||||
}
|
||||
if errWrapper.Failure != FailureDNSNXDOMAINError {
|
||||
if errWrapper.Failure != errorsx.FailureDNSNXDOMAINError {
|
||||
t.Fatal("unexpected failure")
|
||||
}
|
||||
if errWrapper.Operation != ResolveOperation {
|
||||
if errWrapper.Operation != errorsx.ResolveOperation {
|
||||
t.Fatal("unexpected Operation")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@ package errorsx
|
|||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
// TLSHandshaker is the generic TLS handshaker
|
||||
|
@ -25,31 +25,9 @@ func (h *ErrorWrapperTLSHandshaker) Handshake(
|
|||
) (net.Conn, tls.ConnectionState, error) {
|
||||
tlsconn, state, err := h.TLSHandshaker.Handshake(ctx, conn, config)
|
||||
err = SafeErrWrapperBuilder{
|
||||
Classifier: ClassifyTLSHandshakeError,
|
||||
Classifier: errorsx.ClassifyTLSHandshakeError,
|
||||
Error: err,
|
||||
Operation: TLSHandshakeOperation,
|
||||
Operation: errorsx.TLSHandshakeOperation,
|
||||
}.MaybeBuild()
|
||||
return tlsconn, state, err
|
||||
}
|
||||
|
||||
// ClassifyTLSHandshakeError maps an error occurred during the TLS
|
||||
// handshake to an OONI failure string.
|
||||
func ClassifyTLSHandshakeError(err error) string {
|
||||
var x509HostnameError x509.HostnameError
|
||||
if errors.As(err, &x509HostnameError) {
|
||||
// Test case: https://wrong.host.badssl.com/
|
||||
return FailureSSLInvalidHostname
|
||||
}
|
||||
var x509UnknownAuthorityError x509.UnknownAuthorityError
|
||||
if errors.As(err, &x509UnknownAuthorityError) {
|
||||
// Test case: https://self-signed.badssl.com/. This error has
|
||||
// never been among the ones returned by MK.
|
||||
return FailureSSLUnknownAuthority
|
||||
}
|
||||
var x509CertificateInvalidError x509.CertificateInvalidError
|
||||
if errors.As(err, &x509CertificateInvalidError) {
|
||||
// Test case: https://expired.badssl.com/
|
||||
return FailureSSLInvalidCertificate
|
||||
}
|
||||
return ClassifyGenericError(err)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/mocks"
|
||||
)
|
||||
|
||||
|
@ -29,14 +30,14 @@ func TestErrorWrapperTLSHandshakerFailure(t *testing.T) {
|
|||
if conn != nil {
|
||||
t.Fatal("expected nil con here")
|
||||
}
|
||||
var errWrapper *ErrWrapper
|
||||
var errWrapper *errorsx.ErrWrapper
|
||||
if !errors.As(err, &errWrapper) {
|
||||
t.Fatal("cannot cast to ErrWrapper")
|
||||
}
|
||||
if errWrapper.Failure != FailureEOFError {
|
||||
if errWrapper.Failure != errorsx.FailureEOFError {
|
||||
t.Fatal("unexpected Failure")
|
||||
}
|
||||
if errWrapper.Operation != TLSHandshakeOperation {
|
||||
if errWrapper.Operation != errorsx.TLSHandshakeOperation {
|
||||
t.Fatal("unexpected Operation")
|
||||
}
|
||||
}
|
||||
|
|
197
internal/netxlite/errorsx/classify.go
Normal file
197
internal/netxlite/errorsx/classify.go
Normal file
|
@ -0,0 +1,197 @@
|
|||
package errorsx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/ooni/probe-cli/v3/internal/scrubber"
|
||||
)
|
||||
|
||||
// TODO (kelmenhorst, bassosimone):
|
||||
// Use errors.Is / errors.As more often, when possible, in this classifier.
|
||||
// These methods are more robust to library changes than strings.
|
||||
// errors.Is / errors.As can only be used when the error is exported.
|
||||
|
||||
// ClassifyGenericError is the generic classifier mapping an error
|
||||
// occurred during an operation to an OONI failure string.
|
||||
func ClassifyGenericError(err error) string {
|
||||
// The list returned here matches the values used by MK unless
|
||||
// explicitly noted otherwise with a comment.
|
||||
|
||||
var errwrapper *ErrWrapper
|
||||
if errors.As(err, &errwrapper) {
|
||||
return errwrapper.Error() // we've already wrapped it
|
||||
}
|
||||
|
||||
if failure := classifySyscallError(err); failure != "" {
|
||||
return failure
|
||||
}
|
||||
|
||||
if errors.Is(err, context.Canceled) {
|
||||
return FailureInterrupted
|
||||
}
|
||||
s := err.Error()
|
||||
if strings.HasSuffix(s, "operation was canceled") {
|
||||
return FailureInterrupted
|
||||
}
|
||||
if strings.HasSuffix(s, "EOF") {
|
||||
return FailureEOFError
|
||||
}
|
||||
if strings.HasSuffix(s, "context deadline exceeded") {
|
||||
return FailureGenericTimeoutError
|
||||
}
|
||||
if strings.HasSuffix(s, "transaction is timed out") {
|
||||
return FailureGenericTimeoutError
|
||||
}
|
||||
if strings.HasSuffix(s, "i/o timeout") {
|
||||
return FailureGenericTimeoutError
|
||||
}
|
||||
// TODO(kelmenhorst,bassosimone): this can probably be moved since it's TLS specific
|
||||
if strings.HasSuffix(s, "TLS handshake timeout") {
|
||||
return FailureGenericTimeoutError
|
||||
}
|
||||
if strings.HasSuffix(s, "no such host") {
|
||||
// This is dns_lookup_error in MK but such error is used as a
|
||||
// generic "hey, the lookup failed" error. Instead, this error
|
||||
// that we return here is significantly more specific.
|
||||
return FailureDNSNXDOMAINError
|
||||
}
|
||||
formatted := fmt.Sprintf("unknown_failure: %s", s)
|
||||
return scrubber.Scrub(formatted) // scrub IP addresses in the error
|
||||
}
|
||||
|
||||
// TLS alert protocol as defined in RFC8446
|
||||
const (
|
||||
// Sender was unable to negotiate an acceptable set of security parameters given the options available.
|
||||
quicTLSAlertHandshakeFailure = 40
|
||||
|
||||
// Certificate was corrupt, contained signatures that did not verify correctly, etc.
|
||||
quicTLSAlertBadCertificate = 42
|
||||
|
||||
// Certificate was of an unsupported type.
|
||||
quicTLSAlertUnsupportedCertificate = 43
|
||||
|
||||
// Certificate was revoked by its signer.
|
||||
quicTLSAlertCertificateRevoked = 44
|
||||
|
||||
// Certificate has expired or is not currently valid.
|
||||
quicTLSAlertCertificateExpired = 45
|
||||
|
||||
// Some unspecified issue arose in processing the certificate, rendering it unacceptable.
|
||||
quicTLSAlertCertificateUnknown = 46
|
||||
|
||||
// Certificate was not accepted because the CA certificate could not be located or could not be matched with a known trust anchor.
|
||||
quicTLSAlertUnknownCA = 48
|
||||
|
||||
// Handshake (not record layer) cryptographic operation failed.
|
||||
quicTLSAlertDecryptError = 51
|
||||
|
||||
// Sent by servers when no server exists identified by the name provided by the client via the "server_name" extension.
|
||||
quicTLSUnrecognizedName = 112
|
||||
)
|
||||
|
||||
func quicIsCertificateError(alert uint8) bool {
|
||||
return (alert == quicTLSAlertBadCertificate ||
|
||||
alert == quicTLSAlertUnsupportedCertificate ||
|
||||
alert == quicTLSAlertCertificateExpired ||
|
||||
alert == quicTLSAlertCertificateRevoked ||
|
||||
alert == quicTLSAlertCertificateUnknown)
|
||||
}
|
||||
|
||||
// ClassifyQUICHandshakeError maps an error occurred during the QUIC
|
||||
// handshake to an OONI failure string.
|
||||
func ClassifyQUICHandshakeError(err error) string {
|
||||
var errwrapper *ErrWrapper
|
||||
if errors.As(err, &errwrapper) {
|
||||
return errwrapper.Error() // we've already wrapped it
|
||||
}
|
||||
|
||||
var versionNegotiation *quic.VersionNegotiationError
|
||||
var statelessReset *quic.StatelessResetError
|
||||
var handshakeTimeout *quic.HandshakeTimeoutError
|
||||
var idleTimeout *quic.IdleTimeoutError
|
||||
var transportError *quic.TransportError
|
||||
|
||||
if errors.As(err, &versionNegotiation) {
|
||||
return FailureQUICIncompatibleVersion
|
||||
}
|
||||
if errors.As(err, &statelessReset) {
|
||||
return FailureConnectionReset
|
||||
}
|
||||
if errors.As(err, &handshakeTimeout) {
|
||||
return FailureGenericTimeoutError
|
||||
}
|
||||
if errors.As(err, &idleTimeout) {
|
||||
return FailureGenericTimeoutError
|
||||
}
|
||||
if errors.As(err, &transportError) {
|
||||
if transportError.ErrorCode == quic.ConnectionRefused {
|
||||
return FailureConnectionRefused
|
||||
}
|
||||
// the TLS Alert constants are taken from RFC8446
|
||||
errCode := uint8(transportError.ErrorCode)
|
||||
if quicIsCertificateError(errCode) {
|
||||
return FailureSSLInvalidCertificate
|
||||
}
|
||||
// TLSAlertDecryptError and TLSAlertHandshakeFailure are summarized to a FailureSSLHandshake error because both
|
||||
// alerts are caused by a failed or corrupted parameter negotiation during the TLS handshake.
|
||||
if errCode == quicTLSAlertDecryptError || errCode == quicTLSAlertHandshakeFailure {
|
||||
return FailureSSLFailedHandshake
|
||||
}
|
||||
if errCode == quicTLSAlertUnknownCA {
|
||||
return FailureSSLUnknownAuthority
|
||||
}
|
||||
if errCode == quicTLSUnrecognizedName {
|
||||
return FailureSSLInvalidHostname
|
||||
}
|
||||
}
|
||||
return ClassifyGenericError(err)
|
||||
}
|
||||
|
||||
// ErrDNSBogon indicates that we found a bogon address. This is the
|
||||
// correct value with which to initialize MeasurementRoot.ErrDNSBogon
|
||||
// to tell this library to return an error when a bogon is found.
|
||||
var ErrDNSBogon = errors.New("dns: detected bogon address")
|
||||
|
||||
// ClassifyResolverError maps an error occurred during a domain name
|
||||
// resolution to the corresponding OONI failure string.
|
||||
func ClassifyResolverError(err error) string {
|
||||
var errwrapper *ErrWrapper
|
||||
if errors.As(err, &errwrapper) {
|
||||
return errwrapper.Error() // we've already wrapped it
|
||||
}
|
||||
if errors.Is(err, ErrDNSBogon) {
|
||||
return FailureDNSBogonError // not in MK
|
||||
}
|
||||
return ClassifyGenericError(err)
|
||||
}
|
||||
|
||||
// ClassifyTLSHandshakeError maps an error occurred during the TLS
|
||||
// handshake to an OONI failure string.
|
||||
func ClassifyTLSHandshakeError(err error) string {
|
||||
var errwrapper *ErrWrapper
|
||||
if errors.As(err, &errwrapper) {
|
||||
return errwrapper.Error() // we've already wrapped it
|
||||
}
|
||||
var x509HostnameError x509.HostnameError
|
||||
if errors.As(err, &x509HostnameError) {
|
||||
// Test case: https://wrong.host.badssl.com/
|
||||
return FailureSSLInvalidHostname
|
||||
}
|
||||
var x509UnknownAuthorityError x509.UnknownAuthorityError
|
||||
if errors.As(err, &x509UnknownAuthorityError) {
|
||||
// Test case: https://self-signed.badssl.com/. This error has
|
||||
// never been among the ones returned by MK.
|
||||
return FailureSSLUnknownAuthority
|
||||
}
|
||||
var x509CertificateInvalidError x509.CertificateInvalidError
|
||||
if errors.As(err, &x509CertificateInvalidError) {
|
||||
// Test case: https://expired.badssl.com/
|
||||
return FailureSSLInvalidCertificate
|
||||
}
|
||||
return ClassifyGenericError(err)
|
||||
}
|
260
internal/netxlite/errorsx/classify_test.go
Normal file
260
internal/netxlite/errorsx/classify_test.go
Normal file
|
@ -0,0 +1,260 @@
|
|||
package errorsx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/pion/stun"
|
||||
)
|
||||
|
||||
func TestClassifyGenericError(t *testing.T) {
|
||||
t.Run("for input being already an ErrWrapper", func(t *testing.T) {
|
||||
err := &ErrWrapper{Failure: FailureEOFError}
|
||||
if ClassifyGenericError(err) != FailureEOFError {
|
||||
t.Fatal("did not classify existing ErrWrapper correctly")
|
||||
}
|
||||
})
|
||||
t.Run("for already wrapped error", func(t *testing.T) {
|
||||
err := io.EOF
|
||||
if ClassifyGenericError(err) != FailureEOFError {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
t.Run("for context.Canceled", func(t *testing.T) {
|
||||
if ClassifyGenericError(context.Canceled) != FailureInterrupted {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
t.Run("for operation was canceled error", func(t *testing.T) {
|
||||
if ClassifyGenericError(errors.New("operation was canceled")) != FailureInterrupted {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
t.Run("for EOF", func(t *testing.T) {
|
||||
if ClassifyGenericError(io.EOF) != FailureEOFError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for canceled", func(t *testing.T) {
|
||||
if ClassifyGenericError(syscall.ECANCELED) != FailureOperationCanceled {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for connection_refused", func(t *testing.T) {
|
||||
if ClassifyGenericError(syscall.ECONNREFUSED) != FailureConnectionRefused {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for connection_reset", func(t *testing.T) {
|
||||
if ClassifyGenericError(syscall.ECONNRESET) != FailureConnectionReset {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for host_unreachable", func(t *testing.T) {
|
||||
if ClassifyGenericError(syscall.EHOSTUNREACH) != FailureHostUnreachable {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for system timeout", func(t *testing.T) {
|
||||
if ClassifyGenericError(syscall.ETIMEDOUT) != FailureTimedOut {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for context deadline exceeded", func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1)
|
||||
defer cancel()
|
||||
<-ctx.Done()
|
||||
if ClassifyGenericError(ctx.Err()) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for stun's transaction is timed out", func(t *testing.T) {
|
||||
if ClassifyGenericError(stun.ErrTransactionTimeOut) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for i/o error", func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1)
|
||||
defer cancel() // fail immediately
|
||||
conn, err := (&net.Dialer{}).DialContext(ctx, "tcp", "www.google.com:80")
|
||||
if err == nil {
|
||||
t.Fatal("expected an error here")
|
||||
}
|
||||
if conn != nil {
|
||||
t.Fatal("expected nil connection here")
|
||||
}
|
||||
if ClassifyGenericError(err) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for TLS handshake timeout error", func(t *testing.T) {
|
||||
err := errors.New("net/http: TLS handshake timeout")
|
||||
if ClassifyGenericError(err) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for no such host", func(t *testing.T) {
|
||||
if ClassifyGenericError(&net.DNSError{
|
||||
Err: "no such host",
|
||||
}) != FailureDNSNXDOMAINError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for errors including IPv4 address", func(t *testing.T) {
|
||||
input := errors.New("read tcp 10.0.2.15:56948->93.184.216.34:443: use of closed network connection")
|
||||
expected := "unknown_failure: read tcp [scrubbed]->[scrubbed]: use of closed network connection"
|
||||
out := ClassifyGenericError(input)
|
||||
if out != expected {
|
||||
t.Fatal(cmp.Diff(expected, out))
|
||||
}
|
||||
})
|
||||
t.Run("for errors including IPv6 address", func(t *testing.T) {
|
||||
input := errors.New("read tcp [::1]:56948->[::1]:443: use of closed network connection")
|
||||
expected := "unknown_failure: read tcp [scrubbed]->[scrubbed]: use of closed network connection"
|
||||
out := ClassifyGenericError(input)
|
||||
if out != expected {
|
||||
t.Fatal(cmp.Diff(expected, out))
|
||||
}
|
||||
})
|
||||
t.Run("for i/o error", func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1)
|
||||
defer cancel() // fail immediately
|
||||
udpAddr := &net.UDPAddr{IP: net.ParseIP("216.58.212.164"), Port: 80, Zone: ""}
|
||||
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sess, err := quic.DialEarlyContext(ctx, udpConn, udpAddr, "google.com:80", &tls.Config{}, &quic.Config{})
|
||||
if err == nil {
|
||||
t.Fatal("expected an error here")
|
||||
}
|
||||
if sess != nil {
|
||||
t.Fatal("expected nil session here")
|
||||
}
|
||||
if ClassifyGenericError(err) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestClassifyQUICHandshakeError(t *testing.T) {
|
||||
t.Run("for input being already an ErrWrapper", func(t *testing.T) {
|
||||
err := &ErrWrapper{Failure: FailureEOFError}
|
||||
if ClassifyQUICHandshakeError(err) != FailureEOFError {
|
||||
t.Fatal("did not classify existing ErrWrapper correctly")
|
||||
}
|
||||
})
|
||||
t.Run("for connection_reset", func(t *testing.T) {
|
||||
if ClassifyQUICHandshakeError(&quic.StatelessResetError{}) != FailureConnectionReset {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for incompatible quic version", func(t *testing.T) {
|
||||
if ClassifyQUICHandshakeError(&quic.VersionNegotiationError{}) != FailureQUICIncompatibleVersion {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for quic connection refused", func(t *testing.T) {
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: quic.ConnectionRefused}) != FailureConnectionRefused {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for quic handshake timeout", func(t *testing.T) {
|
||||
if ClassifyQUICHandshakeError(&quic.HandshakeTimeoutError{}) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for QUIC idle connection timeout", func(t *testing.T) {
|
||||
if ClassifyQUICHandshakeError(&quic.IdleTimeoutError{}) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for QUIC CRYPTO Handshake", func(t *testing.T) {
|
||||
var err quic.TransportErrorCode = quicTLSAlertHandshakeFailure
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLFailedHandshake {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for QUIC CRYPTO Invalid Certificate", func(t *testing.T) {
|
||||
var err quic.TransportErrorCode = quicTLSAlertBadCertificate
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidCertificate {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for QUIC CRYPTO Unknown CA", func(t *testing.T) {
|
||||
var err quic.TransportErrorCode = quicTLSAlertUnknownCA
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLUnknownAuthority {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for QUIC CRYPTO Bad Hostname", func(t *testing.T) {
|
||||
var err quic.TransportErrorCode = quicTLSUnrecognizedName
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidHostname {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
t.Run("for another kind of error", func(t *testing.T) {
|
||||
if ClassifyQUICHandshakeError(io.EOF) != FailureEOFError {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestClassifyResolverError(t *testing.T) {
|
||||
t.Run("for input being already an ErrWrapper", func(t *testing.T) {
|
||||
err := &ErrWrapper{Failure: FailureEOFError}
|
||||
if ClassifyResolverError(err) != FailureEOFError {
|
||||
t.Fatal("did not classify existing ErrWrapper correctly")
|
||||
}
|
||||
})
|
||||
t.Run("for ErrDNSBogon", func(t *testing.T) {
|
||||
if ClassifyResolverError(ErrDNSBogon) != FailureDNSBogonError {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
t.Run("for another kind of error", func(t *testing.T) {
|
||||
if ClassifyResolverError(io.EOF) != FailureEOFError {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestClassifyTLSHandshakeError(t *testing.T) {
|
||||
t.Run("for input being already an ErrWrapper", func(t *testing.T) {
|
||||
err := &ErrWrapper{Failure: FailureEOFError}
|
||||
if ClassifyTLSHandshakeError(err) != FailureEOFError {
|
||||
t.Fatal("did not classify existing ErrWrapper correctly")
|
||||
}
|
||||
})
|
||||
t.Run("for x509.HostnameError", func(t *testing.T) {
|
||||
var err x509.HostnameError
|
||||
if ClassifyTLSHandshakeError(err) != FailureSSLInvalidHostname {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
t.Run("for x509.UnknownAuthorityError", func(t *testing.T) {
|
||||
var err x509.UnknownAuthorityError
|
||||
if ClassifyTLSHandshakeError(err) != FailureSSLUnknownAuthority {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
t.Run("for x509.CertificateInvalidError", func(t *testing.T) {
|
||||
var err x509.CertificateInvalidError
|
||||
if ClassifyTLSHandshakeError(err) != FailureSSLInvalidCertificate {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
t.Run("for another kind of error", func(t *testing.T) {
|
||||
if ClassifyTLSHandshakeError(io.EOF) != FailureEOFError {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
}
|
2
internal/netxlite/errorsx/doc.go
Normal file
2
internal/netxlite/errorsx/doc.go
Normal file
|
@ -0,0 +1,2 @@
|
|||
// Package errorsx contains code to classify errors.
|
||||
package errorsx
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// Generated: 2021-09-07 15:15:03.350386 +0200 CEST m=+0.135456751
|
||||
// Generated: 2021-09-07 16:43:08.462721 +0200 CEST m=+0.105415376
|
||||
|
||||
package errorsx
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// Generated: 2021-09-07 15:15:03.398087 +0200 CEST m=+0.183158793
|
||||
// Generated: 2021-09-07 16:43:08.510432 +0200 CEST m=+0.153127376
|
||||
|
||||
package errorsx
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// Generated: 2021-09-07 15:15:03.215384 +0200 CEST m=+0.000452543
|
||||
// Generated: 2021-09-07 16:43:08.35751 +0200 CEST m=+0.000202959
|
||||
|
||||
package errorsx
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// Generated: 2021-09-07 15:15:03.324258 +0200 CEST m=+0.109328501
|
||||
// Generated: 2021-09-07 16:43:08.436584 +0200 CEST m=+0.079277834
|
||||
|
||||
package errorsx
|
||||
|
51
internal/netxlite/errorsx/errwrapper.go
Normal file
51
internal/netxlite/errorsx/errwrapper.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package errorsx
|
||||
|
||||
// ErrWrapper is our error wrapper for Go errors. The key objective of
|
||||
// this structure is to properly set Failure, which is also returned by
|
||||
// the Error() method, so be one of the OONI defined strings.
|
||||
type ErrWrapper struct {
|
||||
// Failure is the OONI failure string. The failure strings are
|
||||
// loosely backward compatible with Measurement Kit.
|
||||
//
|
||||
// This is either one of the FailureXXX strings or any other
|
||||
// string like `unknown_failure ...`. The latter represents an
|
||||
// error that we have not yet mapped to a failure.
|
||||
Failure string
|
||||
|
||||
// Operation is the operation that failed. If possible, it
|
||||
// SHOULD be a _major_ operation. Major operations are:
|
||||
//
|
||||
// - ResolveOperation: resolving a domain name failed
|
||||
// - ConnectOperation: connecting to an IP failed
|
||||
// - TLSHandshakeOperation: TLS handshaking failed
|
||||
// - HTTPRoundTripOperation: other errors during round trip
|
||||
//
|
||||
// Because a network connection doesn't necessarily know
|
||||
// what is the current major operation we also have the
|
||||
// following _minor_ operations:
|
||||
//
|
||||
// - CloseOperation: CLOSE failed
|
||||
// - ReadOperation: READ failed
|
||||
// - WriteOperation: WRITE failed
|
||||
//
|
||||
// If an ErrWrapper referring to a major operation is wrapping
|
||||
// another ErrWrapper and such ErrWrapper already refers to
|
||||
// a major operation, then the new ErrWrapper should use the
|
||||
// child ErrWrapper major operation. Otherwise, it should use
|
||||
// its own major operation. This way, the topmost wrapper is
|
||||
// supposed to refer to the major operation that failed.
|
||||
Operation string
|
||||
|
||||
// WrappedErr is the error that we're wrapping.
|
||||
WrappedErr error
|
||||
}
|
||||
|
||||
// Error returns a description of the error that occurred.
|
||||
func (e *ErrWrapper) Error() string {
|
||||
return e.Failure
|
||||
}
|
||||
|
||||
// Unwrap allows to access the underlying error
|
||||
func (e *ErrWrapper) Unwrap() error {
|
||||
return e.WrappedErr
|
||||
}
|
24
internal/netxlite/errorsx/errwrapper_test.go
Normal file
24
internal/netxlite/errorsx/errwrapper_test.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package errorsx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestErrWrapperError(t *testing.T) {
|
||||
err := &ErrWrapper{Failure: FailureDNSNXDOMAINError}
|
||||
if err.Error() != FailureDNSNXDOMAINError {
|
||||
t.Fatal("invalid return value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrWrapperUnwrap(t *testing.T) {
|
||||
err := &ErrWrapper{
|
||||
Failure: FailureEOFError,
|
||||
WrappedErr: io.EOF,
|
||||
}
|
||||
if !errors.Is(err, io.EOF) {
|
||||
t.Fatal("cannot unwrap error")
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import (
|
|||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
// This file contains weird stuff that we carried over from
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/ooni/probe-cli/v3/internal/errorsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
||||
)
|
||||
|
||||
func TestQuirkReduceErrors(t *testing.T) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user