cloudflare / cloudflare/quiche

PMTUD state is consumed by wrong path when sending on probing path

Open
#2,565 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
11.8k
Forks
1.1k
Avg merge
21h 9m
Merged PRs (30d)
6

Description

## Summary
When a connection has an active path (`old_path`) with a pending PMTUD probe, sending a packet on a different path being validated (`new_path`) can incorrectly consume the PMTUD state of `old_path`.

The `new_path` is not active yet. It is only selected as the send path because the connection needs to transmit `PATH_RESPONSE` frame.

As a result, the PMTUD probe on `old_path` is marked as in flight even though no PMTUD probe was sent on that path.

## Scenario

1. `old_path` is the current active path.
2. PMTU revalidation is requested on `old_path`.
3. `old_path` now has a pending PMTUD probe.
4. The client starts validating `new_path` and sends a
`PATH_CHALLENGE` frame on it.
5. The server receives the `PATH_CHALLENGE`, creates `new_path`, and
queues a `PATH_RESPONSE` frame.
6. The next server packet is sent on `new_path`.
7. The send logic incorrectly updates the PMTUD state associated with
`old_path`.

## Regression test
```rust
#[test]
fn sending_on_probing_path_does_not_consume_active_path_pmtud_probe() {
const PROBE_SIZE: usize = 1400;

let mut config = Config::new(PROTOCOL_VERSION).unwrap();

config
.load_cert_chain_from_pem_file("examples/cert.crt")
.unwrap();

config
.load_priv_key_from_pem_file("examples/cert.key")
.unwrap();

config.set_application_protos(&[b"proto1"]).unwrap();
config.verify_peer(false);
config.set_active_connection_id_limit(2);
config.set_max_send_udp_payload_size(PROBE_SIZE);
config.discover_pmtu(true);

let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 1);

// `old_path` is the current active path.
let old_path_id = pipe.server.paths.get_active_path_id().unwrap();

// PMTU revalidation is requested on `old_path`.
pipe.server.revalidate_pmtu();

// `old_path` now has a pending PMTUD probe.
assert!(
pipe.server
.paths
.get(old_path_id)
.unwrap()
.pmtud
.as_ref()
.unwrap()
.should_probe(),
"`old_path` should have a pending PMTUD probe"
);

let server_addr = test_utils::Pipe::server_addr();
let new_client_addr = "127.0.0.1:5678".parse().unwrap();
let mut out = vec![0; PROBE_SIZE];

// The client starts validating `new_path`.
pipe.client
.probe_path(new_client_addr, server_addr)
.unwrap();

// The client sends a PATH_CHALLENGE frame on `new_path`.
let (written, info) = pipe
.client
.send_on_path(
&mut out,
Some(new_client_addr),
Some(server_addr),
)
.unwrap();

assert_eq!(info.from, new_client_addr);
assert_eq!(info.to, server_addr);

// The server receives the PATH_CHALLENGE, creates `new_path`, and
// queues a PATH_RESPONSE frame.
pipe.server
.recv(
&mut out[..written],
RecvInfo {
from: info.from,
to: info.to,
},
)
.unwrap();

// `new_path` is being validated, but `old_path` remains active.
assert_eq!(
pipe.server.paths.get_active_path_id().unwrap(),
old_path_id
);

// The next server packet is sent on `new_path`.
let (_, info) = pipe.server.send(&mut out).unwrap();

assert_eq!(info.from, server_addr);
assert_eq!(info.to, new_client_addr);

// Sending on `new_path` must not consume the pending PMTUD probe
// associated with `old_path`.
assert!(
pipe.server
.paths
.get(old_path_id)
.unwrap()
.pmtud
.as_ref()
.unwrap()
.should_probe(),
"sending on `new_path` consumed `old_path`'s PMTUD probe"
);
}
```

Contributor guide

Open the contributing guide

Research direction

Start by locating the regression test sending_on_probing_path_does_not_consume_active_path_pmtud_probe and trace the send(), send_on_path(), and PMTUD state updates it exercises. Reproduce the scenario with the test, then verify that sending PATH_RESPONSE on new_path leaves old_path's pending PMTUD probe unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
networking
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.