Description
β I'm submitting a ...
- π bug report
- π£ feature request
- β question about the decisions made in the repository
π Describe the bug. What is the current behavior?
At least the test suite (haven't checked other code) seems to hardcode a number of errno.h
error codes, based on the values present on x86 Linux. This causes the tests to fail on other Linux architectures, particularly Alpha, MIPS, PA/RISC, SPARC.
β What is the motivation / use case for changing the behavior?
A passing test suite would help us verify that the package will work on expected on these architectures that aren't tested upstream.
π‘ To Reproduce
Run pytest
on one of the affected architectures.
π‘ Expected behavior
The package not hardcoding errno values, and instead using the value obtained from the errno
Python module.
π Details
For example, on Gentoo Linux SPARC we're seeing the following failures:
=================================== FAILURES ===================================
_______________ test_plat_specific_errors[err_names1-err_nums1] ________________
err_names = ('EPROTOTYPE', 'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK', 'EPIPE')
err_nums = (91, 11, 32)
@pytest.mark.parametrize(
('err_names', 'err_nums'),
(
(('', 'some-nonsense-name'), []),
(
(
'EPROTOTYPE', 'EAGAIN', 'EWOULDBLOCK',
'WSAEWOULDBLOCK', 'EPIPE',
),
(91, 11, 32) if IS_LINUX else
(32, 35, 41) if IS_MACOS else
(98, 11, 32) if IS_SOLARIS else
(32, 10041, 11, 10035) if IS_WINDOWS else
(),
),
),
)
def test_plat_specific_errors(err_names, err_nums):
"""Test that ``plat_specific_errors`` gets correct error numbers list."""
actual_err_nums = errors.plat_specific_errors(*err_names)
assert len(actual_err_nums) == len(err_nums)
> assert sorted(actual_err_nums) == sorted(err_nums)
E assert [11, 32, 41] == [11, 32, 91]
E
E At index 2 diff: 41 != 91
E
E Full diff:
E [
E 11,
E 32,
E - 91,
E ? ^
E + 41,
E ? ^
E ]
actual_err_nums = [32, 41, 11]
err_names = ('EPROTOTYPE', 'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK', 'EPIPE')
err_nums = (91, 11, 32)
cheroot/test/test_errors.py:31: AssertionError
_________________ test_http_over_https_error[0.0.0.0-builtin] __________________
http_request_timeout = 0.1
tls_http_server = functools.partial(<function make_tls_http_server at 0xfff800011772e2a0>, request=<SubRequest 'tls_http_server' for <Function test_http_over_https_error[0.0.0.0-builtin]>>)
adapter_type = 'builtin', ca = <trustme.CA object at 0xfff800011bab9cd0>
ip_addr = '0.0.0.0'
tls_certificate = <trustme.LeafCert object at 0xfff800011baf3230>
tls_certificate_chain_pem_path = '/var/tmp/portage/dev-python/cheroot-10.0.1/temp/tmpbg1u6oc_.pem'
tls_certificate_private_key_pem_path = '/var/tmp/portage/dev-python/cheroot-10.0.1/temp/tmp_toxj2g6.pem'
@pytest.mark.parametrize(
'adapter_type',
(
'builtin',
'pyopenssl',
),
)
@pytest.mark.parametrize(
'ip_addr',
(
ANY_INTERFACE_IPV4,
pytest.param(ANY_INTERFACE_IPV6, marks=missing_ipv6),
),
)
@pytest.mark.flaky(reruns=3, reruns_delay=2)
def test_http_over_https_error(
http_request_timeout,
tls_http_server, adapter_type,
ca, ip_addr,
tls_certificate,
tls_certificate_chain_pem_path,
tls_certificate_private_key_pem_path,
):
"""Ensure that connecting over HTTP to HTTPS port is handled."""
# disable some flaky tests
# https://github.com/cherrypy/cheroot/issues/225
issue_225 = (
IS_MACOS
and adapter_type == 'builtin'
)
if issue_225:
pytest.xfail('Test fails in Travis-CI')
tls_adapter_cls = get_ssl_adapter_class(name=adapter_type)
tls_adapter = tls_adapter_cls(
tls_certificate_chain_pem_path, tls_certificate_private_key_pem_path,
)
if adapter_type == 'pyopenssl':
tls_adapter.context = tls_adapter.get_context()
tls_certificate.configure_cert(tls_adapter.context)
interface, _host, port = _get_conn_data(ip_addr)
tlshttpserver = tls_http_server((interface, port), tls_adapter)
interface, _host, port = _get_conn_data(
tlshttpserver.bind_addr,
)
fqdn = interface
if ip_addr is ANY_INTERFACE_IPV6:
fqdn = '[{fqdn}]'.format(**locals())
expect_fallback_response_over_plain_http = (
(
adapter_type == 'pyopenssl'
)
)
if expect_fallback_response_over_plain_http:
resp = requests.get(
'http://{host!s}:{port!s}/'.format(host=fqdn, port=port),
timeout=http_request_timeout,
)
assert resp.status_code == 400
assert resp.text == (
'The client sent a plain HTTP request, '
'but this server only speaks HTTPS on this port.'
)
return
with pytest.raises(requests.exceptions.ConnectionError) as ssl_err:
requests.get( # FIXME: make stdlib ssl behave like PyOpenSSL
'http://{host!s}:{port!s}/'.format(host=fqdn, port=port),
timeout=http_request_timeout,
)
if IS_LINUX:
expected_error_code, expected_error_text = (
104, 'Connection reset by peer',
)
if IS_MACOS:
expected_error_code, expected_error_text = (
54, 'Connection reset by peer',
)
if IS_WINDOWS:
expected_error_code, expected_error_text = (
10054,
'An existing connection was forcibly closed by the remote host',
)
underlying_error = ssl_err.value.args[0].args[-1]
err_text = str(underlying_error)
> assert underlying_error.errno == expected_error_code, (
'The underlying error is {underlying_error!r}'.
format(**locals())
)
E AssertionError: The underlying error is ConnectionResetError(54, 'Connection reset by peer')
E assert 54 == 104
E + where 54 = ConnectionResetError(54, 'Connection reset by peer').errno
_host = '127.0.0.1'
adapter_type = 'builtin'
ca = <trustme.CA object at 0xfff800011bab9cd0>
err_text = '[Errno 54] Connection reset by peer'
expect_fallback_response_over_plain_http = False
expected_error_code = 104
expected_error_text = 'Connection reset by peer'
fqdn = '127.0.0.1'
http_request_timeout = 0.1
interface = '127.0.0.1'
ip_addr = '0.0.0.0'
issue_225 = False
port = 37263
ssl_err = <ExceptionInfo ConnectionError(ProtocolError('Connection aborted.', ConnectionResetError(54, 'Connection reset by peer'))) tblen=6>
tls_adapter = <cheroot.ssl.builtin.BuiltinSSLAdapter object at 0xfff800011be1fe60>
tls_adapter_cls = <class 'cheroot.ssl.builtin.BuiltinSSLAdapter'>
tls_certificate = <trustme.LeafCert object at 0xfff800011baf3230>
tls_certificate_chain_pem_path = '/var/tmp/portage/dev-python/cheroot-10.0.1/temp/tmpbg1u6oc_.pem'
tls_certificate_private_key_pem_path = '/var/tmp/portage/dev-python/cheroot-10.0.1/temp/tmp_toxj2g6.pem'
tls_http_server = functools.partial(<function make_tls_http_server at 0xfff800011772e2a0>, request=<SubRequest 'tls_http_server' for <Function test_http_over_https_error[0.0.0.0-builtin]>>)
tlshttpserver = <cheroot.server.HTTPServer object at 0xfff800011be3e7b0>
underlying_error = ConnectionResetError(54, 'Connection reset by peer')
cheroot/test/test_ssl.py:697: AssertionError
----------------------------- Captured stderr call -----------------------------
Client ('127.0.0.1', 50238) attempted to speak plain HTTP into a TCP connection configured for TLS-only traffic β trying to send back a plain HTTP error response: (1, '[SSL: HTTP_REQUEST] http request (_ssl.c:1004)')
____________________ test_http_over_https_error[::-builtin] ____________________
http_request_timeout = 0.1
tls_http_server = functools.partial(<function make_tls_http_server at 0xfff800011772e2a0>, request=<SubRequest 'tls_http_server' for <Function test_http_over_https_error[::-builtin]>>)
adapter_type = 'builtin', ca = <trustme.CA object at 0xfff800011be69100>
ip_addr = '::'
tls_certificate = <trustme.LeafCert object at 0xfff800011b060c80>
tls_certificate_chain_pem_path = '/var/tmp/portage/dev-python/cheroot-10.0.1/temp/tmpeqe56lh4.pem'
tls_certificate_private_key_pem_path = '/var/tmp/portage/dev-python/cheroot-10.0.1/temp/tmptjfpplob.pem'
@pytest.mark.parametrize(
'adapter_type',
(
'builtin',
'pyopenssl',
),
)
@pytest.mark.parametrize(
'ip_addr',
(
ANY_INTERFACE_IPV4,
pytest.param(ANY_INTERFACE_IPV6, marks=missing_ipv6),
),
)
@pytest.mark.flaky(reruns=3, reruns_delay=2)
def test_http_over_https_error(
http_request_timeout,
tls_http_server, adapter_type,
ca, ip_addr,
tls_certificate,
tls_certificate_chain_pem_path,
tls_certificate_private_key_pem_path,
):
"""Ensure that connecting over HTTP to HTTPS port is handled."""
# disable some flaky tests
# https://github.com/cherrypy/cheroot/issues/225
issue_225 = (
IS_MACOS
and adapter_type == 'builtin'
)
if issue_225:
pytest.xfail('Test fails in Travis-CI')
tls_adapter_cls = get_ssl_adapter_class(name=adapter_type)
tls_adapter = tls_adapter_cls(
tls_certificate_chain_pem_path, tls_certificate_private_key_pem_path,
)
if adapter_type == 'pyopenssl':
tls_adapter.context = tls_adapter.get_context()
tls_certificate.configure_cert(tls_adapter.context)
interface, _host, port = _get_conn_data(ip_addr)
tlshttpserver = tls_http_server((interface, port), tls_adapter)
interface, _host, port = _get_conn_data(
tlshttpserver.bind_addr,
)
fqdn = interface
if ip_addr is ANY_INTERFACE_IPV6:
fqdn = '[{fqdn}]'.format(**locals())
expect_fallback_response_over_plain_http = (
(
adapter_type == 'pyopenssl'
)
)
if expect_fallback_response_over_plain_http:
resp = requests.get(
'http://{host!s}:{port!s}/'.format(host=fqdn, port=port),
timeout=http_request_timeout,
)
assert resp.status_code == 400
assert resp.text == (
'The client sent a plain HTTP request, '
'but this server only speaks HTTPS on this port.'
)
return
with pytest.raises(requests.exceptions.ConnectionError) as ssl_err:
requests.get( # FIXME: make stdlib ssl behave like PyOpenSSL
'http://{host!s}:{port!s}/'.format(host=fqdn, port=port),
timeout=http_request_timeout,
)
if IS_LINUX:
expected_error_code, expected_error_text = (
104, 'Connection reset by peer',
)
if IS_MACOS:
expected_error_code, expected_error_text = (
54, 'Connection reset by peer',
)
if IS_WINDOWS:
expected_error_code, expected_error_text = (
10054,
'An existing connection was forcibly closed by the remote host',
)
underlying_error = ssl_err.value.args[0].args[-1]
err_text = str(underlying_error)
> assert underlying_error.errno == expected_error_code, (
'The underlying error is {underlying_error!r}'.
format(**locals())
)
E AssertionError: The underlying error is ConnectionResetError(54, 'Connection reset by peer')
E assert 54 == 104
E + where 54 = ConnectionResetError(54, 'Connection reset by peer').errno
_host = '::1'
adapter_type = 'builtin'
ca = <trustme.CA object at 0xfff800011be69100>
err_text = '[Errno 54] Connection reset by peer'
expect_fallback_response_over_plain_http = False
expected_error_code = 104
expected_error_text = 'Connection reset by peer'
fqdn = '[::1]'
http_request_timeout = 0.1
interface = '::1'
ip_addr = '::'
issue_225 = False
port = 48763
ssl_err = <ExceptionInfo ConnectionError(ProtocolError('Connection aborted.', ConnectionResetError(54, 'Connection reset by peer'))) tblen=6>
tls_adapter = <cheroot.ssl.builtin.BuiltinSSLAdapter object at 0xfff800011beadf70>
tls_adapter_cls = <class 'cheroot.ssl.builtin.BuiltinSSLAdapter'>
tls_certificate = <trustme.LeafCert object at 0xfff800011b060c80>
tls_certificate_chain_pem_path = '/var/tmp/portage/dev-python/cheroot-10.0.1/temp/tmpeqe56lh4.pem'
tls_certificate_private_key_pem_path = '/var/tmp/portage/dev-python/cheroot-10.0.1/temp/tmptjfpplob.pem'
tls_http_server = functools.partial(<function make_tls_http_server at 0xfff800011772e2a0>, request=<SubRequest 'tls_http_server' for <Function test_http_over_https_error[::-builtin]>>)
tlshttpserver = <cheroot.server.HTTPServer object at 0xfff800011beae390>
underlying_error = ConnectionResetError(54, 'Connection reset by peer')
cheroot/test/test_ssl.py:697: AssertionError
----------------------------- Captured stderr call -----------------------------
Client ('::1', 36804, 0, 0) attempted to speak plain HTTP into a TCP connection configured for TLS-only traffic β trying to send back a plain HTTP error response: (1, '[SSL: HTTP_REQUEST] http request (_ssl.c:1004)')
π Environment
- Cheroot version: 10.0.1
- CherryPy version: 18.10.0
- Python version: 3.12.7
- OS: Gentoo Linux
- Browser: n/a
π Additional context
The arch-specific errno values can be found in the Linux kernel sources, e.g.:
$ grep -R '#define.*EPROTOTYPE' arch
arch/alpha/include/uapi/asm/errno.h:#define EPROTOTYPE 41 /* Protocol wrong type for socket */
arch/mips/include/uapi/asm/errno.h:#define EPROTOTYPE 98 /* Protocol wrong type for socket */
arch/parisc/include/uapi/asm/errno.h:#define EPROTOTYPE 219 /* Protocol wrong type for socket */
arch/sparc/include/uapi/asm/errno.h:#define EPROTOTYPE 41 /* Protocol wrong type for socket */