Skip to content

Commit

Permalink
Use max_datagram_size instead of max_packet_size
Browse files Browse the repository at this point in the history
This is more consistent with RFC 9002.

Lower the default max datagram size to 1200, in line with RFC 9000.
  • Loading branch information
jlaine committed Nov 4, 2023
1 parent 724ddd5 commit d8638bb
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 120 deletions.
21 changes: 10 additions & 11 deletions examples/http3_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@
HeadersReceived,
PushPromiseReceived,
)
from aioquic.quic.configuration import QuicConfiguration
from aioquic.quic.configuration import SMALLEST_MAX_DATAGRAM_SIZE, QuicConfiguration
from aioquic.quic.events import QuicEvent
from aioquic.quic.logger import QuicFileLogger
from aioquic.quic.packet_builder import PACKET_MAX_SIZE
from aioquic.tls import CipherSuite, SessionTicket

try:
Expand Down Expand Up @@ -428,12 +427,12 @@ async def main(
client._quic.close(error_code=ErrorCode.H3_NO_ERROR)


def check_packet_size(arg):
def check_max_datagram_size(arg):
iarg = int(arg)
if iarg < PACKET_MAX_SIZE:
if iarg < SMALLEST_MAX_DATAGRAM_SIZE:
raise argparse.ArgumentTypeError(
f"The specified packet size {iarg} is smaller than the "
f"minimum allowed maximum packet size {PACKET_MAX_SIZE}."
f"The specified datagram size {iarg} is less than the smallest "
f"allowed maximum datagram size {SMALLEST_MAX_DATAGRAM_SIZE}."
)
return iarg

Expand Down Expand Up @@ -515,10 +514,10 @@ def check_packet_size(arg):
help="local port to bind for connections",
)
parser.add_argument(
"--packet-size",
type=check_packet_size,
default=PACKET_MAX_SIZE,
help="specify custom packet size",
"--max-datagram-size",
type=check_max_datagram_size,
default=defaults.max_datagram_size,
help="maximum datagram size to send, excluding UDP or IP overhead",
)
parser.add_argument(
"--zero-rtt", action="store_true", help="try to send requests using 0-RTT"
Expand All @@ -538,7 +537,7 @@ def check_packet_size(arg):
configuration = QuicConfiguration(
is_client=True,
alpn_protocols=H0_ALPN if args.legacy_http else H3_ALPN,
max_packet_size=args.packet_size,
max_datagram_size=args.max_datagram_size,
)
if args.ca_certs:
configuration.load_verify_locations(args.ca_certs)
Expand Down
23 changes: 12 additions & 11 deletions examples/http3_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@
WebTransportStreamDataReceived,
)
from aioquic.h3.exceptions import NoAvailablePushIDError
from aioquic.quic.configuration import QuicConfiguration
from aioquic.quic.configuration import SMALLEST_MAX_DATAGRAM_SIZE, QuicConfiguration
from aioquic.quic.events import DatagramFrameReceived, ProtocolNegotiated, QuicEvent
from aioquic.quic.logger import QuicFileLogger
from aioquic.quic.packet_builder import PACKET_MAX_SIZE
from aioquic.tls import SessionTicket

try:
Expand Down Expand Up @@ -495,17 +494,19 @@ async def main(
await asyncio.Future()


def check_packet_size(arg):
def check_max_datagram_size(arg):
iarg = int(arg)
if iarg < PACKET_MAX_SIZE:
if iarg < SMALLEST_MAX_DATAGRAM_SIZE:
raise argparse.ArgumentTypeError(
f"The specified packet size {iarg} is smaller than the "
f"minimum allowed maximum packet size {PACKET_MAX_SIZE}."
f"The specified datagram size {iarg} is less than the smallest "
f"allowed maximum datagram size {SMALLEST_MAX_DATAGRAM_SIZE}."
)
return iarg


if __name__ == "__main__":
defaults = QuicConfiguration(is_client=False)

parser = argparse.ArgumentParser(description="QUIC server")
parser.add_argument(
"app",
Expand Down Expand Up @@ -546,10 +547,10 @@ def check_packet_size(arg):
help="log secrets to a file, for use with Wireshark",
)
parser.add_argument(
"--packet-size",
type=check_packet_size,
default=PACKET_MAX_SIZE,
help="specify custom packet size",
"--max-datagram-size",
type=check_max_datagram_size,
default=defaults.max_datagram_size,
help="maximum datagram size to send, excluding UDP or IP overhead",
)
parser.add_argument(
"-q",
Expand Down Expand Up @@ -593,7 +594,7 @@ def check_packet_size(arg):
alpn_protocols=H3_ALPN + H0_ALPN + ["siduck"],
is_client=False,
max_datagram_frame_size=65536,
max_packet_size=args.packet_size,
max_datagram_size=args.max_datagram_size,
quic_logger=quic_logger,
secrets_log_file=secrets_log_file,
)
Expand Down
4 changes: 2 additions & 2 deletions src/aioquic/asyncio/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from typing import Callable, Dict, Optional, Text, Union, cast

from ..buffer import Buffer
from ..quic.configuration import QuicConfiguration
from ..quic.configuration import SMALLEST_MAX_DATAGRAM_SIZE, QuicConfiguration
from ..quic.connection import NetworkAddress, QuicConnection
from ..quic.packet import (
PACKET_TYPE_INITIAL,
Expand Down Expand Up @@ -85,7 +85,7 @@ def datagram_received(self, data: Union[bytes, Text], addr: NetworkAddress) -> N
retry_source_connection_id: Optional[bytes] = None
if (
protocol is None
and len(data) >= 1200
and len(data) >= SMALLEST_MAX_DATAGRAM_SIZE
and header.packet_type == PACKET_TYPE_INITIAL
):
# retry
Expand Down
6 changes: 4 additions & 2 deletions src/aioquic/quic/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from .logger import QuicLogger
from .packet import QuicProtocolVersion

SMALLEST_MAX_DATAGRAM_SIZE = 1200


@dataclass
class QuicConfiguration:
Expand Down Expand Up @@ -46,9 +48,9 @@ class QuicConfiguration:
Connection-wide flow control limit.
"""

max_packet_size: int = 1280
max_datagram_size: int = SMALLEST_MAX_DATAGRAM_SIZE
"""
Maximum size of QUIC packets.
The maximum QUIC payload size in bytes, excluding UDP or IP overhead.
"""

max_stream_data: int = 1048576
Expand Down
36 changes: 21 additions & 15 deletions src/aioquic/quic/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
size_uint_var,
)
from . import events
from .configuration import QuicConfiguration
from .configuration import SMALLEST_MAX_DATAGRAM_SIZE, QuicConfiguration
from .crypto import CryptoError, CryptoPair, KeyUnavailableError
from .logger import QuicLoggerTrace
from .packet import (
Expand Down Expand Up @@ -240,6 +240,10 @@ def __init__(
session_ticket_fetcher: Optional[tls.SessionTicketFetcher] = None,
session_ticket_handler: Optional[tls.SessionTicketHandler] = None,
) -> None:
assert configuration.max_datagram_size >= SMALLEST_MAX_DATAGRAM_SIZE, (
"The smallest allowed maximum packet size is "
f"{SMALLEST_MAX_DATAGRAM_SIZE} bytes"
)
if configuration.is_client:
assert (
original_destination_connection_id is None
Expand Down Expand Up @@ -303,10 +307,7 @@ def __init__(
frame_type=QuicFrameType.MAX_STREAMS_UNI, name="max_streams_uni", value=128
)
self._loss_at: Optional[float] = None
assert (
configuration.max_packet_size >= 1200
), "The smallest allowed maximum packet size is 1200 bytes"
self._max_packet_size = configuration.max_packet_size
self._max_datagram_size = configuration.max_datagram_size
self._network_paths: List[QuicNetworkPath] = []
self._pacing_at: Optional[float] = None
self._packet_number = 0
Expand Down Expand Up @@ -363,7 +364,7 @@ def __init__(
# loss recovery
self._loss = QuicPacketRecovery(
initial_rtt=configuration.initial_rtt,
max_packet_size=self._max_packet_size,
max_datagram_size=self._max_datagram_size,
peer_completed_address_validation=not self._is_client,
quic_logger=self._quic_logger,
send_probe=self._send_probe,
Expand Down Expand Up @@ -505,7 +506,7 @@ def datagrams_to_send(self, now: float) -> List[Tuple[bytes, NetworkAddress]]:
builder = QuicPacketBuilder(
host_cid=self.host_cid,
is_client=self._is_client,
max_packet_size=self._max_packet_size,
max_datagram_size=self._max_datagram_size,
packet_number=self._packet_number,
peer_cid=self._peer_cid.cid,
peer_token=self._peer_token,
Expand Down Expand Up @@ -544,8 +545,11 @@ def datagrams_to_send(self, now: float) -> List[Tuple[bytes, NetworkAddress]]:
builder.max_flight_bytes = (
self._loss.congestion_window - self._loss.bytes_in_flight
)
if self._probe_pending and builder.max_flight_bytes < self._max_packet_size:
builder.max_flight_bytes = self._max_packet_size
if (
self._probe_pending
and builder.max_flight_bytes < self._max_datagram_size
):
builder.max_flight_bytes = self._max_datagram_size

# limit data on un-validated network paths
if not network_path.is_validated:
Expand Down Expand Up @@ -2462,14 +2466,16 @@ def _parse_transport_parameters(
frame_type=QuicFrameType.CRYPTO,
reason_phrase="max_ack_delay must be < 2^14",
)
if (
quic_transport_parameters.max_udp_payload_size is not None
and quic_transport_parameters.max_udp_payload_size < 1200
if quic_transport_parameters.max_udp_payload_size is not None and (
quic_transport_parameters.max_udp_payload_size
< SMALLEST_MAX_DATAGRAM_SIZE
):
raise QuicConnectionError(
error_code=QuicErrorCode.TRANSPORT_PARAMETER_ERROR,
frame_type=QuicFrameType.CRYPTO,
reason_phrase="max_udp_payload_size must be >= 1200",
reason_phrase=(
f"max_udp_payload_size must be >= {SMALLEST_MAX_DATAGRAM_SIZE}"
),
)

# store remote parameters
Expand Down Expand Up @@ -2526,7 +2532,7 @@ def _serialize_transport_parameters(self) -> bytes:
initial_source_connection_id=self._local_initial_source_connection_id,
max_ack_delay=25,
max_datagram_frame_size=self._configuration.max_datagram_frame_size,
quantum_readiness=b"Q" * 1200
quantum_readiness=b"Q" * SMALLEST_MAX_DATAGRAM_SIZE
if self._configuration.quantum_readiness_test
else None,
stateless_reset_token=self._host_cids[0].stateless_reset_token,
Expand All @@ -2549,7 +2555,7 @@ def _serialize_transport_parameters(self) -> bytes:
),
)

buf = Buffer(capacity=3 * self._max_packet_size)
buf = Buffer(capacity=3 * self._max_datagram_size)
push_quic_transport_parameters(buf, quic_transport_parameters)
return buf.data

Expand Down
9 changes: 4 additions & 5 deletions src/aioquic/quic/packet_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
is_long_header,
)

PACKET_MAX_SIZE = 1280
PACKET_LENGTH_SEND_SIZE = 2
PACKET_NUMBER_SEND_SIZE = 2

Expand Down Expand Up @@ -64,7 +63,7 @@ def __init__(
peer_cid: bytes,
version: int,
is_client: bool,
max_packet_size: int,
max_datagram_size: int,
packet_number: int = 0,
peer_token: bytes = b"",
quic_logger: Optional[QuicLoggerTrace] = None,
Expand Down Expand Up @@ -99,9 +98,9 @@ def __init__(
self._packet_start = 0
self._packet_type = 0

self._buffer = Buffer(max_packet_size)
self._buffer_capacity = max_packet_size
self._flight_capacity = max_packet_size
self._buffer = Buffer(max_datagram_size)
self._buffer_capacity = max_datagram_size
self._flight_capacity = max_datagram_size

@property
def packet_is_empty(self) -> bool:
Expand Down
26 changes: 13 additions & 13 deletions src/aioquic/quic/recovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ def __init__(self) -> None:


class QuicPacketPacer:
def __init__(self, *, max_packet_size: int) -> None:
self._max_packet_size = max_packet_size
def __init__(self, *, max_datagram_size: int) -> None:
self._max_datagram_size = max_datagram_size
self.bucket_max: float = 0.0
self.bucket_time: float = 0.0
self.evaluation_time: float = 0.0
Expand Down Expand Up @@ -68,13 +68,13 @@ def update_bucket(self, now: float) -> None:
def update_rate(self, congestion_window: int, smoothed_rtt: float) -> None:
pacing_rate = congestion_window / max(smoothed_rtt, K_MICRO_SECOND)
self.packet_time = max(
K_MICRO_SECOND, min(self._max_packet_size / pacing_rate, K_SECOND)
K_MICRO_SECOND, min(self._max_datagram_size / pacing_rate, K_SECOND)
)

self.bucket_max = (
max(
2 * self._max_packet_size,
min(congestion_window // 4, 16 * self._max_packet_size),
2 * self._max_datagram_size,
min(congestion_window // 4, 16 * self._max_datagram_size),
)
/ pacing_rate
)
Expand All @@ -87,10 +87,10 @@ class QuicCongestionControl:
New Reno congestion control.
"""

def __init__(self, *, max_packet_size: int) -> None:
self._max_packet_size = max_packet_size
def __init__(self, *, max_datagram_size: int) -> None:
self._max_datagram_size = max_datagram_size
self.bytes_in_flight = 0
self.congestion_window = K_INITIAL_WINDOW * self._max_packet_size
self.congestion_window = K_INITIAL_WINDOW * self._max_datagram_size
self._congestion_recovery_start_time = 0.0
self._congestion_stash = 0
self._rtt_monitor = QuicRttMonitor()
Expand All @@ -112,7 +112,7 @@ def on_packet_acked(self, packet: QuicSentPacket) -> None:
count = self._congestion_stash // self.congestion_window
if count:
self._congestion_stash -= count * self.congestion_window
self.congestion_window += count * self._max_packet_size
self.congestion_window += count * self._max_datagram_size

def on_packet_sent(self, packet: QuicSentPacket) -> None:
self.bytes_in_flight += packet.sent_bytes
Expand All @@ -133,7 +133,7 @@ def on_packets_lost(self, packets: Iterable[QuicSentPacket], now: float) -> None
self._congestion_recovery_start_time = now
self.congestion_window = max(
int(self.congestion_window * K_LOSS_REDUCTION_FACTOR),
K_MINIMUM_WINDOW * self._max_packet_size,
K_MINIMUM_WINDOW * self._max_datagram_size,
)
self.ssthresh = self.congestion_window

Expand All @@ -155,7 +155,7 @@ class QuicPacketRecovery:
def __init__(
self,
initial_rtt: float,
max_packet_size: int,
max_datagram_size: int,
peer_completed_address_validation: bool,
send_probe: Callable[[], None],
logger: Optional[logging.LoggerAdapter] = None,
Expand All @@ -181,8 +181,8 @@ def __init__(
self._time_of_last_sent_ack_eliciting_packet = 0.0

# congestion control
self._cc = QuicCongestionControl(max_packet_size=max_packet_size)
self._pacer = QuicPacketPacer(max_packet_size=max_packet_size)
self._cc = QuicCongestionControl(max_datagram_size=max_datagram_size)
self._pacer = QuicPacketPacer(max_datagram_size=max_datagram_size)

@property
def bytes_in_flight(self) -> int:
Expand Down
Loading

0 comments on commit d8638bb

Please sign in to comment.