1use std::cell::{Cell, RefCell, RefMut};
2use std::io;
3use std::io::Read;
4use std::net::{Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4};
5use std::sync::atomic::AtomicBool;
6use std::time::Duration;
7
8use mio::event::Source;
9use mio::net::{TcpListener, TcpStream};
10use rustc_const_eval::interpret::{InterpResult, interp_ok};
11use rustc_middle::throw_unsup_format;
12use rustc_target::spec::Os;
13
14use crate::shims::files::{EvalContextExt as _, FdNum, FileDescription, FileDescriptionRef};
15use crate::shims::unix::UnixFileDescription;
16use crate::shims::unix::socket::{SocketFamily, UnixSocketFileDescription};
17use crate::*;
18
19#[derive(Debug)]
20enum SocketState {
21 Initial,
23 Bound(SocketAddr),
26 Listening(TcpListener),
29 Connecting(TcpStream),
33 Connected(TcpStream),
39 ConnectionFailed(TcpStream),
46}
47
48#[derive(Debug)]
49pub(super) struct TcpSocket {
50 family: SocketFamily,
53 state: RefCell<SocketState>,
55 is_non_block: Cell<bool>,
57 io_readiness: RefCell<Readiness>,
59 error: RefCell<Option<io::Error>>,
61 read_timeout: Cell<Option<Duration>>,
67 write_timeout: Cell<Option<Duration>>,
73 watched: ReadinessWatched,
75}
76
77impl TcpSocket {
78 pub fn new(family: SocketFamily, is_non_block: bool) -> Self {
79 TcpSocket {
80 family,
81 state: RefCell::new(SocketState::Initial),
82 is_non_block: Cell::new(is_non_block),
83 io_readiness: RefCell::new(Readiness::EMPTY),
84 error: RefCell::new(None),
85 read_timeout: Cell::new(None),
86 write_timeout: Cell::new(None),
87 watched: ReadinessWatched::default(),
88 }
89 }
90}
91
92impl FileDescription for TcpSocket {
93 fn name(&self) -> &'static str {
94 "socket"
95 }
96
97 fn read<'tcx>(
98 self: FileDescriptionRef<Self>,
99 communicate_allowed: bool,
100 ptr: Pointer,
101 len: usize,
102 ecx: &mut MiriInterpCx<'tcx>,
103 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
104 ) -> InterpResult<'tcx> {
105 self.recv(
106 communicate_allowed,
107 ptr,
108 len,
109 false,
110 false,
111 ecx,
112 finish,
113 )
114 }
115
116 fn write<'tcx>(
117 self: FileDescriptionRef<Self>,
118 communicate_allowed: bool,
119 ptr: Pointer,
120 len: usize,
121 ecx: &mut MiriInterpCx<'tcx>,
122 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
123 ) -> InterpResult<'tcx> {
124 self.send(communicate_allowed, ptr, len, false, ecx, finish)
125 }
126
127 fn short_fd_operations(&self) -> bool {
128 false
133 }
134
135 fn as_unix<'tcx>(
136 self: FileDescriptionRef<Self>,
137 _ecx: &MiriInterpCx<'tcx>,
138 ) -> FileDescriptionRef<dyn UnixFileDescription> {
139 self
140 }
141
142 fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
143 let mut flags = ecx.eval_libc_i32("O_RDWR");
144
145 if self.is_non_block.get() {
146 flags |= ecx.eval_libc_i32("O_NONBLOCK");
147 }
148
149 interp_ok(Scalar::from_i32(flags))
150 }
151
152 fn set_flags<'tcx>(
153 &self,
154 mut flag: i32,
155 ecx: &mut MiriInterpCx<'tcx>,
156 ) -> InterpResult<'tcx, Scalar> {
157 let o_nonblock = ecx.eval_libc_i32("O_NONBLOCK");
158
159 if flag & o_nonblock == o_nonblock {
161 self.is_non_block.set(true);
162 flag &= !o_nonblock;
163 } else {
164 self.is_non_block.set(false);
165 }
166
167 if flag != 0 {
169 throw_unsup_format!("fcntl: only O_NONBLOCK is supported for sockets")
170 }
171
172 interp_ok(Scalar::from_i32(0))
173 }
174
175 fn readiness_watched(&self) -> Option<&ReadinessWatched> {
176 Some(&self.watched)
177 }
178
179 fn readiness(&self) -> Readiness {
180 *self.io_readiness.borrow()
181 }
182}
183
184impl UnixFileDescription for TcpSocket {
185 fn ioctl<'tcx>(
186 &self,
187 op: Scalar,
188 arg: Option<&OpTy<'tcx>>,
189 ecx: &mut MiriInterpCx<'tcx>,
190 ) -> InterpResult<'tcx, i32> {
191 assert!(ecx.machine.communicate(), "cannot have `TcpSocket` with isolation enabled!");
192
193 let fionbio = ecx.eval_libc("FIONBIO");
194
195 if op == fionbio {
196 if !matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android | Os::MacOs | Os::FreeBsd)
199 {
200 throw_unsup_format!(
205 "ioctl: setting FIONBIO on sockets is unsupported on target {}",
206 ecx.tcx.sess.target.os
207 );
208 }
209
210 let Some(value_ptr) = arg else {
211 throw_ub_format!("ioctl: setting FIONBIO on sockets requires a third argument");
212 };
213 let value = ecx.deref_pointer_as(value_ptr, ecx.machine.layouts.i32)?;
214 let non_block = ecx.read_scalar(&value)?.to_i32()? != 0;
215 self.is_non_block.set(non_block);
216 return interp_ok(0);
217 }
218
219 throw_unsup_format!("ioctl: unsupported operation {op:#x} on socket");
220 }
221
222 fn as_socket<'tcx>(
223 self: FileDescriptionRef<Self>,
224 _ecx: &MiriInterpCx<'tcx>,
225 ) -> Option<FileDescriptionRef<dyn UnixSocketFileDescription>> {
226 Some(self)
227 }
228}
229
230impl UnixSocketFileDescription for TcpSocket {
231 fn bind<'tcx>(
232 self: FileDescriptionRef<TcpSocket>,
233 communicate_allowed: bool,
234 address: SocketAddr,
235 ecx: &mut MiriInterpCx<'tcx>,
236 ) -> InterpResult<'tcx, Result<(), IoError>> {
237 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
238 ecx.ensure_not_failed(&self, "bind")?;
239
240 let mut state = self.state.borrow_mut();
241
242 match *state {
243 SocketState::Initial => {
244 let address_family = match &address {
245 SocketAddr::V4(_) => SocketFamily::IPv4,
246 SocketAddr::V6(_) => SocketFamily::IPv6,
247 };
248
249 if self.family != address_family {
250 let err = if matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android) {
253 LibcError("EINVAL")
256 } else {
257 LibcError("EAFNOSUPPORT")
261 };
262 return interp_ok(Err(err));
263 }
264
265 *state = SocketState::Bound(address);
266 }
267 SocketState::Connecting(_) | SocketState::Connected(_) =>
268 throw_unsup_format!(
269 "bind: tcp socket is already connected and binding a
270 connected socket is unsupported"
271 ),
272 SocketState::Bound(_) | SocketState::Listening(_) =>
273 throw_unsup_format!(
274 "bind: tcp socket is already bound and binding a socket \
275 multiple times is unsupported"
276 ),
277 SocketState::ConnectionFailed(_) => unreachable!(),
278 }
279
280 interp_ok(Ok(()))
281 }
282
283 fn listen<'tcx>(
284 self: FileDescriptionRef<TcpSocket>,
285 communicate_allowed: bool,
286 _backlog: i32,
288 ecx: &mut MiriInterpCx<'tcx>,
289 ) -> InterpResult<'tcx, Result<(), IoError>> {
290 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
291 ecx.ensure_not_failed(&self, "listen")?;
292
293 let mut state = self.state.borrow_mut();
294
295 match *state {
296 SocketState::Bound(socket_addr) =>
297 match TcpListener::bind(socket_addr) {
298 Ok(listener) => {
299 *state = SocketState::Listening(listener);
300 drop(state);
301 ecx.machine.blocking_io.register(self);
304 }
305 Err(e) => return interp_ok(Err(IoError::HostError(e))),
306 },
307 SocketState::Initial => {
308 throw_unsup_format!(
309 "listen: listening on a tcp socket which isn't bound is unsupported"
310 )
311 }
312 SocketState::Listening(_) => {
313 throw_unsup_format!(
314 "listen: listening on a tcp socket multiple times is unsupported"
315 )
316 }
317 SocketState::Connecting(_) | SocketState::Connected(_) => {
318 throw_unsup_format!("listen: listening on a connected tcp socket is unsupported")
319 }
320 SocketState::ConnectionFailed(_) => unreachable!(),
321 }
322
323 interp_ok(Ok(()))
324 }
325
326 fn accept<'tcx>(
327 self: FileDescriptionRef<Self>,
328 communicate_allowed: bool,
329 is_client_sock_non_block: bool,
330 ecx: &mut MiriInterpCx<'tcx>,
331 finish: DynMachineCallback<'tcx, Result<(FdNum, SocketAddr), IoError>>,
332 ) -> InterpResult<'tcx> {
333 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
334
335 if !matches!(*self.state.borrow(), SocketState::Listening(_)) {
336 throw_unsup_format!(
337 "accept: accepting incoming connections is only allowed when tcp socket is listening"
338 )
339 };
340
341 if self.is_non_block.get() {
342 let result = ecx.try_non_block_accept(&self, is_client_sock_non_block)?;
345 finish.call(ecx, result)
346 } else {
347 if self.read_timeout.get().is_some() {
351 throw_unsup_format!(
356 "accept: blocking tcp accept is not supported when SO_RCVTIMEO is non-zero"
357 )
358 }
359
360 ecx.block_for_accept(self, is_client_sock_non_block, finish)
361 }
362 }
363
364 fn connect<'tcx>(
365 self: FileDescriptionRef<Self>,
366 communicate_allowed: bool,
367 address: SocketAddr,
368 ecx: &mut MiriInterpCx<'tcx>,
369 finish: DynMachineCallback<'tcx, Result<(), IoError>>,
370 ) -> InterpResult<'tcx> {
371 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
372 ecx.ensure_not_failed(&self, "connect")?;
373
374 match &*self.state.borrow() {
375 SocketState::Initial => { }
376 SocketState::Connecting(_) => return finish.call(ecx, Err(LibcError("EALREADY"))),
378 _ =>
382 throw_unsup_format!(
383 "connect: connecting is only supported for tcp sockets which are neither \
384 bound, listening nor already connected"
385 ),
386 }
387
388 match TcpStream::connect(address) {
391 Ok(stream) => {
392 *self.state.borrow_mut() = SocketState::Connecting(stream);
393 ecx.machine.blocking_io.register(self.clone());
396 }
397 Err(e) => return finish.call(ecx, Err(IoError::HostError(e))),
398 };
399
400 if self.is_non_block.get() {
401 finish.call(ecx, Err(LibcError("EINPROGRESS")))
408 } else {
409 if self.write_timeout.get().is_some() {
413 throw_unsup_format!(
418 "connect: blocking connect is not supported when SO_SNDTIMEO is non-zero"
419 )
420 }
421
422 let socket = self;
423 ecx.ensure_connected(
424 socket.clone(),
425 None,
426 "connect",
427 callback!(
428 @capture<'tcx> {
429 socket: FileDescriptionRef<TcpSocket>,
430 finish: DynMachineCallback<'tcx, Result<(), IoError>>,
431 } |this, result: Result<(), ()>| {
432 if result.is_err() {
433 let err = socket.error.take().unwrap();
437 finish.call(this, Err(IoError::HostError(err)))
438 } else {
439 finish.call(this, Ok(()))
440 }
441 }
442 ),
443 )
444 }
445 }
446
447 fn send<'tcx>(
448 self: FileDescriptionRef<Self>,
449 communicate_allowed: bool,
450 ptr: Pointer,
451 len: usize,
452 is_non_block: bool,
453 ecx: &mut MiriInterpCx<'tcx>,
454 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
455 ) -> InterpResult<'tcx> {
456 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
457
458 let is_non_block = is_non_block || self.is_non_block.get();
459 let deadline = ecx.action_deadline(is_non_block, self.write_timeout.get());
460
461 let socket = self;
462 ecx.ensure_connected(
463 socket.clone(),
464 deadline.clone(),
465 "send",
466 callback!(
467 @capture<'tcx> {
468 socket: FileDescriptionRef<TcpSocket>,
469 deadline: Option<Deadline>,
470 ptr: Pointer,
471 len: usize,
472 is_non_block: bool,
473 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
474 } |this, result: Result<(), ()>| {
475 if result.is_err() {
476 return finish.call(this, Err(LibcError("ENOTCONN")))
477 }
478
479 if is_non_block {
480 let result = this.try_non_block_send(&socket, ptr, len)?;
483 finish.call(this, result)
484 } else {
485 this.block_for_send(socket, deadline, ptr, len, finish)
488 }
489 }
490 ),
491 )
492 }
493
494 fn recv<'tcx>(
495 self: FileDescriptionRef<Self>,
496 communicate_allowed: bool,
497 ptr: Pointer,
498 len: usize,
499 is_peek: bool,
500 is_non_block: bool,
501 ecx: &mut MiriInterpCx<'tcx>,
502 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
503 ) -> InterpResult<'tcx> {
504 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
505
506 let is_non_block = is_non_block || self.is_non_block.get();
507 let deadline = ecx.action_deadline(is_non_block, self.read_timeout.get());
508
509 let socket = self;
510 ecx.ensure_connected(
511 socket.clone(),
512 deadline.clone(),
513 "recv",
514 callback!(
515 @capture<'tcx> {
516 socket: FileDescriptionRef<TcpSocket>,
517 deadline: Option<Deadline>,
518 ptr: Pointer,
519 len: usize,
520 is_peek: bool,
521 is_non_block: bool,
522 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
523 } |this, result: Result<(), ()>| {
524 if result.is_err() {
525 return finish.call(this, Err(LibcError("ENOTCONN")))
526 }
527
528 if is_non_block {
529 let result = this.try_non_block_recv(&socket, ptr, len, is_peek)?;
532 finish.call(this, result)
533 } else {
534 this.block_for_recv(socket, deadline, ptr, len, is_peek, finish)
537 }
538 }
539 ),
540 )
541 }
542
543 fn setsockopt<'tcx>(
544 self: FileDescriptionRef<Self>,
545 level: i32,
546 option: i32,
547 value_ptr: Pointer,
548 value_len: u64,
549 ecx: &mut MiriInterpCx<'tcx>,
550 ) -> InterpResult<'tcx, Result<(), IoError>> {
551 if level == ecx.eval_libc_i32("SOL_SOCKET") {
552 let opt_so_rcvtimeo = ecx.eval_libc_i32("SO_RCVTIMEO");
553 let opt_so_sndtimeo = ecx.eval_libc_i32("SO_SNDTIMEO");
554 let opt_so_reuseaddr = ecx.eval_libc_i32("SO_REUSEADDR");
555
556 if matches!(ecx.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::NetBsd) {
557 let opt_so_nosigpipe = ecx.eval_libc_i32("SO_NOSIGPIPE");
559
560 if option == opt_so_nosigpipe {
561 if value_len != 4 {
562 return interp_ok(Err(LibcError("EINVAL")));
564 }
565 let option_value = ecx.ptr_to_mplace(value_ptr, ecx.machine.layouts.i32);
566 let _val = ecx.read_scalar(&option_value)?.to_i32()?;
567 return interp_ok(Ok(()));
570 }
571 }
572
573 if option == opt_so_rcvtimeo || option == opt_so_sndtimeo {
574 let timeval_layout = ecx.libc_ty_layout("timeval");
575 let option_value = ecx.ptr_to_mplace(value_ptr, timeval_layout);
576
577 let timeout = match ecx.read_timeval(&option_value)? {
578 None => return interp_ok(Err(LibcError("EINVAL"))),
579 Some(Duration::ZERO) => None,
580 Some(duration) => Some(duration),
581 };
582
583 if option == opt_so_rcvtimeo {
584 self.read_timeout.set(timeout);
585 } else {
586 self.write_timeout.set(timeout);
587 }
588
589 return interp_ok(Ok(()));
590 }
591
592 if option == opt_so_reuseaddr {
593 if value_len != 4 {
594 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 return interp_ok(Ok(()));
602 } else {
603 throw_unsup_format!(
604 "setsockopt: option {option:#x} is unsupported for level SOL_SOCKET",
605 );
606 }
607 } else if level == ecx.eval_libc_i32("IPPROTO_IP") {
608 let opt_ip_ttl = ecx.eval_libc_i32("IP_TTL");
609
610 if option == opt_ip_ttl {
611 if value_len != 4 {
612 return interp_ok(Err(LibcError("EINVAL")));
614 }
615 let option_value = ecx.ptr_to_mplace(value_ptr, ecx.machine.layouts.u32);
616 let ttl = ecx.read_scalar(&option_value)?.to_u32()?;
617
618 let result = match &*self.state.borrow() {
619 SocketState::Initial | SocketState::Bound(_) =>
620 throw_unsup_format!(
621 "setsockopt: setting option IP_TTL on level IPPROTO_IP is only supported \
622 on connected and listening tcp sockets"
623 ),
624 SocketState::Listening(listener) => listener.set_ttl(ttl),
625 SocketState::Connecting(stream) | SocketState::Connected(stream) =>
626 stream.set_ttl(ttl),
627 SocketState::ConnectionFailed(_) => unreachable!(),
628 };
629
630 return match result {
631 Ok(_) => interp_ok(Ok(())),
632 Err(e) => interp_ok(Err(IoError::HostError(e))),
633 };
634 } else {
635 throw_unsup_format!(
636 "setsockopt: option {option:#x} is unsupported for level IPPROTO_IP",
637 );
638 }
639 } else if level == ecx.eval_libc_i32("IPPROTO_TCP") {
640 let opt_tcp_nodelay = ecx.eval_libc_i32("TCP_NODELAY");
641
642 if option == opt_tcp_nodelay {
643 if value_len != 4 {
644 return interp_ok(Err(LibcError("EINVAL")));
646 }
647 let option_value = ecx.ptr_to_mplace(value_ptr, ecx.machine.layouts.i32);
648 let nodelay = ecx.read_scalar(&option_value)?.to_i32()? != 0;
649
650 let result = match &*self.state.borrow() {
651 SocketState::Initial | SocketState::Bound(_) | SocketState::Listening(_) =>
652 throw_unsup_format!(
653 "setsockopt: setting option TCP_NODELAY on level IPPROTO_TCP is only supported \
654 on connected tcp sockets"
655 ),
656 SocketState::Connecting(stream) | SocketState::Connected(stream) =>
657 stream.set_nodelay(nodelay),
658 SocketState::ConnectionFailed(_) => unreachable!(),
659 };
660
661 return match result {
662 Ok(_) => interp_ok(Ok(())),
663 Err(e) => interp_ok(Err(IoError::HostError(e))),
664 };
665 } else {
666 throw_unsup_format!(
667 "setsockopt: option {option:#x} is unsupported for level IPPROTO_TCP"
668 );
669 }
670 }
671
672 throw_unsup_format!(
673 "setsockopt: level {level:#x} is unsupported, only SOL_SOCKET, IPPROTO_IP \
674 and IPPROTO_TCP are allowed"
675 );
676 }
677
678 fn getsockopt<'tcx>(
679 self: FileDescriptionRef<Self>,
680 level: i32,
681 option: i32,
682 ecx: &mut MiriInterpCx<'tcx>,
683 ) -> InterpResult<'tcx, Result<MPlaceTy<'tcx>, IoError>> {
684 if level == ecx.eval_libc_i32("SOL_SOCKET") {
685 let opt_so_error = ecx.eval_libc_i32("SO_ERROR");
686 let opt_so_rcvtimeo = ecx.eval_libc_i32("SO_RCVTIMEO");
687 let opt_so_sndtimeo = ecx.eval_libc_i32("SO_SNDTIMEO");
688
689 if option == opt_so_error {
690 ecx.update_last_error(&self);
693
694 let return_value = match self.error.take() {
695 Some(err) => ecx.io_error_to_errnum(err)?.to_i32()?,
696 None => 0,
698 };
699
700 self.error.replace(None);
702
703 self.io_readiness.borrow_mut().error = false;
706 ecx.update_fd_readiness(self, ReadinessUpdateFlags::DEFAULT)?;
707
708 let value_buffer = ecx.allocate(ecx.machine.layouts.i32, MemoryKind::Stack)?;
710 ecx.write_int(return_value, &value_buffer)?;
711 interp_ok(Ok(value_buffer))
712 } else if option == opt_so_rcvtimeo || option == opt_so_sndtimeo {
713 let timeout = if option == opt_so_rcvtimeo {
714 self.read_timeout.get()
715 } else {
716 self.write_timeout.get()
717 }
718 .unwrap_or_default();
719
720 let secs = timeout.as_secs();
721 let usecs = timeout.subsec_micros();
722
723 let timeval_layout = ecx.libc_ty_layout("timeval");
724 let timeval_buffer = ecx.allocate(timeval_layout, MemoryKind::Stack)?;
726
727 let sec_field = ecx.project_field_named(&timeval_buffer, "tv_sec")?;
728 ecx.write_int(secs, &sec_field)?;
729
730 let usec_field = ecx.project_field_named(&timeval_buffer, "tv_usec")?;
731 ecx.write_int(usecs, &usec_field)?;
732
733 interp_ok(Ok(timeval_buffer))
734 } else {
735 throw_unsup_format!(
736 "getsockopt: option {option:#x} is unsupported for level SOL_SOCKET",
737 );
738 }
739 } else if level == ecx.eval_libc_i32("IPPROTO_IP") {
740 let opt_ip_ttl = ecx.eval_libc_i32("IP_TTL");
741
742 if option == opt_ip_ttl {
743 let ttl = match &*self.state.borrow() {
744 SocketState::Initial | SocketState::Bound(_) =>
745 throw_unsup_format!(
746 "getsockopt: reading option IP_TTL on level IPPROTO_IP is only supported \
747 on connected and listening tcp sockets"
748 ),
749 SocketState::Listening(listener) => listener.ttl(),
750 SocketState::Connecting(stream) | SocketState::Connected(stream) =>
751 stream.ttl(),
752 SocketState::ConnectionFailed(_) => unreachable!(),
753 };
754
755 let ttl = match ttl {
756 Ok(ttl) => ttl,
757 Err(e) => return interp_ok(Err(IoError::HostError(e))),
758 };
759
760 let value_buffer = ecx.allocate(ecx.machine.layouts.u32, MemoryKind::Stack)?;
762 ecx.write_int(ttl, &value_buffer)?;
763 interp_ok(Ok(value_buffer))
764 } else {
765 throw_unsup_format!(
766 "getsockopt: option {option:#x} is unsupported for level IPPROTO_IP",
767 );
768 }
769 } else if level == ecx.eval_libc_i32("IPPROTO_TCP") {
770 let opt_tcp_nodelay = ecx.eval_libc_i32("TCP_NODELAY");
771
772 if option == opt_tcp_nodelay {
773 let nodelay = match &*self.state.borrow() {
774 SocketState::Initial | SocketState::Bound(_) | SocketState::Listening(_) =>
775 throw_unsup_format!(
776 "getsockopt: reading option TCP_NODELAY on level IPPROTO_TCP is only supported \
777 on connected tcp sockets"
778 ),
779 SocketState::Connecting(stream) | SocketState::Connected(stream) =>
780 stream.nodelay(),
781 SocketState::ConnectionFailed(_) => unreachable!(),
782 };
783
784 let nodelay = match nodelay {
785 Ok(nodelay) => nodelay,
786 Err(e) => return interp_ok(Err(IoError::HostError(e))),
787 };
788
789 let value_buffer = ecx.allocate(ecx.machine.layouts.i32, MemoryKind::Stack)?;
791 ecx.write_int(i32::from(nodelay), &value_buffer)?;
792 interp_ok(Ok(value_buffer))
793 } else {
794 throw_unsup_format!(
795 "getsockopt: option {option:#x} is unsupported for level IPPROTO_TCP"
796 );
797 }
798 } else {
799 throw_unsup_format!(
800 "getsockopt: level {level:#x} is unsupported, only SOL_SOCKET, IPPROTO_IP \
801 and IPPROTO_TCP are allowed"
802 )
803 }
804 }
805
806 fn getsockname<'tcx>(
807 self: FileDescriptionRef<Self>,
808 communicate_allowed: bool,
809 ecx: &mut MiriInterpCx<'tcx>,
810 ) -> InterpResult<'tcx, Result<SocketAddr, IoError>> {
811 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
812 ecx.ensure_not_failed(&self, "getsockname")?;
813
814 let state = self.state.borrow();
815
816 let address = match &*state {
817 SocketState::Bound(address) => {
818 if address.port() == 0 {
819 throw_unsup_format!(
823 "getsockname: when the port is 0, getting the tcp socket address before \
824 calling `listen` or `connect` is unsupported"
825 )
826 }
827
828 *address
829 }
830 SocketState::Listening(listener) =>
831 match listener.local_addr() {
832 Ok(address) => address,
833 Err(e) => return interp_ok(Err(IoError::HostError(e))),
834 },
835 SocketState::Connecting(stream) | SocketState::Connected(stream) => {
836 if cfg!(windows) && matches!(&*state, SocketState::Connecting(_)) {
837 static DEDUP: AtomicBool = AtomicBool::new(false);
844 if !DEDUP.swap(true, std::sync::atomic::Ordering::Relaxed) {
845 ecx.emit_diagnostic(NonHaltingDiagnostic::ConnectingSocketGetsockname);
846 }
847 }
848 match stream.local_addr() {
849 Ok(address) => address,
850 Err(e) => return interp_ok(Err(IoError::HostError(e))),
851 }
852 }
853 SocketState::Initial => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)),
856 SocketState::ConnectionFailed(_) => unreachable!(),
857 };
858
859 interp_ok(Ok(address))
860 }
861
862 fn getpeername<'tcx>(
863 self: FileDescriptionRef<Self>,
864 communicate_allowed: bool,
865 ecx: &mut MiriInterpCx<'tcx>,
866 finish: DynMachineCallback<'tcx, Result<SocketAddr, IoError>>,
867 ) -> InterpResult<'tcx> {
868 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
869
870 let socket = self;
871 ecx.ensure_connected(
874 socket.clone(),
875 Some(ecx.machine.monotonic_clock.now().into()),
877 "getpeername",
878 callback!(
879 @capture<'tcx> {
880 socket: FileDescriptionRef<TcpSocket>,
881 finish: DynMachineCallback<'tcx, Result<SocketAddr, IoError>>,
882 } |this, result: Result<(), ()>| {
883 if result.is_err() {
884 return finish.call(this, Err(LibcError("ENOTCONN")))
885 };
886
887 let SocketState::Connected(stream) = &*socket.state.borrow() else {
888 unreachable!()
889 };
890
891 let result = stream.peer_addr().map_err(IoError::HostError);
892 finish.call(this, result)
893 }
894 ),
895 )
896 }
897
898 fn shutdown<'tcx>(
899 self: FileDescriptionRef<Self>,
900 communicate_allowed: bool,
901 how: Shutdown,
902 ecx: &mut MiriInterpCx<'tcx>,
903 ) -> InterpResult<'tcx, Result<(), IoError>> {
904 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
905 ecx.ensure_not_failed(&self, "shutdown")?;
906
907 let state = self.state.borrow();
908
909 let (SocketState::Connecting(stream) | SocketState::Connected(stream)) = &*state else {
910 return interp_ok(Err(LibcError("ENOTCONN")));
911 };
912
913 if let Err(e) = stream.shutdown(how) {
914 return interp_ok(Err(IoError::HostError(e)));
915 };
916
917 drop(state);
918
919 let mut readiness = self.io_readiness.borrow_mut();
925 readiness.read_closed |= matches!(how, Shutdown::Read | Shutdown::Both);
927 readiness.write_closed |= matches!(how, Shutdown::Both);
930 readiness.readable |= matches!(how, Shutdown::Read | Shutdown::Both);
933
934 drop(readiness);
935
936 ecx.update_fd_readiness(self, ReadinessUpdateFlags::DEFAULT)?;
938
939 interp_ok(Ok(()))
940 }
941}
942
943impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
944trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
945 fn action_deadline(
952 &self,
953 is_non_block: bool,
954 action_timeout: Option<Duration>,
955 ) -> Option<Deadline> {
956 let this = self.eval_context_ref();
957
958 if is_non_block {
959 Some(this.machine.monotonic_clock.now().into())
961 } else {
962 action_timeout
963 .map(|duration| this.machine.monotonic_clock.now().add_lossy(duration).into())
964 }
965 }
966
967 fn block_for_accept(
976 &mut self,
977 socket: FileDescriptionRef<TcpSocket>,
978 is_client_sock_nonblock: bool,
979 finish: DynMachineCallback<'tcx, Result<(FdNum, SocketAddr), IoError>>,
980 ) -> InterpResult<'tcx> {
981 let this = self.eval_context_mut();
982 this.block_thread_for_io(
986 socket.clone(),
987 BlockingIoInterest::Read,
988 None,
989 callback!(@capture<'tcx> {
990 socket: FileDescriptionRef<TcpSocket>,
991 is_client_sock_nonblock: bool,
992 finish: DynMachineCallback<'tcx, Result<(FdNum, SocketAddr), IoError>>,
993 } |this, kind: UnblockKind| {
994 this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
996
997 match kind {
998 UnblockKind::Ready => { },
999 UnblockKind::TimedOut => return finish.call(this, Err(LibcError("EWOULDBLOCK")))
1001 }
1002
1003 match this.try_non_block_accept(&socket, is_client_sock_nonblock)? {
1004 Ok((sockfd, addr)) => finish.call(this, Ok((sockfd, addr))),
1005 Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {
1006 this.block_for_accept(socket, is_client_sock_nonblock, finish)
1008 }
1009 Err(e) => finish.call(this, Err(e)),
1010 }
1011 }),
1012 )
1013 }
1014
1015 fn try_non_block_accept(
1022 &mut self,
1023 socket: &FileDescriptionRef<TcpSocket>,
1024 is_client_sock_nonblock: bool,
1025 ) -> InterpResult<'tcx, Result<(FdNum, SocketAddr), IoError>> {
1026 let this = self.eval_context_mut();
1027
1028 let state = socket.state.borrow();
1029 let SocketState::Listening(listener) = &*state else {
1030 panic!(
1031 "try_non_block_accept must only be called when socket is in `SocketState::Listening`"
1032 )
1033 };
1034
1035 let (stream, addr) = match listener.accept() {
1036 Ok(peer) => peer,
1037 Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
1038 socket.io_readiness.borrow_mut().readable = false;
1040 this.update_fd_readiness(socket.clone(), ReadinessUpdateFlags::DEFAULT)?;
1041
1042 return interp_ok(Err(IoError::HostError(e)));
1043 }
1044 Err(e) => return interp_ok(Err(IoError::HostError(e))),
1045 };
1046
1047 let family = match addr {
1048 SocketAddr::V4(_) => SocketFamily::IPv4,
1049 SocketAddr::V6(_) => SocketFamily::IPv6,
1050 };
1051
1052 let fd = this.machine.fds.new_ref(TcpSocket {
1053 family,
1054 state: RefCell::new(SocketState::Connected(stream)),
1055 is_non_block: Cell::new(is_client_sock_nonblock),
1056 io_readiness: RefCell::new(Readiness::EMPTY),
1057 error: RefCell::new(None),
1058 read_timeout: Cell::new(None),
1059 write_timeout: Cell::new(None),
1060 watched: ReadinessWatched::default(),
1061 });
1062 this.machine.blocking_io.register(fd.clone());
1065 let sockfd = this.machine.fds.insert(fd);
1066 interp_ok(Ok((sockfd, addr)))
1067 }
1068
1069 fn block_for_send(
1077 &mut self,
1078 socket: FileDescriptionRef<TcpSocket>,
1079 deadline: Option<Deadline>,
1080 buffer_ptr: Pointer,
1081 length: usize,
1082 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1083 ) -> InterpResult<'tcx> {
1084 let this = self.eval_context_mut();
1085 this.block_thread_for_io(
1089 socket.clone(),
1090 BlockingIoInterest::Write,
1091 deadline.clone(),
1092 callback!(@capture<'tcx> {
1093 socket: FileDescriptionRef<TcpSocket>,
1094 deadline: Option<Deadline>,
1095 buffer_ptr: Pointer,
1096 length: usize,
1097 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1098 } |this, kind: UnblockKind| {
1099 this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
1101
1102 match kind {
1103 UnblockKind::Ready => { },
1104 UnblockKind::TimedOut => return finish.call(this, Err(LibcError("EWOULDBLOCK")))
1106 }
1107
1108 match this.try_non_block_send(&socket, buffer_ptr, length)? {
1109 Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {
1110 this.block_for_send(socket, deadline, buffer_ptr, length, finish)
1112 },
1113 result => finish.call(this, result)
1114 }
1115 }),
1116 )
1117 }
1118
1119 fn try_non_block_send(
1124 &mut self,
1125 socket: &FileDescriptionRef<TcpSocket>,
1126 buffer_ptr: Pointer,
1127 length: usize,
1128 ) -> InterpResult<'tcx, Result<usize, IoError>> {
1129 let this = self.eval_context_mut();
1130
1131 let mut state = socket.state.borrow_mut();
1132 let SocketState::Connected(stream) = &mut *state else {
1133 panic!("try_non_block_send must only be called when the socket is connected")
1134 };
1135
1136 let result = this.write_to_host(stream, length, buffer_ptr)?;
1138
1139 drop(state);
1140
1141 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 socket.io_readiness.borrow_mut().writable = false;
1152 this.update_fd_readiness(socket.clone(), ReadinessUpdateFlags::DEFAULT)?;
1153
1154 interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into())))
1157 }
1158 Ok(bytes_written) if bytes_written < length => {
1159 if cfg!(any(
1165 target_os = "android",
1167 target_os = "illumos",
1168 target_os = "linux",
1169 target_os = "redox",
1170 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 this.update_fd_readiness(socket.clone(), ReadinessUpdateFlags::FORCE_EDGE)?;
1194 }
1195 interp_ok(result)
1196 }
1197 result => interp_ok(result),
1198 }
1199 }
1200
1201 fn block_for_recv(
1209 &mut self,
1210 socket: FileDescriptionRef<TcpSocket>,
1211 deadline: Option<Deadline>,
1212 buffer_ptr: Pointer,
1213 length: usize,
1214 should_peek: bool,
1215 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1216 ) -> InterpResult<'tcx> {
1217 let this = self.eval_context_mut();
1218 this.block_thread_for_io(
1222 socket.clone(),
1223 BlockingIoInterest::Read,
1224 deadline.clone(),
1225 callback!(@capture<'tcx> {
1226 socket: FileDescriptionRef<TcpSocket>,
1227 deadline: Option<Deadline>,
1228 buffer_ptr: Pointer,
1229 length: usize,
1230 should_peek: bool,
1231 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1232 } |this, kind: UnblockKind| {
1233 this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
1235
1236 match kind {
1237 UnblockKind::Ready => { },
1238 UnblockKind::TimedOut => return finish.call(this, Err(LibcError("EWOULDBLOCK")))
1240 }
1241
1242 match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? {
1243 Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {
1244 this.block_for_recv(socket, deadline, buffer_ptr, length, should_peek, finish)
1246 },
1247 result => finish.call(this, result)
1248 }
1249 }),
1250 )
1251 }
1252
1253 fn try_non_block_recv(
1258 &mut self,
1259 socket: &FileDescriptionRef<TcpSocket>,
1260 buffer_ptr: Pointer,
1261 length: usize,
1262 should_peek: bool,
1263 ) -> InterpResult<'tcx, Result<usize, IoError>> {
1264 let this = self.eval_context_mut();
1265
1266 let mut state = socket.state.borrow_mut();
1267 let SocketState::Connected(stream) = &mut *state else {
1268 panic!("try_non_block_recv must only be called when the socket is connected")
1269 };
1270
1271 let result = this.read_from_host(
1273 |buf| {
1274 if should_peek { stream.peek(buf) } else { stream.read(buf) }
1275 },
1276 length,
1277 buffer_ptr,
1278 )?;
1279
1280 drop(state);
1281
1282 match result {
1283 Err(IoError::HostError(e))
1284 if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::WouldBlock) =>
1285 {
1286 socket.io_readiness.borrow_mut().readable = false;
1288 this.update_fd_readiness(socket.clone(), ReadinessUpdateFlags::DEFAULT)?;
1289
1290 interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into())))
1293 }
1294 Ok(bytes_read)
1295 if !should_peek
1296 && bytes_read < length
1297 && bytes_read > 0
1298 && !socket.io_readiness.borrow().read_closed =>
1299 {
1300 if cfg!(any(
1310 target_os = "android",
1312 target_os = "illumos",
1313 target_os = "linux",
1314 target_os = "redox",
1315 target_os = "dragonfly",
1317 target_os = "freebsd",
1318 target_os = "ios",
1319 target_os = "macos",
1320 target_os = "netbsd",
1321 target_os = "openbsd",
1322 target_os = "tvos",
1323 target_os = "visionos",
1324 target_os = "watchos",
1325 )) {
1326 socket.io_readiness.borrow_mut().readable = false;
1327 this.update_fd_readiness(socket.clone(), ReadinessUpdateFlags::DEFAULT)?;
1328 } else {
1329 this.update_fd_readiness(socket.clone(), ReadinessUpdateFlags::FORCE_EDGE)?;
1339 }
1340 interp_ok(result)
1341 }
1342 result => interp_ok(result),
1343 }
1344 }
1345
1346 fn ensure_connected(
1359 &mut self,
1360 socket: FileDescriptionRef<TcpSocket>,
1361 deadline: Option<Deadline>,
1362 foreign_name: &'static str,
1363 action: DynMachineCallback<'tcx, Result<(), ()>>,
1364 ) -> InterpResult<'tcx> {
1365 let this = self.eval_context_mut();
1366
1367 let state = socket.state.borrow();
1368 match &*state {
1369 SocketState::Connecting(_) => { }
1370 SocketState::Connected(_) => {
1371 drop(state);
1372 return action.call(this, Ok(()));
1373 }
1374 _ => {
1375 drop(state);
1376 this.ensure_not_failed(&socket, foreign_name)?;
1377 return action.call(this, Err(()));
1378 }
1379 };
1380
1381 drop(state);
1382
1383 this.block_thread_for_io(
1387 socket.clone(),
1388 BlockingIoInterest::Write,
1389 deadline,
1390 callback!(
1391 @capture<'tcx> {
1392 socket: FileDescriptionRef<TcpSocket>,
1393 foreign_name: &'static str,
1394 action: DynMachineCallback<'tcx, Result<(), ()>>,
1395 } |this, kind: UnblockKind| {
1396 this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
1398
1399 if UnblockKind::TimedOut == kind {
1400 return action.call(this, Err(()))
1402 }
1403
1404 let state = socket.state.borrow();
1407 match &*state {
1408 SocketState::Connecting(_) => { },
1409 SocketState::Connected(_) => {
1410 drop(state);
1411 return action.call(this, Ok(()))
1414 },
1415 _ => {
1416 drop(state);
1417 this.ensure_not_failed(&socket, foreign_name)?;
1422 return action.call(this, Err(()))
1423 }
1424 };
1425
1426 drop(state);
1427
1428 this.update_last_error(&socket);
1430
1431 if socket.error.borrow().is_some() {
1432 return action.call(this, Err(()))
1435 }
1436
1437 let mut state = socket.state.borrow_mut();
1456 let SocketState::Connecting(stream) = std::mem::replace(&mut*state, SocketState::Initial) else {
1457 unreachable!()
1459 };
1460 *state = SocketState::Connected(stream);
1461 drop(state);
1462 action.call(this, Ok(()))
1463 }
1464 ),
1465 )
1466 }
1467
1468 fn ensure_not_failed(
1472 &self,
1473 socket: &FileDescriptionRef<TcpSocket>,
1474 foreign_name: &'static str,
1475 ) -> InterpResult<'tcx> {
1476 if let SocketState::ConnectionFailed(_) = &*socket.state.borrow() {
1477 throw_unsup_format!(
1478 "{foreign_name}: sockets are in an unspecified state after a failed `connect`; \
1479 any operation on such a socket is thus unsupported"
1480 );
1481 } else {
1482 interp_ok(())
1483 }
1484 }
1485
1486 fn update_last_error(&self, socket: &FileDescriptionRef<TcpSocket>) {
1494 let mut state = socket.state.borrow_mut();
1495
1496 let new_error = match &*state {
1497 SocketState::Listening(listener) =>
1498 listener.take_error().expect("Reading SO_ERROR should not fail"),
1499 SocketState::Connecting(stream) | SocketState::Connected(stream) =>
1500 stream.take_error().expect("Reading SO_ERROR should not fail"),
1501 SocketState::Initial | SocketState::Bound(_) | SocketState::ConnectionFailed(_) => None,
1502 };
1503
1504 let Some(new_error) = new_error else { return };
1505
1506 socket.error.replace(Some(new_error));
1509
1510 if matches!(&*state, SocketState::Connecting(_)) {
1511 let SocketState::Connecting(stream) =
1518 std::mem::replace(&mut *state, SocketState::Initial)
1519 else {
1520 unreachable!()
1521 };
1522 *state = SocketState::ConnectionFailed(stream);
1523 }
1524 }
1525}
1526
1527impl SourceFileDescription for TcpSocket {
1528 fn with_source(&self, f: &mut dyn FnMut(&mut dyn Source) -> io::Result<()>) -> io::Result<()> {
1529 let mut state = self.state.borrow_mut();
1530 match &mut *state {
1531 SocketState::Listening(listener) => f(listener),
1532 SocketState::Connecting(stream)
1533 | SocketState::Connected(stream)
1534 | SocketState::ConnectionFailed(stream) => f(stream),
1535 _ => unreachable!(),
1537 }
1538 }
1539
1540 fn get_readiness_mut(&self) -> RefMut<'_, Readiness> {
1541 self.io_readiness.borrow_mut()
1542 }
1543}