Skip to main content

miri/shims/unix/
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_abi::Size;
11use rustc_const_eval::interpret::{InterpResult, interp_ok};
12use rustc_middle::throw_unsup_format;
13use rustc_target::spec::Os;
14
15use crate::shims::files::{EvalContextExt as _, FdId, FileDescription, FileDescriptionRef};
16use crate::shims::unix::UnixFileDescription;
17use crate::shims::unix::linux_like::epoll::{EpollReadiness, EvalContextExt as _};
18use crate::shims::unix::socket_address::EvalContextExt as _;
19use crate::*;
20
21#[derive(Debug, PartialEq)]
22enum SocketFamily {
23    // IPv4 internet protocols
24    IPv4,
25    // IPv6 internet protocols
26    IPv6,
27}
28
29#[derive(Debug)]
30enum SocketState {
31    /// No syscall after `socket` has been made.
32    Initial,
33    /// The `bind` syscall has been called on the socket.
34    /// This is only reachable from the [`SocketState::Initial`] state.
35    Bound(SocketAddr),
36    /// The `listen` syscall has been called on the socket.
37    /// This is only reachable from the [`SocketState::Bound`] state.
38    Listening(TcpListener),
39    /// The `connect` syscall has been called and we weren't yet able
40    /// to ensure the connection is established. This is only reachable
41    /// from the [`SocketState::Initial`] state.
42    Connecting(TcpStream),
43    /// The `connect` syscall has been called on the socket and
44    /// we ensured that the connection is established, or
45    /// the socket was created by the `accept` syscall.
46    /// For a socket created using the `connect` syscall, this is
47    /// only reachable from the [`SocketState::Connecting`] state.
48    Connected(TcpStream),
49    /// The SO_ERROR socket option has been set after calling
50    /// the `connect` syscall, indicating that the connection
51    /// attempt failed. By the POSIX specification, a socket is
52    /// is an unspecified state after a failed connection attempt
53    /// and thus nothing (except destroying the socket) should be
54    /// supported when a socket is in this state.
55    ConnectionFailed(TcpStream),
56}
57
58#[derive(Debug)]
59struct Socket {
60    /// Family of the socket, used to ensure socket only binds/connects to address of
61    /// same family.
62    family: SocketFamily,
63    /// Current state of the inner socket.
64    state: RefCell<SocketState>,
65    /// Whether this fd is non-blocking or not.
66    is_non_block: Cell<bool>,
67    /// The current blocking I/O readiness of the file description.
68    io_readiness: RefCell<BlockingIoSourceReadiness>,
69    /// [`Some`] when the socket had an async error which has not yet been fetched via `SO_ERROR`.
70    error: RefCell<Option<io::Error>>,
71    /// Read timeout of the socket. [`None`] means that reads can block indefinitely.
72    /// The timeout is applied to the monotonic clock (the Unix specification doesn't
73    /// specify which clock to use, but the monotonic clock is more common for
74    /// relative timeouts).
75    /// This is ignored when the socket is non-blocking.
76    read_timeout: Cell<Option<Duration>>,
77    /// Write timeout of the socket. [`None`] means that writes 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
80    /// for relative timeouts).
81    /// This is ignored when the socket is non-blocking.
82    write_timeout: Cell<Option<Duration>>,
83}
84
85impl FileDescription for Socket {
86    fn name(&self) -> &'static str {
87        "socket"
88    }
89
90    fn destroy<'tcx>(
91        self,
92        self_id: FdId,
93        communicate_allowed: bool,
94        ecx: &mut MiriInterpCx<'tcx>,
95    ) -> InterpResult<'tcx, io::Result<()>> {
96        assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!");
97
98        if matches!(
99            &*self.state.borrow(),
100            SocketState::Listening(_)
101                | SocketState::Connecting(_)
102                | SocketState::Connected(_)
103                | SocketState::ConnectionFailed(_)
104        ) {
105            // There exists an associated host socket so we need to deregister it
106            // from the blocking I/O manager.
107            ecx.machine.blocking_io.deregister(self_id, self)
108        };
109
110        interp_ok(Ok(()))
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        assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!");
122
123        let socket = self;
124        let deadline = ecx.action_deadline(socket.is_non_block.get(), socket.read_timeout.get());
125
126        ecx.ensure_connected(
127            socket.clone(),
128            deadline.clone(),
129            "read",
130            callback!(
131                @capture<'tcx> {
132                    socket: FileDescriptionRef<Socket>,
133                    deadline: Option<Deadline>,
134                    ptr: Pointer,
135                    len: usize,
136                    finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
137                } |this, result: Result<(), ()>| {
138                    if result.is_err() {
139                        return finish.call(this, Err(LibcError("ENOTCONN")))
140                    }
141
142                    // Since `read` is the same as `recv` with no flags, we just treat
143                    // the `read` as a `recv` here.
144
145                    if socket.is_non_block.get() {
146                        // We have a non-blocking socket and thus don't want to block until
147                        // we can read.
148                        let result = this.try_non_block_recv(&socket, ptr, len, /* should_peek */ false)?;
149                        finish.call(this, result)
150                    } else {
151                        // The socket is in blocking mode and thus the read call should block
152                        // until we can read some bytes from the socket or the timeout exceeded.
153                        this.block_for_recv(socket, deadline, ptr, len, /* should_peek */ false, finish)
154                    }
155                }
156            ),
157        )
158    }
159
160    fn write<'tcx>(
161        self: FileDescriptionRef<Self>,
162        communicate_allowed: bool,
163        ptr: Pointer,
164        len: usize,
165        ecx: &mut MiriInterpCx<'tcx>,
166        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
167    ) -> InterpResult<'tcx> {
168        assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!");
169
170        let socket = self;
171        let deadline = ecx.action_deadline(socket.is_non_block.get(), socket.write_timeout.get());
172
173        ecx.ensure_connected(
174            socket.clone(),
175            deadline.clone(),
176            "write",
177            callback!(
178                @capture<'tcx> {
179                    socket: FileDescriptionRef<Socket>,
180                    deadline: Option<Deadline>,
181                    ptr: Pointer,
182                    len: usize,
183                    finish: DynMachineCallback<'tcx, Result<usize, IoError>>
184                } |this, result: Result<(), ()>| {
185                    if result.is_err() {
186                        return finish.call(this, Err(LibcError("ENOTCONN")))
187                    }
188
189                    // Since `write` is the same as `send` with no flags, we just treat
190                    // the `write` as a `send` here.
191
192                    if socket.is_non_block.get() {
193                        // We have a non-blocking socket and thus don't want to block until
194                        // we can write.
195                        let result = this.try_non_block_send(&socket, ptr, len)?;
196                        return finish.call(this, result)
197                    } else {
198                        // The socket is in blocking mode and thus the write call should block
199                        // until we can write some bytes into the socket or the timeout exceeded.
200                        this.block_for_send(socket, deadline, ptr, len, finish)
201                    }
202                }
203            ),
204        )
205    }
206
207    fn short_fd_operations(&self) -> bool {
208        // Linux guarantees that when a read/write on a streaming socket comes back short,
209        // the kernel buffer is empty/full:
210        // See <https://man7.org/linux/man-pages/man7/epoll.7.html> in Q&A section.
211        // So we can't do short reads/writes here.
212        false
213    }
214
215    fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
216        self
217    }
218
219    fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
220        let mut flags = ecx.eval_libc_i32("O_RDWR");
221
222        if self.is_non_block.get() {
223            flags |= ecx.eval_libc_i32("O_NONBLOCK");
224        }
225
226        interp_ok(Scalar::from_i32(flags))
227    }
228
229    fn set_flags<'tcx>(
230        &self,
231        mut flag: i32,
232        ecx: &mut MiriInterpCx<'tcx>,
233    ) -> InterpResult<'tcx, Scalar> {
234        let o_nonblock = ecx.eval_libc_i32("O_NONBLOCK");
235
236        // O_NONBLOCK flag can be set / unset by user.
237        if flag & o_nonblock == o_nonblock {
238            self.is_non_block.set(true);
239            flag &= !o_nonblock;
240        } else {
241            self.is_non_block.set(false);
242        }
243
244        // Throw error if there is any unsupported flag.
245        if flag != 0 {
246            throw_unsup_format!("fcntl: only O_NONBLOCK is supported for sockets")
247        }
248
249        interp_ok(Scalar::from_i32(0))
250    }
251}
252
253impl UnixFileDescription for Socket {
254    fn ioctl<'tcx>(
255        &self,
256        op: Scalar,
257        arg: Option<&OpTy<'tcx>>,
258        ecx: &mut MiriInterpCx<'tcx>,
259    ) -> InterpResult<'tcx, i32> {
260        assert!(ecx.machine.communicate(), "cannot have `Socket` with isolation enabled!");
261
262        let fionbio = ecx.eval_libc("FIONBIO");
263
264        if op == fionbio {
265            // On these OSes, Rust uses the ioctl, so we trust that it is reasonable and controls
266            // the same internal flag as fcntl.
267            if !matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android | Os::MacOs | Os::FreeBsd)
268            {
269                // FIONBIO cannot be used to change the blocking mode of a socket on solarish targets:
270                // <https://github.com/rust-lang/rust/commit/dda5c97675b4f5b1f6fdab64606c8a1f21021b0a>
271                // Since there might be more targets which do weird things with this option, we use
272                // an allowlist instead of just denying solarish targets.
273                throw_unsup_format!(
274                    "ioctl: setting FIONBIO on sockets is unsupported on target {}",
275                    ecx.tcx.sess.target.os
276                );
277            }
278
279            let Some(value_ptr) = arg else {
280                throw_ub_format!("ioctl: setting FIONBIO on sockets requires a third argument");
281            };
282            let value = ecx.deref_pointer_as(value_ptr, ecx.machine.layouts.i32)?;
283            let non_block = ecx.read_scalar(&value)?.to_i32()? != 0;
284            self.is_non_block.set(non_block);
285            return interp_ok(0);
286        }
287
288        throw_unsup_format!("ioctl: unsupported operation {op:#x} on socket");
289    }
290
291    fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadiness> {
292        interp_ok(EpollReadiness::from(&*self.io_readiness.borrow()))
293    }
294}
295
296impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
297pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
298    /// For more information on the arguments see the socket manpage:
299    /// <https://linux.die.net/man/2/socket>
300    fn socket(
301        &mut self,
302        domain: &OpTy<'tcx>,
303        type_: &OpTy<'tcx>,
304        protocol: &OpTy<'tcx>,
305    ) -> InterpResult<'tcx, Scalar> {
306        let this = self.eval_context_mut();
307
308        let domain = this.read_scalar(domain)?.to_i32()?;
309        let mut flags = this.read_scalar(type_)?.to_i32()?;
310        let protocol = this.read_scalar(protocol)?.to_i32()?;
311
312        // Reject if isolation is enabled
313        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
314            this.reject_in_isolation("`socket`", reject_with)?;
315            return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));
316        }
317
318        let mut is_sock_nonblock = false;
319
320        // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so
321        // if there is anything left at the end, that's an unsupported flag.
322        if matches!(
323            this.tcx.sess.target.os,
324            Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos
325        ) {
326            // SOCK_NONBLOCK and SOCK_CLOEXEC only exist on Linux, Android, FreeBSD,
327            // Solaris, and Illumos targets.
328            let sock_nonblock = this.eval_libc_i32("SOCK_NONBLOCK");
329            let sock_cloexec = this.eval_libc_i32("SOCK_CLOEXEC");
330            if flags & sock_nonblock == sock_nonblock {
331                is_sock_nonblock = true;
332                flags &= !sock_nonblock;
333            }
334            if flags & sock_cloexec == sock_cloexec {
335                // We don't support `exec` so we can ignore this.
336                flags &= !sock_cloexec;
337            }
338        }
339
340        let family = if domain == this.eval_libc_i32("AF_INET") {
341            SocketFamily::IPv4
342        } else if domain == this.eval_libc_i32("AF_INET6") {
343            SocketFamily::IPv6
344        } else {
345            throw_unsup_format!(
346                "socket: domain {:#x} is unsupported, only AF_INET and \
347                AF_INET6 are allowed.",
348                domain
349            );
350        };
351
352        if flags != this.eval_libc_i32("SOCK_STREAM") {
353            throw_unsup_format!(
354                "socket: type {:#x} is unsupported, only SOCK_STREAM, \
355                SOCK_CLOEXEC and SOCK_NONBLOCK are allowed",
356                flags
357            );
358        }
359        if protocol != 0 && protocol != this.eval_libc_i32("IPPROTO_TCP") {
360            throw_unsup_format!(
361                "socket: socket protocol {protocol} is unsupported, \
362                only IPPROTO_TCP and 0 are allowed"
363            );
364        }
365
366        let fds = &mut this.machine.fds;
367        let fd = fds.new_ref(Socket {
368            family,
369            state: RefCell::new(SocketState::Initial),
370            is_non_block: Cell::new(is_sock_nonblock),
371            io_readiness: RefCell::new(BlockingIoSourceReadiness::empty()),
372            error: RefCell::new(None),
373            read_timeout: Cell::new(None),
374            write_timeout: Cell::new(None),
375        });
376
377        interp_ok(Scalar::from_i32(fds.insert(fd)))
378    }
379
380    fn bind(
381        &mut self,
382        socket: &OpTy<'tcx>,
383        address: &OpTy<'tcx>,
384        address_len: &OpTy<'tcx>,
385    ) -> InterpResult<'tcx, Scalar> {
386        let this = self.eval_context_mut();
387
388        let socket = this.read_scalar(socket)?.to_i32()?;
389        let address = match this.read_socket_address(address, address_len, "bind")? {
390            Ok(addr) => addr,
391            Err(e) => return this.set_errno_and_return_neg1_i32(e),
392        };
393
394        // Get the file handle
395        let Some(fd) = this.machine.fds.get(socket) else {
396            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
397        };
398
399        let Some(socket) = fd.downcast::<Socket>() else {
400            // Man page specifies to return ENOTSOCK if `fd` is not a socket.
401            return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));
402        };
403
404        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
405        this.ensure_not_failed(&socket, "bind")?;
406
407        let mut state = socket.state.borrow_mut();
408
409        match *state {
410            SocketState::Initial => {
411                let address_family = match &address {
412                    SocketAddr::V4(_) => SocketFamily::IPv4,
413                    SocketAddr::V6(_) => SocketFamily::IPv6,
414                };
415
416                if socket.family != address_family {
417                    // Attempted to bind an address from a family that doesn't match
418                    // the family of the socket.
419                    let err = if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android) {
420                        // Linux man page states that `EINVAL` is used when there is an address family mismatch.
421                        // See <https://man7.org/linux/man-pages/man2/bind.2.html>
422                        LibcError("EINVAL")
423                    } else {
424                        // POSIX man page states that `EAFNOSUPPORT` should be used when there is an address
425                        // family mismatch.
426                        // See <https://man7.org/linux/man-pages/man3/bind.3p.html>
427                        LibcError("EAFNOSUPPORT")
428                    };
429                    return this.set_errno_and_return_neg1_i32(err);
430                }
431
432                *state = SocketState::Bound(address);
433            }
434            SocketState::Connecting(_) | SocketState::Connected(_) =>
435                throw_unsup_format!(
436                    "bind: socket is already connected and binding a
437                    connected socket is unsupported"
438                ),
439            SocketState::Bound(_) | SocketState::Listening(_) =>
440                throw_unsup_format!(
441                    "bind: socket is already bound and binding a socket \
442                    multiple times is unsupported"
443                ),
444            SocketState::ConnectionFailed(_) => unreachable!(),
445        }
446
447        interp_ok(Scalar::from_i32(0))
448    }
449
450    fn listen(&mut self, socket: &OpTy<'tcx>, backlog: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
451        let this = self.eval_context_mut();
452
453        let socket = this.read_scalar(socket)?.to_i32()?;
454        // Since the backlog value is just a performance hint we can ignore it.
455        let _backlog = this.read_scalar(backlog)?.to_i32()?;
456
457        // Get the file handle
458        let Some(fd) = this.machine.fds.get(socket) else {
459            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
460        };
461
462        let Some(socket) = fd.downcast::<Socket>() else {
463            // Man page specifies to return ENOTSOCK if `fd` is not a socket.
464            return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));
465        };
466
467        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
468        this.ensure_not_failed(&socket, "listen")?;
469
470        let mut state = socket.state.borrow_mut();
471
472        match *state {
473            SocketState::Bound(socket_addr) =>
474                match TcpListener::bind(socket_addr) {
475                    Ok(listener) => {
476                        *state = SocketState::Listening(listener);
477                        drop(state);
478                        // Register the socket to the blocking I/O manager because
479                        // we now have an associated host socket.
480                        this.machine.blocking_io.register(socket);
481                    }
482                    Err(e) => return this.set_errno_and_return_neg1_i32(e),
483                },
484            SocketState::Initial => {
485                throw_unsup_format!(
486                    "listen: listening on a socket which isn't bound is unsupported"
487                )
488            }
489            SocketState::Listening(_) => {
490                throw_unsup_format!("listen: listening on a socket multiple times is unsupported")
491            }
492            SocketState::Connecting(_) | SocketState::Connected(_) => {
493                throw_unsup_format!("listen: listening on a connected socket is unsupported")
494            }
495            SocketState::ConnectionFailed(_) => unreachable!(),
496        }
497
498        interp_ok(Scalar::from_i32(0))
499    }
500
501    /// For more information on the arguments see the accept manpage:
502    /// <https://linux.die.net/man/2/accept4>
503    fn accept4(
504        &mut self,
505        socket: &OpTy<'tcx>,
506        address: &OpTy<'tcx>,
507        address_len: &OpTy<'tcx>,
508        flags: Option<&OpTy<'tcx>>,
509        // Location where the output scalar is written to.
510        dest: &MPlaceTy<'tcx>,
511    ) -> InterpResult<'tcx> {
512        let this = self.eval_context_mut();
513
514        let socket = this.read_scalar(socket)?.to_i32()?;
515        let address_ptr = this.read_pointer(address)?;
516        let address_len_ptr = this.read_pointer(address_len)?;
517        let mut flags =
518            if let Some(flags) = flags { this.read_scalar(flags)?.to_i32()? } else { 0 };
519
520        // Get the file handle
521        let Some(fd) = this.machine.fds.get(socket) else {
522            return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
523        };
524
525        let Some(socket) = fd.downcast::<Socket>() else {
526            // Man page specifies to return ENOTSOCK if `fd` is not a socket.
527            return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);
528        };
529
530        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
531        this.ensure_not_failed(&socket, "accept4")?;
532
533        if !matches!(*socket.state.borrow(), SocketState::Listening(_)) {
534            throw_unsup_format!(
535                "accept4: accepting incoming connections is only allowed when socket is listening"
536            )
537        };
538
539        let mut is_client_sock_nonblock = false;
540
541        // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so
542        // if there is anything left at the end, that's an unsupported flag.
543        if matches!(
544            this.tcx.sess.target.os,
545            Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos
546        ) {
547            // SOCK_NONBLOCK and SOCK_CLOEXEC only exist on Linux, Android, FreeBSD,
548            // Solaris, and Illumos targets.
549            let sock_nonblock = this.eval_libc_i32("SOCK_NONBLOCK");
550            let sock_cloexec = this.eval_libc_i32("SOCK_CLOEXEC");
551            if flags & sock_nonblock == sock_nonblock {
552                is_client_sock_nonblock = true;
553                flags &= !sock_nonblock;
554            }
555            if flags & sock_cloexec == sock_cloexec {
556                // We don't support `exec` so we can ignore this.
557                flags &= !sock_cloexec;
558            }
559        }
560
561        if flags != 0 {
562            throw_unsup_format!(
563                "accept4: flag {flags:#x} is unsupported, only SOCK_CLOEXEC \
564                and SOCK_NONBLOCK are allowed",
565            );
566        }
567
568        if socket.is_non_block.get() {
569            // We have a non-blocking socket and thus don't want to block until
570            // we can accept an incoming connection.
571            match this.try_non_block_accept(
572                &socket,
573                address_ptr,
574                address_len_ptr,
575                is_client_sock_nonblock,
576            )? {
577                Ok(sockfd) => {
578                    // We need to create the scalar using the destination size since
579                    // `syscall(SYS_accept4, ...)` returns a long which doesn't match
580                    // the int returned from the `accept`/`accept4` syscalls.
581                    // See <https://man7.org/linux/man-pages/man2/syscall.2.html>.
582                    this.write_scalar(Scalar::from_int(sockfd, dest.layout.size), dest)
583                }
584                Err(e) => this.set_errno_and_return_neg1(e, dest),
585            }
586        } else {
587            // The socket is in blocking mode and thus the accept call should block
588            // until an incoming connection is ready.
589
590            if socket.read_timeout.get().is_some() {
591                // Some Unixes like Linux also apply the SO_RCVTIMEO socket option
592                // to `accept` calls:
593                // <https://github.com/torvalds/linux/blob/HEAD/net/ipv4/inet_connection_sock.c#L668-L675>
594                // This is currently not supported by Miri.
595                throw_unsup_format!(
596                    "accept4: blocking accept is not supported when SO_RCVTIMEO is non-zero"
597                )
598            }
599
600            this.block_for_accept(
601                socket,
602                address_ptr,
603                address_len_ptr,
604                is_client_sock_nonblock,
605                dest.clone(),
606            )
607        }
608    }
609
610    fn connect(
611        &mut self,
612        socket: &OpTy<'tcx>,
613        address: &OpTy<'tcx>,
614        address_len: &OpTy<'tcx>,
615        // Location where the output scalar is written to.
616        dest: &MPlaceTy<'tcx>,
617    ) -> InterpResult<'tcx> {
618        let this = self.eval_context_mut();
619
620        let socket = this.read_scalar(socket)?.to_i32()?;
621        let address = match this.read_socket_address(address, address_len, "connect")? {
622            Ok(address) => address,
623            Err(e) => return this.set_errno_and_return_neg1(e, dest),
624        };
625
626        // Get the file handle
627        let Some(fd) = this.machine.fds.get(socket) else {
628            return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
629        };
630
631        let Some(socket) = fd.downcast::<Socket>() else {
632            // Man page specifies to return ENOTSOCK if `fd` is not a socket
633            return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);
634        };
635
636        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
637        this.ensure_not_failed(&socket, "connect")?;
638
639        match &*socket.state.borrow() {
640            SocketState::Initial => { /* fall-through to below */ }
641            // The socket is already in a connecting state.
642            SocketState::Connecting(_) =>
643                return this.set_errno_and_return_neg1(LibcError("EALREADY"), dest),
644            // We don't return EISCONN for already connected sockets, for which we're
645            // sure that the connection is established, since TCP sockets are usually
646            // allowed to be connected multiple times.
647            _ =>
648                throw_unsup_format!(
649                    "connect: connecting is only supported for sockets which are neither \
650                    bound, listening nor already connected"
651                ),
652        }
653
654        // This begins establishing the connection, but does not block until the stream is fully connected.
655        // We deal with that below.
656        match TcpStream::connect(address) {
657            Ok(stream) => {
658                *socket.state.borrow_mut() = SocketState::Connecting(stream);
659                // Register the socket to the blocking I/O manager because
660                // we now have an associated host socket.
661                this.machine.blocking_io.register(socket.clone());
662            }
663            Err(e) => return this.set_errno_and_return_neg1(e, dest),
664        };
665
666        if socket.is_non_block.get() {
667            // We have a non-blocking socket and thus don't want to block until
668            // the connection is established.
669
670            // Since the [`TcpStream::connect`] function of mio hides the EINPROGRESS
671            // we just always return EINPROGRESS and check whether the connection succeeded
672            // once we want to use the connected socket.
673            this.set_errno_and_return_neg1(LibcError("EINPROGRESS"), dest)
674        } else {
675            // The socket is in blocking mode and thus the connect call should block
676            // until the connection with the server is established.
677
678            if socket.write_timeout.get().is_some() {
679                // Some Unixes like Linux also apply the SO_SNDTIMEO socket option
680                // to `connect` calls:
681                // <https://github.com/torvalds/linux/blob/HEAD/net/ipv4/af_inet.c#L701-L710>
682                // This is currently not supported by Miri.
683                throw_unsup_format!(
684                    "connect: blocking connect is not supported when SO_SNDTIMEO is non-zero"
685                )
686            }
687
688            let dest = dest.clone();
689            this.ensure_connected(
690                socket.clone(),
691                /* deadline */ None,
692                "connect",
693                callback!(
694                    @capture<'tcx> {
695                        socket: FileDescriptionRef<Socket>,
696                        dest: MPlaceTy<'tcx>
697                    } |this, result: Result<(), ()>| {
698                        if result.is_err() {
699                            // An error occurred whilst connecting. We know
700                            // that it has been consumed by `ensure_connected`
701                            // and is now stored in `socket.error`.
702                            let err = socket.error.take().unwrap();
703                            this.set_errno_and_return_neg1(err, &dest)
704                        } else {
705                            this.write_scalar(Scalar::from_i32(0), &dest)
706                        }
707                    }
708                ),
709            )
710        }
711    }
712
713    fn send(
714        &mut self,
715        socket: &OpTy<'tcx>,
716        buffer: &OpTy<'tcx>,
717        length: &OpTy<'tcx>,
718        flags: &OpTy<'tcx>,
719        // Location where the output scalar is written to.
720        dest: &MPlaceTy<'tcx>,
721    ) -> InterpResult<'tcx> {
722        let this = self.eval_context_mut();
723
724        let socket = this.read_scalar(socket)?.to_i32()?;
725        let buffer_ptr = this.read_pointer(buffer)?;
726        let size_layout = this.libc_ty_layout("size_t");
727        let length: usize =
728            this.read_scalar(length)?.to_uint(size_layout.size)?.try_into().unwrap();
729        let mut flags = this.read_scalar(flags)?.to_i32()?;
730
731        // Get the file handle
732        let Some(fd) = this.machine.fds.get(socket) else {
733            return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
734        };
735
736        let Some(socket) = fd.downcast::<Socket>() else {
737            // Man page specifies to return ENOTSOCK if `fd` is not a socket
738            return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);
739        };
740
741        let mut is_op_non_block = false;
742
743        // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so
744        // if there is anything left at the end, that's an unsupported flag.
745        if matches!(
746            this.tcx.sess.target.os,
747            Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos
748        ) {
749            // MSG_NOSIGNAL and MSG_DONTWAIT only exist on Linux, Android, FreeBSD,
750            // Solaris, and Illumos targets.
751            let msg_nosignal = this.eval_libc_i32("MSG_NOSIGNAL");
752            let msg_dontwait = this.eval_libc_i32("MSG_DONTWAIT");
753            if flags & msg_nosignal == msg_nosignal {
754                // This is only needed to ensure that no EPIPE signal is sent when
755                // trying to send into a stream which is no longer connected.
756                // Since we don't support signals, we can ignore this.
757                flags &= !msg_nosignal;
758            }
759            if flags & msg_dontwait == msg_dontwait {
760                flags &= !msg_dontwait;
761                is_op_non_block = true;
762            }
763        }
764
765        if flags != 0 {
766            throw_unsup_format!(
767                "send: flag {flags:#x} is unsupported, only MSG_NOSIGNAL and MSG_DONTWAIT are allowed",
768            );
769        }
770
771        let is_non_block = is_op_non_block || socket.is_non_block.get();
772        let deadline = this.action_deadline(is_non_block, socket.write_timeout.get());
773        let dest = dest.clone();
774
775        this.ensure_connected(
776            socket.clone(),
777            deadline.clone(),
778            "send",
779            callback!(
780                @capture<'tcx> {
781                    socket: FileDescriptionRef<Socket>,
782                    deadline: Option<Deadline>,
783                    flags: i32,
784                    buffer_ptr: Pointer,
785                    length: usize,
786                    is_non_block: bool,
787                    dest: MPlaceTy<'tcx>,
788                } |this, result: Result<(), ()>| {
789                    if result.is_err() {
790                        return this.set_errno_and_return_neg1(LibcError("ENOTCONN"), &dest)
791                    }
792
793                    if is_non_block {
794                        // We have a non-blocking operation or a non-blocking socket and
795                        // thus don't want to block until we can send.
796                        match this.try_non_block_send(&socket, buffer_ptr, length)? {
797                            Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
798                            Err(e) => this.set_errno_and_return_neg1(e, &dest),
799                        }
800                    } else {
801                        // The socket is in blocking mode and thus the send call should block
802                        // until we can send some bytes into the socket or the timeout exceeded.
803                        this.block_for_send(
804                            socket,
805                            deadline,
806                            buffer_ptr,
807                            length,
808                            callback!(@capture<'tcx> {
809                                dest: MPlaceTy<'tcx>
810                            } |this, result: Result<usize, IoError>| {
811                                match result {
812                                    Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
813                                    Err(e) => this.set_errno_and_return_neg1(e, &dest)
814                                }
815                            }),
816                        )
817                    }
818                }
819            ),
820        )
821    }
822
823    fn recv(
824        &mut self,
825        socket: &OpTy<'tcx>,
826        buffer: &OpTy<'tcx>,
827        length: &OpTy<'tcx>,
828        flags: &OpTy<'tcx>,
829        // Location where the output scalar is written to.
830        dest: &MPlaceTy<'tcx>,
831    ) -> InterpResult<'tcx> {
832        let this = self.eval_context_mut();
833
834        let socket = this.read_scalar(socket)?.to_i32()?;
835        let buffer_ptr = this.read_pointer(buffer)?;
836        let size_layout = this.libc_ty_layout("size_t");
837        let length: usize =
838            this.read_scalar(length)?.to_uint(size_layout.size)?.try_into().unwrap();
839        let mut flags = this.read_scalar(flags)?.to_i32()?;
840
841        // Get the file handle
842        let Some(fd) = this.machine.fds.get(socket) else {
843            return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
844        };
845
846        let Some(socket) = fd.downcast::<Socket>() else {
847            // Man page specifies to return ENOTSOCK if `fd` is not a socket
848            return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);
849        };
850
851        let mut should_peek = false;
852        let mut is_op_non_block = false;
853
854        // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so
855        // if there is anything left at the end, that's an unsupported flag.
856
857        let msg_peek = this.eval_libc_i32("MSG_PEEK");
858        if flags & msg_peek == msg_peek {
859            should_peek = true;
860            flags &= !msg_peek;
861        }
862
863        if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::FreeBsd | Os::Illumos) {
864            // MSG_CMSG_CLOEXEC only exists on Linux, Android, FreeBSD,
865            // and Illumos targets.
866            let msg_cmsg_cloexec = this.eval_libc_i32("MSG_CMSG_CLOEXEC");
867            if flags & msg_cmsg_cloexec == msg_cmsg_cloexec {
868                // We don't support `exec` so we can ignore this.
869                flags &= !msg_cmsg_cloexec;
870            }
871        }
872
873        if matches!(
874            this.tcx.sess.target.os,
875            Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos
876        ) {
877            // MSG_DONTWAIT only exists on Linux, Android, FreeBSD,
878            // Solaris, and Illumos targets.
879            let msg_dontwait = this.eval_libc_i32("MSG_DONTWAIT");
880            if flags & msg_dontwait == msg_dontwait {
881                flags &= !msg_dontwait;
882                is_op_non_block = true;
883            }
884        }
885
886        if flags != 0 {
887            throw_unsup_format!(
888                "recv: flag {flags:#x} is unsupported, only MSG_PEEK, MSG_DONTWAIT \
889                and MSG_CMSG_CLOEXEC are allowed",
890            );
891        }
892
893        let is_non_block = is_op_non_block || socket.is_non_block.get();
894        let deadline = this.action_deadline(is_non_block, socket.read_timeout.get());
895        let dest = dest.clone();
896
897        this.ensure_connected(
898            socket.clone(),
899            deadline.clone(),
900            "recv",
901            callback!(
902                @capture<'tcx> {
903                    socket: FileDescriptionRef<Socket>,
904                    deadline: Option<Deadline>,
905                    buffer_ptr: Pointer,
906                    length: usize,
907                    should_peek: bool,
908                    is_non_block: bool,
909                    dest: MPlaceTy<'tcx>,
910                } |this, result: Result<(), ()>| {
911                    if result.is_err() {
912                        return this.set_errno_and_return_neg1(LibcError("ENOTCONN"), &dest)
913                    }
914
915                    if is_non_block {
916                        // We have a non-blocking operation or a non-blocking socket and
917                        // thus don't want to block until we can receive.
918                        match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? {
919                            Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
920                            Err(e) => this.set_errno_and_return_neg1(e, &dest),
921                        }
922                    } else {
923                        // The socket is in blocking mode and thus the receive call should block
924                        // until we can receive some bytes from the socket or the timeout exceeded.
925                        this.block_for_recv(
926                            socket,
927                            deadline,
928                            buffer_ptr,
929                            length,
930                            should_peek,
931                            callback!(@capture<'tcx> {
932                                dest: MPlaceTy<'tcx>
933                            } |this, result: Result<usize, IoError>| {
934                                match result {
935                                    Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
936                                    Err(e) => this.set_errno_and_return_neg1(e, &dest)
937                                }
938                            }),
939                        )
940                    }
941                }
942            ),
943        )
944    }
945
946    fn setsockopt(
947        &mut self,
948        socket: &OpTy<'tcx>,
949        level: &OpTy<'tcx>,
950        option_name: &OpTy<'tcx>,
951        option_value: &OpTy<'tcx>,
952        option_len: &OpTy<'tcx>,
953    ) -> InterpResult<'tcx, Scalar> {
954        let this = self.eval_context_mut();
955
956        let socket = this.read_scalar(socket)?.to_i32()?;
957        let level = this.read_scalar(level)?.to_i32()?;
958        let option_name = this.read_scalar(option_name)?.to_i32()?;
959        let option_value_ptr = this.read_pointer(option_value)?;
960        let socklen_layout = this.libc_ty_layout("socklen_t");
961        let option_len = this.read_scalar(option_len)?.to_int(socklen_layout.size)?;
962
963        // Get the file handle
964        let Some(fd) = this.machine.fds.get(socket) else {
965            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
966        };
967
968        let Some(socket) = fd.downcast::<Socket>() else {
969            // Man page specifies to return ENOTSOCK if `fd` is not a socket.
970            return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));
971        };
972
973        if level == this.eval_libc_i32("SOL_SOCKET") {
974            let opt_so_rcvtimeo = this.eval_libc_i32("SO_RCVTIMEO");
975            let opt_so_sndtimeo = this.eval_libc_i32("SO_SNDTIMEO");
976            let opt_so_reuseaddr = this.eval_libc_i32("SO_REUSEADDR");
977
978            if matches!(this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::NetBsd) {
979                // SO_NOSIGPIPE only exists on MacOS, FreeBSD, and NetBSD.
980                let opt_so_nosigpipe = this.eval_libc_i32("SO_NOSIGPIPE");
981
982                if option_name == opt_so_nosigpipe {
983                    if option_len != 4 {
984                        // Option value should be C-int which is usually 4 bytes.
985                        return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
986                    }
987                    let option_value =
988                        this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32);
989                    let _val = this.read_scalar(&option_value)?.to_i32()?;
990                    // We entirely ignore this value since we do not support signals anyway.
991
992                    return interp_ok(Scalar::from_i32(0));
993                }
994            }
995
996            if option_name == opt_so_rcvtimeo || option_name == opt_so_sndtimeo {
997                let timeval_layout = this.libc_ty_layout("timeval");
998                let option_value = this.ptr_to_mplace(option_value_ptr, timeval_layout);
999
1000                let timeout = match this.read_timeval(&option_value)? {
1001                    None => return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")),
1002                    Some(Duration::ZERO) => None,
1003                    Some(duration) => Some(duration),
1004                };
1005
1006                if option_name == opt_so_rcvtimeo {
1007                    socket.read_timeout.set(timeout);
1008                } else {
1009                    socket.write_timeout.set(timeout);
1010                }
1011
1012                return interp_ok(Scalar::from_i32(0));
1013            }
1014
1015            if option_name == opt_so_reuseaddr {
1016                if option_len != 4 {
1017                    // Option value should be C-int which is usually 4 bytes.
1018                    return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1019                }
1020                let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32);
1021                let _val = this.read_scalar(&option_value)?.to_i32()?;
1022                // We entirely ignore this: std always sets REUSEADDR for us, and in the end it's more of a
1023                // hint to bypass some arbitrary timeout anyway.
1024                return interp_ok(Scalar::from_i32(0));
1025            } else {
1026                throw_unsup_format!(
1027                    "setsockopt: option {option_name:#x} is unsupported for level SOL_SOCKET",
1028                );
1029            }
1030        } else if level == this.eval_libc_i32("IPPROTO_IP") {
1031            let opt_ip_ttl = this.eval_libc_i32("IP_TTL");
1032
1033            if option_name == opt_ip_ttl {
1034                if option_len != 4 {
1035                    // Option value should be C-uint which is usually 4 bytes.
1036                    return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1037                }
1038                let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.u32);
1039                let ttl = this.read_scalar(&option_value)?.to_u32()?;
1040
1041                let result = match &*socket.state.borrow() {
1042                    SocketState::Initial | SocketState::Bound(_) =>
1043                        throw_unsup_format!(
1044                            "setsockopt: setting option IP_TTL on level IPPROTO_IP is only supported \
1045                            on connected and listening sockets"
1046                        ),
1047                    SocketState::Listening(listener) => listener.set_ttl(ttl),
1048                    SocketState::Connecting(stream) | SocketState::Connected(stream) =>
1049                        stream.set_ttl(ttl),
1050                    SocketState::ConnectionFailed(_) => unreachable!(),
1051                };
1052
1053                return match result {
1054                    Ok(_) => interp_ok(Scalar::from_i32(0)),
1055                    Err(e) => this.set_errno_and_return_neg1_i32(e),
1056                };
1057            } else {
1058                throw_unsup_format!(
1059                    "setsockopt: option {option_name:#x} is unsupported for level IPPROTO_IP",
1060                );
1061            }
1062        } else if level == this.eval_libc_i32("IPPROTO_TCP") {
1063            let opt_tcp_nodelay = this.eval_libc_i32("TCP_NODELAY");
1064
1065            if option_name == opt_tcp_nodelay {
1066                if option_len != 4 {
1067                    // Option value should be C-int which is usually 4 bytes.
1068                    return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));
1069                }
1070                let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32);
1071                let nodelay = this.read_scalar(&option_value)?.to_i32()? != 0;
1072
1073                let result = match &*socket.state.borrow() {
1074                    SocketState::Initial | SocketState::Bound(_) | SocketState::Listening(_) =>
1075                        throw_unsup_format!(
1076                            "setsockopt: setting option TCP_NODELAY on level IPPROTO_TCP is only supported \
1077                            on connected sockets"
1078                        ),
1079                    SocketState::Connecting(stream) | SocketState::Connected(stream) =>
1080                        stream.set_nodelay(nodelay),
1081                    SocketState::ConnectionFailed(_) => unreachable!(),
1082                };
1083
1084                return match result {
1085                    Ok(_) => interp_ok(Scalar::from_i32(0)),
1086                    Err(e) => this.set_errno_and_return_neg1_i32(e),
1087                };
1088            } else {
1089                throw_unsup_format!(
1090                    "setsockopt: option {option_name:#x} is unsupported for level IPPROTO_TCP"
1091                );
1092            }
1093        }
1094
1095        throw_unsup_format!(
1096            "setsockopt: level {level:#x} is unsupported, only SOL_SOCKET, IPPROTO_IP \
1097            and IPPROTO_TCP are allowed"
1098        );
1099    }
1100
1101    fn getsockopt(
1102        &mut self,
1103        socket: &OpTy<'tcx>,
1104        level: &OpTy<'tcx>,
1105        option_name: &OpTy<'tcx>,
1106        option_value: &OpTy<'tcx>,
1107        option_len: &OpTy<'tcx>,
1108    ) -> InterpResult<'tcx, Scalar> {
1109        let this = self.eval_context_mut();
1110
1111        let socket = this.read_scalar(socket)?.to_i32()?;
1112        let level = this.read_scalar(level)?.to_i32()?;
1113        let option_name = this.read_scalar(option_name)?.to_i32()?;
1114        // These two pointers are used to return the value: `len_ptr` initially stores how much space
1115        // is available. If the actual value fits into that space, it is written to
1116        // `value_ptr` and `len_ptr` is updated to represent how many bytes
1117        // were actually written. If the value does not fit, it is silently truncated.
1118        // Also see <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getsockopt.html>.
1119        let option_value_ptr = this.read_pointer(option_value)?;
1120        let option_len_ptr = this.read_pointer(option_len)?;
1121
1122        // Get the file handle
1123        let Some(fd) = this.machine.fds.get(socket) else {
1124            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1125        };
1126
1127        let Some(socket) = fd.downcast::<Socket>() else {
1128            // Man page specifies to return ENOTSOCK if `fd` is not a socket.
1129            return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));
1130        };
1131
1132        if option_value_ptr == Pointer::null() || option_len_ptr == Pointer::null() {
1133            // This socket option returns a value and thus we need to return EFAULT
1134            // when either the value or the length pointers are null pointers.
1135            return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
1136        }
1137
1138        let socklen_layout = this.libc_ty_layout("socklen_t");
1139        let option_len_ptr_mplace = this.ptr_to_mplace(option_len_ptr, socklen_layout);
1140        let option_len: usize = this
1141            .read_scalar(&option_len_ptr_mplace)?
1142            .to_int(socklen_layout.size)?
1143            .try_into()
1144            .unwrap();
1145
1146        // We need a temporary buffer as `option_value_ptr` might not point to a large enough
1147        // buffer, in which case we have to truncate.
1148        let value_buffer = if level == this.eval_libc_i32("SOL_SOCKET") {
1149            let opt_so_error = this.eval_libc_i32("SO_ERROR");
1150            let opt_so_rcvtimeo = this.eval_libc_i32("SO_RCVTIMEO");
1151            let opt_so_sndtimeo = this.eval_libc_i32("SO_SNDTIMEO");
1152
1153            if option_name == opt_so_error {
1154                // Reading SO_ERROR should always return the latest async error. Because our stored
1155                // `socket.error` could be outdated, we attempt to update it here.
1156                this.update_last_error(&socket);
1157
1158                let return_value = match socket.error.take() {
1159                    Some(err) => this.io_error_to_errnum(err)?.to_i32()?,
1160                    // If there is no error, we return 0 as the option value.
1161                    None => 0,
1162                };
1163
1164                // Clear our own stored error -- it was either `take`n above or it is outdated.
1165                socket.error.replace(None);
1166
1167                // We know there is no longer an async error and thus we need to update the
1168                // I/O and epoll readiness of the socket.
1169                socket.io_readiness.borrow_mut().error = false;
1170                this.update_epoll_active_events(socket, /* force_edge */ false)?;
1171
1172                // Allocate new buffer on the stack with the `i32` layout.
1173                let value_buffer = this.allocate(this.machine.layouts.i32, MemoryKind::Stack)?;
1174                this.write_int(return_value, &value_buffer)?;
1175                value_buffer
1176            } else if option_name == opt_so_rcvtimeo || option_name == opt_so_sndtimeo {
1177                let timeout = if option_name == opt_so_rcvtimeo {
1178                    socket.read_timeout.get()
1179                } else {
1180                    socket.write_timeout.get()
1181                }
1182                .unwrap_or_default();
1183
1184                let secs = timeout.as_secs();
1185                let usecs = timeout.subsec_micros();
1186
1187                let timeval_layout = this.libc_ty_layout("timeval");
1188                // Allocate new buffer on the stack with the `timeval` layout.
1189                let timeval_buffer = this.allocate(timeval_layout, MemoryKind::Stack)?;
1190
1191                let sec_field = this.project_field_named(&timeval_buffer, "tv_sec")?;
1192                this.write_int(secs, &sec_field)?;
1193
1194                let usec_field = this.project_field_named(&timeval_buffer, "tv_usec")?;
1195                this.write_int(usecs, &usec_field)?;
1196
1197                timeval_buffer
1198            } else {
1199                throw_unsup_format!(
1200                    "getsockopt: option {option_name:#x} is unsupported for level SOL_SOCKET",
1201                );
1202            }
1203        } else if level == this.eval_libc_i32("IPPROTO_IP") {
1204            let opt_ip_ttl = this.eval_libc_i32("IP_TTL");
1205
1206            if option_name == opt_ip_ttl {
1207                let ttl = match &*socket.state.borrow() {
1208                    SocketState::Initial | SocketState::Bound(_) =>
1209                        throw_unsup_format!(
1210                            "getsockopt: reading option IP_TTL on level IPPROTO_IP is only supported \
1211                            on connected and listening sockets"
1212                        ),
1213                    SocketState::Listening(listener) => listener.ttl(),
1214                    SocketState::Connecting(stream) | SocketState::Connected(stream) =>
1215                        stream.ttl(),
1216                    SocketState::ConnectionFailed(_) => unreachable!(),
1217                };
1218
1219                let ttl = match ttl {
1220                    Ok(ttl) => ttl,
1221                    Err(e) => return this.set_errno_and_return_neg1_i32(e),
1222                };
1223
1224                // Allocate new buffer on the stack with the `u32` layout.
1225                let value_buffer = this.allocate(this.machine.layouts.u32, MemoryKind::Stack)?;
1226                this.write_int(ttl, &value_buffer)?;
1227                value_buffer
1228            } else {
1229                throw_unsup_format!(
1230                    "getsockopt: option {option_name:#x} is unsupported for level IPPROTO_IP",
1231                );
1232            }
1233        } else if level == this.eval_libc_i32("IPPROTO_TCP") {
1234            let opt_tcp_nodelay = this.eval_libc_i32("TCP_NODELAY");
1235
1236            if option_name == opt_tcp_nodelay {
1237                let nodelay = match &*socket.state.borrow() {
1238                    SocketState::Initial | SocketState::Bound(_) | SocketState::Listening(_) =>
1239                        throw_unsup_format!(
1240                            "getsockopt: reading option TCP_NODELAY on level IPPROTO_TCP is only supported \
1241                            on connected sockets"
1242                        ),
1243                    SocketState::Connecting(stream) | SocketState::Connected(stream) =>
1244                        stream.nodelay(),
1245                    SocketState::ConnectionFailed(_) => unreachable!(),
1246                };
1247
1248                let nodelay = match nodelay {
1249                    Ok(nodelay) => nodelay,
1250                    Err(e) => return this.set_errno_and_return_neg1_i32(e),
1251                };
1252
1253                // Allocate new buffer on the stack with the `i32` layout.
1254                let value_buffer = this.allocate(this.machine.layouts.i32, MemoryKind::Stack)?;
1255                this.write_int(i32::from(nodelay), &value_buffer)?;
1256                value_buffer
1257            } else {
1258                throw_unsup_format!(
1259                    "getsockopt: option {option_name:#x} is unsupported for level IPPROTO_TCP"
1260                );
1261            }
1262        } else {
1263            throw_unsup_format!(
1264                "getsockopt: level {level:#x} is unsupported, only SOL_SOCKET, IPPROTO_IP \
1265                and IPPROTO_TCP are allowed"
1266            )
1267        };
1268
1269        // Truncated size of the output value.
1270        let output_value_len = value_buffer.layout.size.min(Size::from_bytes(option_len));
1271        // Copy the truncated value into the buffer pointed to by `option_value_ptr`.
1272        this.mem_copy(
1273            value_buffer.ptr(),
1274            option_value_ptr,
1275            // Truncate the value to fit the provided buffer.
1276            output_value_len,
1277            // The buffers are guaranteed to not overlap since the `value_buffer`
1278            // was just newly allocated on the stack.
1279            true,
1280        )?;
1281        // Deallocate the value buffer as it was only needed to store the value and
1282        // copy it into the buffer pointed to by `option_value_ptr`.
1283        this.deallocate_ptr(value_buffer.ptr(), None, MemoryKind::Stack)?;
1284
1285        // On output, the length pointer contains the amount of bytes written -- not the size
1286        // of the value before truncation.
1287        this.write_scalar(
1288            Scalar::from_uint(output_value_len.bytes(), socklen_layout.size),
1289            &option_len_ptr_mplace,
1290        )?;
1291
1292        interp_ok(Scalar::from_i32(0))
1293    }
1294
1295    fn getsockname(
1296        &mut self,
1297        socket: &OpTy<'tcx>,
1298        address: &OpTy<'tcx>,
1299        address_len: &OpTy<'tcx>,
1300    ) -> InterpResult<'tcx, Scalar> {
1301        let this = self.eval_context_mut();
1302
1303        let socket = this.read_scalar(socket)?.to_i32()?;
1304        let address_ptr = this.read_pointer(address)?;
1305        let address_len_ptr = this.read_pointer(address_len)?;
1306
1307        // Get the file handle
1308        let Some(fd) = this.machine.fds.get(socket) else {
1309            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1310        };
1311
1312        let Some(socket) = fd.downcast::<Socket>() else {
1313            // Man page specifies to return ENOTSOCK if `fd` is not a socket.
1314            return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));
1315        };
1316
1317        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
1318        this.ensure_not_failed(&socket, "getsockname")?;
1319
1320        let state = socket.state.borrow();
1321
1322        let address = match &*state {
1323            SocketState::Bound(address) => {
1324                if address.port() == 0 {
1325                    // The socket is bound to a zero-port which means it gets assigned a random
1326                    // port. Since we don't yet have an underlying socket, we don't know what this
1327                    // random port will be and thus this is unsupported.
1328                    throw_unsup_format!(
1329                        "getsockname: when the port is 0, getting the socket address before \
1330                        calling `listen` or `connect` is unsupported"
1331                    )
1332                }
1333
1334                *address
1335            }
1336            SocketState::Listening(listener) =>
1337                match listener.local_addr() {
1338                    Ok(address) => address,
1339                    Err(e) => return this.set_errno_and_return_neg1_i32(e),
1340                },
1341            SocketState::Connecting(stream) | SocketState::Connected(stream) => {
1342                if cfg!(windows) && matches!(&*state, SocketState::Connecting(_)) {
1343                    // FIXME: On Windows hosts `TcpStream::local_addr` returns `0.0.0.0:0` whilst
1344                    // the socket is connecting:
1345                    // <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockname#remarks>
1346                    // This is problematic because UNIX targets could expect a real local address even
1347                    // for a connecting non-blocking socket.
1348
1349                    static DEDUP: AtomicBool = AtomicBool::new(false);
1350                    if !DEDUP.swap(true, std::sync::atomic::Ordering::Relaxed) {
1351                        this.emit_diagnostic(NonHaltingDiagnostic::ConnectingSocketGetsockname);
1352                    }
1353                }
1354                match stream.local_addr() {
1355                    Ok(address) => address,
1356                    Err(e) => return this.set_errno_and_return_neg1_i32(e),
1357                }
1358            }
1359            // For non-bound sockets the POSIX manual says the returned address is unspecified.
1360            // Often this is 0.0.0.0:0 and thus we set it to this value.
1361            SocketState::Initial => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)),
1362            SocketState::ConnectionFailed(_) => unreachable!(),
1363        };
1364
1365        this.write_socket_address(&address, address_ptr, address_len_ptr, "getsockname")
1366            .map(|_| Scalar::from_i32(0))
1367    }
1368
1369    fn getpeername(
1370        &mut self,
1371        socket: &OpTy<'tcx>,
1372        address: &OpTy<'tcx>,
1373        address_len: &OpTy<'tcx>,
1374        // Location where the output scalar is written to.
1375        dest: &MPlaceTy<'tcx>,
1376    ) -> InterpResult<'tcx> {
1377        let this = self.eval_context_mut();
1378
1379        let socket = this.read_scalar(socket)?.to_i32()?;
1380        let address_ptr = this.read_pointer(address)?;
1381        let address_len_ptr = this.read_pointer(address_len)?;
1382
1383        // Get the file handle
1384        let Some(fd) = this.machine.fds.get(socket) else {
1385            return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
1386        };
1387
1388        let Some(socket) = fd.downcast::<Socket>() else {
1389            // Man page specifies to return ENOTSOCK if `fd` is not a socket.
1390            return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);
1391        };
1392
1393        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
1394
1395        let dest = dest.clone();
1396
1397        // It's only safe to call [`TcpStream::peer_addr`] after the socket is connected since
1398        // UNIX targets should return ENOTCONN when the connection is not yet established.
1399        this.ensure_connected(
1400            socket.clone(),
1401            // Check whether the socket is connected without blocking.
1402            Some(this.machine.monotonic_clock.now().into()),
1403            "getpeername",
1404            callback!(
1405                @capture<'tcx> {
1406                    socket: FileDescriptionRef<Socket>,
1407                    address_ptr: Pointer,
1408                    address_len_ptr: Pointer,
1409                    dest: MPlaceTy<'tcx>,
1410                } |this, result: Result<(), ()>| {
1411                    if result.is_err() {
1412                        return this.set_errno_and_return_neg1(LibcError("ENOTCONN"), &dest)
1413                    };
1414
1415                    let SocketState::Connected(stream) = &*socket.state.borrow() else {
1416                        unreachable!()
1417                    };
1418
1419                    let address = match stream.peer_addr() {
1420                        Ok(address) => address,
1421                        Err(e) => return this.set_errno_and_return_neg1(e, &dest),
1422                    };
1423
1424                    this.write_socket_address(
1425                        &address,
1426                        address_ptr,
1427                        address_len_ptr,
1428                        "getpeername",
1429                    )?;
1430                   this.write_scalar(Scalar::from_i32(0), &dest)
1431                }
1432            ),
1433        )
1434    }
1435
1436    fn shutdown(&mut self, socket: &OpTy<'tcx>, how: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1437        let this = self.eval_context_mut();
1438
1439        let socket = this.read_scalar(socket)?.to_i32()?;
1440        let how = this.read_scalar(how)?.to_i32()?;
1441
1442        // Get the file handle
1443        let Some(fd) = this.machine.fds.get(socket) else {
1444            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));
1445        };
1446
1447        let Some(socket) = fd.downcast::<Socket>() else {
1448            // Man page specifies to return ENOTSOCK if `fd` is not a socket.
1449            return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));
1450        };
1451
1452        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
1453        this.ensure_not_failed(&socket, "shutdown")?;
1454
1455        let state = socket.state.borrow();
1456
1457        let (SocketState::Connecting(stream) | SocketState::Connected(stream)) = &*state else {
1458            return this.set_errno_and_return_neg1_i32(LibcError("ENOTCONN"));
1459        };
1460
1461        let is_read_shutdown = how == this.eval_libc_i32("SHUT_RD");
1462        let is_write_shutdown = how == this.eval_libc_i32("SHUT_WR");
1463        let is_read_write_shutdown = how == this.eval_libc_i32("SHUT_RDWR");
1464
1465        let how = match () {
1466            _ if is_read_shutdown => Shutdown::Read,
1467            _ if is_write_shutdown => Shutdown::Write,
1468            _ if is_read_write_shutdown => Shutdown::Both,
1469            // An invalid value was passed to `how`.
1470            _ => return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")),
1471        };
1472
1473        if let Err(e) = stream.shutdown(how) {
1474            return this.set_errno_and_return_neg1_i32(e);
1475        };
1476
1477        drop(state);
1478
1479        // Because we map cross platform mio readiness to epoll readiness and
1480        // the different platforms don't treat `shutdown` the same way, we set
1481        // the readiness after a `shutdown` manually to achieve more consistent
1482        // epoll readiness. Otherwise we do not generate enough epoll events
1483        // on partial shutdowns on Windows hosts.
1484        let mut readiness = socket.io_readiness.borrow_mut();
1485        // Closing the read end of a socket causes an EPOLLRDHUP event.
1486        readiness.read_closed |= is_read_shutdown || is_read_write_shutdown;
1487        // Only shutting down the write end doesn't cause an EPOLLHUP event
1488        // and thus we won't set the `write_closed` readiness for it here.
1489        readiness.write_closed |= is_read_write_shutdown;
1490        // The Linux kernel also sets EPOLLIN when both ends of a socket are closed:
1491        // <https://github.com/torvalds/linux/blob/HEAD/net/ipv4/tcp.c#L584-L588>
1492        readiness.readable |= is_read_write_shutdown;
1493
1494        drop(readiness);
1495
1496        // Update the epoll readiness for the socket.
1497        this.update_epoll_active_events(socket, /* force_edge */ false)?;
1498
1499        interp_ok(Scalar::from_i32(0))
1500    }
1501}
1502
1503impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
1504trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
1505    /// Get the deadline for an action (e.g. reading or writing).
1506    /// When `is_non_block` is [`true`], the returned deadline is "now", i.e.,
1507    /// we wake up immediately if the action cannot be completed.
1508    /// If `action_timeout` is `Some(duration)`, the returned deadline is in the
1509    /// future be the specified `duration`. Otherwise, no deadline ([`None`]) is
1510    /// returned, indicating that the action can block indefinitely.
1511    fn action_deadline(
1512        &self,
1513        is_non_block: bool,
1514        action_timeout: Option<Duration>,
1515    ) -> Option<Deadline> {
1516        let this = self.eval_context_ref();
1517
1518        if is_non_block {
1519            // Non-blocking sockets always have a zero timeout.
1520            Some(this.machine.monotonic_clock.now().into())
1521        } else {
1522            action_timeout
1523                .map(|duration| this.machine.monotonic_clock.now().add_lossy(duration).into())
1524        }
1525    }
1526
1527    /// Block the thread until there's an incoming connection or an error occurred.
1528    ///
1529    /// This recursively calls itself should the operation still block for some reason.
1530    ///
1531    /// **Note**: This function is only safe to call when having previously ensured
1532    /// that the socket is in [`SocketState::Listening`].
1533    fn block_for_accept(
1534        &mut self,
1535        socket: FileDescriptionRef<Socket>,
1536        address_ptr: Pointer,
1537        address_len_ptr: Pointer,
1538        is_client_sock_nonblock: bool,
1539        dest: MPlaceTy<'tcx>,
1540    ) -> InterpResult<'tcx> {
1541        let this = self.eval_context_mut();
1542        this.block_thread_for_io(
1543            socket.clone(),
1544            BlockingIoInterest::Read,
1545            /* deadline */ None,
1546            callback!(@capture<'tcx> {
1547                socket: FileDescriptionRef<Socket>,
1548                address_ptr: Pointer,
1549                address_len_ptr: Pointer,
1550                is_client_sock_nonblock: bool,
1551                dest: MPlaceTy<'tcx>,
1552            } |this, kind: UnblockKind| {
1553                // Remove the blocking I/O interest for unblocking this thread.
1554                this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
1555
1556                match kind {
1557                    UnblockKind::Ready => { /* fall-through to below */ },
1558                    // When the read timeout is exceeded EAGAIN/EWOULDBLOCK is returned.
1559                    UnblockKind::TimedOut => return this.set_errno_and_return_neg1(LibcError("EWOULDBLOCK"), &dest)
1560                }
1561
1562                match this.try_non_block_accept(&socket, address_ptr, address_len_ptr, is_client_sock_nonblock)? {
1563                    Ok(sockfd) => {
1564                        // We need to create the scalar using the destination size since
1565                        // `syscall(SYS_accept4, ...)` returns a long which doesn't match
1566                        // the int returned from the `accept`/`accept4` syscalls.
1567                        // See <https://man7.org/linux/man-pages/man2/syscall.2.html>.
1568                        this.write_scalar(Scalar::from_int(sockfd, dest.layout.size), &dest)
1569                    },
1570                    Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {
1571                        // We need to block the thread again as it would still block.
1572                        this.block_for_accept(socket, address_ptr, address_len_ptr, is_client_sock_nonblock, dest)
1573                    }
1574                    Err(e) => this.set_errno_and_return_neg1(e, &dest),
1575                }
1576            }),
1577        )
1578    }
1579
1580    /// Attempt to accept an incoming connection on the listening socket in a
1581    /// non-blocking manner.
1582    ///
1583    /// **Note**: This function is only safe to call when having previously ensured
1584    /// that the socket is in [`SocketState::Listening`].
1585    fn try_non_block_accept(
1586        &mut self,
1587        socket: &FileDescriptionRef<Socket>,
1588        address_ptr: Pointer,
1589        address_len_ptr: Pointer,
1590        is_client_sock_nonblock: bool,
1591    ) -> InterpResult<'tcx, Result<i32, IoError>> {
1592        let this = self.eval_context_mut();
1593
1594        let state = socket.state.borrow();
1595        let SocketState::Listening(listener) = &*state else {
1596            panic!(
1597                "try_non_block_accept must only be called when socket is in `SocketState::Listening`"
1598            )
1599        };
1600
1601        let (stream, addr) = match listener.accept() {
1602            Ok(peer) => peer,
1603            Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
1604                // We know that the source is not readable so we need to update its readiness.
1605                socket.io_readiness.borrow_mut().readable = false;
1606                this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;
1607
1608                return interp_ok(Err(IoError::HostError(e)));
1609            }
1610            Err(e) => return interp_ok(Err(IoError::HostError(e))),
1611        };
1612
1613        let family = match addr {
1614            SocketAddr::V4(_) => SocketFamily::IPv4,
1615            SocketAddr::V6(_) => SocketFamily::IPv6,
1616        };
1617
1618        if address_ptr != Pointer::null() {
1619            // We only attempt a write if the address pointer is not a null pointer.
1620            // If the address pointer is a null pointer the user isn't interested in the
1621            // address and we don't need to write anything.
1622            this.write_socket_address(&addr, address_ptr, address_len_ptr, "accept4")?;
1623        }
1624
1625        let fd = this.machine.fds.new_ref(Socket {
1626            family,
1627            state: RefCell::new(SocketState::Connected(stream)),
1628            is_non_block: Cell::new(is_client_sock_nonblock),
1629            io_readiness: RefCell::new(BlockingIoSourceReadiness::empty()),
1630            error: RefCell::new(None),
1631            read_timeout: Cell::new(None),
1632            write_timeout: Cell::new(None),
1633        });
1634        // Register the socket to the blocking I/O manager because
1635        // there is an associated host socket.
1636        this.machine.blocking_io.register(fd.clone());
1637        let sockfd = this.machine.fds.insert(fd);
1638        interp_ok(Ok(sockfd))
1639    }
1640
1641    /// Block the thread until we can send bytes into the connected socket
1642    /// or an error occurred.
1643    ///
1644    /// This recursively calls itself should the operation still block for some reason.
1645    ///
1646    /// **Note**: This function is only safe to call when having previously ensured
1647    /// that the socket is in [`SocketState::Connected`].
1648    fn block_for_send(
1649        &mut self,
1650        socket: FileDescriptionRef<Socket>,
1651        deadline: Option<Deadline>,
1652        buffer_ptr: Pointer,
1653        length: usize,
1654        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1655    ) -> InterpResult<'tcx> {
1656        let this = self.eval_context_mut();
1657        this.block_thread_for_io(
1658            socket.clone(),
1659            BlockingIoInterest::Write,
1660            deadline.clone(),
1661            callback!(@capture<'tcx> {
1662                socket: FileDescriptionRef<Socket>,
1663                deadline: Option<Deadline>,
1664                buffer_ptr: Pointer,
1665                length: usize,
1666                finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1667            } |this, kind: UnblockKind| {
1668                // Remove the blocking I/O interest for unblocking this thread.
1669                this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
1670
1671                match kind {
1672                    UnblockKind::Ready => { /* fall-through to below */ },
1673                    // When the write timeout is exceeded EAGAIN/EWOULDBLOCK is returned.
1674                    UnblockKind::TimedOut => return finish.call(this, Err(LibcError("EWOULDBLOCK")))
1675                }
1676
1677                match this.try_non_block_send(&socket, buffer_ptr, length)? {
1678                    Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {
1679                        // We need to block the thread again as it would still block.
1680                        this.block_for_send(socket, deadline, buffer_ptr, length, finish)
1681                    },
1682                    result => finish.call(this, result)
1683                }
1684            }),
1685        )
1686    }
1687
1688    /// Attempt to send bytes into the connected socket in a non-blocking manner.
1689    ///
1690    /// **Note**: This function is only safe to call when having previously ensured
1691    /// that the socket is in [`SocketState::Connected`].
1692    fn try_non_block_send(
1693        &mut self,
1694        socket: &FileDescriptionRef<Socket>,
1695        buffer_ptr: Pointer,
1696        length: usize,
1697    ) -> InterpResult<'tcx, Result<usize, IoError>> {
1698        let this = self.eval_context_mut();
1699
1700        let SocketState::Connected(stream) = &mut *socket.state.borrow_mut() else {
1701            panic!("try_non_block_send must only be called when the socket is connected")
1702        };
1703
1704        // This is a *non-blocking* write.
1705        let result = this.write_to_host(stream, length, buffer_ptr)?;
1706        match result {
1707            Err(IoError::HostError(e))
1708                if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::WouldBlock) =>
1709            {
1710                // We know that the source is not writable so we need to update it's readiness.
1711                socket.io_readiness.borrow_mut().writable = false;
1712                this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;
1713
1714                // On Windows hosts, `send` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK
1715                // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK.
1716                interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into())))
1717            }
1718            Ok(bytes_written) if bytes_written < length => {
1719                // We had a short write. On Unix hosts using the `epoll` and `kqueue` backends, a
1720                // short write means that the write buffer is full. We update the readiness
1721                // accordingly, which means that next time we see "writable" we will report an epoll
1722                // edge. Some applications (e.g. tokio) rely on this behavior; see
1723                // <https://github.com/tokio-rs/tokio/blob/HEAD/tokio/src/io/poll_evented.rs#L244-L264>.
1724                if cfg!(any(
1725                    // epoll
1726                    target_os = "android",
1727                    target_os = "illumos",
1728                    target_os = "linux",
1729                    target_os = "redox",
1730                    // kqueue
1731                    target_os = "dragonfly",
1732                    target_os = "freebsd",
1733                    target_os = "ios",
1734                    target_os = "macos",
1735                    target_os = "netbsd",
1736                    target_os = "openbsd",
1737                    target_os = "tvos",
1738                    target_os = "visionos",
1739                    target_os = "watchos",
1740                )) {
1741                    socket.io_readiness.borrow_mut().writable = false;
1742                    this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;
1743                } else {
1744                    // On hosts which don't use the `epoll` or `kqueue` backends, a short write
1745                    // doesn't imply a full write buffer. However, the target we are emulating might
1746                    // guarantee this behavior. To prevent applications from being stuck on such
1747                    // targets waiting on a new readiness event, we emit a new edge which still
1748                    // contains a writable readiness. This should trick the applications into trying
1749                    // another write which would then return EWOULDBLOCK should it really be full.
1750                    // This results in an unrealistic execution but we don't have another way of
1751                    // finding out whether the write buffer is full. The "default case" of linux
1752                    // host and linux target isn't affected by this.
1753                    this.update_epoll_active_events(socket.clone(), /* force_edge */ true)?;
1754                }
1755                interp_ok(result)
1756            }
1757            result => interp_ok(result),
1758        }
1759    }
1760
1761    /// Block the thread until we can receive bytes from the connected socket
1762    /// or an error occurred.
1763    ///
1764    /// This recursively calls itself should the operation still block for some reason.
1765    ///
1766    /// **Note**: This function is only safe to call when having previously ensured
1767    /// that the socket is in [`SocketState::Connected`].
1768    fn block_for_recv(
1769        &mut self,
1770        socket: FileDescriptionRef<Socket>,
1771        deadline: Option<Deadline>,
1772        buffer_ptr: Pointer,
1773        length: usize,
1774        should_peek: bool,
1775        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1776    ) -> InterpResult<'tcx> {
1777        let this = self.eval_context_mut();
1778        this.block_thread_for_io(
1779            socket.clone(),
1780            BlockingIoInterest::Read,
1781            deadline.clone(),
1782            callback!(@capture<'tcx> {
1783                socket: FileDescriptionRef<Socket>,
1784                deadline: Option<Deadline>,
1785                buffer_ptr: Pointer,
1786                length: usize,
1787                should_peek: bool,
1788                finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1789            } |this, kind: UnblockKind| {
1790                // Remove the blocking I/O interest for unblocking this thread.
1791                this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
1792
1793                match kind {
1794                    UnblockKind::Ready => { /* fall-through to below */ },
1795                    // When the read timeout is exceeded EAGAIN/EWOULDBLOCK is returned.
1796                    UnblockKind::TimedOut => return finish.call(this, Err(LibcError("EWOULDBLOCK")))
1797                }
1798
1799                match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? {
1800                    Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {
1801                        // We need to block the thread again as it would still block.
1802                        this.block_for_recv(socket, deadline, buffer_ptr, length, should_peek, finish)
1803                    },
1804                    result => finish.call(this, result)
1805                }
1806            }),
1807        )
1808    }
1809
1810    /// Attempt to receive bytes from the connected socket in a non-blocking manner.
1811    ///
1812    /// **Note**: This function is only safe to call when having previously ensured
1813    /// that the socket is in [`SocketState::Connected`].
1814    fn try_non_block_recv(
1815        &mut self,
1816        socket: &FileDescriptionRef<Socket>,
1817        buffer_ptr: Pointer,
1818        length: usize,
1819        should_peek: bool,
1820    ) -> InterpResult<'tcx, Result<usize, IoError>> {
1821        let this = self.eval_context_mut();
1822
1823        let SocketState::Connected(stream) = &mut *socket.state.borrow_mut() else {
1824            panic!("try_non_block_recv must only be called when the socket is connected")
1825        };
1826
1827        // This is a *non-blocking* read/peek.
1828        let result = this.read_from_host(
1829            |buf| {
1830                if should_peek { stream.peek(buf) } else { stream.read(buf) }
1831            },
1832            length,
1833            buffer_ptr,
1834        )?;
1835        match result {
1836            Err(IoError::HostError(e))
1837                if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::WouldBlock) =>
1838            {
1839                // We know that the source is not readable so we need to update it's readiness.
1840                socket.io_readiness.borrow_mut().readable = false;
1841                this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;
1842
1843                // On Windows hosts, `recv` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK
1844                // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK.
1845                interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into())))
1846            }
1847            Ok(bytes_read) if !should_peek && bytes_read < length && bytes_read > 0 => {
1848                // We had a short read (and were not peeking). (Note that reading 0 bytes is guaranteed
1849                // to indicate EOF, and can never happen spuriously, so we have to exclude that case.)
1850                // On Unix hosts using the `epoll` and `kqueue` backends, a short read means that the
1851                // read buffer is empty. We update the readiness accordingly, which means that next time
1852                // we see "readable" we will report an epoll edge. Some applications (e.g. tokio) rely on
1853                // this behavior; see
1854                // <https://github.com/tokio-rs/tokio/blob/HEAD/tokio/src/io/poll_evented.rs#L190-L210>
1855                if cfg!(any(
1856                    // epoll
1857                    target_os = "android",
1858                    target_os = "illumos",
1859                    target_os = "linux",
1860                    target_os = "redox",
1861                    // kqueue
1862                    target_os = "dragonfly",
1863                    target_os = "freebsd",
1864                    target_os = "ios",
1865                    target_os = "macos",
1866                    target_os = "netbsd",
1867                    target_os = "openbsd",
1868                    target_os = "tvos",
1869                    target_os = "visionos",
1870                    target_os = "watchos",
1871                )) {
1872                    socket.io_readiness.borrow_mut().readable = false;
1873                    this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;
1874                } else {
1875                    // On hosts which don't use the `epoll` or `kqueue` backends, a short read
1876                    // doesn't imply an empty read buffer. However, the target we are emulating
1877                    // might guarantee this behavior. To prevent applications from being stuck on
1878                    // such targets waiting on a new readiness event, we emit a new edge which still
1879                    // contains a readable readiness. This should trick the applications into trying
1880                    // another read which would then return EWOULDBLOCK should it really be empty.
1881                    // This results in an unrealistic execution but we don't have another way of
1882                    // finding out whether the read buffer is empty. The "default case" of linux
1883                    // host and linux target isn't affected by this.
1884                    this.update_epoll_active_events(socket.clone(), /* force_edge */ true)?;
1885                }
1886                interp_ok(result)
1887            }
1888            result => interp_ok(result),
1889        }
1890    }
1891
1892    // Execute the provided callback function when the socket is either in
1893    // [`SocketState::Connected`] or an error occurred.
1894    /// If the socket is currently neither in the [`SocketState::Connecting`] nor
1895    /// the [`SocketState::Connecting`] state, [`Err`] is returned.
1896    /// When the callback function is called with [`Ok`], then we're guaranteed
1897    /// that the socket is in the [`SocketState::Connected`] state.
1898    ///
1899    /// This method internally calls `ensure_not_failed` and thus an unsupported
1900    /// error is thrown should `socket` be in [`SocketState::ConnectionFailed`].
1901    ///
1902    /// This function can optionally also block until either an error occurred or
1903    /// the socket reached the [`SocketState::Connected`] state.
1904    fn ensure_connected(
1905        &mut self,
1906        socket: FileDescriptionRef<Socket>,
1907        deadline: Option<Deadline>,
1908        foreign_name: &'static str,
1909        action: DynMachineCallback<'tcx, Result<(), ()>>,
1910    ) -> InterpResult<'tcx> {
1911        let this = self.eval_context_mut();
1912
1913        let state = socket.state.borrow();
1914        match &*state {
1915            SocketState::Connecting(_) => { /* fall-through to below */ }
1916            SocketState::Connected(_) => {
1917                drop(state);
1918                return action.call(this, Ok(()));
1919            }
1920            _ => {
1921                drop(state);
1922                this.ensure_not_failed(&socket, foreign_name)?;
1923                return action.call(this, Err(()));
1924            }
1925        };
1926
1927        drop(state);
1928
1929        // We're currently connecting. Since the underlying mio socket is non-blocking,
1930        // the only way to determine whether we are done connecting is by polling.
1931
1932        this.block_thread_for_io(
1933            socket.clone(),
1934            BlockingIoInterest::Write,
1935            deadline,
1936            callback!(
1937                @capture<'tcx> {
1938                    socket: FileDescriptionRef<Socket>,
1939                    foreign_name: &'static str,
1940                    action: DynMachineCallback<'tcx, Result<(), ()>>,
1941                } |this, kind: UnblockKind| {
1942                    // Remove the blocking I/O interest for unblocking this thread.
1943                    this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
1944
1945                    if UnblockKind::TimedOut == kind {
1946                        // This then means that the socket is not yet connected.
1947                        return action.call(this, Err(()))
1948                    }
1949
1950                    // The thread woke up because it's ready, indicating a writeable or error event.
1951
1952                    let state = socket.state.borrow();
1953                    match &*state {
1954                        SocketState::Connecting(_) => { /* fall-through to below */ },
1955                        SocketState::Connected(_) => {
1956                            drop(state);
1957                            // This can happen because we blocked the thread:
1958                            // maybe another thread "upgraded" the connection in the meantime.
1959                            return action.call(this, Ok(()))
1960                        },
1961                        _ => {
1962                            drop(state);
1963                            // We ensured that we only block when we're currently connecting.
1964                            // Since this thread just got rescheduled, it could be that another
1965                            // thread realized that the connection failed and we're thus in
1966                            // an "invalid state".
1967                            this.ensure_not_failed(&socket, foreign_name)?;
1968                            return action.call(this, Err(()))
1969                        }
1970                    };
1971
1972                    drop(state);
1973
1974                    // Set `socket.error` if `socket` currently has an error.
1975                    this.update_last_error(&socket);
1976
1977                    if socket.error.borrow().is_some() {
1978                        // There was an error during connecting.
1979                        // It's the program's responsibility to read SO_ERROR itself.
1980                        return action.call(this, Err(()))
1981                    }
1982
1983                    // There was no error during connecting. Mio advises also reading the peer address
1984                    // to ensure that socket is actually connected and that it wasn't a spurious wake-up:
1985                    // <https://docs.rs/mio/latest/mio/net/struct.TcpStream.html#notes>
1986                    //
1987                    // Attempting to read the peer address would introduce an edge-case where the
1988                    // write end of the socket could already be shutdown before it received a
1989                    // writable event. When we then call [`TcpStream::peer_addr`] we receive an
1990                    // error. This would need extra state for storing whether the write end was
1991                    // manually closed using `shutdown`.
1992                    // Also, tokio doesn't read the peer address and everything seems to be fine,
1993                    // so we don't do that either:
1994                    // <https://github.com/tokio-rs/mio/issues/1942#issuecomment-4162607761>
1995                    // In other words, we are assuming that there will be no spurious
1996                    // wakeups while establishing the connection.
1997
1998                    // The connection is established.
1999
2000                    // Temporarily use dummy state to take ownership of the stream.
2001                    let mut state = socket.state.borrow_mut();
2002                    let SocketState::Connecting(stream) = std::mem::replace(&mut*state, SocketState::Initial) else {
2003                        // At the start of the function we ensured that we're currently connecting.
2004                        unreachable!()
2005                    };
2006                    *state = SocketState::Connected(stream);
2007                    drop(state);
2008                    action.call(this, Ok(()))
2009                }
2010            ),
2011        )
2012    }
2013
2014    /// Ensure that `socket` is not in the [`SocketState::ConnectionFailed`] state.
2015    /// If `socket` is currently in [`SocketState::ConnectionFailed`], an unsupported
2016    /// error is thrown.
2017    fn ensure_not_failed(
2018        &self,
2019        socket: &FileDescriptionRef<Socket>,
2020        foreign_name: &'static str,
2021    ) -> InterpResult<'tcx> {
2022        if let SocketState::ConnectionFailed(_) = &*socket.state.borrow() {
2023            throw_unsup_format!(
2024                "{foreign_name}: sockets are in an unspecified state after a failed `connect`; \
2025                any operation on such a socket is thus unsupported"
2026            );
2027        } else {
2028            interp_ok(())
2029        }
2030    }
2031
2032    /// Check whether the underlying host socket of `socket` contains an error.
2033    /// If there is an error, we store it in `socket.error`.
2034    ///
2035    /// Should `socket` be in the [`SocketState::Connecting`] state whilst there is
2036    /// an error on the host socket, we transition into the [`SocketState::ConnectionFailed`]
2037    /// state because we know that `socket` can no longer successfully establish a
2038    /// connection.
2039    fn update_last_error(&self, socket: &FileDescriptionRef<Socket>) {
2040        let mut state = socket.state.borrow_mut();
2041
2042        let new_error = match &*state {
2043            SocketState::Listening(listener) =>
2044                listener.take_error().expect("Reading SO_ERROR should not fail"),
2045            SocketState::Connecting(stream) | SocketState::Connected(stream) =>
2046                stream.take_error().expect("Reading SO_ERROR should not fail"),
2047            SocketState::Initial | SocketState::Bound(_) | SocketState::ConnectionFailed(_) => None,
2048        };
2049
2050        let Some(new_error) = new_error else { return };
2051
2052        // Store the error such that we can return it when
2053        // `getsockopt(SOL_SOCKET, SO_ERROR, ...)` is called on the socket.
2054        socket.error.replace(Some(new_error));
2055
2056        if matches!(&*state, SocketState::Connecting(_)) {
2057            // After reading an error on a connecting socket, we know that
2058            // the connection won't be established anymore. By the POSIX
2059            // specification, the socket is now in an unspecified state.
2060            // We thus change the socket state to `ConnectionFailed`.
2061
2062            // Temporarily use dummy state to take ownership of the stream.
2063            let SocketState::Connecting(stream) =
2064                std::mem::replace(&mut *state, SocketState::Initial)
2065            else {
2066                unreachable!()
2067            };
2068            *state = SocketState::ConnectionFailed(stream);
2069        }
2070    }
2071}
2072
2073impl VisitProvenance for FileDescriptionRef<Socket> {
2074    // A socket doesn't contain any references to machine memory
2075    // and thus we don't need to propagate the visit.
2076    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
2077}
2078
2079impl SourceFileDescription for Socket {
2080    fn with_source(&self, f: &mut dyn FnMut(&mut dyn Source) -> io::Result<()>) -> io::Result<()> {
2081        let mut state = self.state.borrow_mut();
2082        match &mut *state {
2083            SocketState::Listening(listener) => f(listener),
2084            SocketState::Connecting(stream)
2085            | SocketState::Connected(stream)
2086            | SocketState::ConnectionFailed(stream) => f(stream),
2087            // We never try adding a socket which is not backed by a real socket to the poll registry.
2088            _ => unreachable!(),
2089        }
2090    }
2091
2092    fn get_readiness_mut(&self) -> RefMut<'_, BlockingIoSourceReadiness> {
2093        self.io_readiness.borrow_mut()
2094    }
2095}