Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable max packet size #400

Merged
merged 1 commit into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion examples/http3_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,12 @@ async def main(
default=0,
help="local port to bind for connections",
)
parser.add_argument(
"--max-datagram-size",
type=int,
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 @@ -519,7 +525,9 @@ async def main(

# prepare configuration
configuration = QuicConfiguration(
is_client=True, alpn_protocols=H0_ALPN if args.legacy_http else H3_ALPN
is_client=True,
alpn_protocols=H0_ALPN if args.legacy_http else H3_ALPN,
max_datagram_size=args.max_datagram_size,
)
if args.ca_certs:
configuration.load_verify_locations(args.ca_certs)
Expand Down
9 changes: 9 additions & 0 deletions examples/http3_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,8 @@ async def main(


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

parser = argparse.ArgumentParser(description="QUIC server")
parser.add_argument(
"app",
Expand Down Expand Up @@ -534,6 +536,12 @@ async def main(
type=str,
help="log secrets to a file, for use with Wireshark",
)
parser.add_argument(
"--max-datagram-size",
type=int,
default=defaults.max_datagram_size,
help="maximum datagram size to send, excluding UDP or IP overhead",
)
parser.add_argument(
"-q",
"--quic-log",
Expand Down Expand Up @@ -576,6 +584,7 @@ async def main(
alpn_protocols=H3_ALPN + H0_ALPN + ["siduck"],
is_client=False,
max_datagram_frame_size=65536,
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
7 changes: 7 additions & 0 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,6 +48,11 @@ class QuicConfiguration:
Connection-wide flow control limit.
"""

max_datagram_size: int = SMALLEST_MAX_DATAGRAM_SIZE
"""
The maximum QUIC payload size in bytes to send, excluding UDP or IP overhead.
"""

max_stream_data: int = 1048576
"""
Per-stream flow control limit.
Expand Down
31 changes: 21 additions & 10 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 @@ -46,7 +46,6 @@
push_quic_transport_parameters,
)
from .packet_builder import (
PACKET_MAX_SIZE,
QuicDeliveryState,
QuicPacketBuilder,
QuicPacketBuilderStop,
Expand Down Expand Up @@ -241,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 datagram size is "
f"{SMALLEST_MAX_DATAGRAM_SIZE} bytes"
)
if configuration.is_client:
assert (
original_destination_connection_id is None
Expand Down Expand Up @@ -304,6 +307,7 @@ def __init__(
frame_type=QuicFrameType.MAX_STREAMS_UNI, name="max_streams_uni", value=128
)
self._loss_at: Optional[float] = None
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 @@ -360,6 +364,7 @@ def __init__(
# loss recovery
self._loss = QuicPacketRecovery(
initial_rtt=configuration.initial_rtt,
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 @@ -501,6 +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_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 @@ -539,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 < PACKET_MAX_SIZE:
builder.max_flight_bytes = PACKET_MAX_SIZE
if (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure this is correct. I think this should be exactly 1200 bytes.

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 @@ -2457,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 @@ -2521,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 @@ -2544,7 +2555,7 @@ def _serialize_transport_parameters(self) -> bytes:
),
)

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

Expand Down
8 changes: 4 additions & 4 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,6 +63,7 @@ def __init__(
peer_cid: bytes,
version: int,
is_client: bool,
max_datagram_size: int,
packet_number: int = 0,
peer_token: bytes = b"",
quic_logger: Optional[QuicLoggerTrace] = None,
Expand Down Expand Up @@ -98,9 +98,9 @@ def __init__(
self._packet_start = 0
self._packet_type = 0

self._buffer = Buffer(PACKET_MAX_SIZE)
self._buffer_capacity = PACKET_MAX_SIZE
self._flight_capacity = PACKET_MAX_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
29 changes: 16 additions & 13 deletions src/aioquic/quic/recovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@
K_SECOND = 1.0

# congestion control
K_MAX_DATAGRAM_SIZE = 1280
K_INITIAL_WINDOW = 10 * K_MAX_DATAGRAM_SIZE
K_MINIMUM_WINDOW = 2 * K_MAX_DATAGRAM_SIZE
K_INITIAL_WINDOW = 10
K_MINIMUM_WINDOW = 2
K_LOSS_REDUCTION_FACTOR = 0.5


Expand All @@ -37,7 +36,8 @@ def __init__(self) -> None:


class QuicPacketPacer:
def __init__(self) -> None:
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(K_MAX_DATAGRAM_SIZE / pacing_rate, K_SECOND)
K_MICRO_SECOND, min(self._max_datagram_size / pacing_rate, K_SECOND)
)

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

def __init__(self) -> None:
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.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 @@ -111,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 * K_MAX_DATAGRAM_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 @@ -131,7 +132,8 @@ def on_packets_lost(self, packets: Iterable[QuicSentPacket], now: float) -> None
if lost_largest_time > self._congestion_recovery_start_time:
self._congestion_recovery_start_time = now
self.congestion_window = max(
int(self.congestion_window * K_LOSS_REDUCTION_FACTOR), K_MINIMUM_WINDOW
int(self.congestion_window * K_LOSS_REDUCTION_FACTOR),
K_MINIMUM_WINDOW * self._max_datagram_size,
)
self.ssthresh = self.congestion_window

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

# congestion control
self._cc = QuicCongestionControl()
self._pacer = QuicPacketPacer()
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
Loading