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

feat: QUIC Address discovery extension #2043

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

divagant-martian
Copy link

@divagant-martian divagant-martian commented Nov 14, 2024

Implements the Quic Address discovery extension IETF draft

  • Transport parameters are implemented with tests and 0rtt verification as spec'd
  • The spec declares two frames, one for informing of IpV4 addresses and one for informing IpV6 frames. This is implemented as a single structure with tests.
  • Transmission is done after the initial path has been established, when path validation is required, and sent with path responses. The last part is not required but provides a small reconfirmation of the observed external address to the peer, and it's allowed by the spec being observed address frames defined as probing frames. This is implemented with tests.
  • Retransmission is done by sending a fresh frame if it's identified as lost. This ensures that the path over which the frame is sent is always the correct one, i.e. That the reported observed address is always sent using the path for which the address was observed. This is implemented with tests.
  • Surfacing is done from the protocol implementation via Connection events.
  • Configuration is done with two methods to independently turn on or off the receiving and sending of reports.

@divagant-martian divagant-martian marked this pull request as ready for review November 21, 2024 17:26
Copy link
Collaborator

@Ralith Ralith left a comment

Choose a reason for hiding this comment

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

Thanks for working on this!

quinn/src/connection.rs Outdated Show resolved Hide resolved
@@ -636,6 +636,12 @@ impl Connection {
// May need to send MAX_STREAMS to make progress
conn.wake();
}

/// Track changed on our external address as reported by the peer.
pub fn observed_external_addr(&self) -> watch::Receiver<Option<SocketAddr>> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Explicitly document when this is None, whether it might contain spurious updates, and the influence of configuration and peer support.

Copy link
Member

Choose a reason for hiding this comment

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

Not sure about exposing the watch::Receiver as part of the public API, that seems like it's leaking a bunch of implementation details?

quinn/Cargo.toml Outdated Show resolved Hide resolved
quinn-proto/src/tests/mod.rs Outdated Show resolved Hide resolved
/// NOTE: this test is the same as zero_rtt_happypath, changing client transport parameters on
/// resumption.
#[test]
fn address_discovery_zero_rtt_accepted() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

What additional coverage does this case supply? I don't think changing client transport parameters are ever a concern for 0-RTT.

quinn-proto/src/frame.rs Outdated Show resolved Hide resolved
@@ -3760,6 +3874,8 @@ pub enum Event {
DatagramReceived,
/// One or more application datagrams have been sent after blocking
DatagramsUnblocked,
/// Received an observation of our external address from the peer.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Explicitly document whether this is necessarily different from the previous one, and the required configuration and peer support.

Comment on lines +2947 to +2943
// include in migration
migration_observed_addr = Some(observed)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this a special case?

quinn-proto/src/frame.rs Outdated Show resolved Hide resolved
quinn-proto/src/address_discovery.rs Outdated Show resolved Hide resolved
quinn-proto/src/address_discovery.rs Outdated Show resolved Hide resolved
quinn-proto/src/address_discovery.rs Outdated Show resolved Hide resolved
Comment on lines +2930 to +2928
{
return Err(TransportError::PROTOCOL_VIOLATION(
"received OBSERVED_ADDRESS frame when not negotiated",
));
Copy link
Member

Choose a reason for hiding this comment

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

Could we yield this error from within should_report() instead? (Maybe also pass in packet.header.space()?)

new_path.last_observed_addr_report = self.path.last_observed_addr_report.clone();
if let Some(report) = observed_addr {
if let Some(updated) = new_path.update_observed_addr_report(report) {
self.events.push_back(Event::ObservedAddr(updated));
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Can we push all/most of this logic into a method on PathData?

quinn-proto/src/frame.rs Outdated Show resolved Hide resolved
quinn-proto/src/frame.rs Show resolved Hide resolved
quinn-proto/src/transport_parameters.rs Show resolved Hide resolved
Comment on lines +125 to +135
let mut external_addresses = conn.observed_external_addr();
tokio::spawn(async move {
loop {
if let Some(new_addr) = *external_addresses.borrow_and_update() {
info!(%new_addr, "new external address report");
}
if external_addresses.changed().await.is_err() {
break;
}
}
});
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is widely used enough that it merits real estate in these examples.

@@ -636,6 +636,12 @@ impl Connection {
// May need to send MAX_STREAMS to make progress
conn.wake();
}

/// Track changed on our external address as reported by the peer.
pub fn observed_external_addr(&self) -> watch::Receiver<Option<SocketAddr>> {
Copy link
Member

Choose a reason for hiding this comment

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

Not sure about exposing the watch::Receiver as part of the public API, that seems like it's leaking a bunch of implementation details?

Copy link
Collaborator

@gretchenfrage gretchenfrage left a comment

Choose a reason for hiding this comment

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

Looks promising so far!

@@ -222,6 +222,12 @@ pub struct Connection {
/// no outgoing application data.
app_limited: bool,

//
// ObservedAddr
Copy link
Collaborator

Choose a reason for hiding this comment

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

Probably Address discovery would be a better section divider comment

@@ -3058,6 +3103,53 @@ impl Connection {
self.stats.frame_tx.handshake_done.saturating_add(1);
}

// OBSERVED_ADDR
let mut send_observed_address =
Copy link
Collaborator

Choose a reason for hiding this comment

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

It looks like the only reason for this to be a lambda is because of the short-circuit. I think this can be refactored from:

let do_thing = |a, b, c, d, e| {
    if (!should_do_thing) {
        return;
    }
    do_stuff....
}
do_thing(a, b, c, d, e);

To just:

if (should_do_thing) {
    do_stuff...
}

It would be equally indented.

@@ -37,6 +40,11 @@ pub(super) struct PathData {
/// Used in persistent congestion determination.
pub(super) first_packet_after_rtt_sample: Option<(SpaceId, u64)>,
pub(super) in_flight: InFlight,
/// Whether this path has had it's remote address reported back to the peer. This only happens
/// if both peers agree to so based on their transport parameters.
pub(super) observed_addr_sent: bool,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we avoid tracking this extra state by queueing a frame immediately whenever we change the path?

As an example, I am implementing similar "send frames X constant number of times per path" in #1912 with logic here / here / here

&mut self,
observed: ObservedAddr,
) -> Option<SocketAddr> {
match self.last_observed_addr_report.as_mut() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this could be shorted a lot. ObservedAddr is copy. Maybe something like this?

let prev = self.last_observed_addr_report;
if prev.map(|prev| prev.seq_no < observed.seq_no).unwrap_or(true) {
    self.last_observed_addr_report = Some(observed);
}
if Some(observed.socket_addr()) != prev.map(|prev| prev.socket_addr()) {
    Some(observed.socket_addr())
} else {
    None
}

It could be golfed more but something like the above perhaps is at a nice spot for readability.

IpAddr::V6(ipv6_addr) => {
buf.write(ipv6_addr);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: Maybe just this?

        match self.ip {
            IpAddr::V4(ipv4_addr) => buf.write(ipv4_addr),
            IpAddr::V6(ipv6_addr) => buf.write(ipv6_addr),
        }

Ok(Self { seq_no, ip, port })
}

/// Gives the [`SocketAddr`] reported in the frame.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This project's convention is to have the first line of a doc comment not end with a period

/// Whether this peer should report observed addresses to the other peer.
pub(crate) fn should_report(&self, other: &Self) -> bool {
self.is_reporter() && other.receives_reports()
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think maybe this should just be inlined into its call sites, I don't think the abstraction is helping

quinn-proto/src/address_discovery.rs Outdated Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants