cleanup: merge legacy errorsx in netxlite and hide classifiers (#655)
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.)
This commit is contained in:
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/ooni/probe-cli/v3/internal/scrubber"
|
||||
)
|
||||
|
||||
// ClassifyGenericError is maps an error occurred during an operation
|
||||
// classifyGenericError is maps an error occurred during an operation
|
||||
// to an OONI failure string. This specific classifier is the most
|
||||
// generic one. You usually use it when mapping I/O errors. You should
|
||||
// check whether there is a specific classifier for more specific
|
||||
@@ -34,7 +34,7 @@ import (
|
||||
// If everything else fails, this classifier returns a string
|
||||
// like "unknown_failure: XXX" where XXX has been scrubbed
|
||||
// so to remove any network endpoints from the original error string.
|
||||
func ClassifyGenericError(err error) string {
|
||||
func classifyGenericError(err error) string {
|
||||
// The list returned here matches the values used by MK unless
|
||||
// explicitly noted otherwise with a comment.
|
||||
|
||||
@@ -136,7 +136,7 @@ const (
|
||||
quicTLSUnrecognizedName = 112
|
||||
)
|
||||
|
||||
// ClassifyQUICHandshakeError maps errors during a QUIC
|
||||
// classifyQUICHandshakeError maps errors during a QUIC
|
||||
// handshake to OONI failure strings.
|
||||
//
|
||||
// If the input error is an *ErrWrapper we don't perform
|
||||
@@ -144,7 +144,7 @@ const (
|
||||
//
|
||||
// If this classifier fails, it calls ClassifyGenericError
|
||||
// and returns to the caller its return value.
|
||||
func ClassifyQUICHandshakeError(err error) string {
|
||||
func classifyQUICHandshakeError(err error) string {
|
||||
|
||||
// QUIRK: we cannot remove this check as long as this function
|
||||
// is exported and used independently from NewErrWrapper.
|
||||
@@ -205,7 +205,7 @@ func ClassifyQUICHandshakeError(err error) string {
|
||||
}
|
||||
}
|
||||
}
|
||||
return ClassifyGenericError(err)
|
||||
return classifyGenericError(err)
|
||||
}
|
||||
|
||||
// quicIsCertificateError tells us whether a specific TLS alert error
|
||||
@@ -253,7 +253,7 @@ var (
|
||||
ErrOODNSNoAnswer = fmt.Errorf("ooniresolver: %s", DNSNoAnswerSuffix)
|
||||
)
|
||||
|
||||
// ClassifyResolverError maps DNS resolution errors to
|
||||
// classifyResolverError maps DNS resolution errors to
|
||||
// OONI failure strings.
|
||||
//
|
||||
// If the input error is an *ErrWrapper we don't perform
|
||||
@@ -261,7 +261,7 @@ var (
|
||||
//
|
||||
// If this classifier fails, it calls ClassifyGenericError and
|
||||
// returns to the caller its return value.
|
||||
func ClassifyResolverError(err error) string {
|
||||
func classifyResolverError(err error) string {
|
||||
|
||||
// QUIRK: we cannot remove this check as long as this function
|
||||
// is exported and used independently from NewErrWrapper.
|
||||
@@ -278,10 +278,10 @@ func ClassifyResolverError(err error) string {
|
||||
if errors.Is(err, ErrOODNSRefused) {
|
||||
return FailureDNSRefusedError // not in MK
|
||||
}
|
||||
return ClassifyGenericError(err)
|
||||
return classifyGenericError(err)
|
||||
}
|
||||
|
||||
// ClassifyTLSHandshakeError maps an error occurred during the TLS
|
||||
// classifyTLSHandshakeError maps an error occurred during the TLS
|
||||
// handshake to an OONI failure string.
|
||||
//
|
||||
// If the input error is an *ErrWrapper we don't perform
|
||||
@@ -289,7 +289,7 @@ func ClassifyResolverError(err error) string {
|
||||
//
|
||||
// If this classifier fails, it calls ClassifyGenericError and
|
||||
// returns to the caller its return value.
|
||||
func ClassifyTLSHandshakeError(err error) string {
|
||||
func classifyTLSHandshakeError(err error) string {
|
||||
|
||||
// QUIRK: we cannot remove this check as long as this function
|
||||
// is exported and used independently from NewErrWrapper.
|
||||
@@ -314,5 +314,5 @@ func ClassifyTLSHandshakeError(err error) string {
|
||||
// Test case: https://expired.badssl.com/
|
||||
return FailureSSLInvalidCertificate
|
||||
}
|
||||
return ClassifyGenericError(err)
|
||||
return classifyGenericError(err)
|
||||
}
|
||||
|
||||
@@ -18,13 +18,13 @@ 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 {
|
||||
if classifyGenericError(err) != FailureEOFError {
|
||||
t.Fatal("did not classify existing ErrWrapper correctly")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for a system call error", func(t *testing.T) {
|
||||
if ClassifyGenericError(EWOULDBLOCK) != FailureOperationWouldBlock {
|
||||
if classifyGenericError(EWOULDBLOCK) != FailureOperationWouldBlock {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
@@ -35,63 +35,63 @@ func TestClassifyGenericError(t *testing.T) {
|
||||
// is just an implementation detail.
|
||||
|
||||
t.Run("for operation was canceled", func(t *testing.T) {
|
||||
if ClassifyGenericError(errors.New("operation was canceled")) != FailureInterrupted {
|
||||
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 {
|
||||
if classifyGenericError(io.EOF) != FailureEOFError {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for context deadline exceeded", func(t *testing.T) {
|
||||
if ClassifyGenericError(context.DeadlineExceeded) != FailureGenericTimeoutError {
|
||||
if classifyGenericError(context.DeadlineExceeded) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for stun's transaction is timed out", func(t *testing.T) {
|
||||
if ClassifyGenericError(stun.ErrTransactionTimeOut) != FailureGenericTimeoutError {
|
||||
if classifyGenericError(stun.ErrTransactionTimeOut) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for i/o timeout", func(t *testing.T) {
|
||||
if ClassifyGenericError(errors.New("i/o timeout")) != FailureGenericTimeoutError {
|
||||
if classifyGenericError(errors.New("i/o timeout")) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for TLS handshake timeout", func(t *testing.T) {
|
||||
err := errors.New("net/http: TLS handshake timeout")
|
||||
if ClassifyGenericError(err) != FailureGenericTimeoutError {
|
||||
if classifyGenericError(err) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for no such host", func(t *testing.T) {
|
||||
if ClassifyGenericError(errors.New("no such host")) != FailureDNSNXDOMAINError {
|
||||
if classifyGenericError(errors.New("no such host")) != FailureDNSNXDOMAINError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for dns server misbehaving", func(t *testing.T) {
|
||||
if ClassifyGenericError(errors.New("dns server misbehaving")) != FailureDNSServerMisbehaving {
|
||||
if classifyGenericError(errors.New("dns server misbehaving")) != FailureDNSServerMisbehaving {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for no answer from DNS server", func(t *testing.T) {
|
||||
if ClassifyGenericError(errors.New("no answer from DNS server")) != FailureDNSNoAnswer {
|
||||
if classifyGenericError(errors.New("no answer from DNS server")) != FailureDNSNoAnswer {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for use of closed network connection", func(t *testing.T) {
|
||||
err := errors.New("read tcp 10.0.2.15:56948->93.184.216.34:443: use of closed network connection")
|
||||
if ClassifyGenericError(err) != FailureConnectionAlreadyClosed {
|
||||
if classifyGenericError(err) != FailureConnectionAlreadyClosed {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
@@ -99,7 +99,7 @@ func TestClassifyGenericError(t *testing.T) {
|
||||
// Now we're back in ClassifyGenericError
|
||||
|
||||
t.Run("for context.Canceled", func(t *testing.T) {
|
||||
if ClassifyGenericError(context.Canceled) != FailureInterrupted {
|
||||
if classifyGenericError(context.Canceled) != FailureInterrupted {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
@@ -108,7 +108,7 @@ func TestClassifyGenericError(t *testing.T) {
|
||||
t.Run("with an IPv4 address", func(t *testing.T) {
|
||||
input := errors.New("read tcp 10.0.2.15:56948->93.184.216.34:443: some error")
|
||||
expected := "unknown_failure: read tcp [scrubbed]->[scrubbed]: some error"
|
||||
out := ClassifyGenericError(input)
|
||||
out := classifyGenericError(input)
|
||||
if out != expected {
|
||||
t.Fatal(cmp.Diff(expected, out))
|
||||
}
|
||||
@@ -117,7 +117,7 @@ func TestClassifyGenericError(t *testing.T) {
|
||||
t.Run("with an IPv6 address", func(t *testing.T) {
|
||||
input := errors.New("read tcp [::1]:56948->[::1]:443: some error")
|
||||
expected := "unknown_failure: read tcp [scrubbed]->[scrubbed]: some error"
|
||||
out := ClassifyGenericError(input)
|
||||
out := classifyGenericError(input)
|
||||
if out != expected {
|
||||
t.Fatal(cmp.Diff(expected, out))
|
||||
}
|
||||
@@ -131,100 +131,100 @@ 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 {
|
||||
if classifyQUICHandshakeError(err) != FailureEOFError {
|
||||
t.Fatal("did not classify existing ErrWrapper correctly")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for incompatible quic version", func(t *testing.T) {
|
||||
if ClassifyQUICHandshakeError(&quic.VersionNegotiationError{}) != FailureQUICIncompatibleVersion {
|
||||
if classifyQUICHandshakeError(&quic.VersionNegotiationError{}) != FailureQUICIncompatibleVersion {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for stateless reset", func(t *testing.T) {
|
||||
if ClassifyQUICHandshakeError(&quic.StatelessResetError{}) != FailureConnectionReset {
|
||||
if classifyQUICHandshakeError(&quic.StatelessResetError{}) != FailureConnectionReset {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for handshake timeout", func(t *testing.T) {
|
||||
if ClassifyQUICHandshakeError(&quic.HandshakeTimeoutError{}) != FailureGenericTimeoutError {
|
||||
if classifyQUICHandshakeError(&quic.HandshakeTimeoutError{}) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for idle timeout", func(t *testing.T) {
|
||||
if ClassifyQUICHandshakeError(&quic.IdleTimeoutError{}) != FailureGenericTimeoutError {
|
||||
if classifyQUICHandshakeError(&quic.IdleTimeoutError{}) != FailureGenericTimeoutError {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for connection refused", func(t *testing.T) {
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: quic.ConnectionRefused}) != FailureConnectionRefused {
|
||||
if classifyQUICHandshakeError(&quic.TransportError{ErrorCode: quic.ConnectionRefused}) != FailureConnectionRefused {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for bad certificate", func(t *testing.T) {
|
||||
var err quic.TransportErrorCode = quicTLSAlertBadCertificate
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidCertificate {
|
||||
if classifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidCertificate {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for unsupported certificate", func(t *testing.T) {
|
||||
var err quic.TransportErrorCode = quicTLSAlertUnsupportedCertificate
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidCertificate {
|
||||
if classifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidCertificate {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for certificate expired", func(t *testing.T) {
|
||||
var err quic.TransportErrorCode = quicTLSAlertCertificateExpired
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidCertificate {
|
||||
if classifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidCertificate {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for certificate revoked", func(t *testing.T) {
|
||||
var err quic.TransportErrorCode = quicTLSAlertCertificateRevoked
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidCertificate {
|
||||
if classifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidCertificate {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for certificate unknown", func(t *testing.T) {
|
||||
var err quic.TransportErrorCode = quicTLSAlertCertificateUnknown
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidCertificate {
|
||||
if classifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidCertificate {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for decrypt error", func(t *testing.T) {
|
||||
var err quic.TransportErrorCode = quicTLSAlertDecryptError
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLFailedHandshake {
|
||||
if classifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLFailedHandshake {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for handshake failure", func(t *testing.T) {
|
||||
var err quic.TransportErrorCode = quicTLSAlertHandshakeFailure
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLFailedHandshake {
|
||||
if classifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLFailedHandshake {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for unknown CA", func(t *testing.T) {
|
||||
var err quic.TransportErrorCode = quicTLSAlertUnknownCA
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLUnknownAuthority {
|
||||
if classifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLUnknownAuthority {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for unrecognized hostname", func(t *testing.T) {
|
||||
var err quic.TransportErrorCode = quicTLSUnrecognizedName
|
||||
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidHostname {
|
||||
if classifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidHostname {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
@@ -234,13 +234,13 @@ func TestClassifyQUICHandshakeError(t *testing.T) {
|
||||
ErrorCode: quic.InternalError,
|
||||
ErrorMessage: FailureHostUnreachable,
|
||||
}
|
||||
if ClassifyQUICHandshakeError(err) != FailureHostUnreachable {
|
||||
if classifyQUICHandshakeError(err) != FailureHostUnreachable {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for another kind of error", func(t *testing.T) {
|
||||
if ClassifyQUICHandshakeError(io.EOF) != FailureEOFError {
|
||||
if classifyQUICHandshakeError(io.EOF) != FailureEOFError {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
@@ -252,25 +252,25 @@ 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 {
|
||||
if classifyResolverError(err) != FailureEOFError {
|
||||
t.Fatal("did not classify existing ErrWrapper correctly")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for ErrDNSBogon", func(t *testing.T) {
|
||||
if ClassifyResolverError(ErrDNSBogon) != FailureDNSBogonError {
|
||||
if classifyResolverError(ErrDNSBogon) != FailureDNSBogonError {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for refused", func(t *testing.T) {
|
||||
if ClassifyResolverError(ErrOODNSRefused) != FailureDNSRefusedError {
|
||||
if classifyResolverError(ErrOODNSRefused) != FailureDNSRefusedError {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for another kind of error", func(t *testing.T) {
|
||||
if ClassifyResolverError(io.EOF) != FailureEOFError {
|
||||
if classifyResolverError(io.EOF) != FailureEOFError {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
@@ -282,34 +282,34 @@ 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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
if classifyTLSHandshakeError(err) != FailureSSLInvalidCertificate {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for another kind of error", func(t *testing.T) {
|
||||
if ClassifyTLSHandshakeError(io.EOF) != FailureEOFError {
|
||||
if classifyTLSHandshakeError(io.EOF) != FailureEOFError {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -226,7 +226,7 @@ var _ model.Dialer = &dialerErrWrapper{}
|
||||
func (d *dialerErrWrapper) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
conn, err := d.Dialer.DialContext(ctx, network, address)
|
||||
if err != nil {
|
||||
return nil, NewErrWrapper(ClassifyGenericError, ConnectOperation, err)
|
||||
return nil, NewErrWrapper(classifyGenericError, ConnectOperation, err)
|
||||
}
|
||||
return &dialerErrWrapperConn{Conn: conn}, nil
|
||||
}
|
||||
@@ -241,7 +241,7 @@ var _ net.Conn = &dialerErrWrapperConn{}
|
||||
func (c *dialerErrWrapperConn) Read(b []byte) (int, error) {
|
||||
count, err := c.Conn.Read(b)
|
||||
if err != nil {
|
||||
return 0, NewErrWrapper(ClassifyGenericError, ReadOperation, err)
|
||||
return 0, NewErrWrapper(classifyGenericError, ReadOperation, err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
@@ -249,7 +249,7 @@ func (c *dialerErrWrapperConn) Read(b []byte) (int, error) {
|
||||
func (c *dialerErrWrapperConn) Write(b []byte) (int, error) {
|
||||
count, err := c.Conn.Write(b)
|
||||
if err != nil {
|
||||
return 0, NewErrWrapper(ClassifyGenericError, WriteOperation, err)
|
||||
return 0, NewErrWrapper(classifyGenericError, WriteOperation, err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
@@ -257,7 +257,7 @@ func (c *dialerErrWrapperConn) Write(b []byte) (int, error) {
|
||||
func (c *dialerErrWrapperConn) Close() error {
|
||||
err := c.Conn.Close()
|
||||
if err != nil {
|
||||
return NewErrWrapper(ClassifyGenericError, CloseOperation, err)
|
||||
return NewErrWrapper(classifyGenericError, CloseOperation, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -254,7 +254,7 @@ func TestDialerResolver(t *testing.T) {
|
||||
errorsList := []error{
|
||||
errors.New("a mocked error"),
|
||||
NewErrWrapper(
|
||||
ClassifyGenericError,
|
||||
classifyGenericError,
|
||||
CloseOperation,
|
||||
io.EOF,
|
||||
),
|
||||
@@ -296,7 +296,7 @@ func TestDialerResolver(t *testing.T) {
|
||||
errorsList := []error{
|
||||
errors.New("a mocked error"),
|
||||
NewErrWrapper(
|
||||
ClassifyGenericError,
|
||||
classifyGenericError,
|
||||
CloseOperation,
|
||||
errors.New("antani"),
|
||||
),
|
||||
|
||||
@@ -22,18 +22,13 @@ type ErrWrapper struct {
|
||||
|
||||
// Operation is the operation that failed.
|
||||
//
|
||||
// New code will always nest ErrWrapper and you need to
|
||||
// walk the chain to find what happened.
|
||||
//
|
||||
// The following comment describes the DEPRECATED
|
||||
// legacy behavior implements by internal/engine/legacy/errorsx:
|
||||
//
|
||||
// If possible, the Operation string
|
||||
// SHOULD be a _major_ operation. Major operations are:
|
||||
// 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
|
||||
@@ -84,13 +79,14 @@ type Classifier func(err error) string
|
||||
//
|
||||
// If the err argument has already been classified, the returned
|
||||
// error wrapper will use the same classification string and
|
||||
// failed operation of the original wrapped error.
|
||||
// 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: wrapper.Operation,
|
||||
Operation: classifyOperation(wrapper, op),
|
||||
WrappedErr: err,
|
||||
}
|
||||
}
|
||||
@@ -117,5 +113,37 @@ func NewErrWrapper(c Classifier, op string, err error) *ErrWrapper {
|
||||
// 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)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ func TestNewErrWrapper(t *testing.T) {
|
||||
recovered.Add(1)
|
||||
}
|
||||
}()
|
||||
NewErrWrapper(ClassifyGenericError, "", io.EOF)
|
||||
NewErrWrapper(classifyGenericError, "", io.EOF)
|
||||
}()
|
||||
if recovered.Load() != 1 {
|
||||
t.Fatal("did not panic")
|
||||
@@ -83,7 +83,7 @@ func TestNewErrWrapper(t *testing.T) {
|
||||
recovered.Add(1)
|
||||
}
|
||||
}()
|
||||
NewErrWrapper(ClassifyGenericError, CloseOperation, nil)
|
||||
NewErrWrapper(classifyGenericError, CloseOperation, nil)
|
||||
}()
|
||||
if recovered.Load() != 1 {
|
||||
t.Fatal("did not panic")
|
||||
@@ -91,7 +91,7 @@ func TestNewErrWrapper(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("otherwise, works as intended", func(t *testing.T) {
|
||||
ew := NewErrWrapper(ClassifyGenericError, CloseOperation, io.EOF)
|
||||
ew := NewErrWrapper(classifyGenericError, CloseOperation, io.EOF)
|
||||
if ew.Failure != FailureEOFError {
|
||||
t.Fatal("unexpected failure")
|
||||
}
|
||||
@@ -107,11 +107,11 @@ func TestNewErrWrapper(t *testing.T) {
|
||||
ew := NewErrWrapper(classifySyscallError, ReadOperation, ECONNRESET)
|
||||
var err1 error = ew
|
||||
err2 := fmt.Errorf("cannot read: %w", err1)
|
||||
ew2 := NewErrWrapper(ClassifyGenericError, TopLevelOperation, err2)
|
||||
ew2 := NewErrWrapper(classifyGenericError, HTTPRoundTripOperation, err2)
|
||||
if ew2.Failure != ew.Failure {
|
||||
t.Fatal("not the same failure")
|
||||
}
|
||||
if ew2.Operation != ew.Operation {
|
||||
if ew2.Operation != HTTPRoundTripOperation {
|
||||
t.Fatal("not the same operation")
|
||||
}
|
||||
if ew2.WrappedErr != err2 {
|
||||
@@ -135,3 +135,78 @@ func TestNewTopLevelGenericErrWrapper(t *testing.T) {
|
||||
t.Fatal("invalid WrappedErr")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifyOperation(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 classifyOperation(err, HTTPRoundTripOperation) != 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 classifyOperation(err, ResolveOperation) != 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 classifyOperation(err, HTTPRoundTripOperation) != 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 classifyOperation(err, HTTPRoundTripOperation) != TLSHandshakeOperation {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for minor operation", func(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 classifyOperation(err, TLSHandshakeOperation) != TLSHandshakeOperation {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for quic_handshake", func(t *testing.T) {
|
||||
// You're doing HTTP and the QUIC handshake fails. You want
|
||||
// to know about a QUIC handshake error.
|
||||
err := &ErrWrapper{Operation: QUICHandshakeOperation}
|
||||
if classifyOperation(err, HTTPRoundTripOperation) != QUICHandshakeOperation {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for quic_handshake_start", func(t *testing.T) {
|
||||
// You're doing HTTP and the QUIC handshake fails. You want
|
||||
// to know about a QUIC handshake error.
|
||||
err := &ErrWrapper{Operation: "quic_handshake_start"}
|
||||
if classifyOperation(err, HTTPRoundTripOperation) != QUICHandshakeOperation {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("for quic_handshake_done", func(t *testing.T) {
|
||||
// You're doing HTTP and the QUIC handshake fails. You want
|
||||
// to know about a QUIC handshake error.
|
||||
err := &ErrWrapper{Operation: "quic_handshake_done"}
|
||||
if classifyOperation(err, HTTPRoundTripOperation) != QUICHandshakeOperation {
|
||||
t.Fatal("unexpected result")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ type (
|
||||
DialerResolver = dialerResolver
|
||||
DialerLogger = dialerLogger
|
||||
HTTPTransportLogger = httpTransportLogger
|
||||
ErrorWrapperDialer = dialerErrWrapper
|
||||
ErrorWrapperQUICListener = quicListenerErrWrapper
|
||||
ErrorWrapperQUICDialer = quicDialerErrWrapper
|
||||
ErrorWrapperResolver = resolverErrWrapper
|
||||
ErrorWrapperTLSHandshaker = tlsHandshakerErrWrapper
|
||||
QUICListenerStdlib = quicListenerStdlib
|
||||
QUICDialerQUICGo = quicDialerQUICGo
|
||||
QUICDialerResolver = quicDialerResolver
|
||||
|
||||
@@ -333,7 +333,7 @@ var _ model.QUICListener = &quicListenerErrWrapper{}
|
||||
func (qls *quicListenerErrWrapper) Listen(addr *net.UDPAddr) (model.UDPLikeConn, error) {
|
||||
pconn, err := qls.QUICListener.Listen(addr)
|
||||
if err != nil {
|
||||
return nil, NewErrWrapper(ClassifyGenericError, QUICListenOperation, err)
|
||||
return nil, NewErrWrapper(classifyGenericError, QUICListenOperation, err)
|
||||
}
|
||||
return &quicErrWrapperUDPLikeConn{pconn}, nil
|
||||
}
|
||||
@@ -350,7 +350,7 @@ var _ model.UDPLikeConn = &quicErrWrapperUDPLikeConn{}
|
||||
func (c *quicErrWrapperUDPLikeConn) WriteTo(p []byte, addr net.Addr) (int, error) {
|
||||
count, err := c.UDPLikeConn.WriteTo(p, addr)
|
||||
if err != nil {
|
||||
return 0, NewErrWrapper(ClassifyGenericError, WriteToOperation, err)
|
||||
return 0, NewErrWrapper(classifyGenericError, WriteToOperation, err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
@@ -359,7 +359,7 @@ func (c *quicErrWrapperUDPLikeConn) WriteTo(p []byte, addr net.Addr) (int, error
|
||||
func (c *quicErrWrapperUDPLikeConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
n, addr, err := c.UDPLikeConn.ReadFrom(b)
|
||||
if err != nil {
|
||||
return 0, nil, NewErrWrapper(ClassifyGenericError, ReadFromOperation, err)
|
||||
return 0, nil, NewErrWrapper(classifyGenericError, ReadFromOperation, err)
|
||||
}
|
||||
return n, addr, nil
|
||||
}
|
||||
@@ -368,7 +368,7 @@ func (c *quicErrWrapperUDPLikeConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
func (c *quicErrWrapperUDPLikeConn) Close() error {
|
||||
err := c.UDPLikeConn.Close()
|
||||
if err != nil {
|
||||
return NewErrWrapper(ClassifyGenericError, ReadFromOperation, err)
|
||||
return NewErrWrapper(classifyGenericError, ReadFromOperation, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -385,7 +385,7 @@ func (d *quicDialerErrWrapper) DialContext(
|
||||
sess, err := d.QUICDialer.DialContext(ctx, network, host, tlsCfg, cfg)
|
||||
if err != nil {
|
||||
return nil, NewErrWrapper(
|
||||
ClassifyQUICHandshakeError, QUICHandshakeOperation, err)
|
||||
classifyQUICHandshakeError, QUICHandshakeOperation, err)
|
||||
}
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
@@ -35,12 +35,12 @@ func TestQuirkReduceErrors(t *testing.T) {
|
||||
t.Run("multiple errors with meaningful ones", func(t *testing.T) {
|
||||
err1 := errors.New("mocked error #1")
|
||||
err2 := NewErrWrapper(
|
||||
ClassifyGenericError,
|
||||
classifyGenericError,
|
||||
CloseOperation,
|
||||
errors.New("antani"),
|
||||
)
|
||||
err3 := NewErrWrapper(
|
||||
ClassifyGenericError,
|
||||
classifyGenericError,
|
||||
CloseOperation,
|
||||
ECONNREFUSED,
|
||||
)
|
||||
|
||||
@@ -248,7 +248,7 @@ var _ model.Resolver = &resolverErrWrapper{}
|
||||
func (r *resolverErrWrapper) LookupHost(ctx context.Context, hostname string) ([]string, error) {
|
||||
addrs, err := r.Resolver.LookupHost(ctx, hostname)
|
||||
if err != nil {
|
||||
return nil, NewErrWrapper(ClassifyResolverError, ResolveOperation, err)
|
||||
return nil, NewErrWrapper(classifyResolverError, ResolveOperation, err)
|
||||
}
|
||||
return addrs, nil
|
||||
}
|
||||
@@ -257,7 +257,7 @@ func (r *resolverErrWrapper) LookupHTTPS(
|
||||
ctx context.Context, domain string) (*model.HTTPSSvc, error) {
|
||||
out, err := r.Resolver.LookupHTTPS(ctx, domain)
|
||||
if err != nil {
|
||||
return nil, NewErrWrapper(ClassifyResolverError, ResolveOperation, err)
|
||||
return nil, NewErrWrapper(classifyResolverError, ResolveOperation, err)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -335,7 +335,7 @@ func (h *tlsHandshakerErrWrapper) Handshake(
|
||||
tlsconn, state, err := h.TLSHandshaker.Handshake(ctx, conn, config)
|
||||
if err != nil {
|
||||
return nil, tls.ConnectionState{}, NewErrWrapper(
|
||||
ClassifyTLSHandshakeError, TLSHandshakeOperation, err)
|
||||
classifyTLSHandshakeError, TLSHandshakeOperation, err)
|
||||
}
|
||||
return tlsconn, state, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user