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

SSLEOFError in Python 3.10 not in Python 3.9 #2733

Open
adamtheturtle opened this issue Sep 3, 2022 · 3 comments
Open

SSLEOFError in Python 3.10 not in Python 3.9 #2733

adamtheturtle opened this issue Sep 3, 2022 · 3 comments

Comments

@adamtheturtle
Copy link

adamtheturtle commented Sep 3, 2022

Apologies if this is not the right place for this - I was torn between here and StackOverflow.

Summary: Code raises an exception on Python 3.10.6 and not Python 3.9.13. I would like to fully understand why and hopefully avoid the exception.

Simple reproducible case:

import urllib3 # This is urllib3==1.26.12

http = urllib3.PoolManager()
response = http.request(
    "POST",
    'https://cloudreco.vuforia.com/v1/query',
    body="a" * 100000000,  # A large body to hit the error.
)

print(response.data)

In Python 3.9, I get no exception. I see:

b'<html>\r\n<head><title>413 Request Entity Too Large</title></head>\r\n<body>\r\n<center><h1>413 Request Entity Too Large</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n'

This is the response that I want.

In Python 3.10, I get the following traceback:

Traceback (most recent call last):
  File "/Users/adam/Documents/repositories/personal/vws-python-mock/minimal.py", line 5, in <module>
    response = http.request(
  File "/Users/adam/.virtualenvs/vws-python-mock/lib/python3.10/site-packages/urllib3/request.py", line 78, in request
    return self.request_encode_body(
  File "/Users/adam/.virtualenvs/vws-python-mock/lib/python3.10/site-packages/urllib3/request.py", line 170, in request_encode_body
    return self.urlopen(method, url, **extra_kw)
  File "/Users/adam/.virtualenvs/vws-python-mock/lib/python3.10/site-packages/urllib3/poolmanager.py", line 376, in urlopen
    response = conn.urlopen(method, u.request_uri, **kw)
  File "/Users/adam/.virtualenvs/vws-python-mock/lib/python3.10/site-packages/urllib3/connectionpool.py", line 815, in urlopen
    return self.urlopen(
  File "/Users/adam/.virtualenvs/vws-python-mock/lib/python3.10/site-packages/urllib3/connectionpool.py", line 815, in urlopen
    return self.urlopen(
  File "/Users/adam/.virtualenvs/vws-python-mock/lib/python3.10/site-packages/urllib3/connectionpool.py", line 815, in urlopen
    return self.urlopen(
  File "/Users/adam/.virtualenvs/vws-python-mock/lib/python3.10/site-packages/urllib3/connectionpool.py", line 787, in urlopen
    retries = retries.increment(
  File "/Users/adam/.virtualenvs/vws-python-mock/lib/python3.10/site-packages/urllib3/util/retry.py", line 592, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='cloudreco.vuforia.com', port=443): Max retries exceeded with url: /v1/query (Caused by SSLError(SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:2396)')))
  • If I use a smaller body, I get expected responses from the server.
  • These results are consistent between my Python 3.9 / Python 3.10 installations on macOS with Homebrew, and on GitHub Actions.
  • python -c "import ssl; print(ssl.OPENSSL_VERSION)" shows OpenSSL 1.1.1q 5 Jul 2022 for both Python 3.9 and Python 3.10.
  • In practice I am using requests but I narrowed the example to urllib3.

What I have tried so far:

  • I have found that ssl.OP_IGNORE_UNEXPECTED_EOF is new in Python 3.10, and that looks relevant. However, it only works with OpenSSL >= 3.0.0. I need to support Python with OpenSSL 1.1.1q.
  • I have tried to understand the differences between Python 3.9 and Python 3.10, and I have understood that there is a change in the default ciphers. I have tried many ways of setting the ciphers, and I would appreciate help with finding the "right" way, but I believe that I may have exhausted this avenue.

What I tried:

Likely irrelevant code
# I forget where I got this list from.
cipher_list = [
    "!MD5",
    "!aNULL",
    "!eNULL",
    "AES128-GCM-SHA256",
    "AES128-GCM-SHA256",
    "AES128-SHA",
    "AES128-SHA256",
    "AES256-GCM-SHA384",
    "AES256-SHA",
    "AES256-SHA256",
    "DES-CBC3-SHA",
    "DH+3DES",
    "DH+AES",
    "DH+AES256",
    "DH+AESGCM",
    "DH+HIGH",
    "DHE-RSA-AES128-SHA",
    "DHE-RSA-AES256-SHA",
    "ECDH+3DES",
    "ECDH+AES128",
    "ECDH+AES256",
    "ECDH+AESGCM",
    "ECDH+HIGH",
    "ECDHE-ECDSA-AES128-SHA",
    "ECDHE-ECDSA-AES256-SHA",
    "ECDHE-RSA-AES128-SHA",
    "RSA+3DES",
    "RSA+AES",
    "RSA+AESGCM",
    "RSA+HIGH",
    "TLS_AES_128_GCM_SHA256",
    "TLS_AES_256_GCM_SHA384",
    "TLS_CHACHA20_POLY1305_SHA256",
    "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
    "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
    "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
    "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
    "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
    "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
    "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
    "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
    "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
    "TLS_ECDHE_RSA_AES_256_CBC_SHA1",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
    "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
    "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
    "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
    "TLS_EMPTY_RENEGOTIATION_INFO_SCSV",
    "TLS_RSA_WITH_AES_128_CBC_SHA",
    "TLS_RSA_WITH_AES_128_CBC_SHA256",
    "TLS_RSA_WITH_AES_128_GCM_SHA256",
    "TLS_RSA_WITH_AES_256_CBC_SHA",
    "TLS_RSA_WITH_AES_256_CBC_SHA256",
    "TLS_RSA_WITH_AES_256_GCM_SHA384",
]

ciphers = ":".join(cipher_list)
urllib3.util.ssl_.DEFAULT_CIPHERS = ciphers

Context

I am building a mock for the 'https://cloudreco.vuforia.com/v1/query' service.
I want the mock to behave the same when a body is too large as the real service does, so that the users of the mock can handle errors correctly.
In this case, a requests user for example may need to try ... except SSLError: ... with the real service, and I want them to hit this same error when using the mock.
If I can fully understand what the difference is in the (handling of the) request from Python 3.9 versus Python 3.10, I hope that this will help me to make the mock more accurate, or at least document the exact scenarios in which it is not accurate.

@adamtheturtle
Copy link
Author

adamtheturtle commented Sep 3, 2022

I'm closing this as I reproduced the error with urllib (and not urllib3) and so this isn't the place to dig.

@adamtheturtle
Copy link
Author

I'm re-opening this as I have found what I think may be a behaviour change between Python versions that should be handled differently by urllib3.

See this code with urllib:

from urllib.request import Request, urlopen

# On Python 3.9 we get a "broken pipe", but on 3.10 we get an EOF occurred error.
body = b"a" * int(2 ** 21 + 1)

req = Request('https://cloudreco.vuforia.com/v1/query', data=body)
response = urlopen(req)

The broken pipe error is swallowed on purpose:

# We are swallowing BrokenPipeError (errno.EPIPE) since the server is
# legitimately able to close the connection after sending a valid response.
# With this behaviour, the received response is still readable.
except BrokenPipeError:
pass

but the SSLEOFError is not.

The PR which introduced the broken pipe error skipping: #1524

This is getting a little deep for me (RFCs) and such.
However, I put an except SSLEOFError: pass in connectionpool.py and ran my original sample code, and I saw the response in 3.10 just like in 3.9.
That makes me think that potentially urllib3 should make this change, to match the behaviour with the broken pipes.

@adamtheturtle adamtheturtle reopened this Sep 3, 2022
@XJTLUmedia
Copy link

I met the same problem as you when I trying to connect yahoo finance. When I was using old version of urllib3 it seems work fine, but now I got this error
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='finance.yahoo.com', port=443): Max retries exceeded with url: /quote/EQH (Caused by SSLError(SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:997)')))
hope they can look into it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants