From 9967803c31a7abbdd15f2bc6acc8e3d9e416aa9d Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Wed, 29 Sep 2021 11:21:28 +0200 Subject: [PATCH] fix(netxlite): map additional GetAddrInfoW errors (#521) On Windows, GetAddrInfoW is a syscall and the Go resolver does not attempt to map errors beyond WSA_HOST_NOT_FOUND, which becomes "no such host", which we map to "dns_nxdomain_error". See https://github.com/golang/go/blob/go1.17.1/src/net/lookup_windows.go#L16. To map more GetAddrInfoW errors, thus, we need to enhance our error classifier to have system specific errors. Then, we need to filter for the WSA errors that are most likely to pop up and map them to OONI failures. Those are three: - WSANO_DATA which we have from our own UDP resolver as well and which we can map to `dns_no_answer` - WSANO_RECOVERY which we don't have but existed for MK so we will use `dns_non_recoverable_failure`, which was an MK error - WSATRY_AGAIN which likewise we map to the error that MK used to emit, so `dns_temporary_failure` This diff should address https://github.com/ooni/probe/issues/1467. --- internal/netxlite/certifi.go | 2 +- internal/netxlite/errno.go | 151 +++-------- internal/netxlite/errno_android.go | 78 +++++- internal/netxlite/errno_android_test.go | 188 ++++++++++++++ internal/netxlite/errno_darwin.go | 78 +++++- .../{errno_test.go => errno_darwin_test.go} | 58 +++-- internal/netxlite/errno_freebsd.go | 78 +++++- internal/netxlite/errno_freebsd_test.go | 188 ++++++++++++++ internal/netxlite/errno_ios.go | 78 +++++- internal/netxlite/errno_ios_test.go | 188 ++++++++++++++ internal/netxlite/errno_linux.go | 78 +++++- internal/netxlite/errno_linux_test.go | 188 ++++++++++++++ internal/netxlite/errno_windows.go | 87 ++++++- internal/netxlite/errno_windows_test.go | 206 +++++++++++++++ internal/netxlite/internal/generrno/main.go | 241 ++++++++++++------ 15 files changed, 1657 insertions(+), 230 deletions(-) create mode 100644 internal/netxlite/errno_android_test.go rename internal/netxlite/{errno_test.go => errno_darwin_test.go} (62%) create mode 100644 internal/netxlite/errno_freebsd_test.go create mode 100644 internal/netxlite/errno_ios_test.go create mode 100644 internal/netxlite/errno_linux_test.go create mode 100644 internal/netxlite/errno_windows_test.go diff --git a/internal/netxlite/certifi.go b/internal/netxlite/certifi.go index afcdcd0..79b255f 100644 --- a/internal/netxlite/certifi.go +++ b/internal/netxlite/certifi.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// 2021-09-28 18:13:53.557509 +0200 CEST m=+0.459759459 +// 2021-09-29 10:21:32.800846 +0200 CEST m=+0.427651209 // https://curl.haxx.se/ca/cacert.pem package netxlite diff --git a/internal/netxlite/errno.go b/internal/netxlite/errno.go index 750de6e..29786f3 100644 --- a/internal/netxlite/errno.go +++ b/internal/netxlite/errno.go @@ -1,25 +1,13 @@ // Code generated by go generate; DO NOT EDIT. -// Generated: 2021-09-28 18:13:54.361886 +0200 CEST m=+0.453564501 +// Generated: 2021-09-29 10:33:56.711301 +0200 CEST m=+0.645971001 package netxlite //go:generate go run ./internal/generrno/ -import ( - "errors" - "syscall" -) - // This enumeration lists the failures defined at // https://github.com/ooni/spec/blob/master/data-formats/df-007-errors.md const ( - // - // System errors - // - FailureConnectionRefused = "connection_refused" - FailureConnectionReset = "connection_reset" - FailureHostUnreachable = "host_unreachable" - FailureTimedOut = "timed_out" FailureAddressFamilyNotSupported = "address_family_not_supported" FailureAddressInUse = "address_in_use" FailureAddressNotAvailable = "address_not_available" @@ -27,10 +15,24 @@ const ( FailureBadAddress = "bad_address" FailureBadFileDescriptor = "bad_file_descriptor" FailureConnectionAborted = "connection_aborted" + FailureConnectionAlreadyClosed = "connection_already_closed" FailureConnectionAlreadyInProgress = "connection_already_in_progress" + FailureConnectionRefused = "connection_refused" + FailureConnectionReset = "connection_reset" + FailureDNSBogonError = "dns_bogon_error" + FailureDNSNXDOMAINError = "dns_nxdomain_error" + FailureDNSNoAnswer = "dns_no_answer" + FailureDNSNonRecoverableFailure = "dns_non_recoverable_failure" + FailureDNSRefusedError = "dns_refused_error" + FailureDNSServerMisbehaving = "dns_server_misbehaving" + FailureDNSTemporaryFailure = "dns_temporary_failure" FailureDestinationAddressRequired = "destination_address_required" + FailureEOFError = "eof_error" + FailureGenericTimeoutError = "generic_timeout_error" + FailureHostUnreachable = "host_unreachable" FailureInterrupted = "interrupted" FailureInvalidArgument = "invalid_argument" + FailureJSONParseError = "json_parse_error" FailureMessageSize = "message_size" FailureNetworkDown = "network_down" FailureNetworkReset = "network_reset" @@ -42,34 +44,18 @@ const ( FailureOperationWouldBlock = "operation_would_block" FailurePermissionDenied = "permission_denied" FailureProtocolNotSupported = "protocol_not_supported" + FailureQUICIncompatibleVersion = "quic_incompatible_version" + FailureSSLFailedHandshake = "ssl_failed_handshake" + FailureSSLInvalidCertificate = "ssl_invalid_certificate" + FailureSSLInvalidHostname = "ssl_invalid_hostname" + FailureSSLUnknownAuthority = "ssl_unknown_authority" + FailureTimedOut = "timed_out" FailureWrongProtocolType = "wrong_protocol_type" - - // - // Library errors - // - FailureDNSBogonError = "dns_bogon_error" - FailureDNSNXDOMAINError = "dns_nxdomain_error" - FailureDNSRefusedError = "dns_refused_error" - FailureDNSServerMisbehaving = "dns_server_misbehaving" - FailureDNSNoAnswer = "dns_no_answer" - FailureEOFError = "eof_error" - FailureGenericTimeoutError = "generic_timeout_error" - FailureQUICIncompatibleVersion = "quic_incompatible_version" - FailureSSLFailedHandshake = "ssl_failed_handshake" - FailureSSLInvalidHostname = "ssl_invalid_hostname" - FailureSSLUnknownAuthority = "ssl_unknown_authority" - FailureSSLInvalidCertificate = "ssl_invalid_certificate" - FailureJSONParseError = "json_parse_error" - FailureConnectionAlreadyClosed = "connection_already_closed" ) // failureMap lists all failures so we can match them // when they are wrapped by quic.TransportError. var failuresMap = map[string]string{ - "connection_refused": "connection_refused", - "connection_reset": "connection_reset", - "host_unreachable": "host_unreachable", - "timed_out": "timed_out", "address_family_not_supported": "address_family_not_supported", "address_in_use": "address_in_use", "address_not_available": "address_not_available", @@ -77,10 +63,24 @@ var failuresMap = map[string]string{ "bad_address": "bad_address", "bad_file_descriptor": "bad_file_descriptor", "connection_aborted": "connection_aborted", + "connection_already_closed": "connection_already_closed", "connection_already_in_progress": "connection_already_in_progress", + "connection_refused": "connection_refused", + "connection_reset": "connection_reset", "destination_address_required": "destination_address_required", + "dns_bogon_error": "dns_bogon_error", + "dns_no_answer": "dns_no_answer", + "dns_non_recoverable_failure": "dns_non_recoverable_failure", + "dns_nxdomain_error": "dns_nxdomain_error", + "dns_refused_error": "dns_refused_error", + "dns_server_misbehaving": "dns_server_misbehaving", + "dns_temporary_failure": "dns_temporary_failure", + "eof_error": "eof_error", + "generic_timeout_error": "generic_timeout_error", + "host_unreachable": "host_unreachable", "interrupted": "interrupted", "invalid_argument": "invalid_argument", + "json_parse_error": "json_parse_error", "message_size": "message_size", "network_down": "network_down", "network_reset": "network_reset", @@ -92,86 +92,11 @@ var failuresMap = map[string]string{ "operation_would_block": "operation_would_block", "permission_denied": "permission_denied", "protocol_not_supported": "protocol_not_supported", - "wrong_protocol_type": "wrong_protocol_type", - "dns_bogon_error": "dns_bogon_error", - "dns_nxdomain_error": "dns_nxdomain_error", - "dns_refused_error": "dns_refused_error", - "dns_server_misbehaving": "dns_server_misbehaving", - "dns_no_answer": "dns_no_answer", - "eof_error": "eof_error", - "generic_timeout_error": "generic_timeout_error", "quic_incompatible_version": "quic_incompatible_version", "ssl_failed_handshake": "ssl_failed_handshake", + "ssl_invalid_certificate": "ssl_invalid_certificate", "ssl_invalid_hostname": "ssl_invalid_hostname", "ssl_unknown_authority": "ssl_unknown_authority", - "ssl_invalid_certificate": "ssl_invalid_certificate", - "json_parse_error": "json_parse_error", - "connection_already_closed": "connection_already_closed", -} - -// classifySyscallError converts a syscall error to the -// proper OONI error. Returns the OONI error string -// on success, an empty string otherwise. -func classifySyscallError(err error) string { - var errno syscall.Errno - if !errors.As(err, &errno) { - return "" - } - switch errno { - case ECONNREFUSED: - return FailureConnectionRefused - case ECONNRESET: - return FailureConnectionReset - case EHOSTUNREACH: - return FailureHostUnreachable - case ETIMEDOUT: - return FailureTimedOut - case EAFNOSUPPORT: - return FailureAddressFamilyNotSupported - case EADDRINUSE: - return FailureAddressInUse - case EADDRNOTAVAIL: - return FailureAddressNotAvailable - case EISCONN: - return FailureAlreadyConnected - case EFAULT: - return FailureBadAddress - case EBADF: - return FailureBadFileDescriptor - case ECONNABORTED: - return FailureConnectionAborted - case EALREADY: - return FailureConnectionAlreadyInProgress - case EDESTADDRREQ: - return FailureDestinationAddressRequired - case EINTR: - return FailureInterrupted - case EINVAL: - return FailureInvalidArgument - case EMSGSIZE: - return FailureMessageSize - case ENETDOWN: - return FailureNetworkDown - case ENETRESET: - return FailureNetworkReset - case ENETUNREACH: - return FailureNetworkUnreachable - case ENOBUFS: - return FailureNoBufferSpace - case ENOPROTOOPT: - return FailureNoProtocolOption - case ENOTSOCK: - return FailureNotASocket - case ENOTCONN: - return FailureNotConnected - case EWOULDBLOCK: - return FailureOperationWouldBlock - case EACCES: - return FailurePermissionDenied - case EPROTONOSUPPORT: - return FailureProtocolNotSupported - case EPROTOTYPE: - return FailureWrongProtocolType - } - return "" + "timed_out": "timed_out", + "wrong_protocol_type": "wrong_protocol_type", } diff --git a/internal/netxlite/errno_android.go b/internal/netxlite/errno_android.go index a4eec36..be0bdf1 100644 --- a/internal/netxlite/errno_android.go +++ b/internal/netxlite/errno_android.go @@ -1,10 +1,17 @@ // Code generated by go generate; DO NOT EDIT. -// Generated: 2021-09-28 18:13:53.909532 +0200 CEST m=+0.001205084 +// Generated: 2021-09-29 10:33:56.065909 +0200 CEST m=+0.000565085 package netxlite -import "golang.org/x/sys/unix" +import ( + "errors" + "syscall" + "golang.org/x/sys/unix" +) + +// This enumeration provides a canonical name for +// every system-call error we support on this systems. const ( ECONNREFUSED = unix.ECONNREFUSED ECONNRESET = unix.ECONNRESET @@ -34,3 +41,70 @@ const ( EPROTONOSUPPORT = unix.EPROTONOSUPPORT EPROTOTYPE = unix.EPROTOTYPE ) + +// classifySyscallError converts a syscall error to the +// proper OONI error. Returns the OONI error string +// on success, an empty string otherwise. +func classifySyscallError(err error) string { + var errno syscall.Errno + if !errors.As(err, &errno) { + return "" + } + switch errno { + case unix.ECONNREFUSED: + return FailureConnectionRefused + case unix.ECONNRESET: + return FailureConnectionReset + case unix.EHOSTUNREACH: + return FailureHostUnreachable + case unix.ETIMEDOUT: + return FailureTimedOut + case unix.EAFNOSUPPORT: + return FailureAddressFamilyNotSupported + case unix.EADDRINUSE: + return FailureAddressInUse + case unix.EADDRNOTAVAIL: + return FailureAddressNotAvailable + case unix.EISCONN: + return FailureAlreadyConnected + case unix.EFAULT: + return FailureBadAddress + case unix.EBADF: + return FailureBadFileDescriptor + case unix.ECONNABORTED: + return FailureConnectionAborted + case unix.EALREADY: + return FailureConnectionAlreadyInProgress + case unix.EDESTADDRREQ: + return FailureDestinationAddressRequired + case unix.EINTR: + return FailureInterrupted + case unix.EINVAL: + return FailureInvalidArgument + case unix.EMSGSIZE: + return FailureMessageSize + case unix.ENETDOWN: + return FailureNetworkDown + case unix.ENETRESET: + return FailureNetworkReset + case unix.ENETUNREACH: + return FailureNetworkUnreachable + case unix.ENOBUFS: + return FailureNoBufferSpace + case unix.ENOPROTOOPT: + return FailureNoProtocolOption + case unix.ENOTSOCK: + return FailureNotASocket + case unix.ENOTCONN: + return FailureNotConnected + case unix.EWOULDBLOCK: + return FailureOperationWouldBlock + case unix.EACCES: + return FailurePermissionDenied + case unix.EPROTONOSUPPORT: + return FailureProtocolNotSupported + case unix.EPROTOTYPE: + return FailureWrongProtocolType + } + return "" +} diff --git a/internal/netxlite/errno_android_test.go b/internal/netxlite/errno_android_test.go new file mode 100644 index 0000000..a119f5c --- /dev/null +++ b/internal/netxlite/errno_android_test.go @@ -0,0 +1,188 @@ +// Code generated by go generate; DO NOT EDIT. +// Generated: 2021-09-29 10:33:56.171735 +0200 CEST m=+0.106392751 + +package netxlite + +import ( + "io" + "syscall" + "testing" + + "golang.org/x/sys/unix" +) + +func TestClassifySyscallError(t *testing.T) { + t.Run("for a non-syscall error", func(t *testing.T) { + if v := classifySyscallError(io.EOF); v != "" { + t.Fatalf("expected empty string, got '%s'", v) + } + }) + + t.Run("for ECONNREFUSED", func(t *testing.T) { + if v := classifySyscallError(unix.ECONNREFUSED); v != FailureConnectionRefused { + t.Fatalf("expected '%s', got '%s'", FailureConnectionRefused, v) + } + }) + + t.Run("for ECONNRESET", func(t *testing.T) { + if v := classifySyscallError(unix.ECONNRESET); v != FailureConnectionReset { + t.Fatalf("expected '%s', got '%s'", FailureConnectionReset, v) + } + }) + + t.Run("for EHOSTUNREACH", func(t *testing.T) { + if v := classifySyscallError(unix.EHOSTUNREACH); v != FailureHostUnreachable { + t.Fatalf("expected '%s', got '%s'", FailureHostUnreachable, v) + } + }) + + t.Run("for ETIMEDOUT", func(t *testing.T) { + if v := classifySyscallError(unix.ETIMEDOUT); v != FailureTimedOut { + t.Fatalf("expected '%s', got '%s'", FailureTimedOut, v) + } + }) + + t.Run("for EAFNOSUPPORT", func(t *testing.T) { + if v := classifySyscallError(unix.EAFNOSUPPORT); v != FailureAddressFamilyNotSupported { + t.Fatalf("expected '%s', got '%s'", FailureAddressFamilyNotSupported, v) + } + }) + + t.Run("for EADDRINUSE", func(t *testing.T) { + if v := classifySyscallError(unix.EADDRINUSE); v != FailureAddressInUse { + t.Fatalf("expected '%s', got '%s'", FailureAddressInUse, v) + } + }) + + t.Run("for EADDRNOTAVAIL", func(t *testing.T) { + if v := classifySyscallError(unix.EADDRNOTAVAIL); v != FailureAddressNotAvailable { + t.Fatalf("expected '%s', got '%s'", FailureAddressNotAvailable, v) + } + }) + + t.Run("for EISCONN", func(t *testing.T) { + if v := classifySyscallError(unix.EISCONN); v != FailureAlreadyConnected { + t.Fatalf("expected '%s', got '%s'", FailureAlreadyConnected, v) + } + }) + + t.Run("for EFAULT", func(t *testing.T) { + if v := classifySyscallError(unix.EFAULT); v != FailureBadAddress { + t.Fatalf("expected '%s', got '%s'", FailureBadAddress, v) + } + }) + + t.Run("for EBADF", func(t *testing.T) { + if v := classifySyscallError(unix.EBADF); v != FailureBadFileDescriptor { + t.Fatalf("expected '%s', got '%s'", FailureBadFileDescriptor, v) + } + }) + + t.Run("for ECONNABORTED", func(t *testing.T) { + if v := classifySyscallError(unix.ECONNABORTED); v != FailureConnectionAborted { + t.Fatalf("expected '%s', got '%s'", FailureConnectionAborted, v) + } + }) + + t.Run("for EALREADY", func(t *testing.T) { + if v := classifySyscallError(unix.EALREADY); v != FailureConnectionAlreadyInProgress { + t.Fatalf("expected '%s', got '%s'", FailureConnectionAlreadyInProgress, v) + } + }) + + t.Run("for EDESTADDRREQ", func(t *testing.T) { + if v := classifySyscallError(unix.EDESTADDRREQ); v != FailureDestinationAddressRequired { + t.Fatalf("expected '%s', got '%s'", FailureDestinationAddressRequired, v) + } + }) + + t.Run("for EINTR", func(t *testing.T) { + if v := classifySyscallError(unix.EINTR); v != FailureInterrupted { + t.Fatalf("expected '%s', got '%s'", FailureInterrupted, v) + } + }) + + t.Run("for EINVAL", func(t *testing.T) { + if v := classifySyscallError(unix.EINVAL); v != FailureInvalidArgument { + t.Fatalf("expected '%s', got '%s'", FailureInvalidArgument, v) + } + }) + + t.Run("for EMSGSIZE", func(t *testing.T) { + if v := classifySyscallError(unix.EMSGSIZE); v != FailureMessageSize { + t.Fatalf("expected '%s', got '%s'", FailureMessageSize, v) + } + }) + + t.Run("for ENETDOWN", func(t *testing.T) { + if v := classifySyscallError(unix.ENETDOWN); v != FailureNetworkDown { + t.Fatalf("expected '%s', got '%s'", FailureNetworkDown, v) + } + }) + + t.Run("for ENETRESET", func(t *testing.T) { + if v := classifySyscallError(unix.ENETRESET); v != FailureNetworkReset { + t.Fatalf("expected '%s', got '%s'", FailureNetworkReset, v) + } + }) + + t.Run("for ENETUNREACH", func(t *testing.T) { + if v := classifySyscallError(unix.ENETUNREACH); v != FailureNetworkUnreachable { + t.Fatalf("expected '%s', got '%s'", FailureNetworkUnreachable, v) + } + }) + + t.Run("for ENOBUFS", func(t *testing.T) { + if v := classifySyscallError(unix.ENOBUFS); v != FailureNoBufferSpace { + t.Fatalf("expected '%s', got '%s'", FailureNoBufferSpace, v) + } + }) + + t.Run("for ENOPROTOOPT", func(t *testing.T) { + if v := classifySyscallError(unix.ENOPROTOOPT); v != FailureNoProtocolOption { + t.Fatalf("expected '%s', got '%s'", FailureNoProtocolOption, v) + } + }) + + t.Run("for ENOTSOCK", func(t *testing.T) { + if v := classifySyscallError(unix.ENOTSOCK); v != FailureNotASocket { + t.Fatalf("expected '%s', got '%s'", FailureNotASocket, v) + } + }) + + t.Run("for ENOTCONN", func(t *testing.T) { + if v := classifySyscallError(unix.ENOTCONN); v != FailureNotConnected { + t.Fatalf("expected '%s', got '%s'", FailureNotConnected, v) + } + }) + + t.Run("for EWOULDBLOCK", func(t *testing.T) { + if v := classifySyscallError(unix.EWOULDBLOCK); v != FailureOperationWouldBlock { + t.Fatalf("expected '%s', got '%s'", FailureOperationWouldBlock, v) + } + }) + + t.Run("for EACCES", func(t *testing.T) { + if v := classifySyscallError(unix.EACCES); v != FailurePermissionDenied { + t.Fatalf("expected '%s', got '%s'", FailurePermissionDenied, v) + } + }) + + t.Run("for EPROTONOSUPPORT", func(t *testing.T) { + if v := classifySyscallError(unix.EPROTONOSUPPORT); v != FailureProtocolNotSupported { + t.Fatalf("expected '%s', got '%s'", FailureProtocolNotSupported, v) + } + }) + + t.Run("for EPROTOTYPE", func(t *testing.T) { + if v := classifySyscallError(unix.EPROTOTYPE); v != FailureWrongProtocolType { + t.Fatalf("expected '%s', got '%s'", FailureWrongProtocolType, v) + } + }) + + t.Run("for the zero errno value", func(t *testing.T) { + if v := classifySyscallError(syscall.Errno(0)); v != "" { + t.Fatalf("expected empty string, got '%s'", v) + } + }) +} diff --git a/internal/netxlite/errno_darwin.go b/internal/netxlite/errno_darwin.go index eca953d..c626dd8 100644 --- a/internal/netxlite/errno_darwin.go +++ b/internal/netxlite/errno_darwin.go @@ -1,10 +1,17 @@ // Code generated by go generate; DO NOT EDIT. -// Generated: 2021-09-28 18:13:54.015321 +0200 CEST m=+0.106996042 +// Generated: 2021-09-29 10:33:56.208597 +0200 CEST m=+0.143256126 package netxlite -import "golang.org/x/sys/unix" +import ( + "errors" + "syscall" + "golang.org/x/sys/unix" +) + +// This enumeration provides a canonical name for +// every system-call error we support on this systems. const ( ECONNREFUSED = unix.ECONNREFUSED ECONNRESET = unix.ECONNRESET @@ -34,3 +41,70 @@ const ( EPROTONOSUPPORT = unix.EPROTONOSUPPORT EPROTOTYPE = unix.EPROTOTYPE ) + +// classifySyscallError converts a syscall error to the +// proper OONI error. Returns the OONI error string +// on success, an empty string otherwise. +func classifySyscallError(err error) string { + var errno syscall.Errno + if !errors.As(err, &errno) { + return "" + } + switch errno { + case unix.ECONNREFUSED: + return FailureConnectionRefused + case unix.ECONNRESET: + return FailureConnectionReset + case unix.EHOSTUNREACH: + return FailureHostUnreachable + case unix.ETIMEDOUT: + return FailureTimedOut + case unix.EAFNOSUPPORT: + return FailureAddressFamilyNotSupported + case unix.EADDRINUSE: + return FailureAddressInUse + case unix.EADDRNOTAVAIL: + return FailureAddressNotAvailable + case unix.EISCONN: + return FailureAlreadyConnected + case unix.EFAULT: + return FailureBadAddress + case unix.EBADF: + return FailureBadFileDescriptor + case unix.ECONNABORTED: + return FailureConnectionAborted + case unix.EALREADY: + return FailureConnectionAlreadyInProgress + case unix.EDESTADDRREQ: + return FailureDestinationAddressRequired + case unix.EINTR: + return FailureInterrupted + case unix.EINVAL: + return FailureInvalidArgument + case unix.EMSGSIZE: + return FailureMessageSize + case unix.ENETDOWN: + return FailureNetworkDown + case unix.ENETRESET: + return FailureNetworkReset + case unix.ENETUNREACH: + return FailureNetworkUnreachable + case unix.ENOBUFS: + return FailureNoBufferSpace + case unix.ENOPROTOOPT: + return FailureNoProtocolOption + case unix.ENOTSOCK: + return FailureNotASocket + case unix.ENOTCONN: + return FailureNotConnected + case unix.EWOULDBLOCK: + return FailureOperationWouldBlock + case unix.EACCES: + return FailurePermissionDenied + case unix.EPROTONOSUPPORT: + return FailureProtocolNotSupported + case unix.EPROTOTYPE: + return FailureWrongProtocolType + } + return "" +} diff --git a/internal/netxlite/errno_test.go b/internal/netxlite/errno_darwin_test.go similarity index 62% rename from internal/netxlite/errno_test.go rename to internal/netxlite/errno_darwin_test.go index 79a8b04..d940430 100644 --- a/internal/netxlite/errno_test.go +++ b/internal/netxlite/errno_darwin_test.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// Generated: 2021-09-28 18:13:54.431042 +0200 CEST m=+0.522721376 +// Generated: 2021-09-29 10:33:56.27181 +0200 CEST m=+0.206469960 package netxlite @@ -7,6 +7,8 @@ import ( "io" "syscall" "testing" + + "golang.org/x/sys/unix" ) func TestClassifySyscallError(t *testing.T) { @@ -17,163 +19,163 @@ func TestClassifySyscallError(t *testing.T) { }) t.Run("for ECONNREFUSED", func(t *testing.T) { - if v := classifySyscallError(ECONNREFUSED); v != FailureConnectionRefused { + if v := classifySyscallError(unix.ECONNREFUSED); v != FailureConnectionRefused { t.Fatalf("expected '%s', got '%s'", FailureConnectionRefused, v) } }) t.Run("for ECONNRESET", func(t *testing.T) { - if v := classifySyscallError(ECONNRESET); v != FailureConnectionReset { + if v := classifySyscallError(unix.ECONNRESET); v != FailureConnectionReset { t.Fatalf("expected '%s', got '%s'", FailureConnectionReset, v) } }) t.Run("for EHOSTUNREACH", func(t *testing.T) { - if v := classifySyscallError(EHOSTUNREACH); v != FailureHostUnreachable { + if v := classifySyscallError(unix.EHOSTUNREACH); v != FailureHostUnreachable { t.Fatalf("expected '%s', got '%s'", FailureHostUnreachable, v) } }) t.Run("for ETIMEDOUT", func(t *testing.T) { - if v := classifySyscallError(ETIMEDOUT); v != FailureTimedOut { + if v := classifySyscallError(unix.ETIMEDOUT); v != FailureTimedOut { t.Fatalf("expected '%s', got '%s'", FailureTimedOut, v) } }) t.Run("for EAFNOSUPPORT", func(t *testing.T) { - if v := classifySyscallError(EAFNOSUPPORT); v != FailureAddressFamilyNotSupported { + if v := classifySyscallError(unix.EAFNOSUPPORT); v != FailureAddressFamilyNotSupported { t.Fatalf("expected '%s', got '%s'", FailureAddressFamilyNotSupported, v) } }) t.Run("for EADDRINUSE", func(t *testing.T) { - if v := classifySyscallError(EADDRINUSE); v != FailureAddressInUse { + if v := classifySyscallError(unix.EADDRINUSE); v != FailureAddressInUse { t.Fatalf("expected '%s', got '%s'", FailureAddressInUse, v) } }) t.Run("for EADDRNOTAVAIL", func(t *testing.T) { - if v := classifySyscallError(EADDRNOTAVAIL); v != FailureAddressNotAvailable { + if v := classifySyscallError(unix.EADDRNOTAVAIL); v != FailureAddressNotAvailable { t.Fatalf("expected '%s', got '%s'", FailureAddressNotAvailable, v) } }) t.Run("for EISCONN", func(t *testing.T) { - if v := classifySyscallError(EISCONN); v != FailureAlreadyConnected { + if v := classifySyscallError(unix.EISCONN); v != FailureAlreadyConnected { t.Fatalf("expected '%s', got '%s'", FailureAlreadyConnected, v) } }) t.Run("for EFAULT", func(t *testing.T) { - if v := classifySyscallError(EFAULT); v != FailureBadAddress { + if v := classifySyscallError(unix.EFAULT); v != FailureBadAddress { t.Fatalf("expected '%s', got '%s'", FailureBadAddress, v) } }) t.Run("for EBADF", func(t *testing.T) { - if v := classifySyscallError(EBADF); v != FailureBadFileDescriptor { + if v := classifySyscallError(unix.EBADF); v != FailureBadFileDescriptor { t.Fatalf("expected '%s', got '%s'", FailureBadFileDescriptor, v) } }) t.Run("for ECONNABORTED", func(t *testing.T) { - if v := classifySyscallError(ECONNABORTED); v != FailureConnectionAborted { + if v := classifySyscallError(unix.ECONNABORTED); v != FailureConnectionAborted { t.Fatalf("expected '%s', got '%s'", FailureConnectionAborted, v) } }) t.Run("for EALREADY", func(t *testing.T) { - if v := classifySyscallError(EALREADY); v != FailureConnectionAlreadyInProgress { + if v := classifySyscallError(unix.EALREADY); v != FailureConnectionAlreadyInProgress { t.Fatalf("expected '%s', got '%s'", FailureConnectionAlreadyInProgress, v) } }) t.Run("for EDESTADDRREQ", func(t *testing.T) { - if v := classifySyscallError(EDESTADDRREQ); v != FailureDestinationAddressRequired { + if v := classifySyscallError(unix.EDESTADDRREQ); v != FailureDestinationAddressRequired { t.Fatalf("expected '%s', got '%s'", FailureDestinationAddressRequired, v) } }) t.Run("for EINTR", func(t *testing.T) { - if v := classifySyscallError(EINTR); v != FailureInterrupted { + if v := classifySyscallError(unix.EINTR); v != FailureInterrupted { t.Fatalf("expected '%s', got '%s'", FailureInterrupted, v) } }) t.Run("for EINVAL", func(t *testing.T) { - if v := classifySyscallError(EINVAL); v != FailureInvalidArgument { + if v := classifySyscallError(unix.EINVAL); v != FailureInvalidArgument { t.Fatalf("expected '%s', got '%s'", FailureInvalidArgument, v) } }) t.Run("for EMSGSIZE", func(t *testing.T) { - if v := classifySyscallError(EMSGSIZE); v != FailureMessageSize { + if v := classifySyscallError(unix.EMSGSIZE); v != FailureMessageSize { t.Fatalf("expected '%s', got '%s'", FailureMessageSize, v) } }) t.Run("for ENETDOWN", func(t *testing.T) { - if v := classifySyscallError(ENETDOWN); v != FailureNetworkDown { + if v := classifySyscallError(unix.ENETDOWN); v != FailureNetworkDown { t.Fatalf("expected '%s', got '%s'", FailureNetworkDown, v) } }) t.Run("for ENETRESET", func(t *testing.T) { - if v := classifySyscallError(ENETRESET); v != FailureNetworkReset { + if v := classifySyscallError(unix.ENETRESET); v != FailureNetworkReset { t.Fatalf("expected '%s', got '%s'", FailureNetworkReset, v) } }) t.Run("for ENETUNREACH", func(t *testing.T) { - if v := classifySyscallError(ENETUNREACH); v != FailureNetworkUnreachable { + if v := classifySyscallError(unix.ENETUNREACH); v != FailureNetworkUnreachable { t.Fatalf("expected '%s', got '%s'", FailureNetworkUnreachable, v) } }) t.Run("for ENOBUFS", func(t *testing.T) { - if v := classifySyscallError(ENOBUFS); v != FailureNoBufferSpace { + if v := classifySyscallError(unix.ENOBUFS); v != FailureNoBufferSpace { t.Fatalf("expected '%s', got '%s'", FailureNoBufferSpace, v) } }) t.Run("for ENOPROTOOPT", func(t *testing.T) { - if v := classifySyscallError(ENOPROTOOPT); v != FailureNoProtocolOption { + if v := classifySyscallError(unix.ENOPROTOOPT); v != FailureNoProtocolOption { t.Fatalf("expected '%s', got '%s'", FailureNoProtocolOption, v) } }) t.Run("for ENOTSOCK", func(t *testing.T) { - if v := classifySyscallError(ENOTSOCK); v != FailureNotASocket { + if v := classifySyscallError(unix.ENOTSOCK); v != FailureNotASocket { t.Fatalf("expected '%s', got '%s'", FailureNotASocket, v) } }) t.Run("for ENOTCONN", func(t *testing.T) { - if v := classifySyscallError(ENOTCONN); v != FailureNotConnected { + if v := classifySyscallError(unix.ENOTCONN); v != FailureNotConnected { t.Fatalf("expected '%s', got '%s'", FailureNotConnected, v) } }) t.Run("for EWOULDBLOCK", func(t *testing.T) { - if v := classifySyscallError(EWOULDBLOCK); v != FailureOperationWouldBlock { + if v := classifySyscallError(unix.EWOULDBLOCK); v != FailureOperationWouldBlock { t.Fatalf("expected '%s', got '%s'", FailureOperationWouldBlock, v) } }) t.Run("for EACCES", func(t *testing.T) { - if v := classifySyscallError(EACCES); v != FailurePermissionDenied { + if v := classifySyscallError(unix.EACCES); v != FailurePermissionDenied { t.Fatalf("expected '%s', got '%s'", FailurePermissionDenied, v) } }) t.Run("for EPROTONOSUPPORT", func(t *testing.T) { - if v := classifySyscallError(EPROTONOSUPPORT); v != FailureProtocolNotSupported { + if v := classifySyscallError(unix.EPROTONOSUPPORT); v != FailureProtocolNotSupported { t.Fatalf("expected '%s', got '%s'", FailureProtocolNotSupported, v) } }) t.Run("for EPROTOTYPE", func(t *testing.T) { - if v := classifySyscallError(EPROTOTYPE); v != FailureWrongProtocolType { + if v := classifySyscallError(unix.EPROTOTYPE); v != FailureWrongProtocolType { t.Fatalf("expected '%s', got '%s'", FailureWrongProtocolType, v) } }) diff --git a/internal/netxlite/errno_freebsd.go b/internal/netxlite/errno_freebsd.go index c52b2af..e1c98ca 100644 --- a/internal/netxlite/errno_freebsd.go +++ b/internal/netxlite/errno_freebsd.go @@ -1,10 +1,17 @@ // Code generated by go generate; DO NOT EDIT. -// Generated: 2021-09-28 18:13:54.08291 +0200 CEST m=+0.174585667 +// Generated: 2021-09-29 10:33:56.310236 +0200 CEST m=+0.244897126 package netxlite -import "golang.org/x/sys/unix" +import ( + "errors" + "syscall" + "golang.org/x/sys/unix" +) + +// This enumeration provides a canonical name for +// every system-call error we support on this systems. const ( ECONNREFUSED = unix.ECONNREFUSED ECONNRESET = unix.ECONNRESET @@ -34,3 +41,70 @@ const ( EPROTONOSUPPORT = unix.EPROTONOSUPPORT EPROTOTYPE = unix.EPROTOTYPE ) + +// classifySyscallError converts a syscall error to the +// proper OONI error. Returns the OONI error string +// on success, an empty string otherwise. +func classifySyscallError(err error) string { + var errno syscall.Errno + if !errors.As(err, &errno) { + return "" + } + switch errno { + case unix.ECONNREFUSED: + return FailureConnectionRefused + case unix.ECONNRESET: + return FailureConnectionReset + case unix.EHOSTUNREACH: + return FailureHostUnreachable + case unix.ETIMEDOUT: + return FailureTimedOut + case unix.EAFNOSUPPORT: + return FailureAddressFamilyNotSupported + case unix.EADDRINUSE: + return FailureAddressInUse + case unix.EADDRNOTAVAIL: + return FailureAddressNotAvailable + case unix.EISCONN: + return FailureAlreadyConnected + case unix.EFAULT: + return FailureBadAddress + case unix.EBADF: + return FailureBadFileDescriptor + case unix.ECONNABORTED: + return FailureConnectionAborted + case unix.EALREADY: + return FailureConnectionAlreadyInProgress + case unix.EDESTADDRREQ: + return FailureDestinationAddressRequired + case unix.EINTR: + return FailureInterrupted + case unix.EINVAL: + return FailureInvalidArgument + case unix.EMSGSIZE: + return FailureMessageSize + case unix.ENETDOWN: + return FailureNetworkDown + case unix.ENETRESET: + return FailureNetworkReset + case unix.ENETUNREACH: + return FailureNetworkUnreachable + case unix.ENOBUFS: + return FailureNoBufferSpace + case unix.ENOPROTOOPT: + return FailureNoProtocolOption + case unix.ENOTSOCK: + return FailureNotASocket + case unix.ENOTCONN: + return FailureNotConnected + case unix.EWOULDBLOCK: + return FailureOperationWouldBlock + case unix.EACCES: + return FailurePermissionDenied + case unix.EPROTONOSUPPORT: + return FailureProtocolNotSupported + case unix.EPROTOTYPE: + return FailureWrongProtocolType + } + return "" +} diff --git a/internal/netxlite/errno_freebsd_test.go b/internal/netxlite/errno_freebsd_test.go new file mode 100644 index 0000000..13cd7bf --- /dev/null +++ b/internal/netxlite/errno_freebsd_test.go @@ -0,0 +1,188 @@ +// Code generated by go generate; DO NOT EDIT. +// Generated: 2021-09-29 10:33:56.373611 +0200 CEST m=+0.308273168 + +package netxlite + +import ( + "io" + "syscall" + "testing" + + "golang.org/x/sys/unix" +) + +func TestClassifySyscallError(t *testing.T) { + t.Run("for a non-syscall error", func(t *testing.T) { + if v := classifySyscallError(io.EOF); v != "" { + t.Fatalf("expected empty string, got '%s'", v) + } + }) + + t.Run("for ECONNREFUSED", func(t *testing.T) { + if v := classifySyscallError(unix.ECONNREFUSED); v != FailureConnectionRefused { + t.Fatalf("expected '%s', got '%s'", FailureConnectionRefused, v) + } + }) + + t.Run("for ECONNRESET", func(t *testing.T) { + if v := classifySyscallError(unix.ECONNRESET); v != FailureConnectionReset { + t.Fatalf("expected '%s', got '%s'", FailureConnectionReset, v) + } + }) + + t.Run("for EHOSTUNREACH", func(t *testing.T) { + if v := classifySyscallError(unix.EHOSTUNREACH); v != FailureHostUnreachable { + t.Fatalf("expected '%s', got '%s'", FailureHostUnreachable, v) + } + }) + + t.Run("for ETIMEDOUT", func(t *testing.T) { + if v := classifySyscallError(unix.ETIMEDOUT); v != FailureTimedOut { + t.Fatalf("expected '%s', got '%s'", FailureTimedOut, v) + } + }) + + t.Run("for EAFNOSUPPORT", func(t *testing.T) { + if v := classifySyscallError(unix.EAFNOSUPPORT); v != FailureAddressFamilyNotSupported { + t.Fatalf("expected '%s', got '%s'", FailureAddressFamilyNotSupported, v) + } + }) + + t.Run("for EADDRINUSE", func(t *testing.T) { + if v := classifySyscallError(unix.EADDRINUSE); v != FailureAddressInUse { + t.Fatalf("expected '%s', got '%s'", FailureAddressInUse, v) + } + }) + + t.Run("for EADDRNOTAVAIL", func(t *testing.T) { + if v := classifySyscallError(unix.EADDRNOTAVAIL); v != FailureAddressNotAvailable { + t.Fatalf("expected '%s', got '%s'", FailureAddressNotAvailable, v) + } + }) + + t.Run("for EISCONN", func(t *testing.T) { + if v := classifySyscallError(unix.EISCONN); v != FailureAlreadyConnected { + t.Fatalf("expected '%s', got '%s'", FailureAlreadyConnected, v) + } + }) + + t.Run("for EFAULT", func(t *testing.T) { + if v := classifySyscallError(unix.EFAULT); v != FailureBadAddress { + t.Fatalf("expected '%s', got '%s'", FailureBadAddress, v) + } + }) + + t.Run("for EBADF", func(t *testing.T) { + if v := classifySyscallError(unix.EBADF); v != FailureBadFileDescriptor { + t.Fatalf("expected '%s', got '%s'", FailureBadFileDescriptor, v) + } + }) + + t.Run("for ECONNABORTED", func(t *testing.T) { + if v := classifySyscallError(unix.ECONNABORTED); v != FailureConnectionAborted { + t.Fatalf("expected '%s', got '%s'", FailureConnectionAborted, v) + } + }) + + t.Run("for EALREADY", func(t *testing.T) { + if v := classifySyscallError(unix.EALREADY); v != FailureConnectionAlreadyInProgress { + t.Fatalf("expected '%s', got '%s'", FailureConnectionAlreadyInProgress, v) + } + }) + + t.Run("for EDESTADDRREQ", func(t *testing.T) { + if v := classifySyscallError(unix.EDESTADDRREQ); v != FailureDestinationAddressRequired { + t.Fatalf("expected '%s', got '%s'", FailureDestinationAddressRequired, v) + } + }) + + t.Run("for EINTR", func(t *testing.T) { + if v := classifySyscallError(unix.EINTR); v != FailureInterrupted { + t.Fatalf("expected '%s', got '%s'", FailureInterrupted, v) + } + }) + + t.Run("for EINVAL", func(t *testing.T) { + if v := classifySyscallError(unix.EINVAL); v != FailureInvalidArgument { + t.Fatalf("expected '%s', got '%s'", FailureInvalidArgument, v) + } + }) + + t.Run("for EMSGSIZE", func(t *testing.T) { + if v := classifySyscallError(unix.EMSGSIZE); v != FailureMessageSize { + t.Fatalf("expected '%s', got '%s'", FailureMessageSize, v) + } + }) + + t.Run("for ENETDOWN", func(t *testing.T) { + if v := classifySyscallError(unix.ENETDOWN); v != FailureNetworkDown { + t.Fatalf("expected '%s', got '%s'", FailureNetworkDown, v) + } + }) + + t.Run("for ENETRESET", func(t *testing.T) { + if v := classifySyscallError(unix.ENETRESET); v != FailureNetworkReset { + t.Fatalf("expected '%s', got '%s'", FailureNetworkReset, v) + } + }) + + t.Run("for ENETUNREACH", func(t *testing.T) { + if v := classifySyscallError(unix.ENETUNREACH); v != FailureNetworkUnreachable { + t.Fatalf("expected '%s', got '%s'", FailureNetworkUnreachable, v) + } + }) + + t.Run("for ENOBUFS", func(t *testing.T) { + if v := classifySyscallError(unix.ENOBUFS); v != FailureNoBufferSpace { + t.Fatalf("expected '%s', got '%s'", FailureNoBufferSpace, v) + } + }) + + t.Run("for ENOPROTOOPT", func(t *testing.T) { + if v := classifySyscallError(unix.ENOPROTOOPT); v != FailureNoProtocolOption { + t.Fatalf("expected '%s', got '%s'", FailureNoProtocolOption, v) + } + }) + + t.Run("for ENOTSOCK", func(t *testing.T) { + if v := classifySyscallError(unix.ENOTSOCK); v != FailureNotASocket { + t.Fatalf("expected '%s', got '%s'", FailureNotASocket, v) + } + }) + + t.Run("for ENOTCONN", func(t *testing.T) { + if v := classifySyscallError(unix.ENOTCONN); v != FailureNotConnected { + t.Fatalf("expected '%s', got '%s'", FailureNotConnected, v) + } + }) + + t.Run("for EWOULDBLOCK", func(t *testing.T) { + if v := classifySyscallError(unix.EWOULDBLOCK); v != FailureOperationWouldBlock { + t.Fatalf("expected '%s', got '%s'", FailureOperationWouldBlock, v) + } + }) + + t.Run("for EACCES", func(t *testing.T) { + if v := classifySyscallError(unix.EACCES); v != FailurePermissionDenied { + t.Fatalf("expected '%s', got '%s'", FailurePermissionDenied, v) + } + }) + + t.Run("for EPROTONOSUPPORT", func(t *testing.T) { + if v := classifySyscallError(unix.EPROTONOSUPPORT); v != FailureProtocolNotSupported { + t.Fatalf("expected '%s', got '%s'", FailureProtocolNotSupported, v) + } + }) + + t.Run("for EPROTOTYPE", func(t *testing.T) { + if v := classifySyscallError(unix.EPROTOTYPE); v != FailureWrongProtocolType { + t.Fatalf("expected '%s', got '%s'", FailureWrongProtocolType, v) + } + }) + + t.Run("for the zero errno value", func(t *testing.T) { + if v := classifySyscallError(syscall.Errno(0)); v != "" { + t.Fatalf("expected empty string, got '%s'", v) + } + }) +} diff --git a/internal/netxlite/errno_ios.go b/internal/netxlite/errno_ios.go index a65a10a..e2eadf0 100644 --- a/internal/netxlite/errno_ios.go +++ b/internal/netxlite/errno_ios.go @@ -1,10 +1,17 @@ // Code generated by go generate; DO NOT EDIT. -// Generated: 2021-09-28 18:13:54.151936 +0200 CEST m=+0.243612834 +// Generated: 2021-09-29 10:33:56.410358 +0200 CEST m=+0.345021835 package netxlite -import "golang.org/x/sys/unix" +import ( + "errors" + "syscall" + "golang.org/x/sys/unix" +) + +// This enumeration provides a canonical name for +// every system-call error we support on this systems. const ( ECONNREFUSED = unix.ECONNREFUSED ECONNRESET = unix.ECONNRESET @@ -34,3 +41,70 @@ const ( EPROTONOSUPPORT = unix.EPROTONOSUPPORT EPROTOTYPE = unix.EPROTOTYPE ) + +// classifySyscallError converts a syscall error to the +// proper OONI error. Returns the OONI error string +// on success, an empty string otherwise. +func classifySyscallError(err error) string { + var errno syscall.Errno + if !errors.As(err, &errno) { + return "" + } + switch errno { + case unix.ECONNREFUSED: + return FailureConnectionRefused + case unix.ECONNRESET: + return FailureConnectionReset + case unix.EHOSTUNREACH: + return FailureHostUnreachable + case unix.ETIMEDOUT: + return FailureTimedOut + case unix.EAFNOSUPPORT: + return FailureAddressFamilyNotSupported + case unix.EADDRINUSE: + return FailureAddressInUse + case unix.EADDRNOTAVAIL: + return FailureAddressNotAvailable + case unix.EISCONN: + return FailureAlreadyConnected + case unix.EFAULT: + return FailureBadAddress + case unix.EBADF: + return FailureBadFileDescriptor + case unix.ECONNABORTED: + return FailureConnectionAborted + case unix.EALREADY: + return FailureConnectionAlreadyInProgress + case unix.EDESTADDRREQ: + return FailureDestinationAddressRequired + case unix.EINTR: + return FailureInterrupted + case unix.EINVAL: + return FailureInvalidArgument + case unix.EMSGSIZE: + return FailureMessageSize + case unix.ENETDOWN: + return FailureNetworkDown + case unix.ENETRESET: + return FailureNetworkReset + case unix.ENETUNREACH: + return FailureNetworkUnreachable + case unix.ENOBUFS: + return FailureNoBufferSpace + case unix.ENOPROTOOPT: + return FailureNoProtocolOption + case unix.ENOTSOCK: + return FailureNotASocket + case unix.ENOTCONN: + return FailureNotConnected + case unix.EWOULDBLOCK: + return FailureOperationWouldBlock + case unix.EACCES: + return FailurePermissionDenied + case unix.EPROTONOSUPPORT: + return FailureProtocolNotSupported + case unix.EPROTOTYPE: + return FailureWrongProtocolType + } + return "" +} diff --git a/internal/netxlite/errno_ios_test.go b/internal/netxlite/errno_ios_test.go new file mode 100644 index 0000000..103134d --- /dev/null +++ b/internal/netxlite/errno_ios_test.go @@ -0,0 +1,188 @@ +// Code generated by go generate; DO NOT EDIT. +// Generated: 2021-09-29 10:33:56.491959 +0200 CEST m=+0.426624626 + +package netxlite + +import ( + "io" + "syscall" + "testing" + + "golang.org/x/sys/unix" +) + +func TestClassifySyscallError(t *testing.T) { + t.Run("for a non-syscall error", func(t *testing.T) { + if v := classifySyscallError(io.EOF); v != "" { + t.Fatalf("expected empty string, got '%s'", v) + } + }) + + t.Run("for ECONNREFUSED", func(t *testing.T) { + if v := classifySyscallError(unix.ECONNREFUSED); v != FailureConnectionRefused { + t.Fatalf("expected '%s', got '%s'", FailureConnectionRefused, v) + } + }) + + t.Run("for ECONNRESET", func(t *testing.T) { + if v := classifySyscallError(unix.ECONNRESET); v != FailureConnectionReset { + t.Fatalf("expected '%s', got '%s'", FailureConnectionReset, v) + } + }) + + t.Run("for EHOSTUNREACH", func(t *testing.T) { + if v := classifySyscallError(unix.EHOSTUNREACH); v != FailureHostUnreachable { + t.Fatalf("expected '%s', got '%s'", FailureHostUnreachable, v) + } + }) + + t.Run("for ETIMEDOUT", func(t *testing.T) { + if v := classifySyscallError(unix.ETIMEDOUT); v != FailureTimedOut { + t.Fatalf("expected '%s', got '%s'", FailureTimedOut, v) + } + }) + + t.Run("for EAFNOSUPPORT", func(t *testing.T) { + if v := classifySyscallError(unix.EAFNOSUPPORT); v != FailureAddressFamilyNotSupported { + t.Fatalf("expected '%s', got '%s'", FailureAddressFamilyNotSupported, v) + } + }) + + t.Run("for EADDRINUSE", func(t *testing.T) { + if v := classifySyscallError(unix.EADDRINUSE); v != FailureAddressInUse { + t.Fatalf("expected '%s', got '%s'", FailureAddressInUse, v) + } + }) + + t.Run("for EADDRNOTAVAIL", func(t *testing.T) { + if v := classifySyscallError(unix.EADDRNOTAVAIL); v != FailureAddressNotAvailable { + t.Fatalf("expected '%s', got '%s'", FailureAddressNotAvailable, v) + } + }) + + t.Run("for EISCONN", func(t *testing.T) { + if v := classifySyscallError(unix.EISCONN); v != FailureAlreadyConnected { + t.Fatalf("expected '%s', got '%s'", FailureAlreadyConnected, v) + } + }) + + t.Run("for EFAULT", func(t *testing.T) { + if v := classifySyscallError(unix.EFAULT); v != FailureBadAddress { + t.Fatalf("expected '%s', got '%s'", FailureBadAddress, v) + } + }) + + t.Run("for EBADF", func(t *testing.T) { + if v := classifySyscallError(unix.EBADF); v != FailureBadFileDescriptor { + t.Fatalf("expected '%s', got '%s'", FailureBadFileDescriptor, v) + } + }) + + t.Run("for ECONNABORTED", func(t *testing.T) { + if v := classifySyscallError(unix.ECONNABORTED); v != FailureConnectionAborted { + t.Fatalf("expected '%s', got '%s'", FailureConnectionAborted, v) + } + }) + + t.Run("for EALREADY", func(t *testing.T) { + if v := classifySyscallError(unix.EALREADY); v != FailureConnectionAlreadyInProgress { + t.Fatalf("expected '%s', got '%s'", FailureConnectionAlreadyInProgress, v) + } + }) + + t.Run("for EDESTADDRREQ", func(t *testing.T) { + if v := classifySyscallError(unix.EDESTADDRREQ); v != FailureDestinationAddressRequired { + t.Fatalf("expected '%s', got '%s'", FailureDestinationAddressRequired, v) + } + }) + + t.Run("for EINTR", func(t *testing.T) { + if v := classifySyscallError(unix.EINTR); v != FailureInterrupted { + t.Fatalf("expected '%s', got '%s'", FailureInterrupted, v) + } + }) + + t.Run("for EINVAL", func(t *testing.T) { + if v := classifySyscallError(unix.EINVAL); v != FailureInvalidArgument { + t.Fatalf("expected '%s', got '%s'", FailureInvalidArgument, v) + } + }) + + t.Run("for EMSGSIZE", func(t *testing.T) { + if v := classifySyscallError(unix.EMSGSIZE); v != FailureMessageSize { + t.Fatalf("expected '%s', got '%s'", FailureMessageSize, v) + } + }) + + t.Run("for ENETDOWN", func(t *testing.T) { + if v := classifySyscallError(unix.ENETDOWN); v != FailureNetworkDown { + t.Fatalf("expected '%s', got '%s'", FailureNetworkDown, v) + } + }) + + t.Run("for ENETRESET", func(t *testing.T) { + if v := classifySyscallError(unix.ENETRESET); v != FailureNetworkReset { + t.Fatalf("expected '%s', got '%s'", FailureNetworkReset, v) + } + }) + + t.Run("for ENETUNREACH", func(t *testing.T) { + if v := classifySyscallError(unix.ENETUNREACH); v != FailureNetworkUnreachable { + t.Fatalf("expected '%s', got '%s'", FailureNetworkUnreachable, v) + } + }) + + t.Run("for ENOBUFS", func(t *testing.T) { + if v := classifySyscallError(unix.ENOBUFS); v != FailureNoBufferSpace { + t.Fatalf("expected '%s', got '%s'", FailureNoBufferSpace, v) + } + }) + + t.Run("for ENOPROTOOPT", func(t *testing.T) { + if v := classifySyscallError(unix.ENOPROTOOPT); v != FailureNoProtocolOption { + t.Fatalf("expected '%s', got '%s'", FailureNoProtocolOption, v) + } + }) + + t.Run("for ENOTSOCK", func(t *testing.T) { + if v := classifySyscallError(unix.ENOTSOCK); v != FailureNotASocket { + t.Fatalf("expected '%s', got '%s'", FailureNotASocket, v) + } + }) + + t.Run("for ENOTCONN", func(t *testing.T) { + if v := classifySyscallError(unix.ENOTCONN); v != FailureNotConnected { + t.Fatalf("expected '%s', got '%s'", FailureNotConnected, v) + } + }) + + t.Run("for EWOULDBLOCK", func(t *testing.T) { + if v := classifySyscallError(unix.EWOULDBLOCK); v != FailureOperationWouldBlock { + t.Fatalf("expected '%s', got '%s'", FailureOperationWouldBlock, v) + } + }) + + t.Run("for EACCES", func(t *testing.T) { + if v := classifySyscallError(unix.EACCES); v != FailurePermissionDenied { + t.Fatalf("expected '%s', got '%s'", FailurePermissionDenied, v) + } + }) + + t.Run("for EPROTONOSUPPORT", func(t *testing.T) { + if v := classifySyscallError(unix.EPROTONOSUPPORT); v != FailureProtocolNotSupported { + t.Fatalf("expected '%s', got '%s'", FailureProtocolNotSupported, v) + } + }) + + t.Run("for EPROTOTYPE", func(t *testing.T) { + if v := classifySyscallError(unix.EPROTOTYPE); v != FailureWrongProtocolType { + t.Fatalf("expected '%s', got '%s'", FailureWrongProtocolType, v) + } + }) + + t.Run("for the zero errno value", func(t *testing.T) { + if v := classifySyscallError(syscall.Errno(0)); v != "" { + t.Fatalf("expected empty string, got '%s'", v) + } + }) +} diff --git a/internal/netxlite/errno_linux.go b/internal/netxlite/errno_linux.go index 1329c08..0f1638e 100644 --- a/internal/netxlite/errno_linux.go +++ b/internal/netxlite/errno_linux.go @@ -1,10 +1,17 @@ // Code generated by go generate; DO NOT EDIT. -// Generated: 2021-09-28 18:13:54.230156 +0200 CEST m=+0.321833417 +// Generated: 2021-09-29 10:33:56.529823 +0200 CEST m=+0.464489585 package netxlite -import "golang.org/x/sys/unix" +import ( + "errors" + "syscall" + "golang.org/x/sys/unix" +) + +// This enumeration provides a canonical name for +// every system-call error we support on this systems. const ( ECONNREFUSED = unix.ECONNREFUSED ECONNRESET = unix.ECONNRESET @@ -34,3 +41,70 @@ const ( EPROTONOSUPPORT = unix.EPROTONOSUPPORT EPROTOTYPE = unix.EPROTOTYPE ) + +// classifySyscallError converts a syscall error to the +// proper OONI error. Returns the OONI error string +// on success, an empty string otherwise. +func classifySyscallError(err error) string { + var errno syscall.Errno + if !errors.As(err, &errno) { + return "" + } + switch errno { + case unix.ECONNREFUSED: + return FailureConnectionRefused + case unix.ECONNRESET: + return FailureConnectionReset + case unix.EHOSTUNREACH: + return FailureHostUnreachable + case unix.ETIMEDOUT: + return FailureTimedOut + case unix.EAFNOSUPPORT: + return FailureAddressFamilyNotSupported + case unix.EADDRINUSE: + return FailureAddressInUse + case unix.EADDRNOTAVAIL: + return FailureAddressNotAvailable + case unix.EISCONN: + return FailureAlreadyConnected + case unix.EFAULT: + return FailureBadAddress + case unix.EBADF: + return FailureBadFileDescriptor + case unix.ECONNABORTED: + return FailureConnectionAborted + case unix.EALREADY: + return FailureConnectionAlreadyInProgress + case unix.EDESTADDRREQ: + return FailureDestinationAddressRequired + case unix.EINTR: + return FailureInterrupted + case unix.EINVAL: + return FailureInvalidArgument + case unix.EMSGSIZE: + return FailureMessageSize + case unix.ENETDOWN: + return FailureNetworkDown + case unix.ENETRESET: + return FailureNetworkReset + case unix.ENETUNREACH: + return FailureNetworkUnreachable + case unix.ENOBUFS: + return FailureNoBufferSpace + case unix.ENOPROTOOPT: + return FailureNoProtocolOption + case unix.ENOTSOCK: + return FailureNotASocket + case unix.ENOTCONN: + return FailureNotConnected + case unix.EWOULDBLOCK: + return FailureOperationWouldBlock + case unix.EACCES: + return FailurePermissionDenied + case unix.EPROTONOSUPPORT: + return FailureProtocolNotSupported + case unix.EPROTOTYPE: + return FailureWrongProtocolType + } + return "" +} diff --git a/internal/netxlite/errno_linux_test.go b/internal/netxlite/errno_linux_test.go new file mode 100644 index 0000000..c1c384c --- /dev/null +++ b/internal/netxlite/errno_linux_test.go @@ -0,0 +1,188 @@ +// Code generated by go generate; DO NOT EDIT. +// Generated: 2021-09-29 10:33:56.592275 +0200 CEST m=+0.526942210 + +package netxlite + +import ( + "io" + "syscall" + "testing" + + "golang.org/x/sys/unix" +) + +func TestClassifySyscallError(t *testing.T) { + t.Run("for a non-syscall error", func(t *testing.T) { + if v := classifySyscallError(io.EOF); v != "" { + t.Fatalf("expected empty string, got '%s'", v) + } + }) + + t.Run("for ECONNREFUSED", func(t *testing.T) { + if v := classifySyscallError(unix.ECONNREFUSED); v != FailureConnectionRefused { + t.Fatalf("expected '%s', got '%s'", FailureConnectionRefused, v) + } + }) + + t.Run("for ECONNRESET", func(t *testing.T) { + if v := classifySyscallError(unix.ECONNRESET); v != FailureConnectionReset { + t.Fatalf("expected '%s', got '%s'", FailureConnectionReset, v) + } + }) + + t.Run("for EHOSTUNREACH", func(t *testing.T) { + if v := classifySyscallError(unix.EHOSTUNREACH); v != FailureHostUnreachable { + t.Fatalf("expected '%s', got '%s'", FailureHostUnreachable, v) + } + }) + + t.Run("for ETIMEDOUT", func(t *testing.T) { + if v := classifySyscallError(unix.ETIMEDOUT); v != FailureTimedOut { + t.Fatalf("expected '%s', got '%s'", FailureTimedOut, v) + } + }) + + t.Run("for EAFNOSUPPORT", func(t *testing.T) { + if v := classifySyscallError(unix.EAFNOSUPPORT); v != FailureAddressFamilyNotSupported { + t.Fatalf("expected '%s', got '%s'", FailureAddressFamilyNotSupported, v) + } + }) + + t.Run("for EADDRINUSE", func(t *testing.T) { + if v := classifySyscallError(unix.EADDRINUSE); v != FailureAddressInUse { + t.Fatalf("expected '%s', got '%s'", FailureAddressInUse, v) + } + }) + + t.Run("for EADDRNOTAVAIL", func(t *testing.T) { + if v := classifySyscallError(unix.EADDRNOTAVAIL); v != FailureAddressNotAvailable { + t.Fatalf("expected '%s', got '%s'", FailureAddressNotAvailable, v) + } + }) + + t.Run("for EISCONN", func(t *testing.T) { + if v := classifySyscallError(unix.EISCONN); v != FailureAlreadyConnected { + t.Fatalf("expected '%s', got '%s'", FailureAlreadyConnected, v) + } + }) + + t.Run("for EFAULT", func(t *testing.T) { + if v := classifySyscallError(unix.EFAULT); v != FailureBadAddress { + t.Fatalf("expected '%s', got '%s'", FailureBadAddress, v) + } + }) + + t.Run("for EBADF", func(t *testing.T) { + if v := classifySyscallError(unix.EBADF); v != FailureBadFileDescriptor { + t.Fatalf("expected '%s', got '%s'", FailureBadFileDescriptor, v) + } + }) + + t.Run("for ECONNABORTED", func(t *testing.T) { + if v := classifySyscallError(unix.ECONNABORTED); v != FailureConnectionAborted { + t.Fatalf("expected '%s', got '%s'", FailureConnectionAborted, v) + } + }) + + t.Run("for EALREADY", func(t *testing.T) { + if v := classifySyscallError(unix.EALREADY); v != FailureConnectionAlreadyInProgress { + t.Fatalf("expected '%s', got '%s'", FailureConnectionAlreadyInProgress, v) + } + }) + + t.Run("for EDESTADDRREQ", func(t *testing.T) { + if v := classifySyscallError(unix.EDESTADDRREQ); v != FailureDestinationAddressRequired { + t.Fatalf("expected '%s', got '%s'", FailureDestinationAddressRequired, v) + } + }) + + t.Run("for EINTR", func(t *testing.T) { + if v := classifySyscallError(unix.EINTR); v != FailureInterrupted { + t.Fatalf("expected '%s', got '%s'", FailureInterrupted, v) + } + }) + + t.Run("for EINVAL", func(t *testing.T) { + if v := classifySyscallError(unix.EINVAL); v != FailureInvalidArgument { + t.Fatalf("expected '%s', got '%s'", FailureInvalidArgument, v) + } + }) + + t.Run("for EMSGSIZE", func(t *testing.T) { + if v := classifySyscallError(unix.EMSGSIZE); v != FailureMessageSize { + t.Fatalf("expected '%s', got '%s'", FailureMessageSize, v) + } + }) + + t.Run("for ENETDOWN", func(t *testing.T) { + if v := classifySyscallError(unix.ENETDOWN); v != FailureNetworkDown { + t.Fatalf("expected '%s', got '%s'", FailureNetworkDown, v) + } + }) + + t.Run("for ENETRESET", func(t *testing.T) { + if v := classifySyscallError(unix.ENETRESET); v != FailureNetworkReset { + t.Fatalf("expected '%s', got '%s'", FailureNetworkReset, v) + } + }) + + t.Run("for ENETUNREACH", func(t *testing.T) { + if v := classifySyscallError(unix.ENETUNREACH); v != FailureNetworkUnreachable { + t.Fatalf("expected '%s', got '%s'", FailureNetworkUnreachable, v) + } + }) + + t.Run("for ENOBUFS", func(t *testing.T) { + if v := classifySyscallError(unix.ENOBUFS); v != FailureNoBufferSpace { + t.Fatalf("expected '%s', got '%s'", FailureNoBufferSpace, v) + } + }) + + t.Run("for ENOPROTOOPT", func(t *testing.T) { + if v := classifySyscallError(unix.ENOPROTOOPT); v != FailureNoProtocolOption { + t.Fatalf("expected '%s', got '%s'", FailureNoProtocolOption, v) + } + }) + + t.Run("for ENOTSOCK", func(t *testing.T) { + if v := classifySyscallError(unix.ENOTSOCK); v != FailureNotASocket { + t.Fatalf("expected '%s', got '%s'", FailureNotASocket, v) + } + }) + + t.Run("for ENOTCONN", func(t *testing.T) { + if v := classifySyscallError(unix.ENOTCONN); v != FailureNotConnected { + t.Fatalf("expected '%s', got '%s'", FailureNotConnected, v) + } + }) + + t.Run("for EWOULDBLOCK", func(t *testing.T) { + if v := classifySyscallError(unix.EWOULDBLOCK); v != FailureOperationWouldBlock { + t.Fatalf("expected '%s', got '%s'", FailureOperationWouldBlock, v) + } + }) + + t.Run("for EACCES", func(t *testing.T) { + if v := classifySyscallError(unix.EACCES); v != FailurePermissionDenied { + t.Fatalf("expected '%s', got '%s'", FailurePermissionDenied, v) + } + }) + + t.Run("for EPROTONOSUPPORT", func(t *testing.T) { + if v := classifySyscallError(unix.EPROTONOSUPPORT); v != FailureProtocolNotSupported { + t.Fatalf("expected '%s', got '%s'", FailureProtocolNotSupported, v) + } + }) + + t.Run("for EPROTOTYPE", func(t *testing.T) { + if v := classifySyscallError(unix.EPROTOTYPE); v != FailureWrongProtocolType { + t.Fatalf("expected '%s', got '%s'", FailureWrongProtocolType, v) + } + }) + + t.Run("for the zero errno value", func(t *testing.T) { + if v := classifySyscallError(syscall.Errno(0)); v != "" { + t.Fatalf("expected empty string, got '%s'", v) + } + }) +} diff --git a/internal/netxlite/errno_windows.go b/internal/netxlite/errno_windows.go index faa31f0..7d23f76 100644 --- a/internal/netxlite/errno_windows.go +++ b/internal/netxlite/errno_windows.go @@ -1,10 +1,17 @@ // Code generated by go generate; DO NOT EDIT. -// Generated: 2021-09-28 18:13:54.317744 +0200 CEST m=+0.409422292 +// Generated: 2021-09-29 10:33:56.628771 +0200 CEST m=+0.563439293 package netxlite -import "golang.org/x/sys/windows" +import ( + "errors" + "syscall" + "golang.org/x/sys/windows" +) + +// This enumeration provides a canonical name for +// every system-call error we support on this systems. const ( ECONNREFUSED = windows.WSAECONNREFUSED ECONNRESET = windows.WSAECONNRESET @@ -33,4 +40,80 @@ const ( EACCES = windows.WSAEACCES EPROTONOSUPPORT = windows.WSAEPROTONOSUPPORT EPROTOTYPE = windows.WSAEPROTOTYPE + WSANO_DATA = windows.WSANO_DATA + WSANO_RECOVERY = windows.WSANO_RECOVERY + WSATRY_AGAIN = windows.WSATRY_AGAIN ) + +// classifySyscallError converts a syscall error to the +// proper OONI error. Returns the OONI error string +// on success, an empty string otherwise. +func classifySyscallError(err error) string { + var errno syscall.Errno + if !errors.As(err, &errno) { + return "" + } + switch errno { + case windows.WSAECONNREFUSED: + return FailureConnectionRefused + case windows.WSAECONNRESET: + return FailureConnectionReset + case windows.WSAEHOSTUNREACH: + return FailureHostUnreachable + case windows.WSAETIMEDOUT: + return FailureTimedOut + case windows.WSAEAFNOSUPPORT: + return FailureAddressFamilyNotSupported + case windows.WSAEADDRINUSE: + return FailureAddressInUse + case windows.WSAEADDRNOTAVAIL: + return FailureAddressNotAvailable + case windows.WSAEISCONN: + return FailureAlreadyConnected + case windows.WSAEFAULT: + return FailureBadAddress + case windows.WSAEBADF: + return FailureBadFileDescriptor + case windows.WSAECONNABORTED: + return FailureConnectionAborted + case windows.WSAEALREADY: + return FailureConnectionAlreadyInProgress + case windows.WSAEDESTADDRREQ: + return FailureDestinationAddressRequired + case windows.WSAEINTR: + return FailureInterrupted + case windows.WSAEINVAL: + return FailureInvalidArgument + case windows.WSAEMSGSIZE: + return FailureMessageSize + case windows.WSAENETDOWN: + return FailureNetworkDown + case windows.WSAENETRESET: + return FailureNetworkReset + case windows.WSAENETUNREACH: + return FailureNetworkUnreachable + case windows.WSAENOBUFS: + return FailureNoBufferSpace + case windows.WSAENOPROTOOPT: + return FailureNoProtocolOption + case windows.WSAENOTSOCK: + return FailureNotASocket + case windows.WSAENOTCONN: + return FailureNotConnected + case windows.WSAEWOULDBLOCK: + return FailureOperationWouldBlock + case windows.WSAEACCES: + return FailurePermissionDenied + case windows.WSAEPROTONOSUPPORT: + return FailureProtocolNotSupported + case windows.WSAEPROTOTYPE: + return FailureWrongProtocolType + case windows.WSANO_DATA: + return FailureDNSNoAnswer + case windows.WSANO_RECOVERY: + return FailureDNSNonRecoverableFailure + case windows.WSATRY_AGAIN: + return FailureDNSTemporaryFailure + } + return "" +} diff --git a/internal/netxlite/errno_windows_test.go b/internal/netxlite/errno_windows_test.go new file mode 100644 index 0000000..c411eeb --- /dev/null +++ b/internal/netxlite/errno_windows_test.go @@ -0,0 +1,206 @@ +// Code generated by go generate; DO NOT EDIT. +// Generated: 2021-09-29 10:33:56.675065 +0200 CEST m=+0.609734418 + +package netxlite + +import ( + "io" + "syscall" + "testing" + + "golang.org/x/sys/windows" +) + +func TestClassifySyscallError(t *testing.T) { + t.Run("for a non-syscall error", func(t *testing.T) { + if v := classifySyscallError(io.EOF); v != "" { + t.Fatalf("expected empty string, got '%s'", v) + } + }) + + t.Run("for WSAECONNREFUSED", func(t *testing.T) { + if v := classifySyscallError(windows.WSAECONNREFUSED); v != FailureConnectionRefused { + t.Fatalf("expected '%s', got '%s'", FailureConnectionRefused, v) + } + }) + + t.Run("for WSAECONNRESET", func(t *testing.T) { + if v := classifySyscallError(windows.WSAECONNRESET); v != FailureConnectionReset { + t.Fatalf("expected '%s', got '%s'", FailureConnectionReset, v) + } + }) + + t.Run("for WSAEHOSTUNREACH", func(t *testing.T) { + if v := classifySyscallError(windows.WSAEHOSTUNREACH); v != FailureHostUnreachable { + t.Fatalf("expected '%s', got '%s'", FailureHostUnreachable, v) + } + }) + + t.Run("for WSAETIMEDOUT", func(t *testing.T) { + if v := classifySyscallError(windows.WSAETIMEDOUT); v != FailureTimedOut { + t.Fatalf("expected '%s', got '%s'", FailureTimedOut, v) + } + }) + + t.Run("for WSAEAFNOSUPPORT", func(t *testing.T) { + if v := classifySyscallError(windows.WSAEAFNOSUPPORT); v != FailureAddressFamilyNotSupported { + t.Fatalf("expected '%s', got '%s'", FailureAddressFamilyNotSupported, v) + } + }) + + t.Run("for WSAEADDRINUSE", func(t *testing.T) { + if v := classifySyscallError(windows.WSAEADDRINUSE); v != FailureAddressInUse { + t.Fatalf("expected '%s', got '%s'", FailureAddressInUse, v) + } + }) + + t.Run("for WSAEADDRNOTAVAIL", func(t *testing.T) { + if v := classifySyscallError(windows.WSAEADDRNOTAVAIL); v != FailureAddressNotAvailable { + t.Fatalf("expected '%s', got '%s'", FailureAddressNotAvailable, v) + } + }) + + t.Run("for WSAEISCONN", func(t *testing.T) { + if v := classifySyscallError(windows.WSAEISCONN); v != FailureAlreadyConnected { + t.Fatalf("expected '%s', got '%s'", FailureAlreadyConnected, v) + } + }) + + t.Run("for WSAEFAULT", func(t *testing.T) { + if v := classifySyscallError(windows.WSAEFAULT); v != FailureBadAddress { + t.Fatalf("expected '%s', got '%s'", FailureBadAddress, v) + } + }) + + t.Run("for WSAEBADF", func(t *testing.T) { + if v := classifySyscallError(windows.WSAEBADF); v != FailureBadFileDescriptor { + t.Fatalf("expected '%s', got '%s'", FailureBadFileDescriptor, v) + } + }) + + t.Run("for WSAECONNABORTED", func(t *testing.T) { + if v := classifySyscallError(windows.WSAECONNABORTED); v != FailureConnectionAborted { + t.Fatalf("expected '%s', got '%s'", FailureConnectionAborted, v) + } + }) + + t.Run("for WSAEALREADY", func(t *testing.T) { + if v := classifySyscallError(windows.WSAEALREADY); v != FailureConnectionAlreadyInProgress { + t.Fatalf("expected '%s', got '%s'", FailureConnectionAlreadyInProgress, v) + } + }) + + t.Run("for WSAEDESTADDRREQ", func(t *testing.T) { + if v := classifySyscallError(windows.WSAEDESTADDRREQ); v != FailureDestinationAddressRequired { + t.Fatalf("expected '%s', got '%s'", FailureDestinationAddressRequired, v) + } + }) + + t.Run("for WSAEINTR", func(t *testing.T) { + if v := classifySyscallError(windows.WSAEINTR); v != FailureInterrupted { + t.Fatalf("expected '%s', got '%s'", FailureInterrupted, v) + } + }) + + t.Run("for WSAEINVAL", func(t *testing.T) { + if v := classifySyscallError(windows.WSAEINVAL); v != FailureInvalidArgument { + t.Fatalf("expected '%s', got '%s'", FailureInvalidArgument, v) + } + }) + + t.Run("for WSAEMSGSIZE", func(t *testing.T) { + if v := classifySyscallError(windows.WSAEMSGSIZE); v != FailureMessageSize { + t.Fatalf("expected '%s', got '%s'", FailureMessageSize, v) + } + }) + + t.Run("for WSAENETDOWN", func(t *testing.T) { + if v := classifySyscallError(windows.WSAENETDOWN); v != FailureNetworkDown { + t.Fatalf("expected '%s', got '%s'", FailureNetworkDown, v) + } + }) + + t.Run("for WSAENETRESET", func(t *testing.T) { + if v := classifySyscallError(windows.WSAENETRESET); v != FailureNetworkReset { + t.Fatalf("expected '%s', got '%s'", FailureNetworkReset, v) + } + }) + + t.Run("for WSAENETUNREACH", func(t *testing.T) { + if v := classifySyscallError(windows.WSAENETUNREACH); v != FailureNetworkUnreachable { + t.Fatalf("expected '%s', got '%s'", FailureNetworkUnreachable, v) + } + }) + + t.Run("for WSAENOBUFS", func(t *testing.T) { + if v := classifySyscallError(windows.WSAENOBUFS); v != FailureNoBufferSpace { + t.Fatalf("expected '%s', got '%s'", FailureNoBufferSpace, v) + } + }) + + t.Run("for WSAENOPROTOOPT", func(t *testing.T) { + if v := classifySyscallError(windows.WSAENOPROTOOPT); v != FailureNoProtocolOption { + t.Fatalf("expected '%s', got '%s'", FailureNoProtocolOption, v) + } + }) + + t.Run("for WSAENOTSOCK", func(t *testing.T) { + if v := classifySyscallError(windows.WSAENOTSOCK); v != FailureNotASocket { + t.Fatalf("expected '%s', got '%s'", FailureNotASocket, v) + } + }) + + t.Run("for WSAENOTCONN", func(t *testing.T) { + if v := classifySyscallError(windows.WSAENOTCONN); v != FailureNotConnected { + t.Fatalf("expected '%s', got '%s'", FailureNotConnected, v) + } + }) + + t.Run("for WSAEWOULDBLOCK", func(t *testing.T) { + if v := classifySyscallError(windows.WSAEWOULDBLOCK); v != FailureOperationWouldBlock { + t.Fatalf("expected '%s', got '%s'", FailureOperationWouldBlock, v) + } + }) + + t.Run("for WSAEACCES", func(t *testing.T) { + if v := classifySyscallError(windows.WSAEACCES); v != FailurePermissionDenied { + t.Fatalf("expected '%s', got '%s'", FailurePermissionDenied, v) + } + }) + + t.Run("for WSAEPROTONOSUPPORT", func(t *testing.T) { + if v := classifySyscallError(windows.WSAEPROTONOSUPPORT); v != FailureProtocolNotSupported { + t.Fatalf("expected '%s', got '%s'", FailureProtocolNotSupported, v) + } + }) + + t.Run("for WSAEPROTOTYPE", func(t *testing.T) { + if v := classifySyscallError(windows.WSAEPROTOTYPE); v != FailureWrongProtocolType { + t.Fatalf("expected '%s', got '%s'", FailureWrongProtocolType, v) + } + }) + + t.Run("for WSANO_DATA", func(t *testing.T) { + if v := classifySyscallError(windows.WSANO_DATA); v != FailureDNSNoAnswer { + t.Fatalf("expected '%s', got '%s'", FailureDNSNoAnswer, v) + } + }) + + t.Run("for WSANO_RECOVERY", func(t *testing.T) { + if v := classifySyscallError(windows.WSANO_RECOVERY); v != FailureDNSNonRecoverableFailure { + t.Fatalf("expected '%s', got '%s'", FailureDNSNonRecoverableFailure, v) + } + }) + + t.Run("for WSATRY_AGAIN", func(t *testing.T) { + if v := classifySyscallError(windows.WSATRY_AGAIN); v != FailureDNSTemporaryFailure { + t.Fatalf("expected '%s', got '%s'", FailureDNSTemporaryFailure, v) + } + }) + + t.Run("for the zero errno value", func(t *testing.T) { + if v := classifySyscallError(syscall.Errno(0)); v != "" { + t.Fatalf("expected empty string, got '%s'", v) + } + }) +} diff --git a/internal/netxlite/internal/generrno/main.go b/internal/netxlite/internal/generrno/main.go index e5c2675..536754e 100644 --- a/internal/netxlite/internal/generrno/main.go +++ b/internal/netxlite/internal/generrno/main.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "os" + "sort" "time" "github.com/iancoleman/strcase" @@ -17,15 +18,53 @@ type ErrorSpec struct { // failure is the error name according to OONI (e.g., FailureConnectionRefused). failure string + + // system specifies for which system this error is valid. If + // this value is empty then the spec is valid for all systems. + system string +} + +// IsForSystem returns true when the spec's system matches the +// given system or when the spec's system is "". +func (es *ErrorSpec) IsForSystem(system string) bool { + return es.system == system || es.system == "" } // AsErrnoName returns the name of the corresponding errno, if this // is a system error, or panics otherwise. -func (es *ErrorSpec) AsErrnoName() string { +func (es *ErrorSpec) AsErrnoName(system string) string { if !es.IsSystemError() { panic("not a system error") } - return es.errno + s := es.errno + if system == "windows" { + s = "WSA" + s + } + return s +} + +// AsCanonicalErrnoName attempts to canonicalize the errno name +// using the following algorithm: +// +// - if the error is present on all systems, use the unix name; +// +// - otherwise, use the system-name name for the error. +// +// So, for example, we will get: +// +// - EWOULDBLOCK because it's present on both Unix and Windows; +// +// - WSANO_DATA because it's Windows only. +func (es *ErrorSpec) AsCanonicalErrnoName() string { + if !es.IsSystemError() { + panic("not a system error") + } + switch es.system { + case "windows": + return es.AsErrnoName(es.system) + default: + return es.errno + } } // AsFailureVar returns the name of the failure var. @@ -41,7 +80,13 @@ func (es *ErrorSpec) AsFailureString() string { // NewSystemError constructs a new ErrorSpec representing a system // error, i.e., an error returned by a system call. func NewSystemError(errno, failure string) *ErrorSpec { - return &ErrorSpec{errno: errno, failure: failure} + return &ErrorSpec{errno: errno, failure: failure, system: ""} +} + +// NewWindowsError constructs a new ErrorSpec representing a +// Windows-only system error, i.e., an error returned by a system call. +func NewWindowsError(errno, failure string) *ErrorSpec { + return &ErrorSpec{errno: errno, failure: failure, system: "windows"} } // NewLibraryError constructs a new ErrorSpec representing a library @@ -87,6 +132,24 @@ var Specs = []*ErrorSpec{ NewSystemError("EPROTONOSUPPORT", "protocol_not_supported"), NewSystemError("EPROTOTYPE", "wrong_protocol_type"), + // Windows-only system errors. + // + // Why do we have these extra errors here? Because on Windows + // GetAddrInfoW is a system call while it's a library call + // on Unix. Because of that, the Go stdlib treats Windows and + // Unix differently and allows more syscall errors to slip + // through when we're performing DNS resolutions. + // + // Because MK handled _some_ getaddrinfo return codes, I've + // marked names compatible with MK using [*]. + // + // Implementation note: we need to specify acronyms we + // want to be upper case in uppercase here. For example, + // we must write "DNS" rather than writing "dns". + NewWindowsError("NO_DATA", "DNS_no_answer"), // [ ] WSANO_DATA + NewWindowsError("NO_RECOVERY", "DNS_non_recoverable_failure"), // [*] WSANO_RECOVERY + NewWindowsError("TRY_AGAIN", "DNS_temporary_failure"), // [*] WSATRY_AGAIN + // Implementation note: we need to specify acronyms we // want to be upper case in uppercase here. For example, // we must write "DNS" rather than writing "dns". @@ -106,6 +169,19 @@ var Specs = []*ErrorSpec{ NewLibraryError("connection_already_closed"), } +// mapSystemToLibrary maps the operating system name to the name +// of the related golang.org/x/sys/$name library. +func mapSystemToLibrary(system string) string { + switch system { + case "android", "darwin", "freebsd", "ios", "linux": + return "unix" + case "windows": + return "windows" + default: + panic(fmt.Sprintf("unsupported system: %s", system)) + } +} + func fileCreate(filename string) *os.File { filep, err := os.Create(filename) if err != nil { @@ -137,74 +213,31 @@ func gofmt(filename string) { } } -func writeSystemSpecificFile(kind, library, prefix string) { - filename := "errno_" + kind + ".go" +func writeSystemSpecificFile(system string) { + filename := "errno_" + system + ".go" filep := fileCreate(filename) + library := mapSystemToLibrary(system) fileWrite(filep, "// Code generated by go generate; DO NOT EDIT.\n") filePrintf(filep, "// Generated: %+v\n\n", time.Now()) fileWrite(filep, "package netxlite\n\n") - filePrintf(filep, "import \"golang.org/x/sys/%s\"\n\n", library) - fileWrite(filep, "const (\n") - for _, spec := range Specs { - if !spec.IsSystemError() { - continue - } - filePrintf(filep, "\t%s = %s.%s%s\n", - spec.AsErrnoName(), library, prefix, spec.AsErrnoName()) - } - fileWrite(filep, ")\n\n") - fileClose(filep) - gofmt(filename) -} - -func writeGenericFile() { - filename := "errno.go" - filep := fileCreate(filename) - fileWrite(filep, "// Code generated by go generate; DO NOT EDIT.\n") - filePrintf(filep, "// Generated: %+v\n\n", time.Now()) - fileWrite(filep, "package netxlite\n\n") - fileWrite(filep, "//go:generate go run ./internal/generrno/\n\n") fileWrite(filep, "import (\n") fileWrite(filep, "\t\"errors\"\n") fileWrite(filep, "\t\"syscall\"\n") - fileWrite(filep, ")\n\n") - - fileWrite(filep, "// This enumeration lists the failures defined at\n") - fileWrite(filep, "// https://github.com/ooni/spec/blob/master/data-formats/df-007-errors.md\n") - fileWrite(filep, "const (\n") - fileWrite(filep, "//\n") - fileWrite(filep, "// System errors\n") - fileWrite(filep, "//\n") - for _, spec := range Specs { - if !spec.IsSystemError() { - continue - } - filePrintf(filep, "\t%s = \"%s\"\n", - spec.AsFailureVar(), - spec.AsFailureString()) - } fileWrite(filep, "\n") - fileWrite(filep, "//\n") - fileWrite(filep, "// Library errors\n") - fileWrite(filep, "//\n") - for _, spec := range Specs { - if spec.IsSystemError() { - continue - } - filePrintf(filep, "\t%s = \"%s\"\n", - spec.AsFailureVar(), - spec.AsFailureString()) - } + filePrintf(filep, "\t\"golang.org/x/sys/%s\"\n", library) fileWrite(filep, ")\n\n") - fileWrite(filep, "// failureMap lists all failures so we can match them\n") - fileWrite(filep, "// when they are wrapped by quic.TransportError.\n") - fileWrite(filep, "var failuresMap = map[string]string{\n") + fileWrite(filep, "// This enumeration provides a canonical name for\n") + fileWrite(filep, "// every system-call error we support on this systems.\n") + fileWrite(filep, "const (\n") for _, spec := range Specs { - filePrintf(filep, "\t\"%s\": \"%s\",\n", - spec.AsFailureString(), spec.AsFailureString()) + if !spec.IsSystemError() || !spec.IsForSystem(system) { + continue + } + filePrintf(filep, "\t%s = %s.%s\n", + spec.AsCanonicalErrnoName(), library, spec.AsErrnoName(system)) } - fileWrite(filep, "}\n\n") + fileWrite(filep, ")\n\n") fileWrite(filep, "// classifySyscallError converts a syscall error to the\n") fileWrite(filep, "// proper OONI error. Returns the OONI error string\n") @@ -216,10 +249,10 @@ func writeGenericFile() { fileWrite(filep, "\t}\n") fileWrite(filep, "\tswitch errno {\n") for _, spec := range Specs { - if !spec.IsSystemError() { + if !spec.IsSystemError() || !spec.IsForSystem(library) { continue } - filePrintf(filep, "\tcase %s:\n", spec.AsErrnoName()) + filePrintf(filep, "\tcase %s.%s:\n", library, spec.AsErrnoName(system)) filePrintf(filep, "\t\treturn %s\n", spec.AsFailureVar()) } fileWrite(filep, "\t}\n") @@ -230,9 +263,56 @@ func writeGenericFile() { gofmt(filename) } -func writeGenericTestFile() { - filename := "errno_test.go" +func writeGenericFile() { + filename := "errno.go" filep := fileCreate(filename) + fileWrite(filep, "// Code generated by go generate; DO NOT EDIT.\n") + filePrintf(filep, "// Generated: %+v\n\n", time.Now()) + fileWrite(filep, "package netxlite\n\n") + fileWrite(filep, "//go:generate go run ./internal/generrno/\n\n") + + fileWrite(filep, "// This enumeration lists the failures defined at\n") + fileWrite(filep, "// https://github.com/ooni/spec/blob/master/data-formats/df-007-errors.md\n") + fileWrite(filep, "const (\n") + names := make(map[string]string) + for _, spec := range Specs { + names[spec.AsFailureVar()] = spec.AsFailureString() + } + var nameskeys []string + for key := range names { + nameskeys = append(nameskeys, key) + } + sort.Strings(nameskeys) + for _, key := range nameskeys { + filePrintf(filep, "\t%s = \"%s\"\n", key, names[key]) + } + fileWrite(filep, ")\n\n") + + fileWrite(filep, "// failureMap lists all failures so we can match them\n") + fileWrite(filep, "// when they are wrapped by quic.TransportError.\n") + fileWrite(filep, "var failuresMap = map[string]string{\n") + failures := make(map[string]string) + for _, spec := range Specs { + failures[spec.AsFailureString()] = spec.AsFailureString() + } + var failureskey []string + for key := range failures { + failureskey = append(failureskey, key) + } + sort.Strings(failureskey) + for _, key := range failureskey { + filePrintf(filep, "\t\"%s\": \"%s\",\n", key, failures[key]) + } + fileWrite(filep, "}\n\n") + + fileClose(filep) + gofmt(filename) +} + +func writeSystemSpecificTestFile(system string) { + filename := fmt.Sprintf("errno_%s_test.go", system) + filep := fileCreate(filename) + library := mapSystemToLibrary(system) fileWrite(filep, "// Code generated by go generate; DO NOT EDIT.\n") filePrintf(filep, "// Generated: %+v\n\n", time.Now()) @@ -241,6 +321,8 @@ func writeGenericTestFile() { fileWrite(filep, "\t\"io\"\n") fileWrite(filep, "\t\"syscall\"\n") fileWrite(filep, "\t\"testing\"\n") + fileWrite(filep, "\n") + filePrintf(filep, "\t\"golang.org/x/sys/%s\"\n", library) fileWrite(filep, ")\n\n") fileWrite(filep, "func TestClassifySyscallError(t *testing.T) {\n") @@ -251,13 +333,13 @@ func writeGenericTestFile() { fileWrite(filep, "\t})\n\n") for _, spec := range Specs { - if !spec.IsSystemError() { + if !spec.IsSystemError() || !spec.IsForSystem(library) { continue } filePrintf(filep, "\tt.Run(\"for %s\", func (t *testing.T) {\n", - spec.AsErrnoName()) - filePrintf(filep, "\t\tif v := classifySyscallError(%s); v != %s {\n", - spec.AsErrnoName(), spec.AsFailureVar()) + spec.AsErrnoName(system)) + filePrintf(filep, "\t\tif v := classifySyscallError(%s.%s); v != %s {\n", + library, spec.AsErrnoName(system), spec.AsFailureVar()) filePrintf(filep, "\t\t\tt.Fatalf(\"expected '%%s', got '%%s'\", %s, v)\n", spec.AsFailureVar()) fileWrite(filep, "\t\t}\n") @@ -275,13 +357,20 @@ func writeGenericTestFile() { gofmt(filename) } -func main() { - writeSystemSpecificFile("android", "unix", "") - writeSystemSpecificFile("darwin", "unix", "") - writeSystemSpecificFile("freebsd", "unix", "") - writeSystemSpecificFile("ios", "unix", "") - writeSystemSpecificFile("linux", "unix", "") - writeSystemSpecificFile("windows", "windows", "WSA") - writeGenericFile() - writeGenericTestFile() +// SupportedSystems contains the list of supported systems. +var SupportedSystems = []string{ + "android", + "darwin", + "freebsd", + "ios", + "linux", + "windows", +} + +func main() { + for _, system := range SupportedSystems { + writeSystemSpecificFile(system) + writeSystemSpecificTestFile(system) + } + writeGenericFile() }