use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::{mpsc, Mutex};
use crate::logging::emit::Log;
pub const MAX_FRAGMENT_BYTES: usize = 2 * 1024 * 1024;
pub const MAX_RECORD_BYTES: usize = 2 * 1024 * 1024;
pub const MAX_FRAGMENTS_PER_RECORD: usize = 64;
pub const FRAGMENT_BODY_TIMEOUT: Duration = Duration::from_secs(30);
pub const MAX_CONCURRENT_CONNECTIONS: usize =
crate::server::accept_bounded::DEFAULT_MAX_CONNECTIONS;
const FIRST_RECORD_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
const IDLE_BETWEEN_RECORDS_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(900);
const CONNECTION_CAP_REFUSAL: &[u8] = b"";
pub const MAX_PATH_COMPONENTS: usize = 32;
const RELAY_CHUNK_BYTES: usize = 64 * 1024;
const XID_RECOVERY_TIMEOUT: Duration = Duration::from_secs(2);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Refusal {
FragmentTooLarge { announced: usize },
RecordTooLarge { announced_total: usize },
TooManyFragments,
BodyStalled,
}
impl Refusal {
pub fn decision_tag(self) -> &'static str {
match self {
Self::FragmentTooLarge { .. } => "fail_closed_oversize_fragment",
Self::RecordTooLarge { .. } => "fail_closed_oversize_record",
Self::TooManyFragments => "fail_closed_fragment_flood",
Self::BodyStalled => "fail_closed_fragment_stalled",
}
}
pub fn describe(self) -> String {
match self {
Self::FragmentTooLarge { announced } => format!(
"fragment announced {} bytes, limit {}",
announced, MAX_FRAGMENT_BYTES
),
Self::RecordTooLarge { announced_total } => format!(
"record would reach {} bytes, limit {}",
announced_total, MAX_RECORD_BYTES
),
Self::TooManyFragments => {
format!("record exceeded {} fragments", MAX_FRAGMENTS_PER_RECORD)
}
Self::BodyStalled => format!(
"announced fragment stalled for more than {}s",
FRAGMENT_BODY_TIMEOUT.as_secs()
),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Fragment {
pub length: usize,
pub is_last: bool,
}
#[derive(Debug, Default)]
pub struct RecordScreen {
bytes_so_far: usize,
fragments_so_far: usize,
}
impl RecordScreen {
pub fn new() -> Self {
Self::default()
}
pub fn at_record_start(&self) -> bool {
self.fragments_so_far == 0
}
pub fn admit_fragment(&mut self, marker: u32) -> Result<Fragment, Refusal> {
let is_last = marker & 0x8000_0000 != 0;
let length = (marker & 0x7FFF_FFFF) as usize;
if length > MAX_FRAGMENT_BYTES {
return Err(Refusal::FragmentTooLarge { announced: length });
}
if self.fragments_so_far >= MAX_FRAGMENTS_PER_RECORD {
return Err(Refusal::TooManyFragments);
}
let total = self.bytes_so_far.saturating_add(length);
if total > MAX_RECORD_BYTES {
return Err(Refusal::RecordTooLarge {
announced_total: total,
});
}
self.bytes_so_far = total;
self.fragments_so_far += 1;
if is_last {
self.bytes_so_far = 0;
self.fragments_so_far = 0;
}
Ok(Fragment { length, is_last })
}
}
pub fn garbage_args_reply(xid: u32) -> [u8; 28] {
let mut out = [0u8; 28];
out[0..4].copy_from_slice(&(0x8000_0000u32 | 24).to_be_bytes());
out[4..8].copy_from_slice(&xid.to_be_bytes());
out[8..12].copy_from_slice(&1u32.to_be_bytes()); out[12..16].copy_from_slice(&0u32.to_be_bytes()); out[16..20].copy_from_slice(&0u32.to_be_bytes()); out[20..24].copy_from_slice(&0u32.to_be_bytes()); out[24..28].copy_from_slice(&4u32.to_be_bytes()); out
}
pub async fn serve_screened(
listener: TcpListener,
backend: SocketAddr,
app_state: Arc<crate::state::app_state::AppState>,
server_id: crate::state::ServerId,
status_tx: mpsc::UnboundedSender<String>,
) {
let limiter = crate::server::accept_bounded::ConnectionLimiter::new(MAX_CONCURRENT_CONNECTIONS);
loop {
let (client, peer_addr, permit) = match crate::server::accept_bounded::accept_bounded(
&listener,
&limiter,
CONNECTION_CAP_REFUSAL,
"NFS",
Some(&status_tx),
)
.await
{
Ok(triple) => triple,
Err(e) => {
Log::new(Some(&status_tx)).error(format!("NFS accept failed: {}", e));
tokio::time::sleep(Duration::from_millis(100)).await;
continue;
}
};
let peer = peer_addr;
let status_tx = status_tx.clone();
let handle = tokio::spawn(async move {
let _permit = permit;
screen_connection(client, backend, peer.to_string(), status_tx).await;
});
app_state.register_server_task(server_id, handle).await;
}
}
enum Relayed {
Complete,
PeerGone,
Stalled,
}
async fn screen_connection(
client: TcpStream,
backend: SocketAddr,
peer: String,
status_tx: mpsc::UnboundedSender<String>,
) {
let log = Log::new(Some(&status_tx));
let _ = client.set_nodelay(true);
let upstream = match TcpStream::connect(backend).await {
Ok(s) => s,
Err(e) => {
log.error(format!(
"NFS could not reach its own backend for {}: {}",
peer, e
));
return;
}
};
let _ = upstream.set_nodelay(true);
let (mut from_peer, to_peer) = client.into_split();
let (mut from_backend, mut to_backend) = upstream.into_split();
let to_peer = Arc::new(Mutex::new(to_peer));
let awaiting_reply = Arc::new(std::sync::atomic::AtomicBool::new(false));
let downstream_out = Arc::clone(&to_peer);
let downstream_awaiting = Arc::clone(&awaiting_reply);
let downstream = tokio::spawn(async move {
let mut buf = vec![0u8; RELAY_CHUNK_BYTES];
loop {
match from_backend.read(&mut buf).await {
Ok(0) | Err(_) => break,
Ok(n) => {
let mut out = downstream_out.lock().await;
if out.write_all(&buf[..n]).await.is_err() {
break;
}
drop(out);
downstream_awaiting.store(false, std::sync::atomic::Ordering::SeqCst);
}
}
}
});
let mut screen = RecordScreen::new();
let mut record_xid: u32 = 0;
let mut buf = vec![0u8; RELAY_CHUNK_BYTES];
let mut seen_record = false;
let outcome: Option<Refusal> = loop {
let mut marker = [0u8; 4];
let marker_bound = if seen_record {
IDLE_BETWEEN_RECORDS_TIMEOUT
} else {
FIRST_RECORD_READ_TIMEOUT
};
let marker_read = loop {
match tokio::time::timeout(marker_bound, from_peer.read_exact(&mut marker)).await {
Ok(read) => break Some(read),
Err(_) => {
if awaiting_reply.load(std::sync::atomic::Ordering::SeqCst) {
continue;
}
log.info(format!(
"NFS peer {} sent nothing for {}s; closing idle connection",
peer,
marker_bound.as_secs()
));
break None;
}
}
};
match marker_read {
Some(Ok(_)) => {}
Some(Err(_)) | None => break None,
}
seen_record = true;
let marker = u32::from_be_bytes(marker);
let at_start = screen.at_record_start();
if at_start {
record_xid = 0;
}
let fragment = match screen.admit_fragment(marker) {
Ok(f) => f,
Err(refusal) => {
if at_start {
let mut xid = [0u8; 4];
if let Ok(Ok(_)) =
tokio::time::timeout(XID_RECOVERY_TIMEOUT, from_peer.read_exact(&mut xid))
.await
{
record_xid = u32::from_be_bytes(xid);
}
}
break Some(refusal);
}
};
awaiting_reply.store(true, std::sync::atomic::Ordering::SeqCst);
if to_backend.write_all(&marker.to_be_bytes()).await.is_err() {
break None;
}
let mut remaining = fragment.length;
let mut captured = 0usize;
let relayed = loop {
if remaining == 0 {
break Relayed::Complete;
}
let want = remaining.min(buf.len());
let read =
match tokio::time::timeout(FRAGMENT_BODY_TIMEOUT, from_peer.read(&mut buf[..want]))
.await
{
Ok(Ok(0)) | Ok(Err(_)) => break Relayed::PeerGone,
Ok(Ok(n)) => n,
Err(_) => break Relayed::Stalled,
};
if at_start && captured < 4 {
let take = read.min(4 - captured);
for &b in &buf[..take] {
record_xid = (record_xid << 8) | b as u32;
captured += 1;
}
}
if to_backend.write_all(&buf[..read]).await.is_err() {
break Relayed::PeerGone;
}
remaining -= read;
};
if at_start && captured > 0 && captured < 4 {
record_xid <<= 8 * (4 - captured) as u32;
}
match relayed {
Relayed::Complete => {}
Relayed::PeerGone => break None,
Relayed::Stalled => break Some(Refusal::BodyStalled),
}
};
if let Some(refusal) = outcome {
log.error(format!(
"NFS record from {} refused decision={} ({}) - answered accept_stat=GARBAGE_ARGS",
peer,
refusal.decision_tag(),
refusal.describe()
));
let reply = garbage_args_reply(record_xid);
let mut out = to_peer.lock().await;
let _ = out.write_all(&reply).await;
let _ = out.flush().await;
let _ = out.shutdown().await;
}
drop(to_backend);
downstream.abort();
}