wireshift_fallback/
net_ops.rs1use std::io::{Read, Write};
4use std::net::{Shutdown, SocketAddr, TcpListener, TcpStream};
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::Arc;
7use std::time::{Duration, Instant};
8
9use wireshift_core::buffer::{Buffer, Submitted};
10use wireshift_core::op::CompletionPayload;
11use wireshift_core::{Error, Result};
12
13macro_rules! retry_eintr {
14 ($expr:expr) => {
15 loop {
16 match $expr {
17 Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
18 result => break result,
19 }
20 }
21 };
22}
23
24pub(crate) fn execute_connect(
25 addr: SocketAddr,
26 timeout: Option<Duration>,
27 canceled: Arc<AtomicBool>,
28) -> Result<CompletionPayload> {
29 let stream = TcpStream::connect_timeout(&addr, timeout.unwrap_or(Duration::from_secs(5)))
30 .map_err(|error| {
31 Error::io(
32 "connect failed",
33 error,
34 "ensure the remote host is reachable",
35 )
36 })?;
37 if canceled.load(Ordering::Relaxed) {
38 let _ = stream.shutdown(Shutdown::Both);
39 return Err(Error::canceled(
40 "connect canceled after socket creation",
41 "avoid canceling the request after the socket connects",
42 ));
43 }
44 Ok(CompletionPayload::Stream(stream))
45}
46
47pub(crate) fn execute_accept(
48 listener: TcpListener,
49 canceled: Arc<AtomicBool>,
50 timeout: Option<Duration>,
51) -> Result<CompletionPayload> {
52 listener.set_nonblocking(true).map_err(|error| {
53 Error::io(
54 "accept setup failed",
55 error,
56 "ensure the listener remains valid",
57 )
58 })?;
59 let deadline = Instant::now() + timeout.unwrap_or(Duration::from_secs(5));
60 loop {
61 if canceled.load(Ordering::Relaxed) {
62 return Err(Error::canceled(
63 "accept canceled before a peer connected",
64 "avoid canceling the request while a connection is expected",
65 ));
66 }
67 match retry_eintr!(listener.accept()) {
68 Ok((stream, _)) => return Ok(CompletionPayload::Stream(stream)),
69 Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
70 let remaining = deadline.saturating_duration_since(Instant::now());
71 if remaining.is_zero() {
72 return Err(Error::timeout(
73 "accept timed out waiting for an inbound peer",
74 "connect a client before waiting on the accept request",
75 ));
76 }
77 wait_for_listener_readiness(&listener, remaining);
86 }
87 Err(error) => {
88 return Err(Error::io(
89 "accept failed",
90 error,
91 "ensure the listener is bound and still accepting connections",
92 ));
93 }
94 }
95 }
96}
97
98fn wait_for_listener_readiness(listener: &TcpListener, remaining: Duration) {
109 use std::os::unix::io::AsRawFd;
110
111 let wait = remaining.min(Duration::from_millis(10));
114 let wait_ms = i32::try_from(wait.as_millis()).unwrap_or(i32::MAX);
115
116 let mut pfd = libc::pollfd {
117 fd: listener.as_raw_fd(),
118 events: libc::POLLIN,
119 revents: 0,
120 };
121 unsafe {
124 libc::poll(std::ptr::addr_of_mut!(pfd), 1, wait_ms);
125 }
126}
127
128fn apply_read_timeout(stream: &TcpStream, timeout: Option<Duration>) -> Result<()> {
135 if let Some(t) = timeout {
136 stream.set_read_timeout(Some(t)).map_err(|error| {
137 Error::io(
138 "recv set_read_timeout failed",
139 error,
140 "ensure the socket is valid and the read timeout is a non-zero duration",
141 )
142 })?;
143 }
144 Ok(())
145}
146
147pub(crate) fn execute_send(
148 mut stream: TcpStream,
149 buffer: Buffer<Submitted>,
150) -> Result<CompletionPayload> {
151 let bytes = retry_eintr!(stream.write(buffer.backend_ref())).map_err(|error| {
152 Error::io(
153 "send failed",
154 error,
155 "ensure the TCP stream remains writable while sending data",
156 )
157 })?;
158 Ok(CompletionPayload::Bytes(bytes))
159}
160
161pub(crate) fn execute_recv(
162 mut stream: TcpStream,
163 mut buffer: Buffer<Submitted>,
164 canceled: Arc<AtomicBool>,
165 timeout: Option<Duration>,
166) -> Result<CompletionPayload> {
167 apply_read_timeout(&stream, timeout)?;
168 if canceled.load(Ordering::Relaxed) {
169 return Err(Error::canceled(
170 "recv canceled before data transfer started",
171 "avoid canceling the request before the backend starts receiving data",
172 ));
173 }
174 let bytes = retry_eintr!(stream.read(buffer.backend_mut())).map_err(|error| {
175 Error::io(
176 "recv failed",
177 error,
178 "ensure the TCP stream remains readable while receiving data",
179 )
180 })?;
181 let completed = buffer.into_completed(bytes)?;
182 Ok(CompletionPayload::Recv {
183 stream,
184 buffer: completed,
185 bytes,
186 })
187}
188
189#[cfg(test)]
190mod net_ops_tests {
191 use super::{apply_read_timeout, execute_accept};
192 use std::net::{TcpListener, TcpStream};
193 use std::sync::atomic::AtomicBool;
194 use std::sync::Arc;
195 use std::time::{Duration, Instant};
196 use wireshift_core::op::CompletionPayload;
197
198 #[test]
199 fn apply_read_timeout_surfaces_invalid_zero_duration() {
200 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
204 let addr = listener.local_addr().unwrap();
205 let client = TcpStream::connect(addr).unwrap();
206
207 let result = apply_read_timeout(&client, Some(Duration::from_secs(0)));
208 let err = result.expect_err("zero-duration read timeout must be rejected loudly");
209 assert!(
210 err.to_string().to_lowercase().contains("timeout"),
211 "error must identify the read-timeout configuration failure, got: {err}"
212 );
213 }
214
215 #[test]
216 fn apply_read_timeout_accepts_valid_duration_and_none() {
217 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
219 let addr = listener.local_addr().unwrap();
220 let client = TcpStream::connect(addr).unwrap();
221
222 apply_read_timeout(&client, Some(Duration::from_millis(50)))
223 .expect("a valid read timeout must be accepted");
224 apply_read_timeout(&client, None).expect("no timeout must be a no-op");
225 }
226
227 #[test]
228 fn accept_wakes_on_connection_without_fixed_sleep_latency() {
229 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
236 let addr = listener.local_addr().unwrap();
237 let canceled = Arc::new(AtomicBool::new(false));
238
239 let connector = std::thread::spawn(move || {
240 std::thread::sleep(Duration::from_millis(25));
241 let connect_at = Instant::now();
242 let stream = TcpStream::connect(addr).unwrap();
243 (connect_at, stream) });
245
246 let payload = execute_accept(listener, canceled, Some(Duration::from_secs(2)))
247 .expect("accept must succeed once a peer connects");
248 let accept_done = Instant::now();
249 let (connect_at, _client) = connector.join().unwrap();
250
251 assert!(
252 matches!(payload, CompletionPayload::Stream(_)),
253 "accept must yield the accepted stream"
254 );
255 let latency = accept_done.saturating_duration_since(connect_at);
256 assert!(
257 latency < Duration::from_millis(5),
258 "accept must wake on listener readiness (sub-quantum), not after a fixed 10ms sleep; \
259 connect->accept latency was {latency:?}"
260 );
261 }
262}