#[allow(unused_imports)]
use std::io::{self, Read as _, Write as _};
use std::sync::Arc;
use rustls::pki_types::ServerName;
use rustls::{ClientConnection, ServerConnection};
#[allow(unused_imports)]
use crate::accumulator::AccumulatorTable;
#[cfg(has_io_uring)]
use crate::backend::Ring;
#[allow(unused_imports)]
use crate::buffer::send_copy::SendCopyPool;
pub struct TlsInfo {
pub(crate) protocol_version: Option<rustls::ProtocolVersion>,
pub(crate) cipher_suite: Option<rustls::SupportedCipherSuite>,
pub(crate) alpn_protocol: Option<Vec<u8>>,
pub(crate) sni_hostname: Option<String>,
}
impl TlsInfo {
pub fn protocol_version(&self) -> Option<rustls::ProtocolVersion> {
self.protocol_version
}
pub fn cipher_suite(&self) -> Option<rustls::SupportedCipherSuite> {
self.cipher_suite
}
pub fn alpn_protocol(&self) -> Option<&[u8]> {
self.alpn_protocol.as_deref()
}
pub fn sni_hostname(&self) -> Option<&str> {
self.sni_hostname.as_deref()
}
}
pub enum TlsConnKind {
Server(ServerConnection),
Client(ClientConnection),
}
impl TlsConnKind {
pub fn read_tls(&mut self, rd: &mut dyn io::Read) -> io::Result<usize> {
match self {
TlsConnKind::Server(c) => c.read_tls(rd),
TlsConnKind::Client(c) => c.read_tls(rd),
}
}
pub fn write_tls(&mut self, wr: &mut dyn io::Write) -> io::Result<usize> {
match self {
TlsConnKind::Server(c) => c.write_tls(wr),
TlsConnKind::Client(c) => c.write_tls(wr),
}
}
pub fn process_new_packets(&mut self) -> Result<rustls::IoState, rustls::Error> {
match self {
TlsConnKind::Server(c) => c.process_new_packets(),
TlsConnKind::Client(c) => c.process_new_packets(),
}
}
pub fn reader(&mut self) -> rustls::Reader<'_> {
match self {
TlsConnKind::Server(c) => c.reader(),
TlsConnKind::Client(c) => c.reader(),
}
}
pub fn writer(&mut self) -> rustls::Writer<'_> {
match self {
TlsConnKind::Server(c) => c.writer(),
TlsConnKind::Client(c) => c.writer(),
}
}
pub fn wants_write(&self) -> bool {
match self {
TlsConnKind::Server(c) => c.wants_write(),
TlsConnKind::Client(c) => c.wants_write(),
}
}
pub fn is_handshaking(&self) -> bool {
match self {
TlsConnKind::Server(c) => c.is_handshaking(),
TlsConnKind::Client(c) => c.is_handshaking(),
}
}
pub fn alpn_protocol(&self) -> Option<&[u8]> {
match self {
TlsConnKind::Server(c) => c.alpn_protocol(),
TlsConnKind::Client(c) => c.alpn_protocol(),
}
}
pub fn negotiated_cipher_suite(&self) -> Option<rustls::SupportedCipherSuite> {
match self {
TlsConnKind::Server(c) => c.negotiated_cipher_suite(),
TlsConnKind::Client(c) => c.negotiated_cipher_suite(),
}
}
pub fn protocol_version(&self) -> Option<rustls::ProtocolVersion> {
match self {
TlsConnKind::Server(c) => c.protocol_version(),
TlsConnKind::Client(c) => c.protocol_version(),
}
}
pub fn sni_hostname(&self) -> Option<&str> {
match self {
TlsConnKind::Server(c) => c.server_name(),
TlsConnKind::Client(_) => None,
}
}
pub fn send_close_notify(&mut self) {
match self {
TlsConnKind::Server(c) => c.send_close_notify(),
TlsConnKind::Client(c) => c.send_close_notify(),
}
}
}
pub struct TlsConn {
pub conn: TlsConnKind,
pub handshake_complete: bool,
pub peer_sent_close_notify: bool,
pub close_notify_sent: bool,
}
pub struct TlsTable {
conns: Vec<Option<TlsConn>>,
server_config: Option<Arc<rustls::ServerConfig>>,
client_config: Option<Arc<rustls::ClientConfig>>,
#[cfg(not(has_io_uring))]
write_buf: Vec<u8>,
}
impl TlsTable {
pub fn new(
max_connections: u32,
server_config: Option<Arc<rustls::ServerConfig>>,
client_config: Option<Arc<rustls::ClientConfig>>,
) -> Self {
let mut conns = Vec::with_capacity(max_connections as usize);
conns.resize_with(max_connections as usize, || None);
TlsTable {
conns,
server_config,
client_config,
#[cfg(not(has_io_uring))]
write_buf: Vec::new(),
}
}
pub fn has_server_config(&self) -> bool {
self.server_config.is_some()
}
pub fn has_client_config(&self) -> bool {
self.client_config.is_some()
}
pub fn create(&mut self, conn_index: u32) -> Result<(), rustls::Error> {
let server_config = self
.server_config
.as_ref()
.expect("create() called without server_config");
let conn = ServerConnection::new(server_config.clone())?;
self.conns[conn_index as usize] = Some(TlsConn {
conn: TlsConnKind::Server(conn),
handshake_complete: false,
peer_sent_close_notify: false,
close_notify_sent: false,
});
Ok(())
}
pub fn create_client(
&mut self,
conn_index: u32,
server_name: ServerName<'static>,
) -> Result<(), rustls::Error> {
let client_config = self
.client_config
.as_ref()
.expect("create_client() called without client_config");
let conn = ClientConnection::new(client_config.clone(), server_name)?;
self.conns[conn_index as usize] = Some(TlsConn {
conn: TlsConnKind::Client(conn),
handshake_complete: false,
peer_sent_close_notify: false,
close_notify_sent: false,
});
Ok(())
}
pub fn get_mut(&mut self, conn_index: u32) -> Option<&mut TlsConn> {
self.conns[conn_index as usize].as_mut()
}
pub fn has(&self, conn_index: u32) -> bool {
self.conns[conn_index as usize].is_some()
}
pub fn remove(&mut self, conn_index: u32) {
self.conns[conn_index as usize] = None;
}
pub fn get_info(&self, conn_index: u32) -> Option<TlsInfo> {
let tls_conn = self.conns[conn_index as usize].as_ref()?;
Some(TlsInfo {
protocol_version: tls_conn.conn.protocol_version(),
cipher_suite: tls_conn.conn.negotiated_cipher_suite(),
alpn_protocol: tls_conn.conn.alpn_protocol().map(|s| s.to_vec()),
sni_hostname: tls_conn.conn.sni_hostname().map(|s| s.to_string()),
})
}
#[cfg(has_io_uring)]
pub fn send_close_notify(
&mut self,
conn_index: u32,
ring: &mut Ring,
send_copy_pool: &mut SendCopyPool,
) {
if let Some(tls_conn) = self.get_mut(conn_index) {
tls_conn.conn.send_close_notify();
tls_conn.close_notify_sent = true;
flush_close_notify_linked(tls_conn, ring, send_copy_pool, conn_index);
}
}
}
pub enum TlsRecvResult {
Ok,
HandshakeJustCompleted,
#[allow(dead_code)] Error(rustls::Error),
Closed,
}
#[must_use]
fn drain_tls_plaintext(
tls_conn: &mut TlsConn,
accumulators: &mut AccumulatorTable,
conn_index: u32,
) -> bool {
use std::io::BufRead;
let mut reader = tls_conn.conn.reader();
loop {
let chunk = match reader.fill_buf() {
Ok([]) => break,
Ok(b) => b,
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break,
Err(_) => break,
};
let n = chunk.len();
if !accumulators.append(conn_index, chunk) {
return false;
}
reader.consume(n);
}
true
}
#[cfg(has_io_uring)]
pub fn feed_tls_recv(
tls_table: &mut TlsTable,
accumulators: &mut AccumulatorTable,
send_copy_pool: &mut SendCopyPool,
conn_index: u32,
ciphertext: &[u8],
out_sends: &mut Vec<crate::handler::BuiltSend>,
) -> TlsRecvResult {
let tls_conn = match tls_table.conns[conn_index as usize].as_mut() {
Some(tc) => tc,
None => return TlsRecvResult::Closed,
};
let was_handshaking = !tls_conn.handshake_complete;
let mut cursor = io::Cursor::new(ciphertext);
while cursor.position() < ciphertext.len() as u64 {
match tls_conn.conn.read_tls(&mut cursor) {
Ok(0) => break,
Ok(_) => {}
Err(e) => {
return TlsRecvResult::Error(rustls::Error::General(e.to_string()));
}
}
let state = match tls_conn.conn.process_new_packets() {
Ok(state) => state,
Err(e) => {
if tls_conn.conn.wants_write() {
let _ = take_tls_output_sends(tls_conn, send_copy_pool, conn_index, out_sends);
}
return TlsRecvResult::Error(e);
}
};
if state.plaintext_bytes_to_read() > 0
&& !drain_tls_plaintext(tls_conn, accumulators, conn_index)
{
return TlsRecvResult::Error(rustls::Error::General(
"recv accumulator limit exceeded".into(),
));
}
}
let state = match tls_conn.conn.process_new_packets() {
Ok(state) => state,
Err(e) => {
if tls_conn.conn.wants_write() {
let _ = take_tls_output_sends(tls_conn, send_copy_pool, conn_index, out_sends);
}
return TlsRecvResult::Error(e);
}
};
if state.plaintext_bytes_to_read() > 0
&& !drain_tls_plaintext(tls_conn, accumulators, conn_index)
{
return TlsRecvResult::Error(rustls::Error::General(
"recv accumulator limit exceeded".into(),
));
}
if tls_conn.conn.wants_write()
&& !take_tls_output_sends(tls_conn, send_copy_pool, conn_index, out_sends)
{
return TlsRecvResult::Error(rustls::Error::General(
"send pool exhausted during TLS output flush".into(),
));
}
if was_handshaking && !tls_conn.conn.is_handshaking() {
tls_conn.handshake_complete = true;
return TlsRecvResult::HandshakeJustCompleted;
}
if state.peer_has_closed() {
tls_conn.peer_sent_close_notify = true;
return TlsRecvResult::Closed;
}
TlsRecvResult::Ok
}
#[cfg(has_io_uring)]
pub fn flush_tls_output(
tls_table: &mut TlsTable,
send_copy_pool: &mut SendCopyPool,
conn_index: u32,
out_sends: &mut Vec<crate::handler::BuiltSend>,
) -> bool {
if let Some(tls_conn) = tls_table.get_mut(conn_index) {
take_tls_output_sends(tls_conn, send_copy_pool, conn_index, out_sends)
} else {
true
}
}
#[cfg(has_io_uring)]
struct PoolWriter<'a> {
pool: &'a mut SendCopyPool,
current: Option<(u16, *mut u8, usize, usize)>,
filled: Vec<(u16, u32)>,
exhausted: bool,
}
#[cfg(has_io_uring)]
impl<'a> PoolWriter<'a> {
fn new(pool: &'a mut SendCopyPool) -> Self {
PoolWriter {
pool,
current: None,
filled: Vec::new(),
exhausted: false,
}
}
fn seal_current(&mut self) {
if let Some((slot, _, _, used)) = self.current.take() {
if used > 0 {
self.pool.set_filled(slot, used as u32);
self.filled.push((slot, used as u32));
} else {
self.pool.release(slot);
}
}
}
fn into_filled(mut self) -> Vec<(u16, u32)> {
self.seal_current();
std::mem::take(&mut self.filled)
}
fn release_all(mut self) {
if let Some((slot, _, _, _)) = self.current.take() {
self.pool.release(slot);
}
for (slot, _) in self.filled.drain(..) {
self.pool.release(slot);
}
}
}
#[cfg(has_io_uring)]
impl io::Write for PoolWriter<'_> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if self.exhausted {
return Err(io::Error::other("send copy pool exhausted for TLS"));
}
let mut copied = 0;
while copied < buf.len() {
if self.current.is_none() {
match self.pool.alloc_raw() {
Some((slot, ptr, cap)) => {
self.current = Some((slot, ptr, cap as usize, 0));
}
None => {
self.exhausted = true;
if copied > 0 {
return Ok(copied);
}
return Err(io::Error::other("send copy pool exhausted for TLS"));
}
}
}
let (_, ptr, cap, used) = self.current.as_mut().expect("just ensured");
let n = (buf.len() - copied).min(*cap - *used);
unsafe {
std::ptr::copy_nonoverlapping(buf.as_ptr().add(copied), ptr.add(*used), n);
}
*used += n;
copied += n;
if *used == *cap {
self.seal_current();
}
}
Ok(copied)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[cfg(has_io_uring)]
fn build_pool_send(
conn_index: u32,
ptr: *const u8,
len: u32,
pool_slot: u16,
tag: crate::completion::OpTag,
) -> crate::handler::BuiltSend {
let user_data = crate::completion::UserData::encode(tag, conn_index, pool_slot as u32);
let entry = io_uring::opcode::Send::new(io_uring::types::Fixed(conn_index), ptr, len)
.flags(crate::completion::STREAM_SEND_FLAGS)
.build()
.user_data(user_data.raw());
crate::handler::BuiltSend {
entry,
pool_slot,
slab_idx: u16::MAX,
total_len: len,
}
}
#[cfg(has_io_uring)]
fn take_tls_output_sends(
tls_conn: &mut TlsConn,
send_copy_pool: &mut SendCopyPool,
conn_index: u32,
out: &mut Vec<crate::handler::BuiltSend>,
) -> bool {
let mut writer = PoolWriter::new(send_copy_pool);
while tls_conn.conn.wants_write() {
match tls_conn.conn.write_tls(&mut writer) {
Ok(0) | Err(_) => {
writer.release_all();
return false;
}
Ok(_) => {}
}
}
for (slot, len) in writer.into_filled() {
let (ptr, _) = send_copy_pool.current_ptr_remaining(slot);
out.push(build_pool_send(
conn_index,
ptr,
len,
slot,
crate::completion::OpTag::TlsSend,
));
}
true
}
#[cfg(has_io_uring)]
fn flush_close_notify_linked(
tls_conn: &mut TlsConn,
ring: &mut Ring,
send_copy_pool: &mut SendCopyPool,
conn_index: u32,
) {
let mut writer = PoolWriter::new(send_copy_pool);
while tls_conn.conn.wants_write() {
match tls_conn.conn.write_tls(&mut writer) {
Ok(0) | Err(_) => {
writer.release_all();
return;
}
Ok(_) => {}
}
}
for (slot, len) in writer.into_filled() {
let (ptr, _) = send_copy_pool.current_ptr_remaining(slot);
if ring
.submit_tls_send_linked(conn_index, ptr, len, slot)
.is_err()
{
send_copy_pool.release(slot);
return;
}
}
}
#[cfg(has_io_uring)]
pub fn encrypt_to_sends(
tls_table: &mut TlsTable,
send_copy_pool: &mut SendCopyPool,
conn_index: u32,
plaintext: &[u8],
) -> io::Result<Vec<crate::handler::BuiltSend>> {
let tls_conn = tls_table.get_mut(conn_index).ok_or_else(|| {
io::Error::new(io::ErrorKind::NotConnected, "no TLS state for connection")
})?;
let mut writer = PoolWriter::new(send_copy_pool);
let mut offset = 0;
while offset < plaintext.len() {
let n = match tls_conn.conn.writer().write(&plaintext[offset..]) {
Ok(n) => n,
Err(e) => {
writer.release_all();
return Err(io::Error::other(e));
}
};
offset += n;
let mut drained = 0usize;
while tls_conn.conn.wants_write() {
match tls_conn.conn.write_tls(&mut writer) {
Ok(0) => break,
Ok(w) => drained += w,
Err(e) => {
writer.release_all();
return Err(io::Error::other(e));
}
}
}
if n == 0 && drained == 0 {
writer.release_all();
return Err(io::Error::other("TLS encryption made no progress"));
}
}
let filled = writer.into_filled();
let mut built = Vec::with_capacity(filled.len());
for (i, &(slot, len)) in filled.iter().enumerate() {
let (ptr, _) = send_copy_pool.current_ptr_remaining(slot);
let tag = if i + 1 == filled.len() {
crate::completion::OpTag::Send
} else {
crate::completion::OpTag::TlsSend
};
built.push(build_pool_send(conn_index, ptr, len, slot, tag));
}
Ok(built)
}
#[cfg(not(has_io_uring))]
pub fn feed_tls_recv_mio(
tls_table: &mut TlsTable,
accumulators: &mut AccumulatorTable,
pending: &mut std::collections::VecDeque<crate::backend::mio::driver::PendingSend>,
conn_index: u32,
ciphertext: &[u8],
) -> TlsRecvResult {
let tls_conn = match tls_table.conns[conn_index as usize].as_mut() {
Some(tc) => tc,
None => return TlsRecvResult::Closed,
};
let was_handshaking = !tls_conn.handshake_complete;
let mut peer_closed = false;
let mut remaining = ciphertext;
while !remaining.is_empty() {
let mut cursor = io::Cursor::new(remaining);
if let Err(e) = tls_conn.conn.read_tls(&mut cursor) {
return TlsRecvResult::Error(rustls::Error::General(e.to_string()));
}
let consumed = cursor.position() as usize;
if consumed == 0 {
break;
}
remaining = &remaining[consumed..];
let state = match tls_conn.conn.process_new_packets() {
Ok(state) => state,
Err(e) => {
if tls_conn.conn.wants_write() {
flush_tls_output_mio_inner(tls_conn, &mut tls_table.write_buf, pending);
}
return TlsRecvResult::Error(e);
}
};
if state.plaintext_bytes_to_read() > 0
&& !drain_tls_plaintext(tls_conn, accumulators, conn_index)
{
return TlsRecvResult::Error(rustls::Error::General(
"recv accumulator limit exceeded".into(),
));
}
if tls_conn.conn.wants_write() {
flush_tls_output_mio_inner(tls_conn, &mut tls_table.write_buf, pending);
}
if state.peer_has_closed() {
peer_closed = true;
tls_conn.peer_sent_close_notify = true;
}
}
if was_handshaking && !tls_conn.conn.is_handshaking() {
tls_conn.handshake_complete = true;
return TlsRecvResult::HandshakeJustCompleted;
}
if peer_closed {
return TlsRecvResult::Closed;
}
TlsRecvResult::Ok
}
#[cfg(not(has_io_uring))]
pub fn flush_tls_output_mio_queued(
tls_table: &mut TlsTable,
pending: &mut std::collections::VecDeque<crate::backend::mio::driver::PendingSend>,
conn_index: u32,
) {
let (conn_slot, write_buf) = borrow_conn_and_buf(tls_table, conn_index);
if let Some(tls_conn) = conn_slot {
flush_tls_output_mio_inner(tls_conn, write_buf, pending);
}
}
#[cfg(not(has_io_uring))]
fn flush_tls_output_mio_inner(
tls_conn: &mut TlsConn,
write_buf: &mut Vec<u8>,
pending: &mut std::collections::VecDeque<crate::backend::mio::driver::PendingSend>,
) {
write_buf.clear();
if tls_conn.conn.write_tls(write_buf).is_err() {
return;
}
if write_buf.is_empty() {
return;
}
pending.push_back((std::mem::take(write_buf), 0, None));
}
#[cfg(not(has_io_uring))]
pub fn flush_tls_output_mio_direct(
tls_table: &mut TlsTable,
stream: &mut mio::net::TcpStream,
conn_index: u32,
) {
let (conn_slot, write_buf) = borrow_conn_and_buf(tls_table, conn_index);
let Some(tls_conn) = conn_slot else { return };
write_buf.clear();
if tls_conn.conn.write_tls(write_buf).is_err() || write_buf.is_empty() {
return;
}
let mut offset = 0;
while offset < write_buf.len() {
match stream.write(&write_buf[offset..]) {
Ok(0) => break,
Ok(n) => offset += n,
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break,
Err(_) => break,
}
}
}
#[cfg(not(has_io_uring))]
pub fn encrypt_for_send_mio(
tls_table: &mut TlsTable,
conn_index: u32,
plaintext: &[u8],
) -> io::Result<Vec<u8>> {
let (conn_slot, _write_buf) = borrow_conn_and_buf(tls_table, conn_index);
let tls_conn = conn_slot.as_mut().ok_or_else(|| {
io::Error::new(io::ErrorKind::NotConnected, "no TLS state for connection")
})?;
let mut ciphertext = Vec::with_capacity(plaintext.len() + 128);
let mut offset = 0;
while offset < plaintext.len() {
let n = tls_conn
.conn
.writer()
.write(&plaintext[offset..])
.map_err(io::Error::other)?;
offset += n;
let before = ciphertext.len();
tls_conn
.conn
.write_tls(&mut ciphertext)
.map_err(io::Error::other)?;
if n == 0 && ciphertext.len() == before {
return Err(io::Error::other("TLS encryption made no progress"));
}
}
Ok(ciphertext)
}
#[cfg(not(has_io_uring))]
fn borrow_conn_and_buf(
table: &mut TlsTable,
conn_index: u32,
) -> (&mut Option<TlsConn>, &mut Vec<u8>) {
(&mut table.conns[conn_index as usize], &mut table.write_buf)
}