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() }