Skip to main content

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}