use std::io;
use std::net::SocketAddr;
use std::ops::Range;
use mio::net::UdpSocket as MioUdpSocket;
use roughenough_protocol::request;
use crate::metrics::types::NetworkMetrics;
use crate::network::CollectResult::{Empty, MoreData};
use crate::responses;
struct QueuedResponse {
range: Range<usize>,
addr: SocketAddr,
}
pub struct NetworkHandler {
batch_size: usize,
metrics: NetworkMetrics,
recv_buf: [u8; request::MAX_REQUEST_SIZE + 1],
send_bufs: Vec<u8>,
send_msgs: Vec<QueuedResponse>,
#[cfg(target_os = "linux")]
mmsg_state: mmsg::MmsgState,
}
impl std::fmt::Debug for NetworkHandler {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NetworkHandler")
.field("batch_size", &self.batch_size)
.field("metrics", &self.metrics)
.field("queued_responses", &self.send_msgs.len())
.finish()
}
}
#[derive(Debug, Eq, PartialEq)]
pub enum CollectResult {
Empty,
MoreData,
}
impl NetworkHandler {
pub fn new(batch_size: usize) -> Self {
Self {
batch_size,
metrics: NetworkMetrics::default(),
recv_buf: [0u8; request::MAX_REQUEST_SIZE + 1],
send_bufs: Vec::with_capacity(batch_size * responses::RESPONSE_BUF_SIZE),
send_msgs: Vec::with_capacity(batch_size),
#[cfg(target_os = "linux")]
mmsg_state: mmsg::MmsgState::with_capacity(batch_size),
}
}
pub fn collect_requests<F>(&mut self, sock: &mut MioUdpSocket, mut callback: F) -> CollectResult
where
F: FnMut(&mut [u8], SocketAddr),
{
for _ in 0..self.batch_size {
match sock.recv_from(&mut self.recv_buf) {
Ok((nbytes, _)) if nbytes > request::MAX_REQUEST_SIZE => {
self.metrics.num_oversized_dropped += 1;
}
Ok((nbytes, src_addr)) => {
callback(&mut self.recv_buf[..nbytes], src_addr);
}
Err(e) => return self.record_recv_error(&e),
}
}
MoreData
}
fn record_recv_error(&mut self, error: &io::Error) -> CollectResult {
if error.kind() == io::ErrorKind::WouldBlock {
self.metrics.num_recv_wouldblock += 1;
} else {
self.metrics.num_failed_recvs += 1;
}
Empty
}
pub fn send_response(&mut self, sock: &mut MioUdpSocket, data: &[u8], addr: SocketAddr) {
match sock.send_to(data, addr) {
Ok(_) => {
self.metrics.num_successful_sends += 1;
}
Err(_) => {
self.metrics.num_failed_sends += 1;
}
}
}
pub fn queue_response(&mut self, data: &[u8], addr: SocketAddr) {
let start = self.send_bufs.len();
self.send_bufs.extend_from_slice(data);
self.send_msgs.push(QueuedResponse {
range: start..self.send_bufs.len(),
addr,
});
}
pub fn flush_responses(&mut self, sock: &mut MioUdpSocket) {
#[cfg(target_os = "linux")]
{
let bufs = &self.send_bufs;
let state = &mut self.mmsg_state;
let (sent, failed) =
drain_queue(&self.send_msgs, |msgs| state.send_batch(sock, bufs, msgs));
self.metrics.num_successful_sends += sent;
self.metrics.num_failed_sends += failed;
}
#[cfg(not(target_os = "linux"))]
for msg in &self.send_msgs {
match sock.send_to(&self.send_bufs[msg.range.clone()], msg.addr) {
Ok(_) => self.metrics.num_successful_sends += 1,
Err(_) => self.metrics.num_failed_sends += 1,
}
}
self.send_msgs.clear();
self.send_bufs.clear();
}
pub fn metrics(&self) -> NetworkMetrics {
self.metrics
}
pub fn reset_metrics(&mut self) {
self.metrics = NetworkMetrics::default();
}
pub fn record_failed_poll(&mut self) {
self.metrics.num_failed_polls += 1;
}
}
#[cfg(target_os = "linux")]
fn drain_queue<F>(msgs: &[QueuedResponse], mut send_batch: F) -> (usize, usize)
where
F: FnMut(&[QueuedResponse]) -> io::Result<usize>,
{
let mut sent = 0;
while sent < msgs.len() {
match send_batch(&msgs[sent..]) {
Ok(0) | Err(_) => break,
Ok(n) => sent += n,
}
}
(sent, msgs.len() - sent)
}
#[cfg(target_os = "linux")]
#[allow(unsafe_code)]
mod mmsg {
use std::io;
use std::os::fd::AsRawFd;
use mio::net::UdpSocket as MioUdpSocket;
use super::QueuedResponse;
pub(super) struct MmsgState {
addrs: Vec<socket2::SockAddr>,
iovecs: Vec<libc::iovec>,
hdrs: Vec<libc::mmsghdr>,
}
unsafe impl Send for MmsgState {}
impl MmsgState {
pub(super) fn with_capacity(batch_size: usize) -> Self {
Self {
addrs: Vec::with_capacity(batch_size),
iovecs: Vec::with_capacity(batch_size),
hdrs: Vec::with_capacity(batch_size),
}
}
pub(super) fn send_batch(
&mut self,
sock: &MioUdpSocket,
bufs: &[u8],
msgs: &[QueuedResponse],
) -> io::Result<usize> {
let Self {
addrs,
iovecs,
hdrs,
} = self;
addrs.clear();
iovecs.clear();
hdrs.clear();
for msg in msgs {
addrs.push(socket2::SockAddr::from(msg.addr));
let payload = &bufs[msg.range.clone()];
iovecs.push(libc::iovec {
iov_base: payload.as_ptr() as *mut libc::c_void,
iov_len: payload.len(),
});
}
for (addr, iovec) in addrs.iter().zip(iovecs.iter_mut()) {
let mut msg_hdr: libc::msghdr = unsafe { std::mem::zeroed() };
msg_hdr.msg_name = addr.as_ptr() as *mut libc::c_void;
msg_hdr.msg_namelen = addr.len();
msg_hdr.msg_iov = iovec;
msg_hdr.msg_iovlen = 1;
hdrs.push(libc::mmsghdr {
msg_hdr,
msg_len: 0,
});
}
let rc = unsafe {
libc::sendmmsg(
sock.as_raw_fd(),
hdrs.as_mut_ptr(),
hdrs.len() as libc::c_uint,
0,
)
};
if rc < 0 {
Err(io::Error::last_os_error())
} else {
Ok(rc as usize)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wouldblock_recv_error_returns_empty_and_is_counted() {
let mut handler = NetworkHandler::new(4);
let error = io::Error::from(io::ErrorKind::WouldBlock);
assert_eq!(handler.record_recv_error(&error), Empty);
assert_eq!(handler.metrics().num_recv_wouldblock, 1);
assert_eq!(handler.metrics().num_failed_recvs, 0);
}
#[test]
fn non_wouldblock_recv_error_returns_empty_and_is_counted() {
let mut handler = NetworkHandler::new(4);
let error = io::Error::other("injected receive failure");
assert_eq!(handler.record_recv_error(&error), Empty);
assert_eq!(handler.metrics().num_failed_recvs, 1);
assert_eq!(handler.metrics().num_recv_wouldblock, 0);
}
#[cfg(target_os = "linux")]
fn queued(n: usize) -> Vec<QueuedResponse> {
let addr: SocketAddr = "127.0.0.1:9999".parse().unwrap();
(0..n)
.map(|i| QueuedResponse {
range: i..i + 1,
addr,
})
.collect()
}
#[cfg(target_os = "linux")]
#[test]
fn drain_queue_resumes_after_partial_sends() {
let msgs = queued(7);
let mut offsets_seen = Vec::new();
let (sent, failed) = drain_queue(&msgs, |remaining| {
offsets_seen.push(msgs.len() - remaining.len());
Ok(remaining.len().min(3))
});
assert_eq!((sent, failed), (7, 0));
assert_eq!(offsets_seen, vec![0, 3, 6]);
}
#[cfg(target_os = "linux")]
#[test]
fn drain_queue_counts_remainder_as_failed_on_error() {
let msgs = queued(5);
let mut calls = 0;
let (sent, failed) = drain_queue(&msgs, |remaining| {
calls += 1;
if calls == 1 {
Ok(remaining.len().min(2))
} else {
Err(io::Error::other("injected send failure"))
}
});
assert_eq!((sent, failed), (2, 3));
}
#[cfg(target_os = "linux")]
#[test]
fn drain_queue_stops_on_zero_progress() {
let msgs = queued(4);
let mut calls = 0;
let (sent, failed) = drain_queue(&msgs, |_| {
calls += 1;
Ok(0)
});
assert_eq!((sent, failed), (0, 4));
assert_eq!(calls, 1, "a zero return must not be retried");
}
}