-
-
Notifications
You must be signed in to change notification settings - Fork 400
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
feat: ack receiver timestamps #1992
base: main
Are you sure you want to change the base?
feat: ack receiver timestamps #1992
Conversation
Surfaces the TransportParameters to the application
Adds ReceiverTimestamps data structure to the space and expose methods.
This commit processes the inbound ACK frames that contain timestamps.
This commit handles sending frames that contain timestamps
Surfaces timing information to the CC interface.
I don't think a feature makes sense for this, what's the motivation for that? Suggest squashing all of these commits because I don't think they make much sense on their own. |
@djc I suggested using a feature bc it is a draft specification, perhaps we should name the feature after the specification |
I don't think that's a good reason to make it a feature. The current API surface will still become part of our public API, and it just adds a bunch of complexity. We've added draft functionality before, should be fine. |
I can remove the feature flag in a follow up commit. Regarding squashing, my intention for the commits were to keep separate different parts of the PR for review and then squash at the end. If you want me to squash it now, I can do that too. |
While we are fans of clean commit histories with atomic commits, I think this falls short of that because the commits are not conceptually independent -- it doesn't make sense to add configuration that doesn't do anything, for example. |
f7bd14c
to
3af9df4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's a bunch of mostly stylistic feedback.
quinn-proto/src/congestion.rs
Outdated
@@ -34,6 +34,20 @@ pub trait Controller: Send + Sync { | |||
) { | |||
} | |||
|
|||
#[allow(unused_variables)] | |||
/// Packet deliveries were confirmed with timestamps information. | |||
fn on_ack_packet( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these actually changes you'd look to make to the on_ack()
interface which you're adding a separate method for to keep it semver-compatible?
If so, should this default implementation call self.on_ack()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, I did it this way to prevent any breaking changes if there were users of the on_ack
method. Yea, I think it makes sense for the default implementation to all self.on_ack
, and then we can remove the congestion.on_ack
call from on_packet_acked
and just have it call congestion.on_ack_packet
.
self.next_pn -= 1; | ||
} | ||
|
||
let delta = self.data.get_var().unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this safe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This follows the same pattern done on AckIter
, where unwrap
is used.
I think it's considered safe because this is defined in the spec. If the value is expected to be a VARINT based on the spec but it isn't, we wouldn't be able to proceed in processing the packet.
Another option is to change the return type to be a tuple that includes an error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't include a SAFETY comment here because the others didn't have them either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's considered safe because this is defined in the spec.
What's in the spec doesn't matter. Quinn must not panic just because a peer is non-conformant. That would be a DoS vulnerability.
I imagine this is safe because scan_ack_timestamps_blocks
validated the ranges already. If that was the intent, document it, even if we've failed to do so elsewhere.
receive_timestamps_exponent: config | ||
.ack_timestamp_config | ||
.as_ref() | ||
.map(|cfg| cfg.exponent), | ||
|
||
max_recv_timestamps_per_ack: config | ||
.ack_timestamp_config | ||
.as_ref() | ||
.map(|cfg| cfg.max_timestamps_per_ack), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these maybe shouldn't be separate Option
s, but a tuple wrapped in a single Option
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this file, rather than using two individual parameters on the TransportParameters
struct, I'm using the AckTimestampConfig
and making max_timestamps_per_ack
an Option
. None
signaling that the feature is disabled.
Exponent defaults to 0, which is what the spec states.
It could be worth proposing for the draft that the transport parameters related to this feature are grouped together similar to how PreferredAddress
does it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could be worth proposing for the draft that the transport parameters related to this feature are grouped together similar to how PreferredAddress does it.
Yes, that would probably be good.
change field name and arg names
c9224f3
to
67af041
Compare
receive_timestamps_exponent: config | ||
.ack_timestamp_config | ||
.as_ref() | ||
.map(|cfg| cfg.exponent), | ||
|
||
max_recv_timestamps_per_ack: config | ||
.ack_timestamp_config | ||
.as_ref() | ||
.map(|cfg| cfg.max_timestamps_per_ack), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this file, rather than using two individual parameters on the TransportParameters
struct, I'm using the AckTimestampConfig
and making max_timestamps_per_ack
an Option
. None
signaling that the feature is disabled.
Exponent defaults to 0, which is what the spec states.
It could be worth proposing for the draft that the transport parameters related to this feature are grouped together similar to how PreferredAddress
does it.
self.next_pn -= 1; | ||
} | ||
|
||
let delta = self.data.get_var().unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This follows the same pattern done on AckIter
, where unwrap
is used.
I think it's considered safe because this is defined in the spec. If the value is expected to be a VARINT based on the spec but it isn't, we wouldn't be able to proceed in processing the packet.
Another option is to change the return type to be a tuple that includes an error.
quinn-proto/src/congestion.rs
Outdated
@@ -34,6 +34,20 @@ pub trait Controller: Send + Sync { | |||
) { | |||
} | |||
|
|||
#[allow(unused_variables)] | |||
/// Packet deliveries were confirmed with timestamps information. | |||
fn on_ack_packet( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, I did it this way to prevent any breaking changes if there were users of the on_ack
method. Yea, I think it makes sense for the default implementation to all self.on_ack
, and then we can remove the congestion.on_ack
call from on_packet_acked
and just have it call congestion.on_ack_packet
.
fn new(largest: u64, basis_instant: Instant, exponent: u64, mut data: &'a [u8]) -> Self { | ||
// We read and throw away the Timestamp Range Count value because | ||
// it was already used to properly slice the data. | ||
let _ = data.get_var().unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This unwrap
is safe because the data
byte slice was sliced using scan_ack_timestamp_blocks
, which this get_var()
was checked using ?
.
self.next_pn -= 1; | ||
} | ||
|
||
let delta = self.data.get_var().unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't include a SAFETY comment here because the others didn't have them either.
fn new(largest: u64, basis_instant: Instant, exponent: u64, mut data: &'a [u8]) -> Self { | ||
// We read and throw away the Timestamp Range Count value because | ||
// it was already used to properly slice the data. | ||
let _ = data.get_var().unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a safety comment.
4a42bec
to
fa004c5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As noted in the issue, please work with the IETF to reserve IDs. To avoid interop problems, public implementations must not use unreserved IDs
pub fn max_ack_timestamps(&mut self, value: VarInt) -> &mut Self { | ||
self.ack_timestamps_config.max_timestamps_per_ack = Some(value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pub fn max_ack_timestamps(&mut self, value: VarInt) -> &mut Self { | |
self.ack_timestamps_config.max_timestamps_per_ack = Some(value); | |
pub fn max_ack_timestamps(&mut self, value: Option<VarInt>) -> &mut Self { | |
self.ack_timestamps_config.max_timestamps_per_ack = value; |
- What happens if someone specifies
Some(0)
? Document. - Is it useful to users for the quantity to be configurable, or should this just be a
bool
flag? We don't expose configuration for the maximum number of ACK ranges, for example.
.finish() | ||
} | ||
} | ||
|
||
/// Parameters for controlling the peer's acknowledgements with receiver timestamps. | ||
#[derive(Clone, Debug, PartialEq, Eq, Copy)] | ||
pub struct AckTimestampsConfig { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is public, then the main config struct should have a single config setter that accepts an Option<AckTimestampsConfig>
. If you want setters for individual fields directly on the main struct, then it's confusing for this to be public.
quinn-proto/src/congestion.rs
Outdated
pn: u64, | ||
now: Instant, | ||
sent: Instant, | ||
received: Option<Instant>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
received
should use a type other than Instant
to represent that it is not comparable to sent
.
@@ -227,6 +229,10 @@ pub struct Connection { | |||
/// no outgoing application data. | |||
app_limited: bool, | |||
|
|||
// Ack Receive Timestamps | |||
// The timestamp config of the peer. | |||
ack_timestamps_cfg: AckTimestampsConfig, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Option
?
quinn-proto/src/congestion.rs
Outdated
@@ -34,6 +34,21 @@ pub trait Controller: Send + Sync { | |||
) { | |||
} | |||
|
|||
#[allow(unused_variables)] | |||
/// Packet deliveries were confirmed with timestamps information. | |||
fn on_ack_packet( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fn on_ack_packet( | |
fn on_ack_timestamped( |
fn default() -> Self { | ||
Self { | ||
packet_number: 0, | ||
timestamp: Instant::now(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's weird for default()
to return different values over time. Maybe encode the delta here instead? Or, do we even need a Default
impl?
} | ||
if let Some(v) = self.data.back() { | ||
if packet_number <= v.packet_number { | ||
warn!("out of order packets are not supported"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be very noisy on some real networks. It should be trace
at most, or omitted entirely.
}); | ||
} | ||
|
||
pub(crate) fn subtract_below(&mut self, packet_number: u64) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document.
} | ||
} | ||
|
||
pub(crate) fn iter(&self) -> impl DoubleEndedIterator<Item = PacketTimestamp> + '_ { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document order.
quinn-proto/src/connection/mod.rs
Outdated
self.ack_timestamps_cfg = params.ack_timestamps_cfg; | ||
if let Some(max) = params.ack_timestamps_cfg.max_timestamps_per_ack { | ||
for space in self.spaces.iter_mut() { | ||
space.pending_acks.set_receiver_timestamp(max.0 as usize); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clamp this maximum to a reasonable value to guard against memory exhaustion attacks.
This PR is the implementation that addresses #1988
Manual E2E Testing/Validation instructions: sterlingdeng#2