This commit is contained in:
Curd Becker 2024-06-09 09:02:26 +08:00 committed by GitHub
commit 36091fa1dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 34 additions and 4 deletions

View File

@ -185,6 +185,11 @@ Traffic Legend:
else: else:
self.heartbeat = None self.heartbeat = None
# see comment below - an assertion to deal with the
# intrinsics of non-blocking SSL sockets and select.
assert not isinstance(target, ssl.SSLSocket) \
or (self.buffer_size >= 16 * 1024 and not target.getblocking())
while True: while True:
wlist = [] wlist = []
@ -242,11 +247,15 @@ Traffic Legend:
self.server.target_host, self.server.target_port) self.server.target_host, self.server.target_port)
raise self.CClose(closed['code'], closed['reason']) raise self.CClose(closed['code'], closed['reason'])
if target in outs: if target in outs:
# Send queued client data to the target # Send queued client data to the target
dat = tqueue.pop(0) dat = tqueue.pop(0)
try:
sent = target.send(dat) sent = target.send(dat)
except ssl.SSLWantWriteError:
# nothing was sent - sending needs to be retried later
sent = 0
if sent == len(dat): if sent == len(dat):
self.print_traffic(">") self.print_traffic(">")
else: else:
@ -254,10 +263,27 @@ Traffic Legend:
tqueue.insert(0, dat[sent:]) tqueue.insert(0, dat[sent:])
self.print_traffic(".>") self.print_traffic(".>")
if target in ins: if target in ins:
# Receive target data, encode it and queue for client # Receive target data, encode it and queue for client
try:
# It is strictly required that buffer size is more than 16kb, so that
# all possible data can be received from a SSL socket in a single call.
# Otherwise, there could be still data available for reading, but select
# wouldn't report the socket as readable again, since all data has already
# been read by the SSL socket from the OS.
# see also: https://docs.python.org/3/library/ssl.html#notes-on-non-blocking-sockets
# The maximum size of a single SSL record is 16kb. And OpenSSL's SSL_read()
# will only return data from the current record - until it has been fully read.
# see also: https://www.openssl.org/docs/man1.1.1/man3/SSL_read.html
buf = target.recv(self.buffer_size) buf = target.recv(self.buffer_size)
except ssl.SSLWantReadError:
# The underlying OS socket had data, but there isn't any data to
# receive from the SSL socket yet (SSL record not yet fully received,
# only SSL control data received, e.g. re-handshake, session tickets, ...)
# Let's retry later.
continue
if len(buf) == 0: if len(buf) == 0:
# Target socket closed, flushing queues and closing client-side websocket # Target socket closed, flushing queues and closing client-side websocket

View File

@ -471,6 +471,10 @@ class WebSockifyServer():
sock.connect(addrs[0][4]) sock.connect(addrs[0][4])
if use_ssl: if use_ssl:
sock = ssl.wrap_socket(sock) sock = ssl.wrap_socket(sock)
# SSL socket must be non-blocking in order to properly
# handle receiving and sending data using select.
# See also the comment(s) in ProxyRequestHandler.do_proxy()
sock.setblocking(0)
else: else:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(addrs[0][4]) sock.bind(addrs[0][4])