0.34.13_cleaup_code
This commit is contained in:
1 parent
ec46a96a29
commit
5c2e50ec06
87 files changed
+4890
-1338
No files matched your search
+10
-1
@@ -153,7 +153,16 @@ fn log_protocol_error(
|
||||
tls_sni: Option<String>,
|
||||
) {
|
||||
log_protocol_error_with_identity(
|
||||
state, kind, detail, peer, local, transport, http_version, status, None, None,
|
||||
state,
|
||||
kind,
|
||||
detail,
|
||||
peer,
|
||||
local,
|
||||
transport,
|
||||
http_version,
|
||||
status,
|
||||
None,
|
||||
None,
|
||||
tls_sni,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -205,4 +205,3 @@ async fn connection<I>(
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,12 +201,12 @@ fn h2_headers_frame<B>(request: &Request<B>) -> std::result::Result<Bytes, &'sta
|
||||
((length >> 16) & 0xff) as u8,
|
||||
((length >> 8) & 0xff) as u8,
|
||||
(length & 0xff) as u8,
|
||||
0x1,
|
||||
0x5,
|
||||
0x1,
|
||||
0x5,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
]);
|
||||
frame.extend_from_slice(&block);
|
||||
Ok(Bytes::from(frame))
|
||||
|
||||
+207
-58
@@ -5,11 +5,14 @@ use std::{
|
||||
io,
|
||||
os::fd::{AsRawFd, RawFd},
|
||||
pin::Pin,
|
||||
sync::{Arc, Mutex},
|
||||
sync::atomic::Ordering,
|
||||
sync::{Arc, Mutex},
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tokio::{io::{AsyncRead, AsyncWrite, Interest, ReadBuf}, net::TcpStream};
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncWrite, Interest, ReadBuf},
|
||||
net::TcpStream,
|
||||
};
|
||||
|
||||
const TAG_LEN: usize = 32;
|
||||
const FRAME_LEN: usize = 64 * 1024;
|
||||
@@ -54,24 +57,37 @@ impl SendfileSlots {
|
||||
return None;
|
||||
}
|
||||
let mut registry = self.0.lock().unwrap_or_else(|e| e.into_inner());
|
||||
if registry.slots.len() >= MAX_PENDING { return None; }
|
||||
if registry.slots.len() >= MAX_PENDING {
|
||||
return None;
|
||||
}
|
||||
let mut tag = [0u8; TAG_LEN];
|
||||
tag[0] = 0;
|
||||
tag[0] = 0;
|
||||
tag[1..].copy_from_slice(&rand::random::<[u8; TAG_LEN - 1]>());
|
||||
if registry.slots.iter().any(|slot| slot.tag == tag) { return None; }
|
||||
if registry.slots.iter().any(|slot| slot.tag == tag) {
|
||||
return None;
|
||||
}
|
||||
let mut data = vec![0u8; FRAME_LEN];
|
||||
for chunk in data.chunks_exact_mut(TAG_LEN) { chunk.copy_from_slice(&tag); }
|
||||
for chunk in data.chunks_exact_mut(TAG_LEN) {
|
||||
chunk.copy_from_slice(&tag);
|
||||
}
|
||||
registry.slots.push(Slot {
|
||||
tag, file: candidate.file, length: candidate.length, written: 0,
|
||||
tag,
|
||||
file: candidate.file,
|
||||
length: candidate.length,
|
||||
written: 0,
|
||||
});
|
||||
Some(SendfileBody {
|
||||
bytes: Bytes::from(data), remaining: candidate.length, web_metrics,
|
||||
bytes: Bytes::from(data),
|
||||
remaining: candidate.length,
|
||||
web_metrics,
|
||||
})
|
||||
}
|
||||
|
||||
fn plan(&self, buf: &[u8]) -> WritePlan {
|
||||
let registry = self.0.lock().unwrap_or_else(|e| e.into_inner());
|
||||
if registry.slots.is_empty() { return WritePlan::Plain(buf.len()); }
|
||||
if registry.slots.is_empty() {
|
||||
return WritePlan::Plain(buf.len());
|
||||
}
|
||||
if let Some(tag) = registry.active {
|
||||
if let Some(slot) = registry.slots.iter().find(|s| s.tag == tag) {
|
||||
return plan_file(slot, buf);
|
||||
@@ -79,12 +95,16 @@ impl SendfileSlots {
|
||||
return WritePlan::Invalid;
|
||||
}
|
||||
for start in 0..buf.len() {
|
||||
if buf[start] != 0 { continue; }
|
||||
if buf[start] != 0 {
|
||||
continue;
|
||||
}
|
||||
for slot in ®istry.slots {
|
||||
let available = buf.len() - start;
|
||||
let compare = available.min(TAG_LEN);
|
||||
if buf[start..start + compare] == slot.tag[..compare] {
|
||||
if start > 0 { return WritePlan::Plain(start); }
|
||||
if start > 0 {
|
||||
return WritePlan::Plain(start);
|
||||
}
|
||||
return plan_file(slot, buf);
|
||||
}
|
||||
}
|
||||
@@ -107,36 +127,58 @@ impl SendfileSlots {
|
||||
|
||||
enum WritePlan {
|
||||
Plain(usize),
|
||||
File { tag: [u8; TAG_LEN], fd: RawFd, file: Arc<File>, offset: u64, count: usize },
|
||||
File {
|
||||
tag: [u8; TAG_LEN],
|
||||
fd: RawFd,
|
||||
file: Arc<File>,
|
||||
offset: u64,
|
||||
count: usize,
|
||||
},
|
||||
Invalid,
|
||||
}
|
||||
|
||||
fn plan_file(slot: &Slot, buf: &[u8]) -> WritePlan {
|
||||
let expected = (slot.length - slot.written).min(buf.len() as u64) as usize;
|
||||
let matched = buf[..expected].iter().enumerate()
|
||||
let matched = buf[..expected]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.take_while(|(n, byte)| **byte == slot.tag[(slot.written as usize + n) % TAG_LEN])
|
||||
.count();
|
||||
if matched == 0 { return WritePlan::Invalid; }
|
||||
if matched == 0 {
|
||||
return WritePlan::Invalid;
|
||||
}
|
||||
WritePlan::File {
|
||||
tag: slot.tag, fd: slot.file.as_raw_fd(), file: slot.file.clone(),
|
||||
offset: slot.written, count: matched,
|
||||
tag: slot.tag,
|
||||
fd: slot.file.as_raw_fd(),
|
||||
file: slot.file.clone(),
|
||||
offset: slot.written,
|
||||
count: matched,
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpBody for SendfileBody {
|
||||
type Data = Bytes;
|
||||
type Error = io::Error;
|
||||
fn poll_frame(mut self: Pin<&mut Self>, _cx: &mut Context<'_>)
|
||||
-> Poll<Option<Result<Frame<Bytes>, io::Error>>>
|
||||
{
|
||||
if self.remaining == 0 { return Poll::Ready(None); }
|
||||
fn poll_frame(
|
||||
mut self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Frame<Bytes>, io::Error>>> {
|
||||
if self.remaining == 0 {
|
||||
return Poll::Ready(None);
|
||||
}
|
||||
let len = self.remaining.min(FRAME_LEN as u64) as usize;
|
||||
self.remaining -= len as u64;
|
||||
self.web_metrics.response_bytes.fetch_add(len as u64, Ordering::Relaxed);
|
||||
self.web_metrics
|
||||
.response_bytes
|
||||
.fetch_add(len as u64, Ordering::Relaxed);
|
||||
Poll::Ready(Some(Ok(Frame::data(self.bytes.slice(0..len)))))
|
||||
}
|
||||
fn size_hint(&self) -> SizeHint { SizeHint::with_exact(self.remaining) }
|
||||
fn is_end_stream(&self) -> bool { self.remaining == 0 }
|
||||
fn size_hint(&self) -> SizeHint {
|
||||
SizeHint::with_exact(self.remaining)
|
||||
}
|
||||
fn is_end_stream(&self) -> bool {
|
||||
self.remaining == 0
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SendfileIo {
|
||||
@@ -146,22 +188,46 @@ pub(crate) struct SendfileIo {
|
||||
impl SendfileIo {
|
||||
pub fn new(stream: TcpStream) -> (Self, SendfileSlots) {
|
||||
let slots = SendfileSlots::default();
|
||||
(Self { stream, slots: slots.clone() }, slots)
|
||||
(
|
||||
Self {
|
||||
stream,
|
||||
slots: slots.clone(),
|
||||
},
|
||||
slots,
|
||||
)
|
||||
}
|
||||
}
|
||||
impl AsyncRead for SendfileIo {
|
||||
fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<()>> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut self.get_mut().stream).poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
impl AsyncWrite for SendfileIo {
|
||||
fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<io::Result<usize>> {
|
||||
if buf.is_empty() { return Poll::Ready(Ok(0)); }
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
if buf.is_empty() {
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
match self.slots.plan(buf) {
|
||||
WritePlan::Plain(count) => Pin::new(&mut self.stream).poll_write(cx, &buf[..count]),
|
||||
WritePlan::Invalid => Poll::Ready(Err(io::Error::new(io::ErrorKind::InvalidData,
|
||||
"sendfile placeholder did not match its registered descriptor"))),
|
||||
WritePlan::File { tag, fd, file, offset, count } => {
|
||||
WritePlan::Invalid => Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"sendfile placeholder did not match its registered descriptor",
|
||||
))),
|
||||
WritePlan::File {
|
||||
tag,
|
||||
fd,
|
||||
file,
|
||||
offset,
|
||||
count,
|
||||
} => {
|
||||
let _keep_open = file;
|
||||
let socket: RawFd = self.stream.as_raw_fd();
|
||||
let mut at: libc::off_t = offset as libc::off_t;
|
||||
@@ -171,13 +237,22 @@ impl AsyncWrite for SendfileIo {
|
||||
Poll::Ready(Ok(())) => {
|
||||
let result = self.stream.try_io(Interest::WRITABLE, || {
|
||||
let n = unsafe { libc::sendfile(socket, fd, &mut at, count) };
|
||||
if n < 0 { Err(io::Error::last_os_error()) }
|
||||
else if n == 0 { Err(io::Error::new(io::ErrorKind::UnexpectedEof,
|
||||
"static source truncated during sendfile")) }
|
||||
else { Ok(n as usize) }
|
||||
if n < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else if n == 0 {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::UnexpectedEof,
|
||||
"static source truncated during sendfile",
|
||||
))
|
||||
} else {
|
||||
Ok(n as usize)
|
||||
}
|
||||
});
|
||||
match result {
|
||||
Ok(n) => { self.slots.commit(tag, n); Poll::Ready(Ok(n)) },
|
||||
Ok(n) => {
|
||||
self.slots.commit(tag, n);
|
||||
Poll::Ready(Ok(n))
|
||||
}
|
||||
Err(e) if e.kind() == io::ErrorKind::WouldBlock => Poll::Pending,
|
||||
Err(e) => Poll::Ready(Err(e)),
|
||||
}
|
||||
@@ -192,9 +267,22 @@ impl AsyncWrite for SendfileIo {
|
||||
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut self.stream).poll_shutdown(cx)
|
||||
}
|
||||
fn is_write_vectored(&self) -> bool { self.stream.is_write_vectored() }
|
||||
fn poll_write_vectored(mut self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[io::IoSlice<'_>]) -> Poll<io::Result<usize>> {
|
||||
if self.slots.0.lock().unwrap_or_else(|e| e.into_inner()).slots.is_empty() {
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
self.stream.is_write_vectored()
|
||||
}
|
||||
fn poll_write_vectored(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[io::IoSlice<'_>],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
if self
|
||||
.slots
|
||||
.0
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
.slots
|
||||
.is_empty()
|
||||
{
|
||||
return Pin::new(&mut self.stream).poll_write_vectored(cx, bufs);
|
||||
}
|
||||
match bufs.iter().find(|part| !part.is_empty()) {
|
||||
@@ -208,8 +296,8 @@ impl AsyncWrite for SendfileIo {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use http_body::Body;
|
||||
use tempfile::tempfile;
|
||||
use std::io::Write;
|
||||
use tempfile::tempfile;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
#[test]
|
||||
@@ -218,19 +306,42 @@ mod tests {
|
||||
file.write_all(&vec![42u8; 100_000]).unwrap();
|
||||
let slots = SendfileSlots::default();
|
||||
let counter = Arc::new(crate::web_server::metrics::PhpMetrics::default());
|
||||
let mut body = slots.create_body(SendfileCandidate { file: Arc::new(file), length: 100_000 }, counter.clone()).unwrap();
|
||||
let first = match Pin::new(&mut body).poll_frame(&mut Context::from_waker(std::task::Waker::noop())) {
|
||||
Poll::Ready(Some(Ok(frame))) => frame.into_data().unwrap(), _ => panic!("frame missing"),
|
||||
let mut body = slots
|
||||
.create_body(
|
||||
SendfileCandidate {
|
||||
file: Arc::new(file),
|
||||
length: 100_000,
|
||||
},
|
||||
counter.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let first = match Pin::new(&mut body)
|
||||
.poll_frame(&mut Context::from_waker(std::task::Waker::noop()))
|
||||
{
|
||||
Poll::Ready(Some(Ok(frame))) => frame.into_data().unwrap(),
|
||||
_ => panic!("frame missing"),
|
||||
};
|
||||
assert_eq!(first.len(), FRAME_LEN);
|
||||
match slots.plan(&first) { WritePlan::File { count, .. } => assert_eq!(count, FRAME_LEN), _ => panic!("missing kernel file plan") }
|
||||
match slots.plan(&first) {
|
||||
WritePlan::File { count, .. } => assert_eq!(count, FRAME_LEN),
|
||||
_ => panic!("missing kernel file plan"),
|
||||
}
|
||||
let tag = slots.0.lock().unwrap().slots[0].tag;
|
||||
slots.commit(tag, FRAME_LEN);
|
||||
let second = match Pin::new(&mut body).poll_frame(&mut Context::from_waker(std::task::Waker::noop())) {
|
||||
Poll::Ready(Some(Ok(frame))) => frame.into_data().unwrap(), _ => panic!("frame missing"),
|
||||
let second = match Pin::new(&mut body)
|
||||
.poll_frame(&mut Context::from_waker(std::task::Waker::noop()))
|
||||
{
|
||||
Poll::Ready(Some(Ok(frame))) => frame.into_data().unwrap(),
|
||||
_ => panic!("frame missing"),
|
||||
};
|
||||
assert_eq!(second.len(), 34_464);
|
||||
match slots.plan(&second) { WritePlan::File { count, offset, .. } => { assert_eq!(offset, 65_536); assert_eq!(count, 34_464); }, _ => panic!("second plan missing") }
|
||||
match slots.plan(&second) {
|
||||
WritePlan::File { count, offset, .. } => {
|
||||
assert_eq!(offset, 65_536);
|
||||
assert_eq!(count, 34_464);
|
||||
}
|
||||
_ => panic!("second plan missing"),
|
||||
}
|
||||
slots.commit(tag, second.len());
|
||||
assert!(slots.0.lock().unwrap().slots.is_empty());
|
||||
assert!(body.is_end_stream());
|
||||
@@ -245,36 +356,74 @@ mod tests {
|
||||
let (tcp, _) = listener.accept().await.unwrap();
|
||||
let (mut socket, slots) = SendfileIo::new(tcp);
|
||||
for round in 0..2 {
|
||||
let expected = if round == 0 { vec![0, 1, 2, 3].repeat(25_000) }
|
||||
else { vec![9u8; 100_003] };
|
||||
let expected = if round == 0 {
|
||||
vec![0, 1, 2, 3].repeat(25_000)
|
||||
} else {
|
||||
vec![9u8; 100_003]
|
||||
};
|
||||
let mut file = tempfile().unwrap();
|
||||
file.write_all(&expected).unwrap();
|
||||
let web = Arc::new(crate::web_server::metrics::PhpMetrics::default());
|
||||
let body = slots.create_body(SendfileCandidate {
|
||||
file: Arc::new(file), length: expected.len() as u64,
|
||||
}, web.clone()).unwrap();
|
||||
let first = format!("HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n", expected.len());
|
||||
let body = slots
|
||||
.create_body(
|
||||
SendfileCandidate {
|
||||
file: Arc::new(file),
|
||||
length: expected.len() as u64,
|
||||
},
|
||||
web.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let first = format!(
|
||||
"HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n",
|
||||
expected.len()
|
||||
);
|
||||
let mut body = body;
|
||||
let first_frame = http_body_util::BodyExt::frame(&mut body).await.unwrap().unwrap().into_data().unwrap();
|
||||
let first_frame = http_body_util::BodyExt::frame(&mut body)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into_data()
|
||||
.unwrap();
|
||||
let mut joined = first.into_bytes();
|
||||
joined.extend_from_slice(&first_frame);
|
||||
socket.write_all(&joined).await.unwrap();
|
||||
while let Some(frame) = http_body_util::BodyExt::frame(&mut body).await {
|
||||
socket.write_all(&frame.unwrap().into_data().unwrap()).await.unwrap();
|
||||
socket
|
||||
.write_all(&frame.unwrap().into_data().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
assert_eq!(web.response_bytes.load(Ordering::Relaxed), expected.len() as u64);
|
||||
assert_eq!(
|
||||
web.response_bytes.load(Ordering::Relaxed),
|
||||
expected.len() as u64
|
||||
);
|
||||
}
|
||||
socket.shutdown().await.unwrap();
|
||||
});
|
||||
let mut client = tokio::net::TcpStream::connect(address).await.unwrap();
|
||||
let mut actual = Vec::new();
|
||||
tokio::time::timeout(std::time::Duration::from_secs(10), client.read_to_end(&mut actual)).await.unwrap().unwrap();
|
||||
tokio::time::timeout(
|
||||
std::time::Duration::from_secs(10),
|
||||
client.read_to_end(&mut actual),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
server.await.unwrap();
|
||||
let mut wanted = Vec::new();
|
||||
for expected in [vec![0, 1, 2, 3].repeat(25_000), vec![9u8; 100_003]] {
|
||||
wanted.extend_from_slice(format!("HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n", expected.len()).as_bytes());
|
||||
wanted.extend_from_slice(
|
||||
format!(
|
||||
"HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n",
|
||||
expected.len()
|
||||
)
|
||||
.as_bytes(),
|
||||
);
|
||||
wanted.extend_from_slice(&expected);
|
||||
}
|
||||
assert_eq!(actual, wanted, "kernel sendfile must never leak placeholder markers");
|
||||
assert_eq!(
|
||||
actual, wanted,
|
||||
"kernel sendfile must never leak placeholder markers"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user