Skip to main content

h3i/client/
mod.rs

1// Copyright (C) 2024, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27//! The main h3i client runner.
28//!
29//! The client is responsible for connecting to an indicated server, executing
30//! as series of [Action]s, and capturing the results in a
31//! [ConnectionSummary].
32
33#[cfg(feature = "async")]
34pub mod async_client;
35pub mod connection_summary;
36pub mod sync_client;
37
38use connection_summary::*;
39use qlog::events::http3::FrameParsed;
40use qlog::events::http3::HttpHeader;
41use quiche::ConnectionError;
42
43use std::collections::HashMap;
44use std::net::SocketAddr;
45use std::time::Instant;
46
47use crate::actions::h3::Action;
48use crate::actions::h3::ExpectedStreamSendResult;
49use crate::actions::h3::StreamEvent;
50use crate::actions::h3::StreamEventType;
51use crate::config::Config;
52use crate::frame::H3iFrame;
53use crate::frame::ResetStream;
54use crate::frame_parser::FrameParseResult;
55use crate::frame_parser::FrameParser;
56use crate::frame_parser::InterruptCause;
57use crate::recordreplay::qlog::QlogEvent;
58use crate::recordreplay::qlog::*;
59use qlog::events::http3::Http3Frame;
60use qlog::events::EventData;
61use qlog::streamer::QlogStreamer;
62use serde::Serialize;
63
64use crate::quiche;
65use quiche::h3::frame::Frame as QFrame;
66use quiche::h3::Error;
67use quiche::h3::NameValue;
68
69const MAX_DATAGRAM_SIZE: usize = 1350;
70const QUIC_VERSION: u32 = 1;
71
72fn handle_qlog(
73    qlog_streamer: Option<&mut QlogStreamer>, qlog_frame: Http3Frame,
74    stream_id: u64,
75) {
76    if let Some(s) = qlog_streamer {
77        let ev_data = EventData::Http3FrameParsed(FrameParsed {
78            stream_id,
79            frame: qlog_frame,
80            ..Default::default()
81        });
82
83        s.add_event_data_now(ev_data).ok();
84    }
85}
86
87#[derive(Debug, Serialize)]
88/// Represents different errors that can occur when the h3i client runs.
89pub enum ClientError {
90    /// An error during the QUIC handshake.
91    HandshakeFail,
92    /// An error during HTTP/3 exchanges.
93    HttpFail,
94    /// Some other type of error.
95    Other(String),
96}
97
98pub(crate) trait Client {
99    /// Gives mutable access to the stream parsers to update their state.
100    fn stream_parsers_mut(&mut self) -> &mut StreamParserMap;
101
102    /// Handles a response frame. This allows [`Client`]s to customize how they
103    /// construct a [`StreamMap`] from a list of frames.
104    fn handle_response_frame(&mut self, stream_id: u64, frame: H3iFrame);
105}
106
107pub(crate) type StreamParserMap = HashMap<u64, FrameParser>;
108
109fn validate_stream_send_result(
110    result: quiche::Result<usize>, expected: &ExpectedStreamSendResult,
111    stream_id: u64,
112) {
113    match expected {
114        ExpectedStreamSendResult::Ok => {
115            result.unwrap_or_else(|err| {
116                panic!(
117                    "Expected stream_send on stream {} to succeed, but got: {:?}",
118                    stream_id, err
119                )
120            });
121        },
122        ExpectedStreamSendResult::OkExact(expected_bytes) => {
123            match result {
124                Ok(actual_bytes) if actual_bytes == *expected_bytes => {},
125                Ok(actual_bytes) => panic!(
126                    "Expected stream_send on stream {} to write {} bytes, got {}",
127                    stream_id, expected_bytes, actual_bytes
128                ),
129                Err(err) => panic!(
130                    "Expected stream_send on stream {} to write {} bytes, got error: {:?}",
131                    stream_id, expected_bytes, err
132                ),
133            }
134        },
135        ExpectedStreamSendResult::Error(expected_err) => {
136            match result {
137                Ok(written) => panic!(
138                    "Expected stream_send on stream {} to fail with {:?}, but wrote {} bytes",
139                    stream_id, expected_err, written
140                ),
141                Err(actual_err) if &actual_err == expected_err => {},
142                Err(actual_err) => panic!(
143                    "Expected stream_send on stream {} to fail with {:?}, got {:?}",
144                    stream_id, expected_err, actual_err
145                ),
146            }
147        },
148    }
149}
150
151pub(crate) fn execute_action<F: quiche::BufFactory>(
152    action: &Action, conn: &mut quiche::Connection<F>,
153    stream_parsers: &mut StreamParserMap,
154) {
155    match action {
156        Action::SendFrame {
157            stream_id,
158            fin_stream,
159            frame,
160            expected_result,
161        } => {
162            log::info!("frame tx id={stream_id} frame={frame:?}");
163
164            // TODO: make serialization smarter
165            let mut d = [42; 9999];
166            let mut b = octets::OctetsMut::with_slice(&mut d);
167
168            if let Some(s) = conn.qlog_streamer() {
169                let events: QlogEvents = action.into();
170                for event in events {
171                    match event {
172                        QlogEvent::Event { data, ex_data } => {
173                            // skip dummy packet
174                            if matches!(
175                                data.as_ref(),
176                                EventData::QuicPacketSent(..)
177                            ) {
178                                continue;
179                            }
180
181                            s.add_event_data_ex_now(*data, ex_data).ok();
182                        },
183
184                        QlogEvent::JsonEvent(mut ev) => {
185                            // need to rewrite the event time
186                            ev.time = Instant::now()
187                                .duration_since(s.start_time())
188                                .as_secs_f64() *
189                                1000.0;
190                            s.add_event(ev).ok();
191                        },
192                    }
193                }
194            }
195            let len = frame.to_bytes(&mut b).unwrap();
196
197            // TODO - consider storying result in ConnectionSummary too.
198            let result = conn.stream_send(*stream_id, &d[..len], *fin_stream);
199            validate_stream_send_result(result, expected_result, *stream_id);
200
201            stream_parsers
202                .entry(*stream_id)
203                .or_insert_with(|| FrameParser::new(*stream_id));
204        },
205
206        Action::SendHeadersFrame {
207            stream_id,
208            fin_stream,
209            headers,
210            frame,
211            expected_result,
212            ..
213        } => {
214            log::info!("headers frame tx stream={stream_id} hdrs={headers:?}");
215
216            // TODO: make serialization smarter
217            let mut d = [42; 9999];
218            let mut b = octets::OctetsMut::with_slice(&mut d);
219
220            if let Some(s) = conn.qlog_streamer() {
221                let events: QlogEvents = action.into();
222                for event in events {
223                    match event {
224                        QlogEvent::Event { data, ex_data } => {
225                            // skip dummy packet
226                            if matches!(
227                                data.as_ref(),
228                                EventData::QuicPacketSent(..)
229                            ) {
230                                continue;
231                            }
232
233                            s.add_event_data_ex_now(*data, ex_data).ok();
234                        },
235
236                        QlogEvent::JsonEvent(mut ev) => {
237                            // need to rewrite the event time
238                            ev.time = Instant::now()
239                                .duration_since(s.start_time())
240                                .as_secs_f64() *
241                                1000.0;
242                            s.add_event(ev).ok();
243                        },
244                    }
245                }
246            }
247            let len = frame.to_bytes(&mut b).unwrap();
248            let result = conn.stream_send(*stream_id, &d[..len], *fin_stream);
249            validate_stream_send_result(result, expected_result, *stream_id);
250
251            stream_parsers
252                .entry(*stream_id)
253                .or_insert_with(|| FrameParser::new(*stream_id));
254        },
255
256        Action::OpenUniStream {
257            stream_id,
258            fin_stream,
259            stream_type,
260            expected_result,
261        } => {
262            log::info!(
263                "open uni stream_id={stream_id} ty={stream_type} fin={fin_stream}"
264            );
265
266            let mut d = [42; 8];
267            let mut b = octets::OctetsMut::with_slice(&mut d);
268            b.put_varint(*stream_type).unwrap();
269            let off = b.off();
270
271            let result = conn.stream_send(*stream_id, &d[..off], *fin_stream);
272            validate_stream_send_result(result, expected_result, *stream_id);
273
274            stream_parsers
275                .entry(*stream_id)
276                .or_insert_with(|| FrameParser::new(*stream_id));
277        },
278
279        Action::StreamBytes {
280            stream_id,
281            bytes,
282            fin_stream,
283            expected_result,
284        } => {
285            log::info!(
286                "stream bytes tx id={} len={} fin={}",
287                stream_id,
288                bytes.len(),
289                fin_stream
290            );
291            let result = conn.stream_send(*stream_id, bytes, *fin_stream);
292            validate_stream_send_result(result, expected_result, *stream_id);
293
294            stream_parsers
295                .entry(*stream_id)
296                .or_insert_with(|| FrameParser::new(*stream_id));
297        },
298
299        Action::SendDatagram { payload } => {
300            log::info!("dgram tx len={}", payload.len(),);
301
302            conn.dgram_send(payload)
303                .expect("datagram extension not enabled by peer");
304        },
305
306        Action::ResetStream {
307            stream_id,
308            error_code,
309        } => {
310            log::info!(
311                "reset_stream stream_id={stream_id} error_code={error_code}"
312            );
313            if let Err(e) = conn.stream_shutdown(
314                *stream_id,
315                quiche::Shutdown::Write,
316                *error_code,
317            ) {
318                log::error!("can't send reset_stream: {e}");
319                // Clients cannot reset streams they do not own. If attempted,
320                // `stream_shutdown()` fails and no parser should be created.
321                return;
322            }
323
324            stream_parsers
325                .entry(*stream_id)
326                .or_insert_with(|| FrameParser::new(*stream_id));
327        },
328
329        Action::StopSending {
330            stream_id,
331            error_code,
332        } => {
333            log::info!(
334                "stop_sending stream id={stream_id} error_code={error_code}"
335            );
336
337            if let Err(e) = conn.stream_shutdown(
338                *stream_id,
339                quiche::Shutdown::Read,
340                *error_code,
341            ) {
342                log::error!("can't send stop_sending: {e}");
343            }
344
345            // A `STOP_SENDING` should elicit a `RESET_STREAM` in response,
346            // which the frame parser can automatically handle.
347            stream_parsers
348                .entry(*stream_id)
349                .or_insert_with(|| FrameParser::new(*stream_id));
350        },
351
352        Action::ConnectionClose { error } => {
353            let ConnectionError {
354                is_app,
355                error_code,
356                reason,
357            } = error;
358
359            log::info!("connection_close={error:?}");
360            let _ = conn.close(*is_app, *error_code, reason);
361        },
362
363        // Neither of these actions will manipulate the Quiche connection
364        Action::FlushPackets | Action::Wait { .. } => unreachable!(),
365    }
366}
367
368pub(crate) fn parse_streams<F: quiche::BufFactory, C: Client>(
369    conn: &mut quiche::Connection<F>, client: &mut C,
370) -> Vec<StreamEvent> {
371    let mut responded_streams: Vec<StreamEvent> =
372        Vec::with_capacity(conn.readable().len());
373
374    for stream in conn.readable() {
375        // TODO: ignoring control streams
376        if stream % 4 != 0 {
377            continue;
378        }
379
380        loop {
381            let stream_parse_result = client
382                .stream_parsers_mut()
383                .get_mut(&stream)
384                .expect("stream readable with no parser")
385                .try_parse_frame(conn);
386
387            match stream_parse_result {
388                Ok(FrameParseResult::FrameParsed { h3i_frame, fin }) => {
389                    if let H3iFrame::Headers(ref headers) = h3i_frame {
390                        log::info!("hdrs={headers:?}");
391                    }
392
393                    handle_response_frame(
394                        client,
395                        conn.qlog_streamer(),
396                        &mut responded_streams,
397                        stream,
398                        h3i_frame,
399                    );
400
401                    if fin {
402                        handle_fin(
403                            &mut responded_streams,
404                            client.stream_parsers_mut(),
405                            stream,
406                        );
407                        break;
408                    }
409                },
410                Ok(FrameParseResult::Retry) => {},
411                Ok(FrameParseResult::Interrupted(cause)) => {
412                    if let InterruptCause::ResetStream(error_code) = cause {
413                        let frame = H3iFrame::ResetStream(ResetStream {
414                            stream_id: stream,
415                            error_code,
416                        });
417
418                        log::info!("received reset stream: {frame:?}");
419                        handle_response_frame(
420                            client,
421                            None,
422                            &mut responded_streams,
423                            stream,
424                            frame,
425                        );
426                    }
427
428                    handle_fin(
429                        &mut responded_streams,
430                        client.stream_parsers_mut(),
431                        stream,
432                    );
433                    break;
434                },
435                Err(e) => {
436                    match e {
437                        Error::TransportError(quiche::Error::Done) => {
438                            log::debug!("stream {stream} exhausted");
439                        },
440                        Error::TransportError(quiche::Error::StreamReset(
441                            error_code,
442                        )) => {
443                            let frame = H3iFrame::ResetStream(ResetStream {
444                                stream_id: stream,
445                                error_code,
446                            });
447
448                            log::info!("received reset stream: {frame:?}");
449
450                            handle_response_frame(
451                                client,
452                                None,
453                                &mut responded_streams,
454                                stream,
455                                frame,
456                            );
457
458                            client.stream_parsers_mut().remove(&stream);
459                        },
460                        _ => {
461                            log::warn!("stream read error: {e}");
462                        },
463                    };
464
465                    break;
466                },
467            }
468        }
469    }
470
471    responded_streams
472}
473
474fn handle_fin(
475    responded_streams: &mut Vec<StreamEvent>,
476    stream_parsers: &mut StreamParserMap, stream_id: u64,
477) {
478    responded_streams.push(StreamEvent {
479        stream_id,
480        event_type: StreamEventType::Finished,
481    });
482
483    stream_parsers.remove(&stream_id);
484}
485
486/// Push any responses to the [StreamMap] as well as store them in the
487/// `responded` vector
488fn handle_response_frame<C: Client>(
489    client: &mut C, qlog_streamer: Option<&mut QlogStreamer>,
490    responded_streams: &mut Vec<StreamEvent>, stream_id: u64, frame: H3iFrame,
491) {
492    let cloned = frame.clone();
493    client.handle_response_frame(stream_id, cloned);
494
495    let mut to_qlog: Option<Http3Frame> = None;
496    let mut push_to_responses: Option<StreamEvent> = None;
497
498    match frame {
499        H3iFrame::Headers(enriched_headers) => {
500            push_to_responses = Some(StreamEvent {
501                stream_id,
502                event_type: StreamEventType::Headers,
503            });
504
505            let qlog_headers: Vec<HttpHeader> = enriched_headers
506                .headers()
507                .iter()
508                .map(|h| qlog::events::http3::HttpHeader {
509                    name: Some(String::from_utf8_lossy(h.name()).into_owned()),
510                    name_bytes: None,
511                    value: Some(String::from_utf8_lossy(h.value()).into_owned()),
512                    value_bytes: None,
513                })
514                .collect();
515
516            to_qlog = Some(Http3Frame::Headers {
517                headers: qlog_headers,
518                raw: None,
519            });
520        },
521        H3iFrame::QuicheH3(quiche_frame) => {
522            if let QFrame::Data { .. } = quiche_frame {
523                push_to_responses = Some(StreamEvent {
524                    stream_id,
525                    event_type: StreamEventType::Data,
526                });
527            }
528
529            to_qlog = Some(quiche_frame.to_qlog());
530        },
531        H3iFrame::ResetStream(_) => {
532            push_to_responses = Some(StreamEvent {
533                stream_id,
534                event_type: StreamEventType::Finished,
535            });
536        },
537    }
538
539    if let Some(to_qlog) = to_qlog {
540        handle_qlog(qlog_streamer, to_qlog, stream_id);
541    }
542
543    if let Some(to_push) = push_to_responses {
544        responded_streams.push(to_push);
545    }
546}
547
548pub(crate) struct ParsedArgs<'a> {
549    pub(crate) bind_addr: SocketAddr,
550    pub(crate) peer_addr: SocketAddr,
551    pub(crate) connect_url: Option<&'a str>,
552}
553
554pub(crate) fn parse_args(args: &Config) -> ParsedArgs<'_> {
555    // We'll only connect to one server.
556    let connect_url = if !args.omit_sni {
557        args.host_port.split(':').next()
558    } else {
559        None
560    };
561
562    let (peer_addr, bind_addr) = resolve_socket_addrs(args);
563
564    ParsedArgs {
565        peer_addr,
566        bind_addr,
567        connect_url,
568    }
569}
570
571fn resolve_socket_addrs(args: &Config) -> (SocketAddr, SocketAddr) {
572    // Resolve server address.
573    let peer_addr = if let Some(addr) = &args.connect_to {
574        addr.parse().expect("--connect-to is expected to be a string containing an IPv4 or IPv6 address with a port. E.g. 192.0.2.0:443")
575    } else {
576        let x = format!("https://{}", args.host_port);
577        *url::Url::parse(&x)
578            .unwrap()
579            .socket_addrs(|| None)
580            .unwrap()
581            .first()
582            .unwrap()
583    };
584
585    // Bind to INADDR_ANY or IN6ADDR_ANY depending on the IP family of the
586    // server address. This is needed on macOS and BSD variants that don't
587    // support binding to IN6ADDR_ANY for both v4 and v6.
588    let bind_addr = match peer_addr {
589        std::net::SocketAddr::V4(_) => format!("0.0.0.0:{}", args.source_port),
590        std::net::SocketAddr::V6(_) => format!("[::]:{}", args.source_port),
591    };
592
593    (
594        peer_addr,
595        bind_addr.parse().expect("unable to parse bind address"),
596    )
597}