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