quiche_apps/
args.rs

1// Copyright (C) 2020, 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
27use super::common::alpns;
28use std::time::Duration;
29
30pub trait Args {
31    fn with_docopt(docopt: &docopt::Docopt) -> Self;
32}
33
34/// Contains commons arguments for creating a quiche QUIC connection.
35pub struct CommonArgs {
36    pub alpns: Vec<&'static [u8]>,
37    pub max_data: u64,
38    pub max_window: u64,
39    pub max_stream_data: u64,
40    pub max_stream_window: u64,
41    pub max_streams_bidi: u64,
42    pub max_streams_uni: u64,
43    pub idle_timeout: u64,
44    pub early_data: bool,
45    pub dump_packet_path: Option<String>,
46    pub no_grease: bool,
47    pub cc_algorithm: String,
48    pub disable_hystart: bool,
49    pub dgrams_enabled: bool,
50    pub dgram_count: u64,
51    pub dgram_data: String,
52    pub max_active_cids: u64,
53    pub enable_active_migration: bool,
54    pub max_field_section_size: Option<u64>,
55    pub qpack_max_table_capacity: Option<u64>,
56    pub qpack_blocked_streams: Option<u64>,
57    pub initial_rtt: Duration,
58    pub initial_cwnd_packets: u64,
59}
60
61/// Creates a new `CommonArgs` structure using the provided [`Docopt`].
62///
63/// The `Docopt` usage String needs to include the following:
64///
65/// --http-version VERSION      HTTP version to use.
66/// --max-data BYTES            Connection-wide flow control limit.
67/// --max-window BYTES          Connection-wide max receiver window.
68/// --max-stream-data BYTES     Per-stream flow control limit.
69/// --max-stream-window BYTES   Per-stream max receiver window.
70/// --max-streams-bidi STREAMS  Number of allowed concurrent streams.
71/// --max-streams-uni STREAMS   Number of allowed concurrent streams.
72/// --dump-packets PATH         Dump the incoming packets in PATH.
73/// --no-grease                 Don't send GREASE.
74/// --cc-algorithm NAME         Set a congestion control algorithm.
75/// --disable-hystart           Disable HyStart++.
76/// --dgram-proto PROTO         DATAGRAM application protocol.
77/// --dgram-count COUNT         Number of DATAGRAMs to send.
78/// --dgram-data DATA           DATAGRAM data to send.
79/// --max-active-cids NUM       Maximum number of active Connection IDs.
80/// --enable-active-migration   Enable active connection migration.
81/// --max-field-section-size BYTES  Max size of uncompressed field section.
82/// --qpack-max-table-capacity BYTES  Max capacity of dynamic QPACK decoding.
83/// --qpack-blocked-streams STREAMS  Limit of blocked streams while decoding.
84/// --initial-cwnd-packets      Size of initial congestion window, in packets.
85///
86/// [`Docopt`]: https://docs.rs/docopt/1.1.0/docopt/
87impl Args for CommonArgs {
88    fn with_docopt(docopt: &docopt::Docopt) -> Self {
89        let args = docopt.parse().unwrap_or_else(|e| e.exit());
90
91        let http_version = args.get_str("--http-version");
92        let dgram_proto = args.get_str("--dgram-proto");
93        let (alpns, dgrams_enabled) = match (http_version, dgram_proto) {
94            ("HTTP/0.9", "none") => (alpns::HTTP_09.to_vec(), false),
95
96            ("HTTP/0.9", _) => {
97                panic!("Unsupported HTTP version and DATAGRAM protocol.")
98            },
99
100            ("HTTP/3", "none") => (alpns::HTTP_3.to_vec(), false),
101
102            ("HTTP/3", "oneway") => (alpns::HTTP_3.to_vec(), true),
103
104            ("all", "none") => (
105                [alpns::HTTP_3.as_slice(), &alpns::HTTP_09]
106                    .concat()
107                    .to_vec(),
108                false,
109            ),
110
111            (..) => panic!("Unsupported HTTP version and DATAGRAM protocol."),
112        };
113
114        let dgram_count = args.get_str("--dgram-count");
115        let dgram_count = dgram_count.parse::<u64>().unwrap();
116
117        let dgram_data = args.get_str("--dgram-data").to_string();
118
119        let max_data = args.get_str("--max-data");
120        let max_data = max_data.parse::<u64>().unwrap();
121
122        let max_window = args.get_str("--max-window");
123        let max_window = max_window.parse::<u64>().unwrap();
124
125        let max_stream_data = args.get_str("--max-stream-data");
126        let max_stream_data = max_stream_data.parse::<u64>().unwrap();
127
128        let max_stream_window = args.get_str("--max-stream-window");
129        let max_stream_window = max_stream_window.parse::<u64>().unwrap();
130
131        let max_streams_bidi = args.get_str("--max-streams-bidi");
132        let max_streams_bidi = max_streams_bidi.parse::<u64>().unwrap();
133
134        let max_streams_uni = args.get_str("--max-streams-uni");
135        let max_streams_uni = max_streams_uni.parse::<u64>().unwrap();
136
137        let idle_timeout = args.get_str("--idle-timeout");
138        let idle_timeout = idle_timeout.parse::<u64>().unwrap();
139
140        let early_data = args.get_bool("--early-data");
141
142        let dump_packet_path = if !args.get_str("--dump-packets").is_empty() {
143            Some(args.get_str("--dump-packets").to_string())
144        } else {
145            None
146        };
147
148        let no_grease = args.get_bool("--no-grease");
149
150        let cc_algorithm = args.get_str("--cc-algorithm");
151
152        let disable_hystart = args.get_bool("--disable-hystart");
153
154        let max_active_cids = args.get_str("--max-active-cids");
155        let max_active_cids = max_active_cids.parse::<u64>().unwrap();
156
157        let enable_active_migration = args.get_bool("--enable-active-migration");
158
159        let max_field_section_size =
160            if !args.get_str("--max-field-section-size").is_empty() {
161                Some(
162                    args.get_str("--max-field-section-size")
163                        .parse::<u64>()
164                        .unwrap(),
165                )
166            } else {
167                None
168            };
169
170        let qpack_max_table_capacity =
171            if !args.get_str("--qpack-max-table-capacity").is_empty() {
172                Some(
173                    args.get_str("--qpack-max-table-capacity")
174                        .parse::<u64>()
175                        .unwrap(),
176                )
177            } else {
178                None
179            };
180
181        let qpack_blocked_streams =
182            if !args.get_str("--qpack-blocked-streams").is_empty() {
183                Some(
184                    args.get_str("--qpack-blocked-streams")
185                        .parse::<u64>()
186                        .unwrap(),
187                )
188            } else {
189                None
190            };
191
192        let initial_rtt_millis =
193            args.get_str("--initial-rtt").parse::<u64>().unwrap();
194        let initial_rtt = Duration::from_millis(initial_rtt_millis);
195
196        let initial_cwnd_packets = args
197            .get_str("--initial-cwnd-packets")
198            .parse::<u64>()
199            .unwrap();
200
201        CommonArgs {
202            alpns,
203            max_data,
204            max_window,
205            max_stream_data,
206            max_stream_window,
207            max_streams_bidi,
208            max_streams_uni,
209            idle_timeout,
210            early_data,
211            dump_packet_path,
212            no_grease,
213            cc_algorithm: cc_algorithm.to_string(),
214            disable_hystart,
215            dgrams_enabled,
216            dgram_count,
217            dgram_data,
218            max_active_cids,
219            enable_active_migration,
220            max_field_section_size,
221            qpack_max_table_capacity,
222            qpack_blocked_streams,
223            initial_rtt,
224            initial_cwnd_packets,
225        }
226    }
227}
228
229impl Default for CommonArgs {
230    fn default() -> Self {
231        CommonArgs {
232            alpns: alpns::HTTP_3.to_vec(),
233            max_data: 10000000,
234            max_window: 25165824,
235            max_stream_data: 1000000,
236            max_stream_window: 16777216,
237            max_streams_bidi: 100,
238            max_streams_uni: 100,
239            idle_timeout: 30000,
240            early_data: false,
241            dump_packet_path: None,
242            no_grease: false,
243            cc_algorithm: "cubic".to_string(),
244            disable_hystart: false,
245            dgrams_enabled: false,
246            dgram_count: 0,
247            dgram_data: "quack".to_string(),
248            max_active_cids: 2,
249            enable_active_migration: false,
250            max_field_section_size: None,
251            qpack_max_table_capacity: None,
252            qpack_blocked_streams: None,
253            initial_rtt: Duration::from_millis(333),
254            initial_cwnd_packets: 10,
255        }
256    }
257}
258
259pub const CLIENT_USAGE: &str = "Usage:
260  quiche-client [options] URL...
261  quiche-client -h | --help
262
263Options:
264  --method METHOD          Use the given HTTP request method [default: GET].
265  --body FILE              Send the given file as request body.
266  --max-data BYTES         Connection-wide flow control limit [default: 10000000].
267  --max-window BYTES       Connection-wide max receiver window [default: 25165824].
268  --max-stream-data BYTES  Per-stream flow control limit [default: 1000000].
269  --max-stream-window BYTES   Per-stream max receiver window [default: 16777216].
270  --max-streams-bidi STREAMS  Number of allowed concurrent streams [default: 100].
271  --max-streams-uni STREAMS   Number of allowed concurrent streams [default: 100].
272  --idle-timeout TIMEOUT   Idle timeout in milliseconds [default: 30000].
273  --wire-version VERSION   The version number to send to the server [default: babababa].
274  --http-version VERSION   HTTP version to use [default: all].
275  --early-data             Enable sending early data.
276  --dgram-proto PROTO      DATAGRAM application protocol to use [default: none].
277  --dgram-count COUNT      Number of DATAGRAMs to send [default: 0].
278  --dgram-data DATA        Data to send for certain types of DATAGRAM application protocol [default: quack].
279  --dump-packets PATH      Dump the incoming packets as files in the given directory.
280  --dump-responses PATH    Dump response payload as files in the given directory.
281  --dump-json              Dump response headers and payload to stdout in JSON format.
282  --max-json-payload BYTES  Per-response payload limit when dumping JSON [default: 10000].
283  --connect-to ADDRESS     Override the server's address.
284  --no-verify              Don't verify server's certificate.
285  --trust-origin-ca-pem <file>  Path to the pem file of the origin's CA, if not publicly trusted.
286  --no-grease              Don't send GREASE.
287  --cc-algorithm NAME      Specify which congestion control algorithm to use [default: cubic].
288  --disable-hystart        Disable HyStart++.
289  --max-active-cids NUM    The maximum number of active Connection IDs we can support [default: 2].
290  --enable-active-migration   Enable active connection migration.
291  --perform-migration      Perform connection migration on another source port.
292  -H --header HEADER ...   Add a request header.
293  -n --requests REQUESTS   Send the given number of identical requests [default: 1].
294  --send-priority-update   Send HTTP/3 priority updates if the query string params 'u' or 'i' are present in URLs
295  --max-field-section-size BYTES    Max size of uncompressed field section. Default is unlimited.
296  --qpack-max-table-capacity BYTES  Max capacity of dynamic QPACK decoding.. Any value other that 0 is currently unsupported.
297  --qpack-blocked-streams STREAMS   Limit of blocked streams while decoding. Any value other that 0 is currently unsupported.
298  --session-file PATH      File used to cache a TLS session for resumption.
299  --source-port PORT       Source port to use when connecting to the server [default: 0].
300  --initial-rtt MILLIS     The initial RTT in milliseconds [default: 333].
301  --initial-cwnd-packets PACKETS   The initial congestion window size in terms of packet count [default: 10].
302  -h --help                Show this screen.
303";
304
305/// Application-specific arguments that compliment the `CommonArgs`.
306pub struct ClientArgs {
307    pub version: u32,
308    pub dump_response_path: Option<String>,
309    pub dump_json: Option<usize>,
310    pub urls: Vec<url::Url>,
311    pub reqs_cardinal: u64,
312    pub req_headers: Vec<String>,
313    pub no_verify: bool,
314    pub trust_origin_ca_pem: Option<String>,
315    pub body: Option<Vec<u8>>,
316    pub method: String,
317    pub connect_to: Option<String>,
318    pub session_file: Option<String>,
319    pub source_port: u16,
320    pub perform_migration: bool,
321    pub send_priority_update: bool,
322}
323
324impl Args for ClientArgs {
325    fn with_docopt(docopt: &docopt::Docopt) -> Self {
326        let args = docopt.parse().unwrap_or_else(|e| e.exit());
327
328        let version = args.get_str("--wire-version");
329        let version = u32::from_str_radix(version, 16).unwrap();
330
331        let dump_response_path = if !args.get_str("--dump-responses").is_empty() {
332            Some(args.get_str("--dump-responses").to_string())
333        } else {
334            None
335        };
336
337        let dump_json = args.get_bool("--dump-json");
338        let dump_json = if dump_json {
339            let max_payload = args.get_str("--max-json-payload");
340            let max_payload = max_payload.parse::<usize>().unwrap();
341            Some(max_payload)
342        } else {
343            None
344        };
345
346        // URLs (can be multiple).
347        let urls: Vec<url::Url> = args
348            .get_vec("URL")
349            .into_iter()
350            .map(|x| url::Url::parse(x).unwrap())
351            .collect();
352
353        // Request headers (can be multiple).
354        let req_headers = args
355            .get_vec("--header")
356            .into_iter()
357            .map(|x| x.to_string())
358            .collect();
359
360        let reqs_cardinal = args.get_str("--requests");
361        let reqs_cardinal = reqs_cardinal.parse::<u64>().unwrap();
362
363        let no_verify = args.get_bool("--no-verify");
364
365        let trust_origin_ca_pem = args.get_str("--trust-origin-ca-pem");
366        let trust_origin_ca_pem = if !trust_origin_ca_pem.is_empty() {
367            Some(trust_origin_ca_pem.to_string())
368        } else {
369            None
370        };
371
372        let body = if args.get_bool("--body") {
373            std::fs::read(args.get_str("--body")).ok()
374        } else {
375            None
376        };
377
378        let method = args.get_str("--method").to_string();
379
380        let connect_to = if args.get_bool("--connect-to") {
381            Some(args.get_str("--connect-to").to_string())
382        } else {
383            None
384        };
385
386        let session_file = if args.get_bool("--session-file") {
387            Some(args.get_str("--session-file").to_string())
388        } else {
389            None
390        };
391
392        let source_port = args.get_str("--source-port");
393        let source_port = source_port.parse::<u16>().unwrap();
394
395        let perform_migration = args.get_bool("--perform-migration");
396
397        let send_priority_update = args.get_bool("--send-priority-update");
398
399        ClientArgs {
400            version,
401            dump_response_path,
402            dump_json,
403            urls,
404            reqs_cardinal,
405            req_headers,
406            no_verify,
407            trust_origin_ca_pem,
408            body,
409            method,
410            connect_to,
411            session_file,
412            source_port,
413            perform_migration,
414            send_priority_update,
415        }
416    }
417}
418
419impl Default for ClientArgs {
420    fn default() -> Self {
421        ClientArgs {
422            version: 0xbabababa,
423            dump_response_path: None,
424            dump_json: None,
425            urls: vec![],
426            req_headers: vec![],
427            reqs_cardinal: 1,
428            no_verify: false,
429            trust_origin_ca_pem: None,
430            body: None,
431            method: "GET".to_string(),
432            connect_to: None,
433            session_file: None,
434            source_port: 0,
435            perform_migration: false,
436            send_priority_update: false,
437        }
438    }
439}
440
441pub const SERVER_USAGE: &str = "Usage:
442  quiche-server [options]
443  quiche-server -h | --help
444
445Options:
446  --listen <addr>             Listen on the given IP:port [default: 127.0.0.1:4433]
447  --cert <file>               TLS certificate path [default: src/bin/cert.crt]
448  --key <file>                TLS certificate key path [default: src/bin/cert.key]
449  --root <dir>                Root directory [default: src/bin/root/]
450  --index <name>              The file that will be used as index [default: index.html].
451  --name <str>                Name of the server [default: quic.tech]
452  --max-data BYTES            Connection-wide flow control limit [default: 10000000].
453  --max-window BYTES          Connection-wide max receiver window [default: 25165824].
454  --max-stream-data BYTES     Per-stream flow control limit [default: 1000000].
455  --max-stream-window BYTES   Per-stream max receiver window [default: 16777216].
456  --max-streams-bidi STREAMS  Number of allowed concurrent streams [default: 100].
457  --max-streams-uni STREAMS   Number of allowed concurrent streams [default: 100].
458  --idle-timeout TIMEOUT      Idle timeout in milliseconds [default: 30000].
459  --dump-packets PATH         Dump the incoming packets as files in the given directory.
460  --early-data                Enable receiving early data.
461  --no-retry                  Disable stateless retry.
462  --no-grease                 Don't send GREASE.
463  --http-version VERSION      HTTP version to use [default: all].
464  --dgram-proto PROTO         DATAGRAM application protocol to use [default: none].
465  --dgram-count COUNT         Number of DATAGRAMs to send [default: 0].
466  --dgram-data DATA           Data to send for certain types of DATAGRAM application protocol [default: brrr].
467  --cc-algorithm NAME         Specify which congestion control algorithm to use [default: cubic].
468  --disable-hystart           Disable HyStart++.
469  --max-active-cids NUM       The maximum number of active Connection IDs we can support [default: 2].
470  --enable-active-migration   Enable active connection migration.
471  --max-field-section-size BYTES    Max size of uncompressed HTTP/3 field section. Default is unlimited.
472  --qpack-max-table-capacity BYTES  Max capacity of QPACK dynamic table decoding. Any value other that 0 is currently unsupported.
473  --qpack-blocked-streams STREAMS   Limit of streams that can be blocked while decoding. Any value other that 0 is currently unsupported.
474  --disable-gso               Disable GSO (linux only).
475  --disable-pacing            Disable pacing (linux only).
476  --initial-rtt MILLIS     The initial RTT in milliseconds [default: 333].
477  --initial-cwnd-packets PACKETS      The initial congestion window size in terms of packet count [default: 10].
478  -h --help                   Show this screen.
479";
480
481// Application-specific arguments that compliment the `CommonArgs`.
482pub struct ServerArgs {
483    pub listen: String,
484    pub no_retry: bool,
485    pub root: String,
486    pub index: String,
487    pub cert: String,
488    pub key: String,
489    pub disable_gso: bool,
490    pub disable_pacing: bool,
491    pub enable_pmtud: bool,
492}
493
494impl Args for ServerArgs {
495    fn with_docopt(docopt: &docopt::Docopt) -> Self {
496        let args = docopt.parse().unwrap_or_else(|e| e.exit());
497
498        let listen = args.get_str("--listen").to_string();
499        let no_retry = args.get_bool("--no-retry");
500        let root = args.get_str("--root").to_string();
501        let index = args.get_str("--index").to_string();
502        let cert = args.get_str("--cert").to_string();
503        let key = args.get_str("--key").to_string();
504        let disable_gso = args.get_bool("--disable-gso");
505        let disable_pacing = args.get_bool("--disable-pacing");
506        let enable_pmtud = args.get_bool("--enable-pmtud");
507
508        ServerArgs {
509            listen,
510            no_retry,
511            root,
512            index,
513            cert,
514            key,
515            disable_gso,
516            disable_pacing,
517            enable_pmtud,
518        }
519    }
520}