%PDF- %PDF-
Direktori : /backups/router/usr/local/lib/python3.11/site-packages/aioquic/quic/ |
Current File : //backups/router/usr/local/lib/python3.11/site-packages/aioquic/quic/packet.py |
import binascii import ipaddress import os from dataclasses import dataclass from enum import Enum, IntEnum from typing import List, Optional, Tuple from cryptography.hazmat.primitives.ciphers.aead import AESGCM from ..buffer import Buffer from .rangeset import RangeSet PACKET_LONG_HEADER = 0x80 PACKET_FIXED_BIT = 0x40 PACKET_SPIN_BIT = 0x20 CONNECTION_ID_MAX_SIZE = 20 PACKET_NUMBER_MAX_SIZE = 4 RETRY_AEAD_KEY_VERSION_1 = binascii.unhexlify("be0c690b9f66575a1d766b54e368c84e") RETRY_AEAD_KEY_VERSION_2 = binascii.unhexlify("8fb4b01b56ac48e260fbcbcead7ccc92") RETRY_AEAD_NONCE_VERSION_1 = binascii.unhexlify("461599d35d632bf2239825bb") RETRY_AEAD_NONCE_VERSION_2 = binascii.unhexlify("d86969bc2d7c6d9990efb04a") RETRY_INTEGRITY_TAG_SIZE = 16 STATELESS_RESET_TOKEN_SIZE = 16 class QuicErrorCode(IntEnum): NO_ERROR = 0x0 INTERNAL_ERROR = 0x1 CONNECTION_REFUSED = 0x2 FLOW_CONTROL_ERROR = 0x3 STREAM_LIMIT_ERROR = 0x4 STREAM_STATE_ERROR = 0x5 FINAL_SIZE_ERROR = 0x6 FRAME_ENCODING_ERROR = 0x7 TRANSPORT_PARAMETER_ERROR = 0x8 CONNECTION_ID_LIMIT_ERROR = 0x9 PROTOCOL_VIOLATION = 0xA INVALID_TOKEN = 0xB APPLICATION_ERROR = 0xC CRYPTO_BUFFER_EXCEEDED = 0xD KEY_UPDATE_ERROR = 0xE AEAD_LIMIT_REACHED = 0xF VERSION_NEGOTIATION_ERROR = 0x11 CRYPTO_ERROR = 0x100 class QuicPacketType(Enum): INITIAL = 0 ZERO_RTT = 1 HANDSHAKE = 2 RETRY = 3 VERSION_NEGOTIATION = 4 ONE_RTT = 5 # For backwards compatibility only, use `QuicPacketType` in new code. PACKET_TYPE_INITIAL = QuicPacketType.INITIAL # QUIC version 1 # https://datatracker.ietf.org/doc/html/rfc9000#section-17.2 PACKET_LONG_TYPE_ENCODE_VERSION_1 = { QuicPacketType.INITIAL: 0, QuicPacketType.ZERO_RTT: 1, QuicPacketType.HANDSHAKE: 2, QuicPacketType.RETRY: 3, } PACKET_LONG_TYPE_DECODE_VERSION_1 = dict( (v, i) for (i, v) in PACKET_LONG_TYPE_ENCODE_VERSION_1.items() ) # QUIC version 2 # https://datatracker.ietf.org/doc/html/rfc9369#section-3.2 PACKET_LONG_TYPE_ENCODE_VERSION_2 = { QuicPacketType.INITIAL: 1, QuicPacketType.ZERO_RTT: 2, QuicPacketType.HANDSHAKE: 3, QuicPacketType.RETRY: 0, } PACKET_LONG_TYPE_DECODE_VERSION_2 = dict( (v, i) for (i, v) in PACKET_LONG_TYPE_ENCODE_VERSION_2.items() ) class QuicProtocolVersion(IntEnum): NEGOTIATION = 0 VERSION_1 = 0x00000001 VERSION_2 = 0x6B3343CF @dataclass class QuicHeader: version: Optional[int] "The protocol version. Only present in long header packets." packet_type: QuicPacketType "The type of the packet." packet_length: int "The total length of the packet, in bytes." destination_cid: bytes "The destination connection ID." source_cid: bytes "The destination connection ID." token: bytes "The address verification token. Only present in `INITIAL` and `RETRY` packets." integrity_tag: bytes "The retry integrity tag. Only present in `RETRY` packets." supported_versions: List[int] "Supported protocol versions. Only present in `VERSION_NEGOTIATION` packets." def decode_packet_number(truncated: int, num_bits: int, expected: int) -> int: """ Recover a packet number from a truncated packet number. See: Appendix A - Sample Packet Number Decoding Algorithm """ window = 1 << num_bits half_window = window // 2 candidate = (expected & ~(window - 1)) | truncated if candidate <= expected - half_window and candidate < (1 << 62) - window: return candidate + window elif candidate > expected + half_window and candidate >= window: return candidate - window else: return candidate def get_retry_integrity_tag( packet_without_tag: bytes, original_destination_cid: bytes, version: int ) -> bytes: """ Calculate the integrity tag for a RETRY packet. """ # build Retry pseudo packet buf = Buffer(capacity=1 + len(original_destination_cid) + len(packet_without_tag)) buf.push_uint8(len(original_destination_cid)) buf.push_bytes(original_destination_cid) buf.push_bytes(packet_without_tag) assert buf.eof() if version == QuicProtocolVersion.VERSION_2: aead_key = RETRY_AEAD_KEY_VERSION_2 aead_nonce = RETRY_AEAD_NONCE_VERSION_2 else: aead_key = RETRY_AEAD_KEY_VERSION_1 aead_nonce = RETRY_AEAD_NONCE_VERSION_1 # run AES-128-GCM aead = AESGCM(aead_key) integrity_tag = aead.encrypt(aead_nonce, b"", buf.data) assert len(integrity_tag) == RETRY_INTEGRITY_TAG_SIZE return integrity_tag def get_spin_bit(first_byte: int) -> bool: return bool(first_byte & PACKET_SPIN_BIT) def is_long_header(first_byte: int) -> bool: return bool(first_byte & PACKET_LONG_HEADER) def pretty_protocol_version(version: int) -> str: """ Return a user-friendly representation of a protocol version. """ try: version_name = QuicProtocolVersion(version).name except ValueError: version_name = "UNKNOWN" return f"0x{version:08x} ({version_name})" def pull_quic_header(buf: Buffer, host_cid_length: Optional[int] = None) -> QuicHeader: packet_start = buf.tell() version = None integrity_tag = b"" supported_versions = [] token = b"" first_byte = buf.pull_uint8() if is_long_header(first_byte): # Long Header Packets. # https://datatracker.ietf.org/doc/html/rfc9000#section-17.2 version = buf.pull_uint32() destination_cid_length = buf.pull_uint8() if destination_cid_length > CONNECTION_ID_MAX_SIZE: raise ValueError( "Destination CID is too long (%d bytes)" % destination_cid_length ) destination_cid = buf.pull_bytes(destination_cid_length) source_cid_length = buf.pull_uint8() if source_cid_length > CONNECTION_ID_MAX_SIZE: raise ValueError("Source CID is too long (%d bytes)" % source_cid_length) source_cid = buf.pull_bytes(source_cid_length) if version == QuicProtocolVersion.NEGOTIATION: # Version Negotiation Packet. # https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.1 packet_type = QuicPacketType.VERSION_NEGOTIATION while not buf.eof(): supported_versions.append(buf.pull_uint32()) packet_end = buf.tell() else: if not (first_byte & PACKET_FIXED_BIT): raise ValueError("Packet fixed bit is zero") if version == QuicProtocolVersion.VERSION_2: packet_type = PACKET_LONG_TYPE_DECODE_VERSION_2[ (first_byte & 0x30) >> 4 ] else: packet_type = PACKET_LONG_TYPE_DECODE_VERSION_1[ (first_byte & 0x30) >> 4 ] if packet_type == QuicPacketType.INITIAL: token_length = buf.pull_uint_var() token = buf.pull_bytes(token_length) rest_length = buf.pull_uint_var() elif packet_type == QuicPacketType.ZERO_RTT: rest_length = buf.pull_uint_var() elif packet_type == QuicPacketType.HANDSHAKE: rest_length = buf.pull_uint_var() else: token_length = buf.capacity - buf.tell() - RETRY_INTEGRITY_TAG_SIZE token = buf.pull_bytes(token_length) integrity_tag = buf.pull_bytes(RETRY_INTEGRITY_TAG_SIZE) rest_length = 0 # Check remainder length. packet_end = buf.tell() + rest_length if packet_end > buf.capacity: raise ValueError("Packet payload is truncated") else: # Short Header Packets. # https://datatracker.ietf.org/doc/html/rfc9000#section-17.3 if not (first_byte & PACKET_FIXED_BIT): raise ValueError("Packet fixed bit is zero") version = None packet_type = QuicPacketType.ONE_RTT destination_cid = buf.pull_bytes(host_cid_length) source_cid = b"" packet_end = buf.capacity return QuicHeader( version=version, packet_type=packet_type, packet_length=packet_end - packet_start, destination_cid=destination_cid, source_cid=source_cid, token=token, integrity_tag=integrity_tag, supported_versions=supported_versions, ) def encode_long_header_first_byte( version: int, packet_type: QuicPacketType, bits: int ) -> int: """ Encode the first byte of a long header packet. """ if version == QuicProtocolVersion.VERSION_2: long_type_encode = PACKET_LONG_TYPE_ENCODE_VERSION_2 else: long_type_encode = PACKET_LONG_TYPE_ENCODE_VERSION_1 return ( PACKET_LONG_HEADER | PACKET_FIXED_BIT | long_type_encode[packet_type] << 4 | bits ) def encode_quic_retry( version: int, source_cid: bytes, destination_cid: bytes, original_destination_cid: bytes, retry_token: bytes, unused: int = 0, ) -> bytes: buf = Buffer( capacity=7 + len(destination_cid) + len(source_cid) + len(retry_token) + RETRY_INTEGRITY_TAG_SIZE ) buf.push_uint8(encode_long_header_first_byte(version, QuicPacketType.RETRY, unused)) buf.push_uint32(version) buf.push_uint8(len(destination_cid)) buf.push_bytes(destination_cid) buf.push_uint8(len(source_cid)) buf.push_bytes(source_cid) buf.push_bytes(retry_token) buf.push_bytes( get_retry_integrity_tag(buf.data, original_destination_cid, version=version) ) assert buf.eof() return buf.data def encode_quic_version_negotiation( source_cid: bytes, destination_cid: bytes, supported_versions: List[int] ) -> bytes: buf = Buffer( capacity=7 + len(destination_cid) + len(source_cid) + 4 * len(supported_versions) ) buf.push_uint8(os.urandom(1)[0] | PACKET_LONG_HEADER) buf.push_uint32(QuicProtocolVersion.NEGOTIATION) buf.push_uint8(len(destination_cid)) buf.push_bytes(destination_cid) buf.push_uint8(len(source_cid)) buf.push_bytes(source_cid) for version in supported_versions: buf.push_uint32(version) return buf.data # TLS EXTENSION @dataclass class QuicPreferredAddress: ipv4_address: Optional[Tuple[str, int]] ipv6_address: Optional[Tuple[str, int]] connection_id: bytes stateless_reset_token: bytes @dataclass class QuicVersionInformation: chosen_version: int available_versions: List[int] @dataclass class QuicTransportParameters: original_destination_connection_id: Optional[bytes] = None max_idle_timeout: Optional[int] = None stateless_reset_token: Optional[bytes] = None max_udp_payload_size: Optional[int] = None initial_max_data: Optional[int] = None initial_max_stream_data_bidi_local: Optional[int] = None initial_max_stream_data_bidi_remote: Optional[int] = None initial_max_stream_data_uni: Optional[int] = None initial_max_streams_bidi: Optional[int] = None initial_max_streams_uni: Optional[int] = None ack_delay_exponent: Optional[int] = None max_ack_delay: Optional[int] = None disable_active_migration: Optional[bool] = False preferred_address: Optional[QuicPreferredAddress] = None active_connection_id_limit: Optional[int] = None initial_source_connection_id: Optional[bytes] = None retry_source_connection_id: Optional[bytes] = None version_information: Optional[QuicVersionInformation] = None max_datagram_frame_size: Optional[int] = None quantum_readiness: Optional[bytes] = None PARAMS = { 0x00: ("original_destination_connection_id", bytes), 0x01: ("max_idle_timeout", int), 0x02: ("stateless_reset_token", bytes), 0x03: ("max_udp_payload_size", int), 0x04: ("initial_max_data", int), 0x05: ("initial_max_stream_data_bidi_local", int), 0x06: ("initial_max_stream_data_bidi_remote", int), 0x07: ("initial_max_stream_data_uni", int), 0x08: ("initial_max_streams_bidi", int), 0x09: ("initial_max_streams_uni", int), 0x0A: ("ack_delay_exponent", int), 0x0B: ("max_ack_delay", int), 0x0C: ("disable_active_migration", bool), 0x0D: ("preferred_address", QuicPreferredAddress), 0x0E: ("active_connection_id_limit", int), 0x0F: ("initial_source_connection_id", bytes), 0x10: ("retry_source_connection_id", bytes), # https://datatracker.ietf.org/doc/html/rfc9368#section-3 0x11: ("version_information", QuicVersionInformation), # extensions 0x0020: ("max_datagram_frame_size", int), 0x0C37: ("quantum_readiness", bytes), } def pull_quic_preferred_address(buf: Buffer) -> QuicPreferredAddress: ipv4_address = None ipv4_host = buf.pull_bytes(4) ipv4_port = buf.pull_uint16() if ipv4_host != bytes(4): ipv4_address = (str(ipaddress.IPv4Address(ipv4_host)), ipv4_port) ipv6_address = None ipv6_host = buf.pull_bytes(16) ipv6_port = buf.pull_uint16() if ipv6_host != bytes(16): ipv6_address = (str(ipaddress.IPv6Address(ipv6_host)), ipv6_port) connection_id_length = buf.pull_uint8() connection_id = buf.pull_bytes(connection_id_length) stateless_reset_token = buf.pull_bytes(16) return QuicPreferredAddress( ipv4_address=ipv4_address, ipv6_address=ipv6_address, connection_id=connection_id, stateless_reset_token=stateless_reset_token, ) def push_quic_preferred_address( buf: Buffer, preferred_address: QuicPreferredAddress ) -> None: if preferred_address.ipv4_address is not None: buf.push_bytes(ipaddress.IPv4Address(preferred_address.ipv4_address[0]).packed) buf.push_uint16(preferred_address.ipv4_address[1]) else: buf.push_bytes(bytes(6)) if preferred_address.ipv6_address is not None: buf.push_bytes(ipaddress.IPv6Address(preferred_address.ipv6_address[0]).packed) buf.push_uint16(preferred_address.ipv6_address[1]) else: buf.push_bytes(bytes(18)) buf.push_uint8(len(preferred_address.connection_id)) buf.push_bytes(preferred_address.connection_id) buf.push_bytes(preferred_address.stateless_reset_token) def pull_quic_version_information(buf: Buffer, length: int) -> QuicVersionInformation: chosen_version = buf.pull_uint32() available_versions = [] for i in range(length // 4 - 1): available_versions.append(buf.pull_uint32()) # If an endpoint receives a Chosen Version equal to zero, or any Available Version # equal to zero, it MUST treat it as a parsing failure. # # https://datatracker.ietf.org/doc/html/rfc9368#section-4 if chosen_version == 0 or 0 in available_versions: raise ValueError("Version Information must not contain version 0") return QuicVersionInformation( chosen_version=chosen_version, available_versions=available_versions, ) def push_quic_version_information( buf: Buffer, version_information: QuicVersionInformation ) -> None: buf.push_uint32(version_information.chosen_version) for version in version_information.available_versions: buf.push_uint32(version) def pull_quic_transport_parameters(buf: Buffer) -> QuicTransportParameters: params = QuicTransportParameters() while not buf.eof(): param_id = buf.pull_uint_var() param_len = buf.pull_uint_var() param_start = buf.tell() if param_id in PARAMS: # Parse known parameter. param_name, param_type = PARAMS[param_id] if param_type is int: setattr(params, param_name, buf.pull_uint_var()) elif param_type is bytes: setattr(params, param_name, buf.pull_bytes(param_len)) elif param_type is QuicPreferredAddress: setattr(params, param_name, pull_quic_preferred_address(buf)) elif param_type is QuicVersionInformation: setattr( params, param_name, pull_quic_version_information(buf, param_len), ) else: setattr(params, param_name, True) else: # Skip unknown parameter. buf.pull_bytes(param_len) if buf.tell() != param_start + param_len: raise ValueError("Transport parameter length does not match") return params def push_quic_transport_parameters( buf: Buffer, params: QuicTransportParameters ) -> None: for param_id, (param_name, param_type) in PARAMS.items(): param_value = getattr(params, param_name) if param_value is not None and param_value is not False: param_buf = Buffer(capacity=65536) if param_type is int: param_buf.push_uint_var(param_value) elif param_type is bytes: param_buf.push_bytes(param_value) elif param_type is QuicPreferredAddress: push_quic_preferred_address(param_buf, param_value) elif param_type is QuicVersionInformation: push_quic_version_information(param_buf, param_value) buf.push_uint_var(param_id) buf.push_uint_var(param_buf.tell()) buf.push_bytes(param_buf.data) # FRAMES class QuicFrameType(IntEnum): PADDING = 0x00 PING = 0x01 ACK = 0x02 ACK_ECN = 0x03 RESET_STREAM = 0x04 STOP_SENDING = 0x05 CRYPTO = 0x06 NEW_TOKEN = 0x07 STREAM_BASE = 0x08 MAX_DATA = 0x10 MAX_STREAM_DATA = 0x11 MAX_STREAMS_BIDI = 0x12 MAX_STREAMS_UNI = 0x13 DATA_BLOCKED = 0x14 STREAM_DATA_BLOCKED = 0x15 STREAMS_BLOCKED_BIDI = 0x16 STREAMS_BLOCKED_UNI = 0x17 NEW_CONNECTION_ID = 0x18 RETIRE_CONNECTION_ID = 0x19 PATH_CHALLENGE = 0x1A PATH_RESPONSE = 0x1B TRANSPORT_CLOSE = 0x1C APPLICATION_CLOSE = 0x1D HANDSHAKE_DONE = 0x1E DATAGRAM = 0x30 DATAGRAM_WITH_LENGTH = 0x31 NON_ACK_ELICITING_FRAME_TYPES = frozenset( [ QuicFrameType.ACK, QuicFrameType.ACK_ECN, QuicFrameType.PADDING, QuicFrameType.TRANSPORT_CLOSE, QuicFrameType.APPLICATION_CLOSE, ] ) NON_IN_FLIGHT_FRAME_TYPES = frozenset( [ QuicFrameType.ACK, QuicFrameType.ACK_ECN, QuicFrameType.TRANSPORT_CLOSE, QuicFrameType.APPLICATION_CLOSE, ] ) PROBING_FRAME_TYPES = frozenset( [ QuicFrameType.PATH_CHALLENGE, QuicFrameType.PATH_RESPONSE, QuicFrameType.PADDING, QuicFrameType.NEW_CONNECTION_ID, ] ) @dataclass class QuicResetStreamFrame: error_code: int final_size: int stream_id: int @dataclass class QuicStopSendingFrame: error_code: int stream_id: int @dataclass class QuicStreamFrame: data: bytes = b"" fin: bool = False offset: int = 0 def pull_ack_frame(buf: Buffer) -> Tuple[RangeSet, int]: rangeset = RangeSet() end = buf.pull_uint_var() # largest acknowledged delay = buf.pull_uint_var() ack_range_count = buf.pull_uint_var() ack_count = buf.pull_uint_var() # first ack range rangeset.add(end - ack_count, end + 1) end -= ack_count for _ in range(ack_range_count): end -= buf.pull_uint_var() + 2 ack_count = buf.pull_uint_var() rangeset.add(end - ack_count, end + 1) end -= ack_count return rangeset, delay def push_ack_frame(buf: Buffer, rangeset: RangeSet, delay: int) -> int: ranges = len(rangeset) index = ranges - 1 r = rangeset[index] buf.push_uint_var(r.stop - 1) buf.push_uint_var(delay) buf.push_uint_var(index) buf.push_uint_var(r.stop - 1 - r.start) start = r.start while index > 0: index -= 1 r = rangeset[index] buf.push_uint_var(start - r.stop - 1) buf.push_uint_var(r.stop - r.start - 1) start = r.start return ranges