Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 41 additions & 2 deletions src/tls/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,34 @@ impl TlsConfig {
Ok(tls_config)
}

pub fn new_mutual_authentication_client_config(
Copy link

Copilot AI Jun 3, 2025

Choose a reason for hiding this comment

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

Add a doc comment for 'new_mutual_authentication_client_config' to explain its purpose and usage, aligning its documentation with that of 'new_server_config' for clarity.

Copilot uses AI. Check for mistakes.
cert_file: &str,
key_file: &str,
application_protos: Vec<Vec<u8>>,
enable_early_data: bool,
) -> Result<Self> {
if cert_file.is_empty() || key_file.is_empty() {
return Err(Error::TlsFail(format!(
"cert_file({:?}) key_file({:?})",
Copy link

Copilot AI Jun 3, 2025

Choose a reason for hiding this comment

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

Consider improving the error message here to clearly state that both certificate and key files are required, e.g., 'Certificate file and key file must be provided, but received cert_file: {:?}, key_file: {:?}'.

Suggested change
"cert_file({:?}) key_file({:?})",
"Certificate file and key file must be provided, but received cert_file: {:?}, key_file: {:?}",

Copilot uses AI. Check for mistakes.
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,
key_file: &str,
application_protos: Vec<Vec<u8>>,
enable_early_data: bool,
) -> Result<Self> {
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)?;
Expand All @@ -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<Vec<u8>>,
enable_early_data: bool,
) -> Result<Self> {
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)
Expand Down Expand Up @@ -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(())
Expand Down
83 changes: 68 additions & 15 deletions tools/src/bin/tquic_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ pub struct ClientOpt {
#[clap(value_delimiter = ' ')]
pub urls: Vec<Url>,

/// 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<String>,

/// Number of threads.
#[clap(
short,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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()?;
Expand Down Expand Up @@ -841,8 +867,9 @@ impl WorkerContext {

struct Request {
url: Url,
line: String, // Used in http/0.9.
headers: Vec<Header>, // Used in h3.
line: String, // Used in http/0.9.
headers: Vec<Header>, // Used in h3.
body: Option<Vec<u8>>, // Used in h3.
response_writer: Option<std::io::BufWriter<std::fs::File>>,
start_time: Option<Instant>,
}
Expand Down Expand Up @@ -881,7 +908,7 @@ impl Request {
}

// TODO: support custom headers.
fn new(method: &str, url: &Url, body: &Option<Vec<u8>>, dump_dir: &Option<String>) -> Self {
fn new(method: &str, url: &Url, body: Option<Vec<u8>>, dump_dir: &Option<String>) -> Self {
let authority = match url.port() {
Some(port) => format!("{}:{}", url.host_str().unwrap(), port),
None => url.host_str().unwrap().to_string(),
Expand All @@ -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,
}
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -1068,7 +1110,8 @@ impl RequestSender {
}

fn send_h3_request(&mut self, conn: &mut Connection, request: &Request) -> Result<u64> {
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());
Expand All @@ -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());
Expand All @@ -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)
}

Expand Down
54 changes: 39 additions & 15 deletions tools/src/bin/tquic_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)?;
Expand Down Expand Up @@ -514,7 +526,23 @@ impl ConnectionHandler {
}
}

fn build_h3_response_header(status: i32, body_len: usize) -> Vec<Header> {
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<Header>, 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" {
Expand All @@ -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(
Expand Down
Loading