def test_connect_with_log(self): client_log_file = io.StringIO() client = QuicConnection(is_client=True, secrets_log_file=client_log_file) server_log_file = io.StringIO() server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, secrets_log_file=server_log_file, ) # perform handshake client_transport, server_transport = create_transport(client, server) self.assertEqual(client_transport.sent, 4) self.assertEqual(server_transport.sent, 4) # check secrets were logged client_log = client_log_file.getvalue() server_log = server_log_file.getvalue() self.assertEqual(client_log, server_log) labels = [] for line in client_log.splitlines(): labels.append(line.split()[0]) self.assertEqual( labels, [ "QUIC_SERVER_HANDSHAKE_TRAFFIC_SECRET", "QUIC_CLIENT_HANDSHAKE_TRAFFIC_SECRET", "QUIC_SERVER_TRAFFIC_SECRET_0", "QUIC_CLIENT_TRAFFIC_SECRET_0", ], )
def test_handle_stream_frame_over_max_stream_data(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) # client receives STREAM frame frame_type = QuicFrameType.STREAM_BASE | QuicStreamFlag.OFF stream_id = 1 with self.assertRaises(QuicConnectionError) as cm: client._handle_stream_frame( tls.Epoch.ONE_RTT, frame_type, Buffer( data=encode_uint_var(stream_id) + encode_uint_var(client._local_max_stream_data_bidi_remote + 1)), ) self.assertEqual(cm.exception.error_code, QuicErrorCode.FLOW_CONTROL_ERROR) self.assertEqual(cm.exception.frame_type, frame_type) self.assertEqual(cm.exception.reason_phrase, "Over stream data limit")
def test_handle_ack_frame_ecn(self): client = QuicConnection(is_client=True) client._handle_ack_frame( tls.Epoch.ONE_RTT, QuicFrameType.ACK_ECN, Buffer(data=b"\x00\x02\x00\x00\x00\x00\x00"), )
def _consume_events(self, connection: QuicConnection) -> None: # process events event = connection.next_event() while event is not None: if isinstance(event, aioquic.events.HandshakeCompleted): if event.alpn_protocol == "h3-20": self._http[connection] = H3Connection(connection) elif event.alpn_protocol == "hq-20": self._http[connection] = H0Connection(connection) elif isinstance(event, aioquic.events.ConnectionIdIssued): self._connections[event.connection_id] = connection elif isinstance(event, aioquic.events.ConnectionIdRetired): assert self._connections[event.connection_id] == connection del self._connections[event.connection_id] # pass event to the HTTP layer http = self._http.get(connection) if http is not None: for http_event in http.handle_event(event): handle_http_event(http, http_event) event = connection.next_event() # send datagrams for data, addr in connection.datagrams_to_send(now=self._loop.time()): self._transport.sendto(data, addr) # re-arm timer """
def create_standalone_client(self): client = QuicConnection(configuration=QuicConfiguration(is_client=True)) client._ack_delay = 0 # kick-off handshake client.connect(SERVER_ADDR, now=time.time()) self.assertEqual(drop(client), 1) return client
def test_handle_path_challenge_frame(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) # server sends PATH_CHALLENGE server._send_path_challenge()
def test_handle_max_stream_data_frame(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) # client creates bidirectional stream 0 stream = client.create_stream()[1].transport self.assertEqual(stream.max_stream_data_remote, 1048576) # client receives MAX_STREAM_DATA raising limit client._handle_max_stream_data_frame( tls.Epoch.ONE_RTT, QuicFrameType.MAX_STREAM_DATA, Buffer(data=b"\x00" + encode_uint_var(1048577)), ) self.assertEqual(stream.max_stream_data_remote, 1048577) # client receives MAX_STREAM_DATA lowering limit client._handle_max_stream_data_frame( tls.Epoch.ONE_RTT, QuicFrameType.MAX_STREAM_DATA, Buffer(data=b"\x00" + encode_uint_var(1048575)), ) self.assertEqual(stream.max_stream_data_remote, 1048577)
def test_handle_stop_sending_frame_receive_only(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) # server creates unidirectional stream 3 server.create_stream(is_unidirectional=True) # client receives STOP_SENDING with self.assertRaises(QuicConnectionError) as cm: client._handle_stop_sending_frame( tls.Epoch.ONE_RTT, QuicFrameType.STOP_SENDING, Buffer(data=b"\x03\x11\x22"), ) self.assertEqual(cm.exception.error_code, QuicErrorCode.STREAM_STATE_ERROR) self.assertEqual(cm.exception.frame_type, QuicFrameType.STOP_SENDING) self.assertEqual(cm.exception.reason_phrase, "Stream is receive-only")
def client_and_server( client_options={}, client_patch=lambda x: None, server_options={}, server_patch=lambda x: None, server_stream_handler=None, transport_options={}, ): client = QuicConnection( configuration=QuicConfiguration(is_client=True, **client_options)) client_patch(client) server = QuicConnection( configuration=QuicConfiguration(is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, **server_options), stream_handler=server_stream_handler, ) server_patch(server) # perform handshake create_transport(client, server, **transport_options) yield client, server # close client.close() server.close()
def test_handle_retire_connection_id_frame(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) # client receives RETIRE_CONNECTION_ID client._handle_retire_connection_id_frame( tls.Epoch.ONE_RTT, QuicFrameType.RETIRE_CONNECTION_ID, Buffer(data=b"\x02"))
def test_handle_streams_blocked_uni_frame(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) # client receives STREAMS_BLOCKED_UNI: 0 client._handle_streams_blocked_frame(tls.Epoch.ONE_RTT, QuicFrameType.STREAMS_BLOCKED_UNI, Buffer(data=b"\x00"))
def run(url: str) -> None: # parse URL parsed = urlparse(url) assert parsed.scheme == "https", "Only HTTPS URLs are supported." if ":" in parsed.netloc: server_name, port_str = parsed.netloc.split(":") port = int(port_str) else: server_name = parsed.netloc port = 443 # prepare socket server_addr = (socket.gethostbyname(server_name), port) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # prepare QUIC connection quic = QuicConnection(configuration=QuicConfiguration( alpn_protocols=["h3-20"], is_client=True, secrets_log_file=open("/tmp/ssl.log", "w"), server_name=server_name, )) quic.connect(server_addr, now=time.time()) # send request http = H3Connection(quic) stream_id = quic.get_next_available_stream_id() http.send_headers( stream_id=stream_id, headers=[ (b":method", b"GET"), (b":scheme", parsed.scheme.encode("utf8")), (b":authority", parsed.netloc.encode("utf8")), (b":path", parsed.path.encode("utf8")), ], ) http.send_data(stream_id=stream_id, data=b"", end_stream=True) for data, addr in quic.datagrams_to_send(now=time.time()): sock.sendto(data, addr) # handle events stream_ended = False while not stream_ended: data, addr = sock.recvfrom(2048) quic.receive_datagram(data, addr, now=time.time()) for event in http.handle_events(): print(event) if isinstance(event, (DataReceived, ResponseReceived)): stream_ended = event.stream_ended for data, addr in quic.datagrams_to_send(now=time.time()): sock.sendto(data, addr)
def test_handle_connection_close_frame_app(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) self.assertEqual(client_transport.sent, 4) self.assertEqual(server_transport.sent, 4) # close server.close(error_code=QuicErrorCode.NO_ERROR) self.assertEqual(client_transport.sent, 5) self.assertEqual(server_transport.sent, 5)
def test_handle_new_token_frame(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) # client receives NEW_TOKEN client._handle_new_token_frame( tls.Epoch.ONE_RTT, QuicFrameType.NEW_TOKEN, Buffer(data=binascii.unhexlify("080102030405060708")), )
def test_handle_data_blocked_frame(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) # client receives DATA_BLOCKED: 12345 client._handle_data_blocked_frame( tls.Epoch.ONE_RTT, QuicFrameType.DATA_BLOCKED, Buffer(data=encode_uint_var(12345)), )
def test_connection_lost_with_exception(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) self.assertEqual(client_transport.sent, 4) self.assertEqual(server_transport.sent, 4) run(client.connect()) # send data over stream client_reader, client_writer = client.create_stream() client_writer.write(b"ping") run(asyncio.sleep(0)) self.assertEqual(client_transport.sent, 5) self.assertEqual(server_transport.sent, 5) # break connection exc = Exception("some error") client.connection_lost(exc) with self.assertRaises(Exception) as cm: run(client_reader.read()) self.assertEqual(cm.exception, exc)
def test_handle_max_streams_uni_frame(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) self.assertEqual(client._remote_max_streams_uni, 128) # client receives MAX_STREAMS_UNI raising limit client._handle_max_streams_uni_frame( tls.Epoch.ONE_RTT, QuicFrameType.MAX_STREAMS_UNI, Buffer(data=encode_uint_var(129)), ) self.assertEqual(client._remote_max_streams_uni, 129) # client receives MAX_STREAMS_UNI raising limit client._handle_max_streams_uni_frame( tls.Epoch.ONE_RTT, QuicFrameType.MAX_STREAMS_UNI, Buffer(data=encode_uint_var(127)), ) self.assertEqual(client._remote_max_streams_uni, 129)
def test_handle_stream_data_blocked_frame_send_only(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) # client creates unidirectional stream 2 client.create_stream(is_unidirectional=True) # client receives STREAM_DATA_BLOCKED with self.assertRaises(QuicConnectionError) as cm: client._handle_stream_data_blocked_frame( tls.Epoch.ONE_RTT, QuicFrameType.STREAM_DATA_BLOCKED, Buffer(data=b"\x02\x01"), ) self.assertEqual(cm.exception.error_code, QuicErrorCode.STREAM_STATE_ERROR) self.assertEqual(cm.exception.frame_type, QuicFrameType.STREAM_DATA_BLOCKED) self.assertEqual(cm.exception.reason_phrase, "Stream is send-only")
def datagram_received(self, datagram, addr): buf = Buffer(data=datagram) header = pull_quic_header(buf, host_cid_length=8) # version negotiation if (header.version is not None and header.version not in QuicConnection.supported_versions): self._transport.sendto( encode_quic_version_negotiation( source_cid=header.destination_cid, destination_cid=header.source_cid, supported_versions=QuicConnection.supported_versions, ), addr, ) return connection = self._connections.get(header.destination_cid, None) if connection is None and header.packet_type == PACKET_TYPE_INITIAL: # create new connection connection = QuicConnection(is_client=False, **self._kwargs) connection.connection_made(QuicConnectionTransport(self, addr)) connection.stream_created_cb = self.stream_created self._connections[connection.host_cid] = connection logger.info("%s New connection from %s" % (connection_id(connection), addr)) if connection is not None: connection.datagram_received(datagram, addr)
def test_datagram_received_retry(self): client = QuicConnection(is_client=True) client.host_cid = binascii.unhexlify("c98343fe8f5f0ff4") client.peer_cid = binascii.unhexlify("85abb547bf28be97") client_transport = FakeTransport() client.connection_made(client_transport) self.assertEqual(client_transport.sent, 1) client.datagram_received(load("retry.bin"), None) self.assertEqual(client_transport.sent, 2)
def create_standalone_client(): client = QuicConnection(is_client=True) client_transport = FakeTransport(CLIENT_ADDR) client.connection_made(client_transport) # like connect() but without waiting client._network_paths = [QuicNetworkPath(SERVER_ADDR, is_validated=True)] client._version = max(client.supported_versions) client._connect() return client, client_transport
def test_handle_unknown_frame(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) # client receives unknown frame with self.assertRaises(QuicConnectionError) as cm: client._payload_received(tls.Epoch.ONE_RTT, b"\x1e") self.assertEqual(cm.exception.error_code, QuicErrorCode.PROTOCOL_VIOLATION) self.assertEqual(cm.exception.frame_type, 0x1E) self.assertEqual(cm.exception.reason_phrase, "Unexpected frame type")
def client_and_server(): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY ) # perform handshake create_transport(client, server) yield client, server client.close() server.close()
def test_handle_new_connection_id_frame(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) # client receives NEW_CONNECTION_ID client._handle_new_connection_id_frame( tls.Epoch.ONE_RTT, QuicFrameType.NEW_CONNECTION_ID, Buffer(data=binascii.unhexlify( "02117813f3d9e45e0cacbb491b4b66b039f20406f68fede38ec4c31aba8ab1245244e8" )), )
def test_tls_error(self): client = QuicConnection(is_client=True) real_initialize = client._initialize def patched_initialize(peer_cid: bytes): real_initialize(peer_cid) client.tls._supported_versions = [tls.TLS_VERSION_1_3_DRAFT_28] client._initialize = patched_initialize server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # fail handshake client_transport, server_transport = create_transport(client, server) self.assertEqual(client_transport.sent, 2) self.assertEqual(server_transport.sent, 1)
def test_handle_stream_frame_wrong_initiator(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) # client receives STREAM frame with self.assertRaises(QuicConnectionError) as cm: client._handle_stream_frame(tls.Epoch.ONE_RTT, QuicFrameType.STREAM_BASE, Buffer(data=b"\x00")) self.assertEqual(cm.exception.error_code, QuicErrorCode.STREAM_STATE_ERROR) self.assertEqual(cm.exception.frame_type, QuicFrameType.STREAM_BASE) self.assertEqual(cm.exception.reason_phrase, "Wrong stream initiator")
def test_handle_path_response_frame_bad(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) # server receives unsollicited PATH_RESPONSE with self.assertRaises(QuicConnectionError) as cm: server._handle_path_response_frame( tls.Epoch.ONE_RTT, QuicFrameType.PATH_RESPONSE, Buffer(data=b"\x11\x22\x33\x44\x55\x66\x77\x88"), ) self.assertEqual(cm.exception.error_code, QuicErrorCode.PROTOCOL_VIOLATION) self.assertEqual(cm.exception.frame_type, QuicFrameType.PATH_RESPONSE)
def test_decryption_error(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) self.assertEqual(client_transport.sent, 4) self.assertEqual(server_transport.sent, 4) # mess with encryption key server.spaces[tls.Epoch.ONE_RTT].crypto.send.setup( tls.CipherSuite.AES_128_GCM_SHA256, bytes(48)) # close server.close(error_code=QuicErrorCode.NO_ERROR) self.assertEqual(client_transport.sent, 4) self.assertEqual(server_transport.sent, 5)
def test_datagram_received_wrong_destination_cid(self): client = QuicConnection(is_client=True) client_transport = FakeTransport() client.connection_made(client_transport) self.assertEqual(client_transport.sent, 1) client.datagram_received(load("retry.bin"), None) self.assertEqual(client_transport.sent, 1)
def _test_connect_with_version(self, client_versions, server_versions): client = QuicConnection(is_client=True) client.supported_versions = client_versions server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) server.supported_versions = server_versions # perform handshake client_transport, server_transport = create_transport(client, server) self.assertEqual(client_transport.sent, 4) self.assertEqual(server_transport.sent, 3) # check each endpoint has available connection IDs for the peer self.assertEqual( sequence_numbers(client._peer_cid_available), [1, 2, 3, 4, 5, 6, 7] ) self.assertEqual( sequence_numbers(server._peer_cid_available), [1, 2, 3, 4, 5, 6, 7] ) # send data over stream client_reader, client_writer = run(client.create_stream()) client_writer.write(b"ping") run(asyncio.sleep(0)) self.assertEqual(client_transport.sent, 5) self.assertEqual(server_transport.sent, 4) # FIXME: needs an API server_reader, server_writer = ( server.streams[0].reader, server.streams[0].writer, ) self.assertEqual(run(server_reader.read(1024)), b"ping") server_writer.write(b"pong") run(asyncio.sleep(0)) self.assertEqual(client_transport.sent, 6) self.assertEqual(server_transport.sent, 5) # client receives pong self.assertEqual(run(client_reader.read(1024)), b"pong") # client writes EOF client_writer.write_eof() run(asyncio.sleep(0)) self.assertEqual(client_transport.sent, 7) self.assertEqual(server_transport.sent, 6) # server receives EOF self.assertEqual(run(server_reader.read()), b"")