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