miri/shims/unix/tcp_socket.rs
1use std::cell::{Cell, RefCell, RefMut};
2use std::io::Read;
3use std::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, SocketAddrV4, SocketAddrV6};
4use std::sync::atomic::AtomicBool;
5use std::time::Duration;
6use std::{io, mem};
7
8use mio::event::Source;
9use mio::net::{TcpListener, TcpStream};
10use rustc_const_eval::interpret::{InterpResult, interp_ok};
11use rustc_middle::throw_unsup_format;
12use rustc_target::spec::Os;
13
14use crate::shims::files::{EvalContextExt as _, FdNum, FileDescription, FileDescriptionRef};
15use crate::shims::sig::Varargs;
16use crate::shims::unix::UnixFileDescription;
17use crate::shims::unix::socket::{EvalContextExt, UnixSocketFileDescription};
18use crate::*;
19
20/// On Linux a TCP socket is initally in the TCP_CLOSE state:
21/// See <https://github.com/torvalds/linux/blob/cee9395/net/core/sock.c#L3753>
22/// For a socket in this state, (E)POLLHUP is reported:
23/// See <https://github.com/torvalds/linux/blob/980a813/net/ipv4/tcp.c#L581-L582>
24/// Additionally, because the write buffer is initially empty and the socket is not
25/// shut down, (E)POLLOUT is also reported for freshly created TCP sockets:
26/// See <https://github.com/torvalds/linux/blob/980a813/net/ipv4/tcp.c#L602>
27///
28/// These events are reported when the "write closed" and "writable" readiness of
29/// our generic [`Readiness`] struct are set. Because the TCP socket is only added
30/// to the blocking I/O manager after it is connected or listening, we manually set
31/// this initial readiness.
32const INITIAL_TCP_SOCKET_READINESS: Readiness =
33 Readiness { writable: true, write_closed: true, ..Readiness::EMPTY };
34
35#[derive(Debug)]
36enum SocketState {
37 /// No syscall after `socket` has been made.
38 Initial(socket2::Socket),
39 /// The `listen` syscall has been called on the socket.
40 /// This is reachable from the [`SocketState::Initial`] and the
41 /// [`SocketState::Listening`] states.
42 Listening(TcpListener),
43 /// The `connect` syscall has been called and we weren't yet able
44 /// to ensure the connection is established. This is only reachable
45 /// from the [`SocketState::Initial`] state.
46 Connecting(TcpStream),
47 /// The `connect` syscall has been called on the socket and
48 /// we ensured that the connection is established, or
49 /// the socket was created by the `accept` syscall.
50 /// For a socket created using the `connect` syscall, this is
51 /// only reachable from the [`SocketState::Connecting`] state.
52 Connected(TcpStream),
53 /// The SO_ERROR socket option has been set after calling
54 /// the `connect` syscall, indicating that the connection
55 /// attempt failed. By the POSIX specification, a socket is
56 /// is an unspecified state after a failed connection attempt
57 /// and thus nothing (except destroying the socket) should be
58 /// supported when a socket is in this state.
59 ConnectionFailed(TcpStream),
60 /// The socket state machine is transitioning between two states
61 /// which contain an underlying host socket. This state is never
62 /// observable, it's only needed as a placeholder state to safely
63 /// acquire ownership of the underlying host socket.
64 // FIXME: This would not be needed with `replace_with`, but sadly
65 // this isn't part of the standard library and we don't want to add
66 // an extra dependency for this.
67 None,
68}
69
70impl SocketState {
71 /// View the underlying host socket as a [`socket2::SockRef`].
72 ///
73 /// **Note**: Potentially blocking operations need to be performed on the
74 /// underlying [`TcpStream`] and [`TcpListener`] as it would break the mio
75 /// poll on Windows hosts when performed directly on the [`socket2::SockRef`].
76 fn as_socket_ref<'a>(&'a self) -> socket2::SockRef<'a> {
77 match self {
78 SocketState::Initial(socket) => socket.into(),
79 SocketState::Listening(listener) => listener.into(),
80 SocketState::Connecting(stream)
81 | SocketState::Connected(stream)
82 | SocketState::ConnectionFailed(stream) => stream.into(),
83 SocketState::None => unreachable!(),
84 }
85 }
86}
87
88#[derive(Debug)]
89pub(super) struct TcpSocket {
90 /// Family of the socket, used to ensure socket only binds/connects to address of
91 /// same family.
92 family: socket2::Domain,
93 /// Current state of the inner socket.
94 state: RefCell<SocketState>,
95 /// Whether this fd is non-blocking or not.
96 is_non_block: Cell<bool>,
97 /// Whether the socket is implicitly or explicitly bound to an address.
98 is_bound: Cell<bool>,
99 /// The current blocking I/O readiness of the file description.
100 io_readiness: RefCell<Readiness>,
101 /// [`Some`] when the socket had an async error which has not yet been fetched via `SO_ERROR`.
102 error: RefCell<Option<io::Error>>,
103 /// Read timeout of the socket. [`None`] means that reads can block indefinitely.
104 /// The timeout is applied to the monotonic clock (the Unix specification doesn't
105 /// specify which clock to use, but the monotonic clock is more common for
106 /// relative timeouts).
107 /// This is ignored when the socket is non-blocking.
108 read_timeout: Cell<Option<Duration>>,
109 /// Write timeout of the socket. [`None`] means that writes can block indefinitely.
110 /// The timeout is applied to the monotonic clock (the Unix specification doesn't
111 /// specify which clock to use, but the monotonic clock is more common
112 /// for relative timeouts).
113 /// This is ignored when the socket is non-blocking.
114 write_timeout: Cell<Option<Duration>>,
115 /// State for being watched by epoll.
116 watched: ReadinessWatched,
117}
118
119impl TcpSocket {
120 pub fn new(family: socket2::Domain, is_non_block: bool) -> io::Result<Self> {
121 let socket =
122 socket2::Socket::new(family, socket2::Type::STREAM, Some(socket2::Protocol::TCP))?;
123 // The underlying host socket needs to be non-blocking. The actual
124 // blocking mode of the socket is stored in `is_non_block`.
125 socket.set_nonblocking(true)?;
126
127 // We don't add the newly created socket to the blocking I/O manager
128 // until we can create a mio TcpStream or TcpListener because
129 // `socket2::Socket` doesn't implement `mio::Source`.
130 // This is fine since the readiness of a TCP socket (at least under Linux)
131 // does have a static initial readiness until `connect` or `listen` is
132 // invoked on it.
133
134 Ok(TcpSocket {
135 family,
136 state: RefCell::new(SocketState::Initial(socket)),
137 is_non_block: Cell::new(is_non_block),
138 is_bound: Cell::new(false),
139 io_readiness: RefCell::new(INITIAL_TCP_SOCKET_READINESS),
140 error: RefCell::new(None),
141 read_timeout: Cell::new(None),
142 write_timeout: Cell::new(None),
143 watched: ReadinessWatched::default(),
144 })
145 }
146}
147
148impl FileDescription for TcpSocket {
149 fn name(&self) -> &'static str {
150 "socket"
151 }
152
153 fn read<'tcx>(
154 self: FileDescriptionRef<Self>,
155 communicate_allowed: bool,
156 ptr: Pointer,
157 len: usize,
158 ecx: &mut MiriInterpCx<'tcx>,
159 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
160 ) -> InterpResult<'tcx> {
161 self.recv(
162 communicate_allowed,
163 ptr,
164 len,
165 /* is_peek */ false,
166 /* is_non_block */ false,
167 ecx,
168 finish,
169 )
170 }
171
172 fn write<'tcx>(
173 self: FileDescriptionRef<Self>,
174 communicate_allowed: bool,
175 ptr: Pointer,
176 len: usize,
177 ecx: &mut MiriInterpCx<'tcx>,
178 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
179 ) -> InterpResult<'tcx> {
180 self.send(communicate_allowed, ptr, len, /* is_non_block */ false, ecx, finish)
181 }
182
183 fn short_fd_operations(&self) -> bool {
184 // Linux guarantees that when a read/write on a streaming socket comes back short,
185 // the kernel buffer is empty/full:
186 // See <https://man7.org/linux/man-pages/man7/epoll.7.html> in Q&A section.
187 // So we can't do short reads/writes here.
188 false
189 }
190
191 fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
192 self
193 }
194
195 fn readiness_watched(&self) -> Option<&ReadinessWatched> {
196 Some(&self.watched)
197 }
198
199 fn readiness(&self) -> Readiness {
200 *self.io_readiness.borrow()
201 }
202}
203
204impl UnixFileDescription for TcpSocket {
205 fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
206 let mut flags = ecx.eval_libc_i32("O_RDWR");
207
208 if self.is_non_block.get() {
209 flags |= ecx.eval_libc_i32("O_NONBLOCK");
210 }
211
212 interp_ok(Scalar::from_i32(flags))
213 }
214
215 fn set_flags<'tcx>(
216 &self,
217 mut flag: i32,
218 ecx: &mut MiriInterpCx<'tcx>,
219 ) -> InterpResult<'tcx, Scalar> {
220 let o_nonblock = ecx.eval_libc_i32("O_NONBLOCK");
221
222 // O_NONBLOCK flag can be set / unset by user.
223 if flag & o_nonblock == o_nonblock {
224 self.is_non_block.set(true);
225 flag &= !o_nonblock;
226 } else {
227 self.is_non_block.set(false);
228 }
229
230 // Throw error if there is any unsupported flag.
231 if flag != 0 {
232 throw_unsup_format!("fcntl: only O_NONBLOCK is supported for sockets")
233 }
234
235 interp_ok(Scalar::from_i32(0))
236 }
237
238 fn ioctl<'tcx>(
239 &self,
240 op: Scalar,
241 args: Varargs<'tcx, '_>,
242 ecx: &mut MiriInterpCx<'tcx>,
243 ) -> InterpResult<'tcx, i32> {
244 assert!(ecx.machine.communicate(), "cannot have `TcpSocket` with isolation enabled!");
245
246 let fionbio = ecx.eval_libc("FIONBIO");
247
248 if op == fionbio {
249 // On these OSes, Rust uses the ioctl, so we trust that it is reasonable and controls
250 // the same internal flag as fcntl.
251 if !matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android | Os::MacOs | Os::FreeBsd)
252 {
253 // FIONBIO cannot be used to change the blocking mode of a socket on solarish targets:
254 // <https://github.com/rust-lang/rust/commit/dda5c97675b4f5b1f6fdab64606c8a1f21021b0a>
255 // Since there might be more targets which do weird things with this option, we use
256 // an allowlist instead of just denying solarish targets.
257 throw_unsup_format!(
258 "ioctl: setting FIONBIO on sockets is unsupported on target {}",
259 ecx.tcx.sess.target.os
260 );
261 }
262
263 let ([value_ptr], _) = ecx.check_varargs(shim_varargs![*i32], args, "ioctl")?;
264 let value = ecx.deref_pointer_as(value_ptr, ecx.machine.layouts.i32)?;
265 let non_block = ecx.read_scalar(&value)?.to_i32()? != 0;
266 self.is_non_block.set(non_block);
267 return interp_ok(0);
268 }
269
270 throw_unsup_format!("ioctl: unsupported operation {op:#x} on socket");
271 }
272
273 fn as_socket(
274 self: FileDescriptionRef<Self>,
275 ) -> Option<FileDescriptionRef<dyn UnixSocketFileDescription>> {
276 Some(self)
277 }
278}
279
280impl UnixSocketFileDescription for TcpSocket {
281 fn bind<'tcx>(
282 self: FileDescriptionRef<TcpSocket>,
283 communicate_allowed: bool,
284 address: SocketAddr,
285 ecx: &mut MiriInterpCx<'tcx>,
286 ) -> InterpResult<'tcx, Result<(), IoError>> {
287 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
288 ecx.ensure_not_failed(&self, "bind")?;
289
290 let address_family = match &address {
291 SocketAddr::V4(_) => socket2::Domain::IPV4,
292 SocketAddr::V6(_) => socket2::Domain::IPV6,
293 };
294
295 if self.family != address_family {
296 // Attempted to bind an address from a family that doesn't match
297 // the family of the socket.
298 let err = if matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android) {
299 // Linux man page states that `EINVAL` is used when there is an address family mismatch.
300 // See <https://man7.org/linux/man-pages/man2/bind.2.html>
301 LibcError("EINVAL")
302 } else {
303 // POSIX man page states that `EAFNOSUPPORT` should be used when there is an address
304 // family mismatch.
305 // See <https://man7.org/linux/man-pages/man3/bind.3p.html>
306 LibcError("EAFNOSUPPORT")
307 };
308 return interp_ok(Err(err));
309 }
310
311 let state = self.state.borrow();
312 let socket = state.as_socket_ref();
313
314 // `bind` is a non-blocking operation for TCP sockets.
315 if let Err(e) = socket.bind(&socket2::SockAddr::from(address)) {
316 return interp_ok(Err(IoError::HostError(e)));
317 }
318
319 // The socket has been explicitly bound to a local address.
320 self.is_bound.set(true);
321
322 interp_ok(Ok(()))
323 }
324
325 fn listen<'tcx>(
326 self: FileDescriptionRef<TcpSocket>,
327 communicate_allowed: bool,
328 backlog: i32,
329 ecx: &mut MiriInterpCx<'tcx>,
330 ) -> InterpResult<'tcx, Result<(), IoError>> {
331 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
332 ecx.ensure_not_failed(&self, "listen")?;
333
334 let mut state = self.state.borrow_mut();
335
336 // We'll only attempt to invoke `listen` on the socket if it's not already
337 // connected because POSIX specifies that EINVAL should be returned in this
338 // case.
339 let socket = match &*state {
340 SocketState::Initial(_) | SocketState::Listening(_) => state.as_socket_ref(),
341 // POSIX states that connected sockets cannot start listening,
342 // so we can return early here to simplify the remaining shim.
343 SocketState::Connecting(_) | SocketState::Connected(_) =>
344 return interp_ok(Err(LibcError("EINVAL"))),
345 SocketState::ConnectionFailed(_) | SocketState::None => unreachable!(),
346 };
347
348 // Despite not being specified by POSIX, on most of our supported Unix-like platforms
349 // (confirmed on Linux, macOS, FreeBSD, and Solaris) TCP sockets get implicitly bound
350 // to the unspecified address at a random port when `listen` is invoked on an unbound
351 // socket. Thus, Miri models the same non-POSIX behavior and treats a socket as bound
352 // once `listen` has successfully been invoked on an unbound socket.
353
354 if cfg!(windows) && !self.is_bound.get() {
355 // Implicitly binding a TCP socket by invoking `listen` on an
356 // unbound socket causes EINVAL on Windows hosts. We thus
357 // explicitly bind the socket to an unspecified address with
358 // a random port before listening.
359 //
360 // When the subsequent `listen` fails we behave incorrectly
361 // because a previously unbound socket is now bound. However,
362 // since `listen` can only fail for system resource errors
363 // (e.g., ENOBUFS) this isn't a problem in reality.
364
365 let address = if self.family == socket2::Domain::IPV4 {
366 SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, /* port */ 0))
367 } else {
368 SocketAddr::V6(SocketAddrV6::new(
369 Ipv6Addr::UNSPECIFIED,
370 /* port */ 0,
371 /* flowinfo */ 0,
372 /* scope_id */ 0,
373 ))
374 };
375
376 if let Err(e) = socket.bind(&socket2::SockAddr::from(address)) {
377 return interp_ok(Err(IoError::HostError(e)));
378 }
379
380 // We just explicitly bound the socket to a local address.
381 self.is_bound.set(true);
382 }
383
384 if let Err(e) = socket.listen(backlog) {
385 return interp_ok(Err(IoError::HostError(e)));
386 }
387
388 // As mentioned above, we treat listening sockets as implicitly bound to a
389 // local address if they weren't already bound before the `listen` invocation.
390 self.is_bound.set(true);
391
392 if let SocketState::Listening(_) = &*state {
393 // The socket was already listening; we just changed the backlog.
394 // The socket is already registered to the blocking I/O manager
395 // and we also don't need to perform a state transition.
396 return interp_ok(Ok(()));
397 }
398
399 // Temporarily use dummy state to take ownership of the underlying socket.
400 let SocketState::Initial(socket) = mem::replace(&mut *state, SocketState::None) else {
401 unreachable!()
402 };
403
404 // FIXME: Rustfmt has a bug where it incorrectly removes the outer braces of {{ .. }} inside
405 // a `cfg_select!` block. See <https://github.com/rust-lang/rustfmt/issues/7045>.
406 #[rustfmt::skip]
407 let listener = cfg_select! {
408 // Turn the `socket2::Socket` into a `mio::TcpListener`.
409 // We follow Tokio's TCP socket implementation:
410 // See <https://github.com/tokio-rs/tokio/blob/2120bee/tokio/src/net/tcp/socket.rs#L906-L925>
411 // SAFETY: mio specifies that it is safe to use `from_raw_fd` and
412 // `from_raw_socket` as long as it's ensured that the socket is non-blocking.
413 // Because we immediately make the socket non-blocking after creation, that
414 // is always guaranteed.
415
416 unix => {{
417 use std::os::fd::{IntoRawFd, FromRawFd};
418 let raw_fd = socket.into_raw_fd();
419 unsafe { TcpListener::from_raw_fd(raw_fd) }
420 }},
421 windows => {{
422 use std::os::windows::io::{IntoRawSocket, FromRawSocket};
423 let raw_socket = socket.into_raw_socket();
424 unsafe { TcpListener::from_raw_socket(raw_socket) }
425 }},
426 _ => unreachable!("unsupported host platform")
427 };
428
429 *state = SocketState::Listening(listener);
430 drop(state);
431
432 // Clear the "artificial" readiness as we are about to put the blocking
433 // I/O manager in charge of handling readiness.
434 self.io_readiness.replace(Readiness::EMPTY);
435 ecx.update_fd_readiness(self.clone(), ReadinessUpdateFlags::DEFAULT)?;
436
437 // Register the socket to the blocking I/O manager. Can only happen
438 // now that we have created a mio TcpListener.
439 ecx.machine.blocking_io.register(self);
440
441 interp_ok(Ok(()))
442 }
443
444 fn accept<'tcx>(
445 self: FileDescriptionRef<Self>,
446 communicate_allowed: bool,
447 is_client_sock_non_block: bool,
448 ecx: &mut MiriInterpCx<'tcx>,
449 finish: DynMachineCallback<'tcx, Result<(FdNum, SocketAddr), IoError>>,
450 ) -> InterpResult<'tcx> {
451 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
452
453 if !matches!(*self.state.borrow(), SocketState::Listening(_)) {
454 throw_unsup_format!(
455 "accept: accepting incoming connections is only allowed when tcp socket is listening"
456 )
457 };
458
459 if self.is_non_block.get() {
460 // We have a non-blocking socket and thus don't want to block until
461 // we can accept an incoming connection.
462 let result = ecx.try_non_block_accept(&self, is_client_sock_non_block)?;
463 finish.call(ecx, result)
464 } else {
465 // The socket is in blocking mode and thus the accept call should block
466 // until an incoming connection is ready.
467
468 if self.read_timeout.get().is_some() {
469 // Some Unixes like Linux also apply the SO_RCVTIMEO socket option
470 // to `accept` calls:
471 // <https://github.com/torvalds/linux/blob/HEAD/net/ipv4/inet_connection_sock.c#L668-L675>
472 // This is currently not supported by Miri.
473 throw_unsup_format!(
474 "accept: blocking tcp accept is not supported when SO_RCVTIMEO is non-zero"
475 )
476 }
477
478 ecx.block_for_accept(self, is_client_sock_non_block, finish)
479 }
480 }
481
482 fn connect<'tcx>(
483 self: FileDescriptionRef<Self>,
484 communicate_allowed: bool,
485 address: SocketAddr,
486 ecx: &mut MiriInterpCx<'tcx>,
487 finish: DynMachineCallback<'tcx, Result<(), IoError>>,
488 ) -> InterpResult<'tcx> {
489 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
490 ecx.ensure_not_failed(&self, "connect")?;
491
492 let mut state = self.state.borrow_mut();
493
494 // We'll only attempt to invoke `connect` on the socket if it's not already
495 // connected or listening because POSIX specifies error codes which should
496 // be returned in those cases.
497 let socket = match &*state {
498 SocketState::Initial(socket) => socket,
499 SocketState::Listening(_) => {
500 let err = if matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android) {
501 // Linux-like targets return EISCONN when attempting to invoke
502 // `connect` on an already listening socket.
503 // See <https://man7.org/linux/man-pages/man2/connect.2.html>
504 LibcError("EISCONN")
505 } else {
506 // POSIX specifies to return EOPNOTSUPP when attempting to invoke
507 // `connect` on an already listening socket.
508 // See <https://man7.org/linux/man-pages/man3/connect.3p.html>
509 LibcError("EOPNOTSUPP")
510 };
511 return finish.call(ecx, Err(err));
512 }
513 // The socket is already connected.
514 // Subsequent connection attempts return EISCONN for TCP sockets.
515 SocketState::Connected(_) => return finish.call(ecx, Err(LibcError("EISCONN"))),
516 // The socket is already in a connecting state.
517 // Subsequent connection attempts return EALREADY for TCP sockets.
518 SocketState::Connecting(_) => return finish.call(ecx, Err(LibcError("EALREADY"))),
519 SocketState::ConnectionFailed(_) | SocketState::None => unreachable!(),
520 };
521
522 let result = socket.connect(&socket2::SockAddr::from(address));
523
524 // Boolean whether the connection attempt "failed" because it could not be
525 // completed immediately without blocking.
526 let is_in_progress = result.as_ref().is_err_and(|e| {
527 // On Windows hosts non-blocking connects fail with EWOULDBLOCK when the connection cannot
528 // be established immediately, while on Unix-like hosts they fail with EINPROGRESS.
529 matches!(e.kind(), io::ErrorKind::WouldBlock | io::ErrorKind::InProgress)
530 });
531
532 if !is_in_progress && let Err(e) = result {
533 // There was a "real" error during connection establishment.
534 return finish.call(ecx, Err(IoError::HostError(e)));
535 }
536
537 // Temporarily use dummy state to take ownership of the underlying socket.
538 let SocketState::Initial(socket) = mem::replace(&mut *state, SocketState::None) else {
539 unreachable!()
540 };
541
542 // FIXME: Rustfmt has a bug where it incorrectly removes the outer braces of {{ .. }} inside
543 // a `cfg_select!` block. See <https://github.com/rust-lang/rustfmt/issues/7045>.
544 #[rustfmt::skip]
545 let stream = cfg_select! {
546 // Turn the `socket2::Socket` into a `mio::TcpStream`.
547 // We follow Tokio's TCP socket implementation:
548 // See <https://github.com/tokio-rs/tokio/blob/2120bee/tokio/src/net/tcp/socket.rs#L841-L869>
549 // SAFETY: mio specifies that it is safe to use `from_raw_fd` and
550 // `from_raw_socket` as long as it's ensured that the socket is non-blocking.
551 // Because we immediately make the socket non-blocking after creation, that
552 // is always guaranteed.
553
554 unix => {{
555 use std::os::fd::{IntoRawFd, FromRawFd};
556 let raw_fd = socket.into_raw_fd();
557 unsafe { TcpStream::from_raw_fd(raw_fd) }
558 }},
559 windows => {{
560 use std::os::windows::io::{IntoRawSocket, FromRawSocket};
561 let raw_socket = socket.into_raw_socket();
562 unsafe { TcpStream::from_raw_socket(raw_socket) }
563 }},
564 _ => unreachable!("unsupported host platform")
565 };
566
567 *state = SocketState::Connecting(stream);
568 drop(state);
569
570 // Connecting sockets are implicitly bound to a local address.
571 self.is_bound.set(true);
572
573 // Clear the "artificial" readiness as we are about to put the blocking
574 // I/O manager in charge of handling readiness.
575 self.io_readiness.replace(Readiness::EMPTY);
576 ecx.update_fd_readiness(self.clone(), ReadinessUpdateFlags::DEFAULT)?;
577
578 // Register the socket to the blocking I/O manager. Can only happen
579 // now that we have created a mio TcpStream.
580 ecx.machine.blocking_io.register(self.clone());
581
582 if self.is_non_block.get() {
583 // We have a non-blocking socket and thus don't want to block until
584 // the connection is established.
585
586 if is_in_progress {
587 finish.call(ecx, Err(LibcError("EINPROGRESS")))
588 } else {
589 finish.call(ecx, Ok(()))
590 }
591 } else {
592 // The socket is in blocking mode and thus the connect call should block
593 // until the connection with the server is established. A potential
594 // EWOULDBLOCK or EINPROGRESS error code is ignored as we emulate a
595 // blocking socket.
596
597 if self.write_timeout.get().is_some() {
598 // Some Unixes like Linux also apply the SO_SNDTIMEO socket option
599 // to `connect` calls:
600 // <https://github.com/torvalds/linux/blob/HEAD/net/ipv4/af_inet.c#L701-L710>
601 // This is currently not supported by Miri.
602 throw_unsup_format!(
603 "connect: blocking connect is not supported when SO_SNDTIMEO is non-zero"
604 )
605 }
606
607 let socket = self;
608 ecx.ensure_connected(
609 socket.clone(),
610 /* deadline */ None,
611 "connect",
612 callback!(
613 @capture<'tcx> {
614 socket: FileDescriptionRef<TcpSocket>,
615 finish: DynMachineCallback<'tcx, Result<(), IoError>>,
616 } |this, result: Result<(), ()>| {
617 if result.is_err() {
618 // An error occurred whilst connecting. We know
619 // that it has been consumed by `ensure_connected`
620 // and is now stored in `socket.error`.
621 let err = socket.error.take().unwrap();
622 finish.call(this, Err(IoError::HostError(err)))
623 } else {
624 finish.call(this, Ok(()))
625 }
626 }
627 ),
628 )
629 }
630 }
631
632 fn send<'tcx>(
633 self: FileDescriptionRef<Self>,
634 communicate_allowed: bool,
635 ptr: Pointer,
636 len: usize,
637 is_non_block: bool,
638 ecx: &mut MiriInterpCx<'tcx>,
639 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
640 ) -> InterpResult<'tcx> {
641 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
642
643 let is_non_block = is_non_block || self.is_non_block.get();
644 let deadline = ecx.action_deadline(is_non_block, self.write_timeout.get());
645
646 let socket = self;
647 ecx.ensure_connected(
648 socket.clone(),
649 deadline.clone(),
650 "send",
651 callback!(
652 @capture<'tcx> {
653 socket: FileDescriptionRef<TcpSocket>,
654 deadline: Option<Deadline>,
655 ptr: Pointer,
656 len: usize,
657 is_non_block: bool,
658 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
659 } |this, result: Result<(), ()>| {
660 if result.is_err() {
661 return finish.call(this, Err(LibcError("ENOTCONN")))
662 }
663
664 if is_non_block {
665 // We have a non-blocking operation or a non-blocking socket and
666 // thus don't want to block until we can send.
667 let result = this.try_non_block_send(&socket, ptr, len)?;
668 finish.call(this, result)
669 } else {
670 // The socket is in blocking mode and thus the send call should block
671 // until we can send some bytes into the socket or the timeout exceeded.
672 this.block_for_send(socket, deadline, ptr, len, finish)
673 }
674 }
675 ),
676 )
677 }
678
679 fn recv<'tcx>(
680 self: FileDescriptionRef<Self>,
681 communicate_allowed: bool,
682 ptr: Pointer,
683 len: usize,
684 is_peek: bool,
685 is_non_block: bool,
686 ecx: &mut MiriInterpCx<'tcx>,
687 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
688 ) -> InterpResult<'tcx> {
689 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
690
691 let is_non_block = is_non_block || self.is_non_block.get();
692 let deadline = ecx.action_deadline(is_non_block, self.read_timeout.get());
693
694 let socket = self;
695 ecx.ensure_connected(
696 socket.clone(),
697 deadline.clone(),
698 "recv",
699 callback!(
700 @capture<'tcx> {
701 socket: FileDescriptionRef<TcpSocket>,
702 deadline: Option<Deadline>,
703 ptr: Pointer,
704 len: usize,
705 is_peek: bool,
706 is_non_block: bool,
707 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
708 } |this, result: Result<(), ()>| {
709 if result.is_err() {
710 return finish.call(this, Err(LibcError("ENOTCONN")))
711 }
712
713 if is_non_block {
714 // We have a non-blocking operation or a non-blocking socket and
715 // thus don't want to block until we can receive.
716 let result = this.try_non_block_recv(&socket, ptr, len, is_peek)?;
717 finish.call(this, result)
718 } else {
719 // The socket is in blocking mode and thus the receive call should block
720 // until we can receive some bytes from the socket or the timeout exceeded.
721 this.block_for_recv(socket, deadline, ptr, len, is_peek, finish)
722 }
723 }
724 ),
725 )
726 }
727
728 fn setsockopt<'tcx>(
729 self: FileDescriptionRef<Self>,
730 level: i32,
731 option: i32,
732 value_ptr: Pointer,
733 value_len: u64,
734 ecx: &mut MiriInterpCx<'tcx>,
735 ) -> InterpResult<'tcx, Result<(), IoError>> {
736 if level == ecx.eval_libc_i32("SOL_SOCKET") {
737 return ecx.set_generic_socket_option(option, value_ptr, value_len, self);
738 } else if level == ecx.eval_libc_i32("IPPROTO_IP") {
739 return ecx.set_ip_socket_option(
740 option,
741 value_ptr,
742 value_len,
743 self.state.borrow().as_socket_ref(),
744 );
745 } else if level == ecx.eval_libc_i32("IPPROTO_TCP") {
746 return ecx.set_tcp_socket_option(
747 option,
748 value_ptr,
749 value_len,
750 self.state.borrow().as_socket_ref(),
751 );
752 }
753
754 throw_unsup_format!(
755 "setsockopt: level {level:#x} is unsupported, only SOL_SOCKET, IPPROTO_IP \
756 and IPPROTO_TCP are allowed"
757 );
758 }
759
760 fn getsockopt<'tcx>(
761 self: FileDescriptionRef<Self>,
762 level: i32,
763 option: i32,
764 ecx: &mut MiriInterpCx<'tcx>,
765 ) -> InterpResult<'tcx, Result<MPlaceTy<'tcx>, IoError>> {
766 if level == ecx.eval_libc_i32("SOL_SOCKET") {
767 ecx.get_generic_socket_option(option, self)
768 } else if level == ecx.eval_libc_i32("IPPROTO_IP") {
769 ecx.get_ip_socket_option(option, self.state.borrow().as_socket_ref())
770 } else if level == ecx.eval_libc_i32("IPPROTO_TCP") {
771 ecx.get_tcp_socket_option(option, self.state.borrow().as_socket_ref())
772 } else {
773 throw_unsup_format!(
774 "getsockopt: level {level:#x} is unsupported, only SOL_SOCKET, IPPROTO_IP \
775 and IPPROTO_TCP are allowed"
776 )
777 }
778 }
779
780 fn getsockname<'tcx>(
781 self: FileDescriptionRef<Self>,
782 communicate_allowed: bool,
783 ecx: &mut MiriInterpCx<'tcx>,
784 ) -> InterpResult<'tcx, Result<SocketAddr, IoError>> {
785 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
786 ecx.ensure_not_failed(&self, "getsockname")?;
787
788 if !self.is_bound.get() {
789 // Since Windows returns EINVAL when invoking `getsockname` on
790 // a socket which hasn't been bound yet, we need to manually
791 // return an unspecified address here.
792
793 let address = if self.family == socket2::Domain::IPV4 {
794 SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, /* port */ 0))
795 } else {
796 SocketAddr::V6(SocketAddrV6::new(
797 Ipv6Addr::UNSPECIFIED,
798 /* port */ 0,
799 /* flowinfo */ 0,
800 /* scope_id */ 0,
801 ))
802 };
803 return interp_ok(Ok(address));
804 }
805
806 let state = self.state.borrow();
807 let socket = state.as_socket_ref();
808
809 if cfg!(windows)
810 && let SocketState::Connecting(_) = &*state
811 {
812 // FIXME: On Windows hosts `getsockname` returns `0.0.0.0:0` whilst the socket is connecting:
813 // <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockname#remarks>
814 // This is problematic because UNIX targets could expect a real local address even
815 // for a connecting non-blocking socket.
816
817 static DEDUP: AtomicBool = AtomicBool::new(false);
818 if !DEDUP.swap(true, std::sync::atomic::Ordering::Relaxed) {
819 ecx.emit_diagnostic(NonHaltingDiagnostic::ConnectingSocketGetsockname);
820 }
821 }
822
823 match socket.local_addr() {
824 Ok(address) => interp_ok(Ok(address.as_socket().unwrap())),
825 Err(e) => interp_ok(Err(IoError::HostError(e))),
826 }
827 }
828
829 fn getpeername<'tcx>(
830 self: FileDescriptionRef<Self>,
831 communicate_allowed: bool,
832 ecx: &mut MiriInterpCx<'tcx>,
833 finish: DynMachineCallback<'tcx, Result<SocketAddr, IoError>>,
834 ) -> InterpResult<'tcx> {
835 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
836
837 let socket = self;
838 // It's only safe to call [`TcpStream::peer_addr`] after the socket is connected since
839 // UNIX targets should return ENOTCONN when the connection is not yet established.
840 ecx.ensure_connected(
841 socket.clone(),
842 // Check whether the socket is connected without blocking.
843 Some(ecx.machine.monotonic_clock.now().into()),
844 "getpeername",
845 callback!(
846 @capture<'tcx> {
847 socket: FileDescriptionRef<TcpSocket>,
848 finish: DynMachineCallback<'tcx, Result<SocketAddr, IoError>>,
849 } |this, result: Result<(), ()>| {
850 if result.is_err() {
851 return finish.call(this, Err(LibcError("ENOTCONN")))
852 };
853
854 let SocketState::Connected(stream) = &*socket.state.borrow() else {
855 unreachable!()
856 };
857
858 let result = stream.peer_addr().map_err(IoError::HostError);
859 finish.call(this, result)
860 }
861 ),
862 )
863 }
864
865 fn shutdown<'tcx>(
866 self: FileDescriptionRef<Self>,
867 communicate_allowed: bool,
868 how: Shutdown,
869 ecx: &mut MiriInterpCx<'tcx>,
870 ) -> InterpResult<'tcx, Result<(), IoError>> {
871 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
872 ecx.ensure_not_failed(&self, "shutdown")?;
873
874 let state = self.state.borrow();
875
876 let (SocketState::Connecting(stream) | SocketState::Connected(stream)) = &*state else {
877 return interp_ok(Err(LibcError("ENOTCONN")));
878 };
879
880 if let Err(e) = stream.shutdown(how) {
881 return interp_ok(Err(IoError::HostError(e)));
882 };
883
884 drop(state);
885
886 // Because we map cross platform mio readiness to our readiness struct and
887 // the different platforms don't treat `shutdown` the same way, we set
888 // the readiness after a `shutdown` manually to achieve a more consistent
889 // readiness. Otherwise we do not generate enough readiness events
890 // on partial shutdowns on Windows hosts.
891 let mut readiness = self.io_readiness.borrow_mut();
892 // Closing the read end of a socket causes an (E)POLLRDHUP event.
893 readiness.read_closed |= matches!(how, Shutdown::Read | Shutdown::Both);
894 // Only shutting down the write end doesn't cause an (E)POLLHUP event
895 // and thus we won't set the `write_closed` readiness for it here.
896 readiness.write_closed |= matches!(how, Shutdown::Both);
897 // The Linux kernel also sets EPOLLIN when the read end of a socket is closed:
898 // <https://github.com/torvalds/linux/blob/HEAD/net/ipv4/tcp.c#L584-L588>
899 readiness.readable |= matches!(how, Shutdown::Read | Shutdown::Both);
900
901 drop(readiness);
902
903 // Update the readiness for the socket.
904 ecx.update_fd_readiness(self, ReadinessUpdateFlags::DEFAULT)?;
905
906 interp_ok(Ok(()))
907 }
908
909 fn set_read_timeout<'tcx>(
910 self: FileDescriptionRef<Self>,
911 timeout: Option<Duration>,
912 ) -> InterpResult<'tcx> {
913 self.read_timeout.set(timeout);
914 interp_ok(())
915 }
916
917 fn get_read_timeout<'tcx>(
918 self: FileDescriptionRef<Self>,
919 ) -> InterpResult<'tcx, Option<Duration>> {
920 interp_ok(self.read_timeout.get())
921 }
922
923 fn set_write_timeout<'tcx>(
924 self: FileDescriptionRef<Self>,
925 timeout: Option<Duration>,
926 ) -> InterpResult<'tcx> {
927 self.write_timeout.set(timeout);
928 interp_ok(())
929 }
930
931 fn get_write_timeout<'tcx>(
932 self: FileDescriptionRef<Self>,
933 ) -> InterpResult<'tcx, Option<Duration>> {
934 interp_ok(self.write_timeout.get())
935 }
936
937 fn take_error<'tcx>(
938 self: FileDescriptionRef<Self>,
939 ecx: &mut MiriInterpCx<'tcx>,
940 ) -> InterpResult<'tcx, Option<io::Error>> {
941 // Reading SO_ERROR should always return the latest async error. Because our stored
942 // `socket.error` could be outdated, we attempt to update it here.
943 ecx.update_last_error(&self);
944
945 // We consume the error. `self.error` is now guaranteed to be `None`.
946 let err = self.error.take();
947
948 // We know there is no longer an async error and thus we need to update the
949 // I/O and fd readiness of the socket.
950 self.io_readiness.borrow_mut().error = false;
951 ecx.update_fd_readiness(self, ReadinessUpdateFlags::DEFAULT)?;
952
953 interp_ok(err)
954 }
955
956 fn with_socket_ref<T>(&self, cb: impl FnOnce(socket2::SockRef<'_>) -> T) -> Result<T, ()>
957 where
958 Self: Sized,
959 {
960 Ok(cb(self.state.borrow().as_socket_ref()))
961 }
962}
963
964impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
965trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
966 /// Get the deadline for an action (e.g. reading or writing).
967 /// When `is_non_block` is [`true`], the returned deadline is "now", i.e.,
968 /// we wake up immediately if the action cannot be completed.
969 /// If `action_timeout` is `Some(duration)`, the returned deadline is in the
970 /// future be the specified `duration`. Otherwise, no deadline ([`None`]) is
971 /// returned, indicating that the action can block indefinitely.
972 fn action_deadline(
973 &self,
974 is_non_block: bool,
975 action_timeout: Option<Duration>,
976 ) -> Option<Deadline> {
977 let this = self.eval_context_ref();
978
979 if is_non_block {
980 // Non-blocking sockets always have a zero timeout.
981 Some(this.machine.monotonic_clock.now().into())
982 } else {
983 action_timeout
984 .map(|duration| this.machine.monotonic_clock.now().add_lossy(duration).into())
985 }
986 }
987
988 /// Block the thread until there's an incoming connection or an error occurred.
989 /// After a successful accept, `finish` is called with a tuple containing the
990 /// file descriptor of the peer socket and it's address.
991 ///
992 /// This recursively calls itself should the operation still block for some reason.
993 ///
994 /// **Note**: This function is only safe to call when having previously ensured
995 /// that the socket is in [`SocketState::Listening`].
996 fn block_for_accept(
997 &mut self,
998 socket: FileDescriptionRef<TcpSocket>,
999 is_client_sock_nonblock: bool,
1000 finish: DynMachineCallback<'tcx, Result<(FdNum, SocketAddr), IoError>>,
1001 ) -> InterpResult<'tcx> {
1002 let this = self.eval_context_mut();
1003 // Since the callback holds a strong reference to the socket, the file description
1004 // won't be closed as long as some thread is blocked on it. While this reflects
1005 // what Linux does, for other Unix systems this might differ from the native behavior.
1006 this.block_thread_for_io(
1007 socket.clone(),
1008 BlockingIoInterest::Read,
1009 /* deadline */ None,
1010 callback!(@capture<'tcx> {
1011 socket: FileDescriptionRef<TcpSocket>,
1012 is_client_sock_nonblock: bool,
1013 finish: DynMachineCallback<'tcx, Result<(FdNum, SocketAddr), IoError>>,
1014 } |this, kind: UnblockKind| {
1015 // Remove the blocking I/O interest for unblocking this thread.
1016 this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
1017
1018 match kind {
1019 UnblockKind::Ready => { /* fall-through to below */ },
1020 // When the read timeout is exceeded EAGAIN/EWOULDBLOCK is returned.
1021 UnblockKind::TimedOut => return finish.call(this, Err(LibcError("EWOULDBLOCK")))
1022 }
1023
1024 match this.try_non_block_accept(&socket, is_client_sock_nonblock)? {
1025 Ok((sockfd, addr)) => finish.call(this, Ok((sockfd, addr))),
1026 Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {
1027 // We need to block the thread again as it would still block.
1028 this.block_for_accept(socket, is_client_sock_nonblock, finish)
1029 }
1030 Err(e) => finish.call(this, Err(e)),
1031 }
1032 }),
1033 )
1034 }
1035
1036 /// Attempt to accept an incoming connection on the listening socket in a
1037 /// non-blocking manner. After a successful accept, a tuple containing the
1038 /// file descriptor of the peer socket and it's address is returned.
1039 ///
1040 /// **Note**: This function is only safe to call when having previously ensured
1041 /// that the socket is in [`SocketState::Listening`].
1042 fn try_non_block_accept(
1043 &mut self,
1044 socket: &FileDescriptionRef<TcpSocket>,
1045 is_client_sock_nonblock: bool,
1046 ) -> InterpResult<'tcx, Result<(FdNum, SocketAddr), IoError>> {
1047 let this = self.eval_context_mut();
1048
1049 let state = socket.state.borrow();
1050 let SocketState::Listening(listener) = &*state else {
1051 panic!(
1052 "try_non_block_accept must only be called when socket is in `SocketState::Listening`"
1053 )
1054 };
1055
1056 let (stream, addr) = match listener.accept() {
1057 Ok(peer) => peer,
1058 Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
1059 // We know that the source is not readable so we need to update its readiness.
1060 socket.io_readiness.borrow_mut().readable = false;
1061 this.update_fd_readiness(socket.clone(), ReadinessUpdateFlags::DEFAULT)?;
1062
1063 return interp_ok(Err(IoError::HostError(e)));
1064 }
1065 Err(e) => return interp_ok(Err(IoError::HostError(e))),
1066 };
1067
1068 let family = match addr {
1069 SocketAddr::V4(_) => socket2::Domain::IPV4,
1070 SocketAddr::V6(_) => socket2::Domain::IPV6,
1071 };
1072
1073 let fd = this.machine.fds.new_ref(TcpSocket {
1074 family,
1075 state: RefCell::new(SocketState::Connected(stream)),
1076 is_non_block: Cell::new(is_client_sock_nonblock),
1077 // Connected sockets are implicitly bound to a local address.
1078 is_bound: Cell::new(true),
1079 io_readiness: RefCell::new(Readiness::EMPTY),
1080 error: RefCell::new(None),
1081 read_timeout: Cell::new(None),
1082 write_timeout: Cell::new(None),
1083 watched: ReadinessWatched::default(),
1084 });
1085 // Register the socket to the blocking I/O manager because
1086 // there is an associated host socket.
1087 this.machine.blocking_io.register(fd.clone());
1088 let sockfd = this.machine.fds.insert(fd);
1089 interp_ok(Ok((sockfd, addr)))
1090 }
1091
1092 /// Block the thread until we can send bytes into the connected socket
1093 /// or an error occurred.
1094 ///
1095 /// This recursively calls itself should the operation still block for some reason.
1096 ///
1097 /// **Note**: This function is only safe to call when having previously ensured
1098 /// that the socket is in [`SocketState::Connected`].
1099 fn block_for_send(
1100 &mut self,
1101 socket: FileDescriptionRef<TcpSocket>,
1102 deadline: Option<Deadline>,
1103 buffer_ptr: Pointer,
1104 length: usize,
1105 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1106 ) -> InterpResult<'tcx> {
1107 let this = self.eval_context_mut();
1108 // Since the callback holds a strong reference to the socket, the file description
1109 // won't be closed as long as some thread is blocked on it. While this reflects
1110 // what Linux does, for other Unix systems this might differ from the native behavior.
1111 this.block_thread_for_io(
1112 socket.clone(),
1113 BlockingIoInterest::Write,
1114 deadline.clone(),
1115 callback!(@capture<'tcx> {
1116 socket: FileDescriptionRef<TcpSocket>,
1117 deadline: Option<Deadline>,
1118 buffer_ptr: Pointer,
1119 length: usize,
1120 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1121 } |this, kind: UnblockKind| {
1122 // Remove the blocking I/O interest for unblocking this thread.
1123 this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
1124
1125 match kind {
1126 UnblockKind::Ready => { /* fall-through to below */ },
1127 // When the write timeout is exceeded EAGAIN/EWOULDBLOCK is returned.
1128 UnblockKind::TimedOut => return finish.call(this, Err(LibcError("EWOULDBLOCK")))
1129 }
1130
1131 match this.try_non_block_send(&socket, buffer_ptr, length)? {
1132 Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {
1133 // We need to block the thread again as it would still block.
1134 this.block_for_send(socket, deadline, buffer_ptr, length, finish)
1135 },
1136 result => finish.call(this, result)
1137 }
1138 }),
1139 )
1140 }
1141
1142 /// Attempt to send bytes into the connected socket in a non-blocking manner.
1143 ///
1144 /// **Note**: This function is only safe to call when having previously ensured
1145 /// that the socket is in [`SocketState::Connected`].
1146 fn try_non_block_send(
1147 &mut self,
1148 socket: &FileDescriptionRef<TcpSocket>,
1149 buffer_ptr: Pointer,
1150 length: usize,
1151 ) -> InterpResult<'tcx, Result<usize, IoError>> {
1152 let this = self.eval_context_mut();
1153
1154 let mut state = socket.state.borrow_mut();
1155 let SocketState::Connected(stream) = &mut *state else {
1156 panic!("try_non_block_send must only be called when the socket is connected")
1157 };
1158
1159 // This is a *non-blocking* write.
1160 let result = this.write_to_host(stream, length, buffer_ptr)?;
1161
1162 drop(state);
1163
1164 // A write should never succeed when the `write_closed` readiness is set for this socket.
1165 if result.is_ok() {
1166 assert!(!socket.io_readiness.borrow().write_closed, "successful write after close");
1167 }
1168
1169 match result {
1170 Err(IoError::HostError(e))
1171 if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::WouldBlock) =>
1172 {
1173 // We know that the source is not writable so we need to update its readiness.
1174 socket.io_readiness.borrow_mut().writable = false;
1175 this.update_fd_readiness(socket.clone(), ReadinessUpdateFlags::DEFAULT)?;
1176
1177 // On Windows hosts, `send` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK
1178 // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK.
1179 interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into())))
1180 }
1181 Ok(bytes_written) if bytes_written < length => {
1182 // We had a short write. On Unix hosts using the `epoll` and `kqueue` backends, a
1183 // short write means that the write buffer is full. We update the readiness
1184 // accordingly, which means that next time we see "writable" we will report an
1185 // edge. Some applications (e.g. tokio) rely on this behavior; see
1186 // <https://github.com/tokio-rs/tokio/blob/HEAD/tokio/src/io/poll_evented.rs#L244-L264>.
1187 if cfg!(any(
1188 // epoll
1189 target_os = "android",
1190 target_os = "illumos",
1191 target_os = "linux",
1192 target_os = "redox",
1193 // kqueue
1194 target_os = "dragonfly",
1195 target_os = "freebsd",
1196 target_os = "ios",
1197 target_os = "macos",
1198 target_os = "netbsd",
1199 target_os = "openbsd",
1200 target_os = "tvos",
1201 target_os = "visionos",
1202 target_os = "watchos",
1203 )) {
1204 socket.io_readiness.borrow_mut().writable = false;
1205 this.update_fd_readiness(socket.clone(), ReadinessUpdateFlags::DEFAULT)?;
1206 } else {
1207 // On hosts which don't use the `epoll` or `kqueue` backends, a short write
1208 // doesn't imply a full write buffer. However, the target we are emulating might
1209 // guarantee this behavior. To prevent applications from being stuck on such
1210 // targets waiting on a new readiness event, we emit a new edge which still
1211 // contains a writable readiness. This should trick the applications into trying
1212 // another write which would then return EWOULDBLOCK should it really be full.
1213 // This results in an unrealistic execution but we don't have another way of
1214 // finding out whether the write buffer is full. The "default case" of linux
1215 // host and linux target isn't affected by this.
1216 this.update_fd_readiness(socket.clone(), ReadinessUpdateFlags::FORCE_EDGE)?;
1217 }
1218 interp_ok(result)
1219 }
1220 result => interp_ok(result),
1221 }
1222 }
1223
1224 /// Block the thread until we can receive bytes from the connected socket
1225 /// or an error occurred.
1226 ///
1227 /// This recursively calls itself should the operation still block for some reason.
1228 ///
1229 /// **Note**: This function is only safe to call when having previously ensured
1230 /// that the socket is in [`SocketState::Connected`].
1231 fn block_for_recv(
1232 &mut self,
1233 socket: FileDescriptionRef<TcpSocket>,
1234 deadline: Option<Deadline>,
1235 buffer_ptr: Pointer,
1236 length: usize,
1237 should_peek: bool,
1238 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1239 ) -> InterpResult<'tcx> {
1240 let this = self.eval_context_mut();
1241 // Since the callback holds a strong reference to the socket, the file description
1242 // won't be closed as long as some thread is blocked on it. While this reflects
1243 // what Linux does, for other Unix systems this might differ from the native behavior.
1244 this.block_thread_for_io(
1245 socket.clone(),
1246 BlockingIoInterest::Read,
1247 deadline.clone(),
1248 callback!(@capture<'tcx> {
1249 socket: FileDescriptionRef<TcpSocket>,
1250 deadline: Option<Deadline>,
1251 buffer_ptr: Pointer,
1252 length: usize,
1253 should_peek: bool,
1254 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1255 } |this, kind: UnblockKind| {
1256 // Remove the blocking I/O interest for unblocking this thread.
1257 this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
1258
1259 match kind {
1260 UnblockKind::Ready => { /* fall-through to below */ },
1261 // When the read timeout is exceeded EAGAIN/EWOULDBLOCK is returned.
1262 UnblockKind::TimedOut => return finish.call(this, Err(LibcError("EWOULDBLOCK")))
1263 }
1264
1265 match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? {
1266 Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {
1267 // We need to block the thread again as it would still block.
1268 this.block_for_recv(socket, deadline, buffer_ptr, length, should_peek, finish)
1269 },
1270 result => finish.call(this, result)
1271 }
1272 }),
1273 )
1274 }
1275
1276 /// Attempt to receive bytes from the connected socket in a non-blocking manner.
1277 ///
1278 /// **Note**: This function is only safe to call when having previously ensured
1279 /// that the socket is in [`SocketState::Connected`].
1280 fn try_non_block_recv(
1281 &mut self,
1282 socket: &FileDescriptionRef<TcpSocket>,
1283 buffer_ptr: Pointer,
1284 length: usize,
1285 should_peek: bool,
1286 ) -> InterpResult<'tcx, Result<usize, IoError>> {
1287 let this = self.eval_context_mut();
1288
1289 let mut state = socket.state.borrow_mut();
1290 let SocketState::Connected(stream) = &mut *state else {
1291 panic!("try_non_block_recv must only be called when the socket is connected")
1292 };
1293
1294 // This is a *non-blocking* read/peek.
1295 let result = this.read_from_host(
1296 |buf| {
1297 if should_peek { stream.peek(buf) } else { stream.read(buf) }
1298 },
1299 length,
1300 buffer_ptr,
1301 )?;
1302
1303 drop(state);
1304
1305 match result {
1306 Err(IoError::HostError(e))
1307 if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::WouldBlock) =>
1308 {
1309 // We know that the source is not readable so we need to update its readiness.
1310 socket.io_readiness.borrow_mut().readable = false;
1311 this.update_fd_readiness(socket.clone(), ReadinessUpdateFlags::DEFAULT)?;
1312
1313 // On Windows hosts, `recv` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK
1314 // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK.
1315 interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into())))
1316 }
1317 Ok(bytes_read)
1318 if !should_peek
1319 && bytes_read < length
1320 && bytes_read > 0
1321 && !socket.io_readiness.borrow().read_closed =>
1322 {
1323 // We had a short read (and were not peeking). (Note that reading 0 bytes is guaranteed
1324 // to indicate EOF, and can never happen spuriously, so we have to exclude that case.
1325 // We also don't want to clear the readable readiness for sockets whose read end has
1326 // already been closed as those never block a read, i.e., they are always read-ready.)
1327 // On Unix hosts using the `epoll` and `kqueue` backends, a short read means that the
1328 // read buffer is empty. We update the readiness accordingly, which means that next time
1329 // we see "readable" we will report an edge. Some applications (e.g. tokio) rely on
1330 // this behavior; see
1331 // <https://github.com/tokio-rs/tokio/blob/HEAD/tokio/src/io/poll_evented.rs#L190-L210>
1332 if cfg!(any(
1333 // epoll
1334 target_os = "android",
1335 target_os = "illumos",
1336 target_os = "linux",
1337 target_os = "redox",
1338 // kqueue
1339 target_os = "dragonfly",
1340 target_os = "freebsd",
1341 target_os = "ios",
1342 target_os = "macos",
1343 target_os = "netbsd",
1344 target_os = "openbsd",
1345 target_os = "tvos",
1346 target_os = "visionos",
1347 target_os = "watchos",
1348 )) {
1349 socket.io_readiness.borrow_mut().readable = false;
1350 this.update_fd_readiness(socket.clone(), ReadinessUpdateFlags::DEFAULT)?;
1351 } else {
1352 // On hosts which don't use the `epoll` or `kqueue` backends, a short read
1353 // doesn't imply an empty read buffer. However, the target we are emulating
1354 // might guarantee this behavior. To prevent applications from being stuck on
1355 // such targets waiting on a new readiness event, we emit a new edge which still
1356 // contains a readable readiness. This should trick the applications into trying
1357 // another read which would then return EWOULDBLOCK should it really be empty.
1358 // This results in an unrealistic execution but we don't have another way of
1359 // finding out whether the read buffer is empty. The "default case" of linux
1360 // host and linux target isn't affected by this.
1361 this.update_fd_readiness(socket.clone(), ReadinessUpdateFlags::FORCE_EDGE)?;
1362 }
1363 interp_ok(result)
1364 }
1365 result => interp_ok(result),
1366 }
1367 }
1368
1369 // Execute the provided callback function when the socket is either in
1370 // [`SocketState::Connected`] or an error occurred.
1371 /// If the socket is currently neither in the [`SocketState::Connecting`] nor
1372 /// the [`SocketState::Connecting`] state, [`Err`] is returned.
1373 /// When the callback function is called with [`Ok`], then we're guaranteed
1374 /// that the socket is in the [`SocketState::Connected`] state.
1375 ///
1376 /// This method internally calls `ensure_not_failed` and thus an unsupported
1377 /// error is thrown should `socket` be in [`SocketState::ConnectionFailed`].
1378 ///
1379 /// This function can optionally also block until either an error occurred or
1380 /// the socket reached the [`SocketState::Connected`] state.
1381 fn ensure_connected(
1382 &mut self,
1383 socket: FileDescriptionRef<TcpSocket>,
1384 deadline: Option<Deadline>,
1385 foreign_name: &'static str,
1386 action: DynMachineCallback<'tcx, Result<(), ()>>,
1387 ) -> InterpResult<'tcx> {
1388 let this = self.eval_context_mut();
1389
1390 let state = socket.state.borrow();
1391 match &*state {
1392 SocketState::Connecting(_) => { /* fall-through to below */ }
1393 SocketState::Connected(_) => {
1394 drop(state);
1395 return action.call(this, Ok(()));
1396 }
1397 _ => {
1398 drop(state);
1399 this.ensure_not_failed(&socket, foreign_name)?;
1400 return action.call(this, Err(()));
1401 }
1402 };
1403
1404 drop(state);
1405
1406 // We're currently connecting. Since the underlying mio socket is non-blocking,
1407 // the only way to determine whether we are done connecting is by polling.
1408
1409 this.block_thread_for_io(
1410 socket.clone(),
1411 BlockingIoInterest::Write,
1412 deadline,
1413 callback!(
1414 @capture<'tcx> {
1415 socket: FileDescriptionRef<TcpSocket>,
1416 foreign_name: &'static str,
1417 action: DynMachineCallback<'tcx, Result<(), ()>>,
1418 } |this, kind: UnblockKind| {
1419 // Remove the blocking I/O interest for unblocking this thread.
1420 this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
1421
1422 if UnblockKind::TimedOut == kind {
1423 // This then means that the socket is not yet connected.
1424 return action.call(this, Err(()))
1425 }
1426
1427 // The thread woke up because it's ready, indicating a writeable or error event.
1428
1429 let state = socket.state.borrow();
1430 match &*state {
1431 SocketState::Connecting(_) => { /* fall-through to below */ },
1432 SocketState::Connected(_) => {
1433 drop(state);
1434 // This can happen because we blocked the thread:
1435 // maybe another thread "upgraded" the connection in the meantime.
1436 return action.call(this, Ok(()))
1437 },
1438 _ => {
1439 drop(state);
1440 // We ensured that we only block when we're currently connecting.
1441 // Since this thread just got rescheduled, it could be that another
1442 // thread realized that the connection failed and we're thus in
1443 // an "invalid state".
1444 this.ensure_not_failed(&socket, foreign_name)?;
1445 return action.call(this, Err(()))
1446 }
1447 };
1448
1449 drop(state);
1450
1451 // Set `socket.error` if `socket` currently has an error.
1452 this.update_last_error(&socket);
1453
1454 if socket.error.borrow().is_some() {
1455 // There was an error during connecting.
1456 // It's the program's responsibility to read SO_ERROR itself.
1457 return action.call(this, Err(()))
1458 }
1459
1460 // There was no error during connecting. Mio advises also reading the peer address
1461 // to ensure that socket is actually connected and that it wasn't a spurious wake-up:
1462 // <https://docs.rs/mio/latest/mio/net/struct.TcpStream.html#notes>
1463 //
1464 // Attempting to read the peer address would introduce an edge-case where the
1465 // write end of the socket could already be shutdown before it received a
1466 // writable event. When we then call [`TcpStream::peer_addr`] we receive an
1467 // error. This would need extra state for storing whether the write end was
1468 // manually closed using `shutdown`.
1469 // Also, tokio doesn't read the peer address and everything seems to be fine,
1470 // so we don't do that either:
1471 // <https://github.com/tokio-rs/mio/issues/1942#issuecomment-4162607761>
1472 // In other words, we are assuming that there will be no spurious
1473 // wakeups while establishing the connection.
1474
1475 // The connection is established. Update the state.
1476 let mut state = socket.state.borrow_mut();
1477
1478 // Temporarily use dummy state to take ownership of the stream.
1479 let SocketState::Connecting(stream) = mem::replace(&mut *state, SocketState::None) else {
1480 // At the start of the function we ensured that we're currently connecting.
1481 unreachable!()
1482 };
1483 *state = SocketState::Connected(stream);
1484 drop(state);
1485
1486 action.call(this, Ok(()))
1487 }
1488 ),
1489 )
1490 }
1491
1492 /// Ensure that `socket` is not in the [`SocketState::ConnectionFailed`] state.
1493 /// If `socket` is currently in [`SocketState::ConnectionFailed`], an unsupported
1494 /// error is thrown.
1495 fn ensure_not_failed(
1496 &self,
1497 socket: &FileDescriptionRef<TcpSocket>,
1498 foreign_name: &'static str,
1499 ) -> InterpResult<'tcx> {
1500 if let SocketState::ConnectionFailed(_) = &*socket.state.borrow() {
1501 throw_unsup_format!(
1502 "{foreign_name}: sockets are in an unspecified state after a failed `connect`; \
1503 any operation on such a socket is thus unsupported"
1504 );
1505 } else {
1506 interp_ok(())
1507 }
1508 }
1509
1510 /// Check whether the underlying host socket of `socket` contains an error.
1511 /// If there is an error, we store it in `socket.error`.
1512 ///
1513 /// Should `socket` be in the [`SocketState::Connecting`] state whilst there is
1514 /// an error on the host socket, we transition into the [`SocketState::ConnectionFailed`]
1515 /// state because we know that `socket` can no longer successfully establish a
1516 /// connection.
1517 fn update_last_error(&self, socket: &FileDescriptionRef<TcpSocket>) {
1518 let mut state = socket.state.borrow_mut();
1519
1520 let new_error =
1521 state.as_socket_ref().take_error().expect("Reading SO_ERROR should not fail");
1522 let Some(new_error) = new_error else { return };
1523
1524 // Store the error such that we can return it when
1525 // `getsockopt(SOL_SOCKET, SO_ERROR, ...)` is called on the socket.
1526 socket.error.replace(Some(new_error));
1527
1528 if matches!(&*state, SocketState::Connecting(_)) {
1529 // After reading an error on a connecting socket, we know that
1530 // the connection won't be established anymore. By the POSIX
1531 // specification, the socket is now in an unspecified state.
1532 // We thus change the socket state to `ConnectionFailed`.
1533
1534 // Temporarily use dummy state to take ownership of the stream.
1535 let SocketState::Connecting(stream) = mem::replace(&mut *state, SocketState::None)
1536 else {
1537 unreachable!()
1538 };
1539 *state = SocketState::ConnectionFailed(stream);
1540 }
1541 }
1542}
1543
1544impl SourceFileDescription for TcpSocket {
1545 fn with_source(&self, f: &mut dyn FnMut(&mut dyn Source) -> io::Result<()>) -> io::Result<()> {
1546 let mut state = self.state.borrow_mut();
1547 match &mut *state {
1548 SocketState::Listening(listener) => f(listener),
1549 SocketState::Connecting(stream)
1550 | SocketState::Connected(stream)
1551 | SocketState::ConnectionFailed(stream) => f(stream),
1552 // We never try adding a socket which is not backed by a real socket to the poll registry.
1553 _ => unreachable!(),
1554 }
1555 }
1556
1557 fn get_readiness_mut(&self) -> RefMut<'_, Readiness> {
1558 self.io_readiness.borrow_mut()
1559 }
1560}