Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cheroot ('s test suite?) is hardcoding error codes specific to x86 Linux #736

Open
1 of 3 tasks
mgorny opened this issue Nov 30, 2024 · 0 comments
Open
1 of 3 tasks
Labels
bug Something is broken triage

Comments

@mgorny
Copy link
Contributor

mgorny commented Nov 30, 2024

❓ 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 */
@mgorny mgorny added bug Something is broken triage labels Nov 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something is broken triage
Projects
None yet
Development

No branches or pull requests

1 participant