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