Skip to main content

miri/shims/unix/
tcp_socket.rs

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