Add MTU control, and test fragmentation paths

This commit is contained in:
Joseph Birr-Pixton 2016-06-17 20:54:33 +01:00
parent adc43ca1fa
commit 5aadeb0e87
5 changed files with 84 additions and 15 deletions

View File

@ -247,7 +247,7 @@ before making the connection. --http replaces this with a
basic HTTP GET request for /.
Usage:
tlsclient [--verbose] [-p PORT] [--http] [--cache CACHE] [--cafile CAFILE] [--suite SUITE...] [--proto PROTOCOL...] <hostname>
tlsclient [--verbose] [-p PORT] [--http] [--mtu MTU] [--cache CACHE] [--cafile CAFILE] [--suite SUITE...] [--proto PROTOCOL...] <hostname>
tlsclient --version
tlsclient --help
@ -260,6 +260,7 @@ Options:
--proto PROTOCOL Send ALPN extension containing PROTOCOL.
--cache CACHE Save session cache to file CACHE.
--verbose Emit log output.
--mtu MTU Limit outgoing messages to MTU bytes.
--version Show tool version.
--help Show this screen.
";
@ -271,6 +272,7 @@ struct Args {
flag_verbose: bool,
flag_suite: Vec<String>,
flag_proto: Vec<String>,
flag_mtu: Option<usize>,
flag_cafile: Option<String>,
flag_cache: Option<String>,
arg_hostname: String
@ -336,6 +338,7 @@ fn make_config(args: &Args) -> Arc<ClientConfig> {
config.set_protocols(&args.flag_proto);
config.set_persistence(persist);
config.set_mtu(&args.flag_mtu);
Arc::new(config)
}

View File

@ -63,7 +63,10 @@ pub struct ClientConfig {
pub alpn_protocols: Vec<String>,
/// How we store session data or tickets.
pub session_persistence: cell::RefCell<Box<StoresSessions>>
pub session_persistence: cell::RefCell<Box<StoresSessions>>,
/// Our MTU. If None, we don't limit TLS message sizes.
pub mtu: Option<usize>
}
impl ClientConfig {
@ -75,7 +78,8 @@ impl ClientConfig {
ciphersuites: DEFAULT_CIPHERSUITES.to_vec(),
root_store: verify::RootCertStore::empty(),
alpn_protocols: Vec::new(),
session_persistence: cell::RefCell::new(Box::new(NoSessionStorage {}))
session_persistence: cell::RefCell::new(Box::new(NoSessionStorage {})),
mtu: None
}
}
@ -88,9 +92,16 @@ impl ClientConfig {
self.alpn_protocols.extend_from_slice(protocols);
}
/// Sets persistence layer to `persist`.
pub fn set_persistence(&mut self, persist: Box<StoresSessions>) {
self.session_persistence = cell::RefCell::new(persist);
}
/// Sets MTU to `mtu`. If None, the default is used.
/// If Some(x) then x must be greater than 5 bytes.
pub fn set_mtu(&mut self, mtu: &Option<usize>) {
self.mtu = mtu.clone();
}
}
pub struct ClientHandshakeData {
@ -173,7 +184,7 @@ pub struct ClientSession {
pub message_fragmenter: MessageFragmenter,
pub sendable_plaintext: Vec<u8>,
pub received_plaintext: Vec<u8>,
pub tls_queue: VecDeque<Message>,
tls_queue: VecDeque<Message>,
pub state: ConnState
}
@ -191,7 +202,8 @@ impl ClientSession {
alpn_protocol: None,
message_deframer: MessageDeframer::new(),
handshake_joiner: HandshakeJoiner::new(),
message_fragmenter: MessageFragmenter::new(MAX_FRAGMENT_LEN),
message_fragmenter: MessageFragmenter::new(client_config.mtu
.unwrap_or(MAX_FRAGMENT_LEN)),
sendable_plaintext: Vec::new(),
received_plaintext: Vec::new(),
tls_queue: VecDeque::new(),
@ -264,11 +276,11 @@ impl ClientSession {
/* Warnings are nonfatal. */
if alert.level == AlertLevel::Warning {
warn!("TLS alert warning received: {:?}", msg);
warn!("TLS alert warning received: {:#?}", msg);
return Ok(())
}
error!("TLS alert received: {:?}", msg);
error!("TLS alert received: {:#?}", msg);
return Err(HandshakeError::AlertReceived(alert.description.clone()));
} else {
unreachable!();
@ -371,6 +383,17 @@ impl ClientSession {
wr.write_all(&data)
}
/// Send a raw TLS message, fragmenting it if needed.
pub fn send_msg(&mut self, m: &Message, must_encrypt: bool) {
if !must_encrypt {
self.message_fragmenter.fragment(m, &mut self.tls_queue);
} else {
self.send_msg_encrypt(m);
}
}
/// Send plaintext application data, fragmenting and
/// encrypting it as it goes out.
pub fn send_plain(&mut self, data: &[u8]) {
use msgs::enums::{ContentType, ProtocolVersion};
@ -391,8 +414,14 @@ impl ClientSession {
payload: MessagePayload::opaque(data.to_vec())
};
self.send_msg_encrypt(&m);
}
/// Fragment `m`, encrypt the fragments, and then queue
/// the encrypted fragments for sending.
pub fn send_msg_encrypt(&mut self, m: &Message) {
let mut plain_messages = VecDeque::new();
self.message_fragmenter.fragment(&m, &mut plain_messages);
self.message_fragmenter.fragment(m, &mut plain_messages);
for m in plain_messages {
let em = self.encrypt_outgoing(&m);
@ -400,6 +429,8 @@ impl ClientSession {
}
}
/// Send any buffered plaintext. Plaintext is buffered if
/// written during handshake.
pub fn flush_plaintext(&mut self) {
if self.state != ConnState::Traffic {
return;

View File

@ -99,10 +99,10 @@ pub fn emit_client_hello(sess: &mut ClientSession) {
)
};
debug!("Sending ClientHello {:?}", sh);
debug!("Sending ClientHello {:#?}", sh);
sh.payload.encode(&mut sess.handshake_data.client_hello);
sess.tls_queue.push_back(sh);
sess.send_msg(&sh, false);
}
fn expect_server_hello() -> Expectation {
@ -114,7 +114,7 @@ fn expect_server_hello() -> Expectation {
fn handle_server_hello(sess: &mut ClientSession, m: &Message) -> Result<ConnState, HandshakeError> {
let server_hello = extract_handshake!(m, HandshakePayload::ServerHello).unwrap();
debug!("We got ServerHello {:?}", server_hello);
debug!("We got ServerHello {:#?}", server_hello);
if server_hello.server_version != ProtocolVersion::TLSv1_2 {
return Err(HandshakeError::General("server does not support TLSv1_2".to_string()));
@ -265,7 +265,7 @@ fn emit_clientkx(sess: &mut ClientSession, kxd: &suites::KeyExchangeResult) {
};
sess.handshake_data.hash_message(&ckx);
sess.tls_queue.push_back(ckx);
sess.send_msg(&ckx, false);
}
fn emit_ccs(sess: &mut ClientSession) {
@ -275,7 +275,7 @@ fn emit_ccs(sess: &mut ClientSession) {
payload: MessagePayload::ChangeCipherSpec(ChangeCipherSpecPayload {})
};
sess.tls_queue.push_back(ccs);
sess.send_msg(&ccs, false);
}
fn emit_finished(sess: &mut ClientSession) {
@ -297,8 +297,7 @@ fn emit_finished(sess: &mut ClientSession) {
};
sess.handshake_data.hash_message(&f);
let ef = sess.encrypt_outgoing(&f);
sess.tls_queue.push_back(ef);
sess.send_msg(&f, true);
}
fn handle_server_hello_done(sess: &mut ClientSession, m: &Message) -> Result<ConnState, HandshakeError> {

View File

@ -18,6 +18,7 @@ pub struct TlsClient {
pub suites: Vec<String>,
pub protos: Vec<String>,
pub verbose: bool,
pub mtu: Option<usize>,
pub expect_fails: bool,
pub expect_output: Vec<String>,
pub expect_log: Vec<String>
@ -32,6 +33,7 @@ impl TlsClient {
cafile: None,
cache: None,
verbose: false,
mtu: None,
suites: Vec::new(),
protos: Vec::new(),
expect_fails: false,
@ -55,6 +57,11 @@ impl TlsClient {
self
}
pub fn mtu(&mut self, mtu: usize) -> &mut TlsClient {
self.mtu = Some(mtu);
self
}
pub fn port(&mut self, port: u16) -> &mut TlsClient {
self.port = port;
self
@ -86,6 +93,7 @@ impl TlsClient {
}
pub fn go(&mut self) -> Option<()> {
let mut mtustring = "".to_string();
let portstring = self.port.to_string();
let mut args = Vec::<&str>::new();
args.push(&self.hostname);
@ -121,6 +129,12 @@ impl TlsClient {
args.push("--verbose");
}
if self.mtu.is_some() {
args.push("--mtu");
mtustring = self.mtu.unwrap().to_string();
args.push(&mtustring);
}
let output = process::Command::new("target/debug/examples/tlsclient")
.args(&args)
.output()

View File

@ -77,3 +77,25 @@ fn resumption() {
.expect("1 session cache hits")
.go();
}
#[test]
fn recv_low_mtu() {
let mut server = OpenSSLServer::new_rsa(8300);
server.arg("-mtu").arg("32");
server.run();
server.client()
.expect("Ciphers common between both SSL end points")
.go();
}
#[test]
fn send_low_mtu() {
let mut server = OpenSSLServer::new_rsa(8400);
server.run();
server.client()
.mtu(128)
.expect("Ciphers common between both SSL end points")
.go();
}