Skip to main content

miri/shims/unix/
tcp_socket.rs

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