async def run_client_ping(): configuration = QuicConfiguration(is_client=True) configuration.load_verify_locations(cafile=SERVER_CACERTFILE) async with connect(self.server_host, self.server_port, configuration=configuration) as client: await client.ping() await client.ping()
async def run_client_key_update(host, port=4433): configuration = QuicConfiguration(is_client=True) configuration.load_verify_locations(cafile=SERVER_CACERTFILE) async with connect(host, port, configuration=configuration) as client: await client.ping() client.request_key_update() await client.ping()
async def run_client_ping(): configuration = QuicConfiguration(is_client=True) configuration.load_verify_locations(cafile=SERVER_CACERTFILE) async with connect(self.server_host, self.server_port, configuration=configuration) as client: coros = [client.ping() for x in range(16)] await asyncio.gather(*coros)
def init_context(certfile, keyfile, pass_phrase): from aioquic.h3.connection import H3_ALPN from aioquic.quic.configuration import QuicConfiguration import ssl ctx = QuicConfiguration(alpn_protocols=H3_ALPN, is_client=False) ctx.load_cert_chain(certfile, keyfile, pass_phrase) ctx.verify_mode = ssl.CERT_NONE return ctx
async def run_server(configuration=None, **kwargs): if configuration is None: configuration = QuicConfiguration(is_client=False) configuration.load_cert_chain(SERVER_CERTFILE, SERVER_KEYFILE) return await serve(host="::", port="4433", configuration=configuration, stream_handler=handle_stream, **kwargs)
async def run_server(self, configuration=None, host="::", **kwargs): if configuration is None: configuration = QuicConfiguration(is_client=False) configuration.load_cert_chain(SERVER_CERTFILE, SERVER_KEYFILE) self.server = await serve(host=host, port=self.server_port, configuration=configuration, stream_handler=handle_stream, **kwargs) return self.server
async def run_client_writelines(host, port=4433): configuration = QuicConfiguration(is_client=True) configuration.load_verify_locations(cafile=SERVER_CACERTFILE) async with connect(host, port, configuration=configuration) as client: reader, writer = await client.create_stream() assert writer.can_write_eof() is True writer.writelines([b"01234567", b"89012345"]) writer.write_eof() return await reader.read()
async def test_throughput(server: Server, configuration: QuicConfiguration): failures = 0 if server.throughput_path is None: return for size in [5000000, 10000000]: path = server.throughput_path % {"size": size} print("Testing %d bytes download: %s" % (size, path)) # perform HTTP request over TCP start = time.time() response = httpx.get("https://" + server.host + path, verify=False) tcp_octets = len(response.content) tcp_elapsed = time.time() - start assert tcp_octets == size, "HTTP/TCP response size mismatch" # perform HTTP request over QUIC if server.http3: configuration.alpn_protocols = H3_ALPN port = server.http3_port or server.port else: configuration.alpn_protocols = H0_ALPN port = server.port start = time.time() async with connect( server.host, port, configuration=configuration, create_protocol=HttpClient, ) as protocol: protocol = cast(HttpClient, protocol) http_events = await protocol.get("https://{}:{}{}".format( server.host, server.port, path)) quic_elapsed = time.time() - start quic_octets = 0 for http_event in http_events: if isinstance(http_event, DataReceived): quic_octets += len(http_event.data) assert quic_octets == size, "HTTP/QUIC response size mismatch" print(" - HTTP/TCP completed in %.3f s" % tcp_elapsed) print(" - HTTP/QUIC completed in %.3f s" % quic_elapsed) if quic_elapsed > 1.1 * tcp_elapsed: failures += 1 print(" => FAIL") else: print(" => PASS") if failures == 0: server.result |= Result.T
def run_server(server_port): logging.info('Starting server at localhost:%s', server_port) configuration = QuicConfiguration(is_client=False) certificates_path = Path(__file__).parent / 'certificates' configuration.load_cert_chain(certificates_path / 'ssl_cert.pem', certificates_path / 'ssl_key.pem') return rsocket_serve(host='localhost', port=server_port, configuration=configuration, handler_factory=Handler)
def test_combined_key(self): config1 = QuicConfiguration() config2 = QuicConfiguration() config1.load_cert_chain(SERVER_CERTFILE, SERVER_KEYFILE) config2.load_cert_chain(SERVER_COMBINEDFILE) self.assertEqual(config1.certificate, config2.certificate)
def create_server(self): configuration = QuicConfiguration(is_client=False) configuration.load_cert_chain(SERVER_CERTFILE, SERVER_KEYFILE) server = Context(is_client=False) server.certificate = configuration.certificate server.certificate_private_key = configuration.private_key server.handshake_extensions = [ ( tls.ExtensionType.QUIC_TRANSPORT_PARAMETERS, SERVER_QUIC_TRANSPORT_PARAMETERS, ) ] self.assertEqual(server.state, State.SERVER_EXPECT_CLIENT_HELLO) return server
def __init__ (self, endpoint): self._calls = [] self.endpoint = endpoint # prepare configuration self.configuration = QuicConfiguration( is_client=True, alpn_protocols=H3_ALPN ) self.configuration.load_verify_locations(os.path.join (os.path.dirname (__file__), 'pycacert.pem')) self.configuration.verify_mode = ssl.CERT_NONE try: with open(SESSION_TICKET, "rb") as fp: self.configuration.session_ticket = pickle.load(fp) except FileNotFoundError: pass
def __init__( self, config: Config, server: Optional[Tuple[str, int]], spawn_app: Callable[[dict, Callable], Awaitable[Callable]], send: Callable[[Event], Awaitable[None]], call_at: Callable[[float, Callable], None], now: Callable[[], float], ) -> None: self.call_at = call_at self.config = config self.connections: Dict[bytes, QuicConnection] = {} self.http_connections: Dict[QuicConnection, H3Protocol] = {} self.now = now self.send = send self.server = server self.spawn_app = spawn_app with open(config.certfile, "rb") as fp: certificate = x509.load_pem_x509_certificate(fp.read(), backend=default_backend()) with open(config.keyfile, "rb") as fp: private_key = serialization.load_pem_private_key( fp.read(), password=None, backend=default_backend() ) self.quic_config = QuicConfiguration( alpn_protocols=["h3-22"], certificate=certificate, is_client=False, private_key=private_key, )
def test_connect_and_serve_with_session_ticket(self): # start server client_ticket = None store = SessionTicketStore() def save_ticket(t): nonlocal client_ticket client_ticket = t run( self.run_server(session_ticket_fetcher=store.pop, session_ticket_handler=store.add)) # first request response = run( self.run_client("127.0.0.1", session_ticket_handler=save_ticket), ) self.assertEqual(response, b"gnip") self.assertIsNotNone(client_ticket) # second request run( self.run_client( "127.0.0.1", configuration=QuicConfiguration(is_client=True, session_ticket=client_ticket), )) self.assertEqual(response, b"gnip")
async def run(servers, tests, quic_log=False, secrets_log_file=None) -> None: for server in servers: for test_name, test_func in tests: print("\n=== %s %s ===\n" % (server.name, test_name)) configuration = QuicConfiguration( alpn_protocols=["h3-23", "h3-22", "hq-23", "hq-22"], is_client=True, quic_logger=QuicLogger(), secrets_log_file=secrets_log_file, ) if test_name == "test_throughput": timeout = 60 else: timeout = 5 try: await asyncio.wait_for(test_func(server, configuration), timeout=timeout) except Exception as exc: print(exc) if quic_log: with open("%s-%s.qlog" % (server.name, test_name), "w") as logger_fp: json.dump(configuration.quic_logger.to_dict(), logger_fp, indent=4) print("") print_result(server) # print summary if len(servers) > 1: print("SUMMARY") for server in servers: print_result(server)
async def probe(self) -> ProbeResult: configuration = QuicConfiguration( alpn_protocols=h3c.H3_ALPN, is_client=True, server_name=self._url.hostname, max_datagram_frame_size=32+MTU, ) def create_protocol(*args, **kwargs): return H3Client(self._url, ORIGIN, MTU, *args, **kwargs) try: ip, port = self._resolve_dns() async with connect( host=ip, port=port, configuration=configuration, create_protocol=create_protocol, wait_connected=False, ) as client: client = T.cast(H3Client, client) await aio.wait_for(client.wait_connected(), CONNECT_TIMEOUT) self._result.connected = True self._result.probes = [ProbeNameResult( ok=False, error="timeout") for name in self._names] self._send_interests(client) received = 0 for _ in range(int(INTEREST_TIMEOUT // INTEREST_TIMEOUT_STEP)): await aio.sleep(INTEREST_TIMEOUT_STEP) received += self._process_received(client) if received >= len(self._names): break except Exception as err: self._result.connectError = str(err) return self._result
async def start_quic_service(waiter: asyncio.Event, container, port: int, generate_test_certificates): index_iterator = iter(range(1, 3)) certificate, private_key = generate_test_certificates server_configuration = QuicConfiguration( certificate=certificate, private_key=private_key, is_client=False ) def handler_factory(*args, **kwargs): return IdentifiedHandlerFactory( next(index_iterator), ServerHandler, delay=timedelta(seconds=1)).factory(*args, **kwargs) def on_server_create(server): container.server = server container.transport = server._transport waiter.set() quic_server = await rsocket_serve(host='localhost', port=port, configuration=server_configuration, on_server_create=on_server_create, handler_factory=handler_factory) return sync(quic_server.close)
async def run(servers, tests, secrets_log_file=None) -> None: for server in servers: for test_name, test_func in tests: print("\n=== %s %s ===\n" % (server.name, test_name)) configuration = QuicConfiguration( alpn_protocols=["hq-22", "h3-22"], is_client=True, quic_logger=QuicLogger(), secrets_log_file=secrets_log_file, ) if test_name == "test_throughput": timeout = 60 else: timeout = 5 try: await asyncio.wait_for(test_func(server, configuration), timeout=timeout) except Exception as exc: print(exc) print("") print_result(server) # print summary if len(servers) > 1: print("SUMMARY") for server in servers: print_result(server)
async def test_session_resumption(server: Server, configuration: QuicConfiguration): port = server.session_resumption_port or server.port saved_ticket = None def session_ticket_handler(ticket): nonlocal saved_ticket saved_ticket = ticket # connect a first time, receive a ticket async with connect( server.host, port, configuration=configuration, session_ticket_handler=session_ticket_handler, ) as protocol: await protocol.ping() # some servers don't send the ticket immediately await asyncio.sleep(1) # connect a second time, with the ticket if saved_ticket is not None: configuration.session_ticket = saved_ticket async with connect(server.host, port, configuration=configuration) as protocol: await protocol.ping() # check session was resumed if protocol._quic.tls.session_resumed: server.result |= Result.R # check early data was accepted if protocol._quic.tls.early_data_accepted: server.result |= Result.Z
async def run(servers, tests, quic_log=False, secrets_log_file=None) -> None: for server in servers: if server.structured_logging: server.result |= Result.L for test_name, test_func in tests: print("\n=== %s %s ===\n" % (server.name, test_name)) configuration = QuicConfiguration( alpn_protocols=H3_ALPN + H0_ALPN, is_client=True, quic_logger=QuicDirectoryLogger(quic_log) if quic_log else QuicLogger(), secrets_log_file=secrets_log_file, verify_mode=server.verify_mode, ) if test_name == "test_throughput": timeout = 120 else: timeout = 10 try: await asyncio.wait_for( test_func(server, configuration), timeout=timeout ) except Exception as exc: print(exc) print("") print_result(server) # print summary if len(servers) > 1: print("SUMMARY") for server in servers: print_result(server)
async def test_http_3(server: Server, configuration: QuicConfiguration): if server.path is None: return configuration.alpn_protocols = H3_ALPN async with connect( server.host, server.port, configuration=configuration, create_protocol=HttpClient, ) as protocol: protocol = cast(HttpClient, protocol) # perform HTTP request events = await protocol.get("https://{}:{}{}".format( server.host, server.port, server.path)) if events and isinstance(events[0], HeadersReceived): server.result |= Result.D server.result |= Result.three # perform more HTTP requests to use QPACK dynamic tables for i in range(2): events = await protocol.get("https://{}:{}{}".format( server.host, server.port, server.path)) if events and isinstance(events[0], HeadersReceived): http = cast(H3Connection, protocol._http) protocol._quic._logger.info( "QPACK decoder bytes RX %d TX %d", http._decoder_bytes_received, http._decoder_bytes_sent, ) protocol._quic._logger.info( "QPACK encoder bytes RX %d TX %d", http._encoder_bytes_received, http._encoder_bytes_sent, ) if (http._decoder_bytes_received and http._decoder_bytes_sent and http._encoder_bytes_received and http._encoder_bytes_sent): server.result |= Result.d # check push support if server.push_path is not None: protocol.pushes.clear() await protocol.get("https://{}:{}{}".format( server.host, server.port, server.push_path)) await asyncio.sleep(0.5) for push_id, events in protocol.pushes.items(): if (len(events) >= 3 and isinstance(events[0], PushPromiseReceived) and isinstance(events[1], HeadersReceived) and isinstance(events[2], DataReceived)): protocol._quic._logger.info( "Push promise %d for %s received (status %s)", push_id, dict(events[0].headers)[b":path"].decode("ascii"), int(dict(events[1].headers)[b":status"]), ) server.result |= Result.p
async def test_quantum_readiness(server: Server, configuration: QuicConfiguration): configuration.quantum_readiness_test = True async with connect( server.host, server.port, configuration=configuration ) as protocol: await protocol.ping() server.result |= Result.Q
def test_send_headers_after_trailers(self): """ We should not send HEADERS after trailers. """ quic_client = FakeQuicConnection(configuration=QuicConfiguration( is_client=True)) h3_client = H3Connection(quic_client) stream_id = quic_client.get_next_available_stream_id() h3_client.send_headers( stream_id=stream_id, headers=[ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/"), ], ) h3_client.send_headers(stream_id=stream_id, headers=[(b"x-some-trailer", b"foo")], end_stream=False) with self.assertRaises(FrameUnexpected): h3_client.send_headers( stream_id=stream_id, headers=[(b"x-other-trailer", b"foo")], end_stream=False, )
def test_handle_control_stream_duplicate(self): """ We must only receive a single control stream. """ quic_server = FakeQuicConnection(configuration=QuicConfiguration( is_client=False)) h3_server = H3Connection(quic_server) # receive a first control stream h3_server.handle_event( StreamDataReceived(stream_id=2, data=encode_uint_var(StreamType.CONTROL), end_stream=False)) # receive a second control stream h3_server.handle_event( StreamDataReceived(stream_id=6, data=encode_uint_var(StreamType.CONTROL), end_stream=False)) self.assertEqual( quic_server.closed, ( ErrorCode.HTTP_STREAM_CREATION_ERROR, "Only one control stream is allowed", ), )
def test_handle_qpack_encoder_duplicate(self): """ We must only receive a single QPACK encoder stream. """ quic_client = FakeQuicConnection(configuration=QuicConfiguration( is_client=True)) h3_client = H3Connection(quic_client) # receive a first encoder stream h3_client.handle_event( StreamDataReceived( stream_id=11, data=encode_uint_var(StreamType.QPACK_ENCODER), end_stream=False, )) # receive a second encoder stream h3_client.handle_event( StreamDataReceived( stream_id=15, data=encode_uint_var(StreamType.QPACK_ENCODER), end_stream=False, )) self.assertEqual( quic_client.closed, ( ErrorCode.HTTP_STREAM_CREATION_ERROR, "Only one QPACK encoder stream is allowed", ), )
def test_blocked_stream(self): quic_client = FakeQuicConnection(configuration=QuicConfiguration( is_client=True)) h3_client = H3Connection(quic_client) h3_client.handle_event( StreamDataReceived( stream_id=3, data=binascii.unhexlify( "0004170150000680020000074064091040bcc0000000faceb00c"), end_stream=False, )) h3_client.handle_event( StreamDataReceived(stream_id=7, data=b"\x02", end_stream=False)) h3_client.handle_event( StreamDataReceived(stream_id=11, data=b"\x03", end_stream=False)) h3_client.handle_event( StreamDataReceived(stream_id=0, data=binascii.unhexlify("01040280d910"), end_stream=False)) h3_client.handle_event( StreamDataReceived( stream_id=0, data=binascii.unhexlify( "00408d796f752072656163686564206d766673742e6e65742c20726561636820" "746865202f6563686f20656e64706f696e7420666f7220616e206563686f2072" "6573706f6e7365207175657279202f3c6e756d6265723e20656e64706f696e74" "7320666f722061207661726961626c652073697a6520726573706f6e73652077" "6974682072616e646f6d206279746573"), end_stream=True, )) self.assertEqual( h3_client.handle_event( StreamDataReceived( stream_id=7, data=binascii.unhexlify( "3fe101c696d07abe941094cb6d0a08017d403971966e32ca98b46f" ), end_stream=False, )), [ HeadersReceived( headers=[ (b":status", b"200"), (b"date", b"Mon, 22 Jul 2019 06:33:33 GMT"), ], stream_id=0, stream_ended=False, ), DataReceived( data= (b"you reached mvfst.net, reach the /echo endpoint for an " b"echo response query /<number> endpoints for a variable " b"size response with random bytes"), stream_id=0, stream_ended=True, ), ], )
def test_connect_timeout(self): with self.assertRaises(ConnectionError): run( self.run_client( port=self.bogus_port, configuration=QuicConfiguration(is_client=True, idle_timeout=5), ))
def test_connect_and_serve_with_retry_bad_token(self, mock_validate): mock_validate.side_effect = ValueError("Decryption failed.") run(self.run_server(retry=True)) with self.assertRaises(ConnectionError): run( self.run_client(configuration=QuicConfiguration( is_client=True, idle_timeout=4.0), ))
async def connection(self, message): if message.unresolved_remote is None: host = message.opt.uri_host port = message.opt.uri_port or self.default_port if host is None: raise ValueError( "No location found to send message to (neither in .opt.uri_host nor in .remote)" ) else: host, port = util.hostportsplit(message.unresolved_remote) port = port or self.default_port try: ipaddress.ip_address(host) server_name = None except ValueError as ve: server_name = host infos = await self.loop.getaddrinfo(host, port, type=socket.SOCK_DGRAM) self.addr = infos[0][4] config = QuicConfiguration(is_client=True, alpn_protocols='coap', idle_timeout=864000, server_name=server_name) config.verify_mode = ssl.CERT_NONE if config.server_name is None: config.server_name = server_name connection = QuicConnection(configuration=config) self.quic = Quic(connection) self.quic.ctx = self try: transport, protocol = await self.loop.create_datagram_endpoint( lambda: self.quic, remote_addr=(host, port)) protocol.connect(self.addr) await protocol.wait_connected() self.con = True except OSError: raise error.NetworkError("Connection failed to %r" % host) return protocol
async def _connect_to_server(host: str, port: int) -> None: configuration = QuicConfiguration( alpn_protocols=H3_ALPN, is_client=True, verify_mode=ssl.CERT_NONE, ) async with connect(host, port, configuration=configuration) as protocol: await protocol.ping()