Skip to main content

miri/shims/unix/
tcp_socket.rs

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