Skip to content

Commit

Permalink
#3376 more complete protocol close methods
Browse files Browse the repository at this point in the history
with aioquic 1.2, we can provide the QuicErrorCode and reason,
and the protocol implementations can send more specific messages before that,
WebSocket and WebTransport protocols have 'close' frames too
  • Loading branch information
totaam committed Jul 13, 2024
1 parent 1123777 commit 8aa7b24
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 22 deletions.
8 changes: 6 additions & 2 deletions xpra/net/quic/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,12 @@ def http_event_received(self, event: H3Event) -> None:
if header == b"sec-websocket-protocol":
subprotocols = value.decode().split(",")
if "xpra" not in subprotocols:
log.warn(f"Warning: unsupported websocket subprotocols {subprotocols}")
self.close()
message = f"unsupported websocket subprotocols {subprotocols}"
log.warn(f"Warning: {message}")
if not self.accepted:
self.send_headers(self.stream_id, headers={":status": 501})
self.transmit()
self.close(QuicErrorCode.APPLICATION_ERROR, message)
return
self.accepted = True
self.flush_writes()
Expand Down
21 changes: 15 additions & 6 deletions xpra/net/quic/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from aioquic.h0.connection import H0Connection
from aioquic.h3.connection import H3Connection
from aioquic.h3.events import DataReceived, DatagramReceived, H3Event
from aioquic.quic.packet import QuicErrorCode

from xpra.net.quic.asyncio_thread import get_threaded_loop
from xpra.net.bytestreams import Connection
Expand Down Expand Up @@ -84,19 +85,27 @@ def http_event_received(self, event: H3Event) -> None:
else:
log.warn(f"Warning: unhandled websocket http event {event}")

def close(self) -> None:
def close(self, code=QuicErrorCode.NO_ERROR, reason="closing") -> None:
log("quic.close()")
if not self.closed:
try:
self.send_close()
self.send_close(code, reason)
finally:
self.closed = True
Connection.close(self)

def send_close(self, code: int = 1000, reason: str = "") -> None:
if not self.accepted:
self.send_headers(self.stream_id, headers={":status": code})
self.transmit()
def send_close(self, code=QuicErrorCode.NO_ERROR, reason="") -> None:
# we just close the quic connection here
# subclasses may also override this method to send close messages specific to the protocol
# ie: WebSocket and WebTransport 'close' frames
quic = getattr(self.connection, "_quic", None)
if not quic:
return
if aioquic_version_info >= (1, 2):
# we can send the error code and message
quic.close(code, reason)
else:
quic.close()

def send_headers(self, stream_id: int, headers: dict) -> None:
self.connection.send_headers(
Expand Down
11 changes: 7 additions & 4 deletions xpra/net/quic/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from aioquic.h0.connection import H0Connection
from aioquic.h3.connection import H3Connection
from aioquic.h3.events import H3Event
from aioquic.quic.packet import QuicErrorCode

from xpra.net.quic.common import SERVER_NAME, http_date, binary_headers
from xpra.net.http.directory_listing import list_directory
Expand Down Expand Up @@ -63,8 +64,9 @@ def http_event_received(self, event: H3Event) -> None:
log(f"http_event_received(%s) scope={self.scope}", Ellipsizer(event))
http_version = self.scope.get("http_version", "0")
if http_version != "3":
log.error(f"Error: http version {http_version} is not supported")
self.protocol.close()
message = "http version {http_version} is not supported"
log.error(f"Error: {message}")
self.protocol.close(QuicErrorCode.APPLICATION_ERROR, message)
return
method = self.scope.get("method", "")
req_path = self.scope.get("path", "")
Expand All @@ -79,8 +81,9 @@ def http_event_received(self, event: H3Event) -> None:
self.send_http3_response(*script_response)
return
if method != "GET":
log.warn(f"Warning: http {method} requests are not supported")
self.protocol.close()
message = f"http {method} requests are not supported"
log.warn(f"Warning: {message}")
self.protocol.close(QuicErrorCode.APPLICATION_ERROR, message)
return
self.handle_get_request(req_path)

Expand Down
18 changes: 13 additions & 5 deletions xpra/net/quic/websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from aioquic.h3.events import HeadersReceived, H3Event
from aioquic.h3.exceptions import NoAvailablePushIDError
from aioquic.quic.packet import QuicErrorCode

from xpra.net.bytestreams import pretty_socket
from xpra.net.quic.connection import XpraQuicConnection
Expand Down Expand Up @@ -56,8 +57,9 @@ def http_event_received(self, event: H3Event) -> None:
if isinstance(event, HeadersReceived):
subprotocols = self.scope.get("subprotocols", ())
if "xpra" not in subprotocols:
log.warn(f"Warning: unsupported websocket subprotocols {subprotocols}")
self.close()
message = f"unsupported websocket subprotocols {subprotocols}"
log.warn(f"Warning: {message}")
self.close(QuicErrorCode.APPLICATION_ERROR, message)
return
log.info("websocket request at %s", self.scope.get("path", "/"))
self.accepted = True
Expand All @@ -74,12 +76,18 @@ def send_accept(self, stream_id: int) -> None:
"sec-websocket-protocol": "xpra",
})

def send_close(self, code: int = 1000, reason: str = "") -> None:
def send_close(self, code=QuicErrorCode.NO_ERROR, reason="") -> None:
wscode = 1000 if code == QuicErrorCode.NO_ERROR else 4000 + int(code)
self.send_ws_close(wscode, reason)
super().send_close(code, reason)

def send_ws_close(self, code: int = 1000, reason: str = "") -> None:
if self.accepted:
data = close_packet(code, reason)
self.write(data, "close")
return
super.send_close(code, reason)
else:
self.send_headers(self.stream_id, headers={":status": code})
self.transmit()

def get_packet_stream_id(self, packet_type: str) -> int:
if self.closed or not self._use_substreams or not packet_type:
Expand Down
16 changes: 11 additions & 5 deletions xpra/net/quic/webtransport.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
H3Event,
WebTransportStreamDataReceived,
)
from aioquic.quic.packet import QuicErrorCode

from xpra.net.bytestreams import pretty_socket
from xpra.net.quic.connection import XpraQuicConnection, HttpConnection
from xpra.net.quic.common import SERVER_NAME, http_date
Expand Down Expand Up @@ -63,12 +65,16 @@ def send_accept(self) -> None:
self.send_headers(self.stream_id, headers)
self.transmit()

def send_close(self, code: int = 403, reason: str = "") -> None:
log(f"send_close({code}, {reason!r})")
def send_close(self, code=QuicErrorCode.NO_ERROR, reason="") -> None:
if not self.accepted:
self.closed = True
self.send_headers(0, {":status": code})
self.transmit()
httpcode = 200 if code == QuicErrorCode.NO_ERROR else 500
self.send_http_close(httpcode, reason)
super().send_close(code, reason)

def send_http_close(self, code: int = 500, reason: str = "") -> None:
log(f"send_http_close({code}, {reason!r})")
self.send_headers(0, {":status": code})
self.transmit()

def do_write(self, stream_id: int, data: bytes) -> None:
log("wt.do_write(%i, %i bytes)", stream_id, len(data))
Expand Down

0 comments on commit 8aa7b24

Please sign in to comment.