mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
* 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"
89 lines
2.2 KiB
Python
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())
|