rustls: keep WRITABLE and drain buffered ciphertext when the socket backs up, and bound the plaintext buffer at 256KiB — rustls accepts all plaintext and buffers ciphertext internally on WouldBlock (unlike openssl, which surfaces backpressure through write); expose wants_write()/flush() so the reactor drains it and the sendq caps govern a slow reader. no-op for the openssl and plaintext paths
This commit is contained in:
parent
ebd6e29589
commit
598a019620
3 changed files with 51 additions and 4 deletions
|
|
@ -204,6 +204,21 @@ impl Sock {
|
||||||
Sock::Tls(t) => t.write(buf),
|
Sock::Tls(t) => t.write(buf),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Whether a TLS session still has outbound bytes buffered internally (rustls
|
||||||
|
/// after a WouldBlock); a plaintext socket never buffers in-process.
|
||||||
|
fn wants_write(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Sock::Plain(_) => false,
|
||||||
|
Sock::Tls(t) => t.wants_write(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Push a TLS session's buffered ciphertext to the socket; no-op for plaintext.
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
match self {
|
||||||
|
Sock::Plain(_) => Ok(()),
|
||||||
|
Sock::Tls(t) => t.flush(),
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Best-effort graceful close. TLS sends a close_notify alert; plaintext relies on
|
/// Best-effort graceful close. TLS sends a close_notify alert; plaintext relies on
|
||||||
/// the socket's own FIN when the stream drops.
|
/// the socket's own FIN when the stream drops.
|
||||||
fn shutdown(&mut self) {
|
fn shutdown(&mut self) {
|
||||||
|
|
@ -245,8 +260,9 @@ impl Conn {
|
||||||
fn set_interest(poll: &mut Poll, c: &mut Conn, t: usize) {
|
fn set_interest(poll: &mut Poll, c: &mut Conn, t: usize) {
|
||||||
let want_read = !c.paused;
|
let want_read = !c.paused;
|
||||||
// a TLS handshake may need to write (its flight) as well as read, so keep both
|
// a TLS handshake may need to write (its flight) as well as read, so keep both
|
||||||
// until it completes; after that, write only when there's a backlog to drain.
|
// until it completes; after that, write when there's a backlog to drain — either
|
||||||
let want_write = c.handshaking || !c.wbuf.is_empty() || c.paused;
|
// our own queued plaintext, or ciphertext still buffered inside a TLS session.
|
||||||
|
let want_write = c.handshaking || !c.wbuf.is_empty() || c.paused || c.sock.wants_write();
|
||||||
if want_read == c.want_read && want_write == c.want_write {
|
if want_read == c.want_read && want_write == c.want_write {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -979,12 +995,20 @@ fn flush_conn(poll: &mut Poll, conns: &mut HashMap<usize, Conn>, t: usize, core:
|
||||||
c.wbuf.clear();
|
c.wbuf.clear();
|
||||||
c.wpos = 0;
|
c.wpos = 0;
|
||||||
}
|
}
|
||||||
|
// push any ciphertext a TLS session still holds buffered (rustls keeps it when
|
||||||
|
// the socket filled mid-write); our plaintext queue draining doesn't mean the
|
||||||
|
// socket has it all. WouldBlock leaves the rest for the next writable event.
|
||||||
|
match c.sock.flush() {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {}
|
||||||
|
Err(_) => close = true,
|
||||||
|
}
|
||||||
if c.paused && c.pending() <= c.softsendq {
|
if c.paused && c.pending() <= c.softsendq {
|
||||||
c.paused = false;
|
c.paused = false;
|
||||||
unpaused = true;
|
unpaused = true;
|
||||||
}
|
}
|
||||||
set_interest(poll, c, t);
|
set_interest(poll, c, t);
|
||||||
if c.closing && c.wbuf.is_empty() {
|
if c.closing && c.wbuf.is_empty() && !c.sock.wants_write() {
|
||||||
close = true;
|
close = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
src/tls.rs
11
src/tls.rs
|
|
@ -55,6 +55,17 @@ pub trait TlsSession: Send {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>;
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>;
|
||||||
/// Encrypt+queue application data; returns the plaintext bytes accepted.
|
/// Encrypt+queue application data; returns the plaintext bytes accepted.
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize>;
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize>;
|
||||||
|
/// Whether the session still holds outbound TLS bytes not yet pushed to the
|
||||||
|
/// socket. rustls buffers ciphertext internally when the socket is full;
|
||||||
|
/// openssl surfaces backpressure through `write`, so it never buffers.
|
||||||
|
fn wants_write(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
/// Push any buffered outbound TLS bytes to the socket. `WouldBlock` leaves the
|
||||||
|
/// remainder for the next writable event; a no-op when nothing is buffered.
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
/// The underlying mio socket, for the reactor's poll (re)registration.
|
/// The underlying mio socket, for the reactor's poll (re)registration.
|
||||||
fn source(&mut self) -> &mut MioStream;
|
fn source(&mut self) -> &mut MioStream;
|
||||||
/// SHA-256 fingerprint of the peer certificate (CertFP / SASL EXTERNAL), if any.
|
/// SHA-256 fingerprint of the peer certificate (CertFP / SASL EXTERNAL), if any.
|
||||||
|
|
|
||||||
|
|
@ -219,7 +219,11 @@ impl TlsBackend for RustlsBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(&self, sock: MioStream) -> io::Result<Box<dyn TlsSession>> {
|
fn start(&self, sock: MioStream) -> io::Result<Box<dyn TlsSession>> {
|
||||||
let conn = ServerConnection::new(self.cfg()).map_err(err)?;
|
let mut conn = ServerConnection::new(self.cfg()).map_err(err)?;
|
||||||
|
// bound the buffered plaintext so a slow-reading client makes writer().write()
|
||||||
|
// return short (backpressure) instead of growing without limit; the reactor's
|
||||||
|
// sendq caps then govern it, matching the openssl backend.
|
||||||
|
conn.set_buffer_limit(Some(256 * 1024));
|
||||||
Ok(Box::new(RustlsSession { conn, sock }))
|
Ok(Box::new(RustlsSession { conn, sock }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -307,6 +311,14 @@ impl TlsSession for RustlsSession {
|
||||||
self.pump_write()?;
|
self.pump_write()?;
|
||||||
Ok(n)
|
Ok(n)
|
||||||
}
|
}
|
||||||
|
fn wants_write(&self) -> bool {
|
||||||
|
// rustls holds encrypted bytes when the socket filled mid-flush; the reactor
|
||||||
|
// must keep WRITABLE interest and drain them, or a burst strands here.
|
||||||
|
self.conn.wants_write()
|
||||||
|
}
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
self.pump_write()
|
||||||
|
}
|
||||||
fn source(&mut self) -> &mut MioStream {
|
fn source(&mut self) -> &mut MioStream {
|
||||||
&mut self.sock
|
&mut self.sock
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue