Files
Ivan Mironov c07384a565 Rustls integration improvements (#7946)
* Do not call `import socket` on each send()/recv() when using rustls

Use method references cached during socket creation.

* Implement reading of at most one TLS record from socket

Previous algorithm didn't take into account that recv() may return less
data than requested even for blocking sockets.

* Remove special handling of rustls "buffer full" errors

First of all, existing code does not really work and this leads to an
infinite loop: https://github.com/RustPython/RustPython/issues/7891

Second, this should never happen when rustls used properly (wrt
wants_read() and wants_write()) and thus all such errors are
implementation bugs that must be properly fixed.

* Replace own TlsConnection with rustls::Connection

* Fix waiting on a socket

1) Ensure that socket_wait() called from TLS glue code allows threads
2) Ensure that socket_wait() called from TLS glue code properly handles
   EINTR on *nix
3) Ensure that select() or poll() error conditions are checked
4) Use poll() on *nix so socket descriptor values are not limited

* Remove dead code from rustls glue

* Do not present rustls errors as OSError(0, "Success")

* Remove infinite loop "detection" from rustls glue

TLS handshake cannot be infinite. Any infinite loop here is a serious
bug in implementation and should be fixed properly.

This code triggers in some cases (very short reads) with misleading
`ssl_error.SSLWantReadError: The operation did not complete (read)`.

* Add test for 1-byte max recv in TLS client

* Add regression test for https://github.com/RustPython/RustPython/issues/7891

* Fix constants in rustls glue code

* Deduplicate verify flags / record-size constants
* Larger "max encrypted TLS record length"
2026-05-22 20:04:15 +09:00

89 lines
2.2 KiB
Python

import os
import socket
import ssl
import sys
import threading
if sys.implementation.name.lower() != "rustpython":
print("Ignored: stdlib_ssl_short_recv (RustPython only)")
raise SystemExit
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
CERTFILE = os.path.join(ROOT_DIR, "Lib/test/certdata/keycert.pem")
DATA = b"x" * 128
orig_recv = socket.socket.recv
client_sockname = None
recv_n = {}
def new_recv(sock, bufsize, flags=0):
sockname = sock.getsockname()
if sockname not in recv_n:
recv_n[sockname] = 0
bufsize = 1
if flags & socket.MSG_PEEK == 0:
recv_n[sockname] += 1
return orig_recv(sock, bufsize, flags)
socket.socket.recv = new_recv
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listener.bind(("127.0.0.1", 0))
listener.listen(1)
addr, port = listener.getsockname()
server_errors = []
def server():
try:
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
server_context.load_cert_chain(CERTFILE)
sock, _ = listener.accept()
sock.settimeout(5.0)
ssock = server_context.wrap_socket(sock, server_side=True)
try:
ssock.sendall(DATA)
finally:
ssock.close()
except BaseException as exc:
server_errors.append(exc)
finally:
listener.close()
thread = threading.Thread(target=server)
thread.start()
raw = socket.create_connection((addr, port), timeout=5.0)
client_sockname = raw.getsockname()
raw.settimeout(5.0)
client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
client_context.check_hostname = False
client_context.verify_mode = ssl.CERT_NONE
client = client_context.wrap_socket(raw, server_hostname=None)
try:
chunks = []
while sum(len(chunk) for chunk in chunks) < len(DATA):
chunk = client.recv(20000)
if not chunk:
break
chunks.append(chunk)
finally:
client.close()
thread.join(10.0)
assert not thread.is_alive(), "server thread did not stop"
assert not server_errors, server_errors
assert b"".join(chunks) == DATA
assert len(recv_n) == 2
assert all(n > 100 for n in recv_n.values())