Skip to main content

miri/shims/unix/
tcp_socket.rs

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