mirror of https://github.com/ctz/rustls
513 lines
16 KiB
Rust
513 lines
16 KiB
Rust
// This program does assorted benchmarking of rustls.
|
|
//
|
|
// Note: we don't use any of the standard 'cargo bench', 'test::Bencher',
|
|
// etc. because it's unstable at the time of writing.
|
|
|
|
use std::time::{Duration, Instant};
|
|
use std::sync::Arc;
|
|
use std::fs;
|
|
use std::io::{self, Write, Read};
|
|
use std::env;
|
|
|
|
use rustls;
|
|
use rustls::{ClientConfig, ClientSession};
|
|
use rustls::{ServerConfig, ServerSession};
|
|
use rustls::ServerSessionMemoryCache;
|
|
use rustls::ClientSessionMemoryCache;
|
|
use rustls::NoServerSessionStorage;
|
|
use rustls::NoClientSessionStorage;
|
|
use rustls::{NoClientAuth, RootCertStore, AllowAnyAuthenticatedClient};
|
|
use rustls::Session;
|
|
use rustls::Ticketer;
|
|
use rustls::internal::pemfile;
|
|
use rustls::internal::msgs::enums::SignatureAlgorithm;
|
|
|
|
use webpki;
|
|
|
|
fn duration_nanos(d: Duration) -> f64 {
|
|
(d.as_secs() as f64) + f64::from(d.subsec_nanos()) / 1e9
|
|
}
|
|
|
|
fn _bench<Fsetup, Ftest, S>(count: usize, name: &'static str, f_setup: Fsetup, f_test: Ftest)
|
|
where Fsetup: Fn() -> S,
|
|
Ftest: Fn(S)
|
|
{
|
|
let mut times = Vec::new();
|
|
|
|
for _ in 0..count {
|
|
let state = f_setup();
|
|
let start = Instant::now();
|
|
f_test(state);
|
|
times.push(duration_nanos(Instant::now().duration_since(start)));
|
|
}
|
|
|
|
println!("{}", name);
|
|
println!("{:?}", times);
|
|
}
|
|
|
|
fn time<F>(mut f: F) -> f64
|
|
where F: FnMut()
|
|
{
|
|
let start = Instant::now();
|
|
f();
|
|
let end = Instant::now();
|
|
let dur = duration_nanos(end.duration_since(start));
|
|
f64::from(dur)
|
|
}
|
|
|
|
fn transfer(left: &mut dyn Session, right: &mut dyn Session) -> f64 {
|
|
let mut buf = [0u8; 262144];
|
|
let mut read_time = 0f64;
|
|
|
|
while left.wants_write() {
|
|
let sz = left.write_tls(&mut buf.as_mut()).unwrap();
|
|
|
|
if sz == 0 {
|
|
return read_time;
|
|
}
|
|
|
|
let mut offs = 0;
|
|
loop {
|
|
let start = Instant::now();
|
|
offs += right.read_tls(&mut buf[offs..sz].as_ref()).unwrap();
|
|
let end = Instant::now();
|
|
read_time += f64::from(duration_nanos(end.duration_since(start)));
|
|
if sz == offs {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
read_time
|
|
}
|
|
|
|
fn drain(d: &mut dyn Session, expect_len: usize) {
|
|
let mut left = expect_len;
|
|
let mut buf = [0u8; 8192];
|
|
loop {
|
|
let sz = d.read(&mut buf).unwrap();
|
|
left -= sz;
|
|
if left == 0 {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(PartialEq, Clone, Copy)]
|
|
enum ClientAuth {
|
|
No,
|
|
Yes,
|
|
}
|
|
|
|
#[derive(PartialEq, Clone, Copy)]
|
|
enum Resumption {
|
|
No,
|
|
SessionID,
|
|
Tickets,
|
|
}
|
|
|
|
impl Resumption {
|
|
fn label(&self) -> &'static str {
|
|
match *self {
|
|
Resumption::No => "no-resume",
|
|
Resumption::SessionID => "sessionid",
|
|
Resumption::Tickets => "tickets",
|
|
}
|
|
}
|
|
}
|
|
|
|
// copied from tests/api.rs
|
|
#[derive(PartialEq, Clone, Copy)]
|
|
enum KeyType {
|
|
RSA,
|
|
ECDSA
|
|
}
|
|
|
|
impl KeyType {
|
|
fn for_suite(suite: &'static rustls::SupportedCipherSuite) -> KeyType {
|
|
if suite.sign == SignatureAlgorithm::ECDSA {
|
|
KeyType::ECDSA
|
|
} else {
|
|
KeyType::RSA
|
|
}
|
|
}
|
|
|
|
fn path_for(&self, part: &str) -> String {
|
|
match self {
|
|
KeyType::RSA => format!("test-ca/rsa/{}", part),
|
|
KeyType::ECDSA => format!("test-ca/ecdsa/{}", part),
|
|
}
|
|
}
|
|
|
|
fn get_chain(&self) -> Vec<rustls::Certificate> {
|
|
pemfile::certs(&mut io::BufReader::new(fs::File::open(self.path_for("end.fullchain"))
|
|
.unwrap()))
|
|
.unwrap()
|
|
}
|
|
|
|
fn get_key(&self) -> rustls::PrivateKey {
|
|
pemfile::pkcs8_private_keys(&mut io::BufReader::new(fs::File::open(self.path_for("end.key"))
|
|
.unwrap()))
|
|
.unwrap()[0]
|
|
.clone()
|
|
}
|
|
|
|
fn get_client_chain(&self) -> Vec<rustls::Certificate> {
|
|
pemfile::certs(&mut io::BufReader::new(fs::File::open(self.path_for("client.fullchain"))
|
|
.unwrap()))
|
|
.unwrap()
|
|
}
|
|
|
|
fn get_client_key(&self) -> rustls::PrivateKey {
|
|
pemfile::pkcs8_private_keys(&mut io::BufReader::new(fs::File::open(self.path_for("client.key"))
|
|
.unwrap()))
|
|
.unwrap()[0]
|
|
.clone()
|
|
}
|
|
}
|
|
|
|
fn make_server_config(version: rustls::ProtocolVersion,
|
|
suite: &'static rustls::SupportedCipherSuite,
|
|
client_auth: ClientAuth,
|
|
resume: Resumption)
|
|
-> ServerConfig {
|
|
let kt = KeyType::for_suite(suite);
|
|
let client_auth = match client_auth {
|
|
ClientAuth::Yes => {
|
|
let roots = kt.get_chain();
|
|
let mut client_auth_roots = RootCertStore::empty();
|
|
for root in roots {
|
|
client_auth_roots.add(&root).unwrap();
|
|
}
|
|
AllowAnyAuthenticatedClient::new(client_auth_roots)
|
|
},
|
|
ClientAuth::No => {
|
|
NoClientAuth::new()
|
|
}
|
|
};
|
|
|
|
let mut cfg = ServerConfig::new(client_auth);
|
|
cfg.set_single_cert(kt.get_chain(), kt.get_key())
|
|
.expect("bad certs/private key?");
|
|
|
|
if resume == Resumption::SessionID {
|
|
cfg.set_persistence(ServerSessionMemoryCache::new(128));
|
|
} else if resume == Resumption::Tickets {
|
|
cfg.ticketer = Ticketer::new();
|
|
} else {
|
|
cfg.set_persistence(Arc::new(NoServerSessionStorage {}));
|
|
}
|
|
|
|
cfg.versions.clear();
|
|
cfg.versions.push(version);
|
|
|
|
cfg
|
|
}
|
|
|
|
fn make_client_config(version: rustls::ProtocolVersion,
|
|
suite: &'static rustls::SupportedCipherSuite,
|
|
clientauth: ClientAuth,
|
|
resume: Resumption)
|
|
-> ClientConfig {
|
|
let kt = KeyType::for_suite(suite);
|
|
let mut cfg = ClientConfig::new();
|
|
let mut rootbuf = io::BufReader::new(fs::File::open(kt.path_for("ca.cert")).unwrap());
|
|
cfg.root_store.add_pem_file(&mut rootbuf).unwrap();
|
|
cfg.ciphersuites.clear();
|
|
cfg.ciphersuites.push(suite);
|
|
cfg.versions.clear();
|
|
cfg.versions.push(version);
|
|
|
|
if clientauth == ClientAuth::Yes {
|
|
cfg.set_single_client_cert(kt.get_client_chain(), kt.get_client_key());
|
|
}
|
|
|
|
if resume != Resumption::No {
|
|
cfg.set_persistence(ClientSessionMemoryCache::new(128));
|
|
} else {
|
|
cfg.set_persistence(Arc::new(NoClientSessionStorage {}));
|
|
}
|
|
|
|
cfg
|
|
}
|
|
|
|
fn apply_work_multiplier(work: u64) -> u64 {
|
|
let mul = match env::var("BENCH_MULTIPLIER") {
|
|
Ok(val) => val.parse::<f64>().expect("invalid BENCH_MULTIPLIER value"),
|
|
Err(_) => 1.
|
|
};
|
|
|
|
((work as f64) * mul).round() as u64
|
|
}
|
|
|
|
fn bench_handshake(version: rustls::ProtocolVersion,
|
|
suite: &'static rustls::SupportedCipherSuite,
|
|
clientauth: ClientAuth,
|
|
resume: Resumption) {
|
|
let client_config = Arc::new(make_client_config(version, suite, clientauth, resume));
|
|
let server_config = Arc::new(make_server_config(version, suite, clientauth, resume));
|
|
|
|
if !suite.usable_for_version(version) {
|
|
return;
|
|
}
|
|
|
|
let rounds = apply_work_multiplier(if resume == Resumption::No { 512 } else { 4096 });
|
|
let mut client_time = 0f64;
|
|
let mut server_time = 0f64;
|
|
|
|
for _ in 0..rounds {
|
|
let dns_name = webpki::DNSNameRef::try_from_ascii_str("localhost").unwrap();
|
|
let mut client = ClientSession::new(&client_config, dns_name);
|
|
let mut server = ServerSession::new(&server_config);
|
|
|
|
server_time += time(|| {
|
|
transfer(&mut client, &mut server);
|
|
server.process_new_packets().unwrap()
|
|
});
|
|
client_time += time(|| {
|
|
transfer(&mut server, &mut client);
|
|
client.process_new_packets().unwrap()
|
|
});
|
|
server_time += time(|| {
|
|
transfer(&mut client, &mut server);
|
|
server.process_new_packets().unwrap()
|
|
});
|
|
client_time += time(|| {
|
|
transfer(&mut server, &mut client);
|
|
client.process_new_packets().unwrap()
|
|
});
|
|
}
|
|
|
|
println!("handshakes\t{:?}\t{:?}\tclient\t{}\t{}\t{:.2}\thandshake/s",
|
|
version,
|
|
suite.suite,
|
|
if clientauth == ClientAuth::Yes {
|
|
"mutual"
|
|
} else {
|
|
"server-auth"
|
|
},
|
|
resume.label(),
|
|
(rounds as f64) / client_time);
|
|
println!("handshakes\t{:?}\t{:?}\tserver\t{}\t{}\t{:.2}\thandshake/s",
|
|
version,
|
|
suite.suite,
|
|
if clientauth == ClientAuth::Yes {
|
|
"mutual"
|
|
} else {
|
|
"server-auth"
|
|
},
|
|
resume.label(),
|
|
(rounds as f64) / server_time);
|
|
}
|
|
|
|
fn do_handshake_step(client: &mut ClientSession, server: &mut ServerSession) -> bool {
|
|
if server.is_handshaking() || client.is_handshaking() {
|
|
transfer(client, server);
|
|
server.process_new_packets().unwrap();
|
|
transfer(server, client);
|
|
client.process_new_packets().unwrap();
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
fn do_handshake(client: &mut ClientSession, server: &mut ServerSession) {
|
|
while do_handshake_step(client, server) {}
|
|
}
|
|
|
|
fn bench_bulk(version: rustls::ProtocolVersion, suite: &'static rustls::SupportedCipherSuite,
|
|
plaintext_size: u64) {
|
|
let client_config =
|
|
Arc::new(make_client_config(version, suite, ClientAuth::No, Resumption::No));
|
|
let server_config = Arc::new(make_server_config(version, suite, ClientAuth::No, Resumption::No));
|
|
|
|
if !suite.usable_for_version(version) {
|
|
return;
|
|
}
|
|
|
|
let dns_name = webpki::DNSNameRef::try_from_ascii_str("localhost").unwrap();
|
|
let mut client = ClientSession::new(&client_config, dns_name);
|
|
let mut server = ServerSession::new(&server_config);
|
|
|
|
do_handshake(&mut client, &mut server);
|
|
|
|
let mut buf = Vec::new();
|
|
buf.resize(plaintext_size as usize, 0u8);
|
|
|
|
let total_data = apply_work_multiplier(
|
|
if plaintext_size < 8192 {
|
|
64 * 1024 * 1024
|
|
} else {
|
|
1024 * 1024 * 1024
|
|
}
|
|
);
|
|
let rounds = total_data / plaintext_size;
|
|
let mut time_send = 0f64;
|
|
let mut time_recv = 0f64;
|
|
|
|
for _ in 0..rounds {
|
|
time_send += time(|| {
|
|
server.write_all(&buf).unwrap();
|
|
()
|
|
});
|
|
|
|
time_recv += transfer(&mut server, &mut client);
|
|
|
|
time_recv += time(|| {
|
|
client.process_new_packets().unwrap()
|
|
});
|
|
drain(&mut client, buf.len());
|
|
}
|
|
|
|
let total_mbs = ((plaintext_size * rounds) as f64) / (1024. * 1024.);
|
|
println!("bulk\t{:?}\t{:?}\tsend\t{:.2}\tMB/s",
|
|
version,
|
|
suite.suite,
|
|
total_mbs / time_send);
|
|
println!("bulk\t{:?}\t{:?}\trecv\t{:.2}\tMB/s",
|
|
version,
|
|
suite.suite,
|
|
total_mbs / time_recv);
|
|
}
|
|
|
|
fn bench_memory(version: rustls::ProtocolVersion,
|
|
suite: &'static rustls::SupportedCipherSuite,
|
|
session_count: u64) {
|
|
let client_config =
|
|
Arc::new(make_client_config(version, suite, ClientAuth::No, Resumption::No));
|
|
let server_config = Arc::new(make_server_config(version, suite, ClientAuth::No, Resumption::No));
|
|
|
|
if !suite.usable_for_version(version) {
|
|
return;
|
|
}
|
|
|
|
// The target here is to end up with session_count post-handshake
|
|
// server and client sessions.
|
|
let session_count = (session_count / 2) as usize;
|
|
let mut servers = Vec::with_capacity(session_count);
|
|
let mut clients = Vec::with_capacity(session_count);
|
|
|
|
for _i in 0..session_count {
|
|
servers.push(ServerSession::new(&server_config));
|
|
let dns_name = webpki::DNSNameRef::try_from_ascii_str("localhost").unwrap();
|
|
clients.push(ClientSession::new(&client_config, dns_name));
|
|
}
|
|
|
|
for _step in 0..5 {
|
|
for (mut client, mut server) in clients.iter_mut().zip(servers.iter_mut()) {
|
|
do_handshake_step(&mut client, &mut server);
|
|
}
|
|
}
|
|
|
|
for client in clients.iter_mut() {
|
|
client.write_all(&[0u8; 1024]).unwrap();
|
|
}
|
|
|
|
for (client, server) in clients.iter_mut().zip(servers.iter_mut()) {
|
|
transfer(client, server);
|
|
let mut buf = [0u8; 1024];
|
|
server.read(&mut buf).unwrap();
|
|
}
|
|
}
|
|
|
|
fn lookup_suite(name: &str) -> &'static rustls::SupportedCipherSuite {
|
|
for suite in &rustls::ALL_CIPHERSUITES {
|
|
if format!("{:?}", suite.suite).to_lowercase() == name.to_lowercase() {
|
|
return suite;
|
|
}
|
|
}
|
|
|
|
panic!("unknown suite {:?}", name);
|
|
}
|
|
|
|
fn selected_tests(mut args: env::Args) {
|
|
let mode = args.next()
|
|
.expect("first argument must be mode");
|
|
|
|
match mode.as_ref() {
|
|
"bulk" => {
|
|
match args.next() {
|
|
Some(suite) => {
|
|
let len = args.next()
|
|
.map(|arg| arg.parse::<u64>()
|
|
.expect("3rd arg must be plaintext size integer"))
|
|
.unwrap_or(1048576);
|
|
let suite = lookup_suite(&suite);
|
|
bench_bulk(rustls::ProtocolVersion::TLSv1_3, suite, len);
|
|
bench_bulk(rustls::ProtocolVersion::TLSv1_2, suite, len);
|
|
}
|
|
None => {
|
|
panic!("bulk needs ciphersuite argument");
|
|
}
|
|
}
|
|
}
|
|
|
|
"handshake" | "handshake-resume" | "handshake-ticket" => {
|
|
match args.next() {
|
|
Some(suite) => {
|
|
let suite = lookup_suite(&suite);
|
|
let resume = if mode == "handshake" {
|
|
Resumption::No
|
|
} else if mode == "handshake-resume" {
|
|
Resumption::SessionID
|
|
} else {
|
|
Resumption::Tickets
|
|
};
|
|
|
|
bench_handshake(rustls::ProtocolVersion::TLSv1_3, suite, ClientAuth::No, resume);
|
|
bench_handshake(rustls::ProtocolVersion::TLSv1_2, suite, ClientAuth::No, resume);
|
|
}
|
|
None => {
|
|
panic!("handshake* needs ciphersuite argument");
|
|
}
|
|
}
|
|
}
|
|
|
|
"memory" => {
|
|
match args.next() {
|
|
Some(suite) => {
|
|
let count = args.next()
|
|
.map(|arg| arg.parse::<u64>()
|
|
.expect("3rd arg must be session count integer"))
|
|
.unwrap_or(1000000);
|
|
let suite = lookup_suite(&suite);
|
|
bench_memory(rustls::ProtocolVersion::TLSv1_3, suite, count);
|
|
bench_memory(rustls::ProtocolVersion::TLSv1_2, suite, count);
|
|
}
|
|
None => {
|
|
panic!("memory needs ciphersuite argument");
|
|
}
|
|
}
|
|
}
|
|
|
|
_ => {
|
|
panic!("unsupported mode {:?}", mode);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn all_tests() {
|
|
for version in &[rustls::ProtocolVersion::TLSv1_3, rustls::ProtocolVersion::TLSv1_2] {
|
|
for suite in &rustls::ALL_CIPHERSUITES {
|
|
bench_bulk(*version, suite, 1024 * 1024);
|
|
bench_handshake(*version, suite, ClientAuth::No, Resumption::No);
|
|
bench_handshake(*version, suite, ClientAuth::Yes, Resumption::No);
|
|
bench_handshake(*version, suite, ClientAuth::No, Resumption::SessionID);
|
|
bench_handshake(*version, suite, ClientAuth::Yes, Resumption::SessionID);
|
|
bench_handshake(*version, suite, ClientAuth::No, Resumption::Tickets);
|
|
bench_handshake(*version, suite, ClientAuth::Yes, Resumption::Tickets);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
let mut args = env::args();
|
|
if args.len() > 1 {
|
|
args.next();
|
|
selected_tests(args);
|
|
} else {
|
|
all_tests();
|
|
}
|
|
}
|