1use std::error;
2use std::fmt::{self, Display, Formatter};
3use std::io::{Error, ErrorKind};
4
5use crate::backend;
6use crate::io::file::RawMetadata;
7
8pub trait Platform {
9 type Sqe;
10 type Gso;
11 type StatBuf;
12 type TimerSpec;
13 fn entropy() -> std::io::Result<[u64; 2]>;
14 fn parse_meta(raw: &Self::StatBuf) -> RawMetadata;
15 fn snapshot() -> std::io::Result<Snapshot>;
16}
17
18cfg_select! {
19 target_os = "linux" => {
20 use std::fs;
21 use std::mem::{MaybeUninit, size_of};
22
23 use crate::driver::Driver;
24
25 impl Platform for Driver {
26 type Sqe = backend::uring::sqe::Sqe;
27 type Gso = backend::uring::platform::gso::Gso;
28 type StatBuf = libc::statx;
29 type TimerSpec = io_uring::types::Timespec;
30
31 fn entropy() -> std::io::Result<[u64; 2]> {
32 let mut words = MaybeUninit::<[u64; 2]>::uninit();
33 let mut data = words.as_mut_ptr().cast::<u8>();
34 let mut len = size_of::<[u64; 2]>();
35 while len != 0 {
36 let written = unsafe { libc::getrandom(data.cast(), len, 0) };
38 if written < 0 {
39 let error = Error::last_os_error();
40 if error.kind() == ErrorKind::Interrupted {
41 continue;
42 }
43 return Err(error);
44 }
45 if written == 0 {
46 return Err(Error::new(
47 ErrorKind::UnexpectedEof,
48 "kernel entropy returned no bytes",
49 ));
50 }
51 let written = written as usize;
52 data = unsafe { data.add(written) };
54 len -= written;
55 }
56 Ok(unsafe { words.assume_init() })
58 }
59
60 fn parse_meta(raw: &Self::StatBuf) -> RawMetadata {
61 RawMetadata {
62 len: raw.stx_size,
63 modified: Some((raw.stx_mtime.tv_sec, raw.stx_mtime.tv_nsec)),
64 regular: u32::from(raw.stx_mode) & libc::S_IFMT == libc::S_IFREG,
65 }
66 }
67
68 fn snapshot() -> std::io::Result<Snapshot> {
69 Ok(Snapshot {
70 syncookies: read_u32("/proc/sys/net/ipv4/tcp_syncookies")? != 0,
71 max_syn_backlog: read_u32("/proc/sys/net/ipv4/tcp_max_syn_backlog")?,
72 somaxconn: read_u32("/proc/sys/net/core/somaxconn")?,
73 rlimit_nofile: OpenFileLimit::get()?.soft(),
74 })
75 }
76 }
77
78 fn read_u32(path: &str) -> std::io::Result<u32> {
79 let value = fs::read_to_string(path)?;
80 value
81 .trim()
82 .parse::<u32>()
83 .map_err(|error| Error::new(ErrorKind::InvalidData, format!("{path}: {error}")))
84 }
85 }
86 _ => {
87 use std::ffi::CStr;
88 use std::mem::{MaybeUninit, size_of};
89 use std::ptr::null_mut;
90
91 use crate::driver::Driver;
92
93 impl Platform for Driver {
94 type Sqe = backend::kqueue::sqe::Sqe;
95 type Gso = backend::kqueue::platform::gso::Gso;
96 type StatBuf = libc::stat;
97 type TimerSpec = backend::kqueue::sqe::TimerSpec;
98
99 fn entropy() -> std::io::Result<[u64; 2]> {
100 let mut words = MaybeUninit::<[u64; 2]>::uninit();
101 loop {
102 if unsafe { libc::getentropy(words.as_mut_ptr().cast(), size_of::<[u64; 2]>()) } == 0 {
104 return Ok(unsafe { words.assume_init() });
106 }
107 let error = Error::last_os_error();
108 if error.kind() != ErrorKind::Interrupted {
109 return Err(error);
110 }
111 }
112 }
113
114 fn parse_meta(raw: &Self::StatBuf) -> RawMetadata {
115 RawMetadata {
116 len: u64::try_from(raw.st_size).unwrap_or(0),
117 modified: u32::try_from(raw.st_mtime_nsec)
118 .ok()
119 .filter(|nanos| *nanos < 1_000_000_000)
120 .map(|nanos| (raw.st_mtime, nanos)),
121 regular: (raw.st_mode as libc::mode_t) & libc::S_IFMT == libc::S_IFREG,
122 }
123 }
124
125 fn snapshot() -> std::io::Result<Snapshot> {
126 let somaxconn = sysctl_u32(c"kern.ipc.somaxconn")?;
127 Ok(Snapshot {
128 syncookies: true,
129 max_syn_backlog: somaxconn,
130 somaxconn,
131 rlimit_nofile: OpenFileLimit::get()?.soft(),
132 })
133 }
134 }
135
136 fn sysctl_u32(name: &CStr) -> std::io::Result<u32> {
137 let mut value: libc::c_int = 0;
138 let mut len = size_of::<libc::c_int>();
139 let rc = unsafe {
141 libc::sysctlbyname(
142 name.as_ptr(),
143 (&mut value as *mut libc::c_int).cast(),
144 &mut len,
145 null_mut(),
146 0,
147 )
148 };
149 if rc != 0 {
150 return Err(Error::last_os_error());
151 }
152 u32::try_from(value).map_err(|_| Error::new(ErrorKind::InvalidData, "negative sysctl"))
153 }
154 }
155}
156
157const SYN_BACKLOG_FLOOR: u32 = 4096;
158const SOMAXCONN_FLOOR: u32 = 4096;
159
160#[derive(Debug)]
161pub struct Snapshot {
162 pub syncookies: bool,
163 pub max_syn_backlog: u32,
164 pub somaxconn: u32,
165 pub rlimit_nofile: u64,
166}
167
168impl Snapshot {
169 pub fn check_slots(&self, requested_slots: u32) -> Result<(), Mismatch> {
170 if u64::from(requested_slots) > self.rlimit_nofile {
171 return Err(Mismatch::NoFileTooLow {
172 requested: requested_slots,
173 rlimit: self.rlimit_nofile,
174 });
175 }
176 if !self.syncookies && self.max_syn_backlog < SYN_BACKLOG_FLOOR {
177 return Err(Mismatch::SynFloodVulnerable {
178 backlog: self.max_syn_backlog,
179 expected: SYN_BACKLOG_FLOOR,
180 });
181 }
182 if self.somaxconn < SOMAXCONN_FLOOR {
183 return Err(Mismatch::SomaxconnTooLow {
184 requested: SOMAXCONN_FLOOR,
185 kernel: self.somaxconn,
186 });
187 }
188 Ok(())
189 }
190}
191
192#[derive(Debug)]
193pub enum Mismatch {
194 NoFileTooLow { requested: u32, rlimit: u64 },
195 SomaxconnTooLow { requested: u32, kernel: u32 },
196 SynFloodVulnerable { backlog: u32, expected: u32 },
197}
198
199impl Display for Mismatch {
200 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
201 match self {
202 Self::NoFileTooLow { requested, rlimit } => write!(
203 formatter,
204 "RLIMIT_NOFILE ({rlimit}) below configured fixed_file_slots ({requested}); raise the limit or lower the slot count"
205 ),
206 Self::SomaxconnTooLow { requested, kernel } => write!(
207 formatter,
208 "somaxconn ({kernel}) below required floor ({requested}); raise it to {requested}"
209 ),
210 Self::SynFloodVulnerable { backlog, expected } => write!(
211 formatter,
212 "SYN-flood vulnerable: syncookies off and max_syn_backlog ({backlog}) below floor ({expected})"
213 ),
214 }
215 }
216}
217
218impl error::Error for Mismatch {}
219
220impl From<Mismatch> for Error {
221 fn from(mismatch: Mismatch) -> Self {
222 Error::new(ErrorKind::InvalidInput, mismatch)
223 }
224}
225
226pub(crate) struct OpenFileLimit(libc::rlimit);
227
228impl OpenFileLimit {
229 pub(crate) fn get() -> std::io::Result<Self> {
230 let mut limit = libc::rlimit {
231 rlim_cur: 0,
232 rlim_max: 0,
233 };
234 let rc = unsafe { libc::getrlimit(libc::RLIMIT_NOFILE, &mut limit) };
235 if rc != 0 {
236 return Err(Error::last_os_error());
237 }
238 Ok(Self(limit))
239 }
240
241 #[allow(clippy::unnecessary_cast)]
242 pub(crate) fn soft(&self) -> u64 {
243 self.0.rlim_cur as u64
244 }
245
246 pub(crate) fn raise(mut self) -> std::io::Result<()> {
247 self.0.rlim_cur = self.0.rlim_max;
248 let rc = unsafe { libc::setrlimit(libc::RLIMIT_NOFILE, &self.0) };
249 if rc != 0 {
250 return Err(Error::last_os_error());
251 }
252 Ok(())
253 }
254}