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