1c057d322d
This diff implements the first two cleanups defined at https://github.com/ooni/probe/issues/1956: > - [ ] observe that `netxlite` and `netx` differ in error wrapping only in the way in which we set `ErrWrapper.Operation`. Observe that the code using `netxlite` does not care about such a field. Therefore, we can modify `netxlite` to set such a field using the code of `netx` and we can remove `netx` specific code for errors (which currently lives inside of the `./internal/engine/legacy/errorsx` package > > - [ ] after we've done the previous cleanup, we can make all the classifiers code private, since there's no code outside `netxlite` that needs them A subsequent diff will address the remaining cleanup. While there, notice that there are failing, unrelated obfs4 tests, so disable them in short mode. (I am confident these tests are unrelated because they fail for me when running test locally from the `master` branch.)
150 lines
4.6 KiB
Go
150 lines
4.6 KiB
Go
package netxlite
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
)
|
|
|
|
// 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, to be one of the OONI failure strings.
|
|
//
|
|
// OONI failure strings are defined in the github.com/ooni/spec repo
|
|
// at https://github.com/ooni/spec/blob/master/data-formats/df-007-errors.md.
|
|
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, the Operation string SHOULD be a _major_
|
|
// operation. Major operations are:
|
|
//
|
|
// - ResolveOperation: resolving a domain name failed
|
|
// - ConnectOperation: connecting to an IP failed
|
|
// - TLSHandshakeOperation: TLS handshaking failed
|
|
// - QUICHandshakeOperation: QUIC 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 the OONI failure string for this error.
|
|
func (e *ErrWrapper) Error() string {
|
|
return e.Failure
|
|
}
|
|
|
|
// Unwrap allows to access the underlying error.
|
|
func (e *ErrWrapper) Unwrap() error {
|
|
return e.WrappedErr
|
|
}
|
|
|
|
// MarshalJSON converts an ErrWrapper to a JSON value.
|
|
func (e *ErrWrapper) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(e.Failure)
|
|
}
|
|
|
|
// Classifier is the type of the function that maps a Go error
|
|
// to a OONI failure string defined at
|
|
// https://github.com/ooni/spec/blob/master/data-formats/df-007-errors.md.
|
|
type Classifier func(err error) string
|
|
|
|
// NewErrWrapper creates a new ErrWrapper using the given
|
|
// classifier, operation name, and underlying error.
|
|
//
|
|
// This function panics if classifier is nil, or operation
|
|
// is the empty string or error is nil.
|
|
//
|
|
// If the err argument has already been classified, the returned
|
|
// error wrapper will use the same classification string and
|
|
// will determine whether to keep the major operation as documented
|
|
// in the ErrWrapper.Operation documentation.
|
|
func NewErrWrapper(c Classifier, op string, err error) *ErrWrapper {
|
|
var wrapper *ErrWrapper
|
|
if errors.As(err, &wrapper) {
|
|
return &ErrWrapper{
|
|
Failure: wrapper.Failure,
|
|
Operation: classifyOperation(wrapper, op),
|
|
WrappedErr: err,
|
|
}
|
|
}
|
|
if c == nil {
|
|
panic("nil classifier")
|
|
}
|
|
if op == "" {
|
|
panic("empty op")
|
|
}
|
|
if err == nil {
|
|
panic("nil err")
|
|
}
|
|
return &ErrWrapper{
|
|
Failure: c(err),
|
|
Operation: op,
|
|
WrappedErr: err,
|
|
}
|
|
}
|
|
|
|
// NewTopLevelGenericErrWrapper wraps an error occurring at top
|
|
// level using ClassifyGenericError as classifier.
|
|
//
|
|
// If the err argument has already been classified, the returned
|
|
// error wrapper will use the same classification string and
|
|
// failed operation of the original error.
|
|
func NewTopLevelGenericErrWrapper(err error) *ErrWrapper {
|
|
return NewErrWrapper(classifyGenericError, TopLevelOperation, err)
|
|
}
|
|
|
|
func classifyOperation(ew *ErrWrapper, operation string) string {
|
|
// Basically, as explained in ErrWrapper docs, let's
|
|
// keep the child major operation, if any.
|
|
//
|
|
// QUIRK: this code is legacy code and we should not change
|
|
// it unless we also change the experiments that depend on it
|
|
// for determining the blocking reason based on the failed
|
|
// operation value (e.g., telegram, web connectivity).
|
|
if ew.Operation == ConnectOperation {
|
|
return ew.Operation
|
|
}
|
|
if ew.Operation == HTTPRoundTripOperation {
|
|
return ew.Operation
|
|
}
|
|
if ew.Operation == ResolveOperation {
|
|
return ew.Operation
|
|
}
|
|
if ew.Operation == TLSHandshakeOperation {
|
|
return ew.Operation
|
|
}
|
|
if ew.Operation == QUICHandshakeOperation {
|
|
return ew.Operation
|
|
}
|
|
if ew.Operation == "quic_handshake_start" {
|
|
return QUICHandshakeOperation
|
|
}
|
|
if ew.Operation == "quic_handshake_done" {
|
|
return QUICHandshakeOperation
|
|
}
|
|
return operation
|
|
}
|