diff --git a/src/tls/tls.rs b/src/tls/tls.rs index 8b6ae3e6c..de9af17af 100644 --- a/src/tls/tls.rs +++ b/src/tls/tls.rs @@ -112,6 +112,26 @@ impl TlsConfig { Ok(tls_config) } + pub fn new_mutual_authentication_client_config( + cert_file: &str, + key_file: &str, + application_protos: Vec>, + enable_early_data: bool, + ) -> Result { + if cert_file.is_empty() || key_file.is_empty() { + return Err(Error::TlsFail(format!( + "cert_file({:?}) key_file({:?})", + cert_file, key_file + ))); + } + let mut tls_config: TlsConfig = + Self::new_client_config(application_protos, enable_early_data)?; + tls_config.set_certificate_file(cert_file)?; + tls_config.set_private_key_file(key_file)?; + + Ok(tls_config) + } + /// Create a new server side TlsConfig. pub fn new_server_config( cert_file: &str, @@ -119,7 +139,7 @@ impl TlsConfig { application_protos: Vec>, enable_early_data: bool, ) -> Result { - let mut tls_config = Self::new()?; + let mut tls_config: TlsConfig = Self::new()?; tls_config.set_certificate_file(cert_file)?; tls_config.set_private_key_file(key_file)?; tls_config.set_application_protos(application_protos)?; @@ -131,6 +151,20 @@ impl TlsConfig { Ok(tls_config) } + /// Create a new server side TlsConfig. + pub fn new_mutual_authentication_server_config( + cert_file: &str, + key_file: &str, + application_protos: Vec>, + enable_early_data: bool, + ) -> Result { + let mut tls_config = + Self::new_server_config(cert_file, key_file, application_protos, enable_early_data)?; + tls_config.set_verify(true); + + Ok(tls_config) + } + /// Set whether early data is allowed. pub fn set_early_data_enabled(&mut self, enable_early_data: bool) { self.tls_ctx.set_early_data_enabled(enable_early_data) @@ -171,8 +205,13 @@ impl TlsConfig { let path = Path::new(ca_path); if path.is_file() { self.tls_ctx.load_verify_locations_from_file(ca_path)?; - } else { + } else if path.is_dir() { self.tls_ctx.load_verify_locations_from_directory(ca_path)?; + } else { + return Err(Error::TlsFail(format!( + "format error ca_path({:?}) neither file nor directory", + ca_path + ))); } Ok(()) diff --git a/tools/src/bin/tquic_client.rs b/tools/src/bin/tquic_client.rs index f7aa2c788..3a15fd221 100644 --- a/tools/src/bin/tquic_client.rs +++ b/tools/src/bin/tquic_client.rs @@ -88,6 +88,11 @@ pub struct ClientOpt { #[clap(value_delimiter = ' ')] pub urls: Vec, + /// local file to send as body + /// method is "POST" rather than "GET" when file_path is not empty + #[clap(value_delimiter = ' ')] + pub file_pathes: Vec, + /// Number of threads. #[clap( short, @@ -330,6 +335,19 @@ pub struct ClientOpt { help_heading = "Misc" )] pub max_sample: usize, + + /// TLS certificate in PEM format. + #[clap( + short, + long = "cert", + default_value = "./cert.crt", + value_name = "cert" + )] + pub cert_file: String, + + /// TLS private key in PEM format. + #[clap(short, long = "key", default_value = "./cert.key", value_name = "cert")] + pub key_file: String, } const MAX_BUF_SIZE: usize = 65536; @@ -547,10 +565,18 @@ impl Worker { config.set_multipath_algorithm(option.multipath_algor); config.set_active_connection_id_limit(option.active_cid_limit); config.enable_encryption(!option.disable_encryption); - let tls_config = TlsConfig::new_client_config( - ApplicationProto::convert_to_vec(&option.alpn), - option.enable_early_data, - )?; + let tls_config = match !option.cert_file.is_empty() && !option.key_file.is_empty() { + false => TlsConfig::new_client_config( + ApplicationProto::convert_to_vec(&option.alpn), + option.enable_early_data, + )?, + true => TlsConfig::new_mutual_authentication_client_config( + &option.cert_file, + &option.key_file, + ApplicationProto::convert_to_vec(&option.alpn), + option.enable_early_data, + )?, + }; config.set_tls_config(tls_config); let poll = mio::Poll::new()?; @@ -841,8 +867,9 @@ impl WorkerContext { struct Request { url: Url, - line: String, // Used in http/0.9. - headers: Vec
, // Used in h3. + line: String, // Used in http/0.9. + headers: Vec
, // Used in h3. + body: Option>, // Used in h3. response_writer: Option>, start_time: Option, } @@ -881,7 +908,7 @@ impl Request { } // TODO: support custom headers. - fn new(method: &str, url: &Url, body: &Option>, dump_dir: &Option) -> Self { + fn new(method: &str, url: &Url, body: Option>, dump_dir: &Option) -> Self { let authority = match url.port() { Some(port) => format!("{}:{}", url.host_str().unwrap(), port), None => url.host_str().unwrap().to_string(), @@ -904,6 +931,7 @@ impl Request { url: url.clone(), line: format!("GET {}\r\n", url.path()), headers, + body, response_writer: Self::make_response_writer(url, dump_dir), start_time: None, } @@ -1016,8 +1044,22 @@ impl RequestSender { } fn send_request(&mut self, conn: &mut Connection) -> Result<()> { + let mut method: &str = "GET"; + let path = &self.option.file_pathes[self.current_url_idx]; + let body = match !path.is_empty() && self.app_proto == ApplicationProto::H3 { + false => None, + true => { + method = "POST"; + match std::fs::read(path) { + Ok(v) => Some(v), + Err(e) => { + return Err(format!("read file fail {:?}, error: {:?}", path, e).into()); + } + } + } + }; let url = &self.option.urls[self.current_url_idx]; - let mut request = Request::new("GET", url, &None, &self.option.dump_dir); + let mut request = Request::new(method, url, body, &self.option.dump_dir); debug!( "{} send request {} current index {}", conn.trace_id(), @@ -1068,7 +1110,8 @@ impl RequestSender { } fn send_h3_request(&mut self, conn: &mut Connection, request: &Request) -> Result { - let s = match self.h3_conn.as_mut().unwrap().stream_new(conn) { + let h3_conn: &mut Http3Connection = self.h3_conn.as_mut().unwrap(); + let s = match h3_conn.stream_new(conn) { Ok(v) => v, Err(tquic::h3::Http3Error::TransportError(Error::StreamLimitError)) => { return Err("stream limit reached".to_string().into()); @@ -1080,12 +1123,7 @@ impl RequestSender { } }; - match self - .h3_conn - .as_mut() - .unwrap() - .send_headers(conn, s, &request.headers, true) - { + match h3_conn.send_headers(conn, s, &request.headers, true) { Ok(v) => v, Err(tquic::h3::Http3Error::StreamBlocked) => { return Err("stream is blocked".to_string().into()); @@ -1097,6 +1135,21 @@ impl RequestSender { } }; + let body: Bytes = match request.body.as_ref() { + Some(vec) => Bytes::copy_from_slice(vec.as_slice()), + None => Bytes::new(), + }; + match h3_conn.send_body(conn, s, body, true) { + Ok(v) => v, + Err(tquic::h3::Http3Error::StreamBlocked) => { + return Err("stream is blocked".to_string().into()); + } + Err(e) => { + return Err( + format!("failed to send request {:?}, error: {:?}", request.url, e).into(), + ); + } + }; Ok(s) } diff --git a/tools/src/bin/tquic_server.rs b/tools/src/bin/tquic_server.rs index 86377a1aa..80ebc4637 100644 --- a/tools/src/bin/tquic_server.rs +++ b/tools/src/bin/tquic_server.rs @@ -62,14 +62,18 @@ pub struct ServerOpt { short, long = "cert", default_value = "./cert.crt", - value_name = "FILE" + value_name = "cert" )] pub cert_file: String, /// TLS private key in PEM format. - #[clap(short, long = "key", default_value = "./cert.key", value_name = "FILE")] + #[clap(short, long = "key", default_value = "./cert.key", value_name = "cert")] pub key_file: String, + /// TLS private key in PEM format. + #[clap(value_name = "cert")] + pub mutual_authentication: bool, + /// Document root directory. #[clap(short, long, default_value = "./", value_name = "DIR")] pub root: String, @@ -298,12 +302,20 @@ impl Server { } let application_protos = vec![b"h3".to_vec(), b"http/0.9".to_vec(), b"hq-interop".to_vec()]; - let mut tls_config = TlsConfig::new_server_config( - &option.cert_file, - &option.key_file, - application_protos, - true, - )?; + let mut tls_config = match option.mutual_authentication { + false => TlsConfig::new_server_config( + &option.cert_file, + &option.key_file, + application_protos, + true, + )?, + true => TlsConfig::new_mutual_authentication_server_config( + &option.cert_file, + &option.key_file, + application_protos, + true, + )?, + }; let mut ticket_key = option.ticket_key.clone().into_bytes(); ticket_key.resize(48, 0); tls_config.set_ticket_key(&ticket_key)?; @@ -514,7 +526,23 @@ impl ConnectionHandler { } } + fn build_h3_response_header(status: i32, body_len: usize) -> Vec
{ + let headers = vec![ + tquic::h3::Header::new(b":status", status.to_string().as_bytes()), + tquic::h3::Header::new(b"server", b"tquic"), + tquic::h3::Header::new(b"content-length", body_len.to_string().as_bytes()), + ]; + headers + } + fn build_h3_response(&self, headers: &[Header]) -> (Vec
, Bytes) { + for header in headers { + if header.name() == b":method" && std::str::from_utf8(header.value()).unwrap() != "GET" + { + // Method Not Allowed + return (Self::build_h3_response_header(405, 0usize), Bytes::new()); + } + } let mut path = ""; for header in headers { if header.name() == b":path" { @@ -530,13 +558,9 @@ impl ConnectionHandler { } }; - let headers = vec![ - tquic::h3::Header::new(b":status", status.to_string().as_bytes()), - tquic::h3::Header::new(b"server", b"tquic"), - tquic::h3::Header::new(b"content-length", body.len().to_string().as_bytes()), - ]; - - (headers, Bytes::from(body)) + ( + Self::build_h3_response_header(status, body.len()), Bytes::from(body), + ) } fn process_h3_request(