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 _, FdId, 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}
74
75impl TcpSocket {
76 pub fn new(family: SocketFamily, is_non_block: bool) -> Self {
77 TcpSocket {
78 family,
79 state: RefCell::new(SocketState::Initial),
80 is_non_block: Cell::new(is_non_block),
81 io_readiness: RefCell::new(Readiness::EMPTY),
82 error: RefCell::new(None),
83 read_timeout: Cell::new(None),
84 write_timeout: Cell::new(None),
85 }
86 }
87}
88
89impl FileDescription for TcpSocket {
90 fn name(&self) -> &'static str {
91 "socket"
92 }
93
94 fn destroy<'tcx>(
95 self,
96 self_id: FdId,
97 communicate_allowed: bool,
98 ecx: &mut MiriInterpCx<'tcx>,
99 ) -> InterpResult<'tcx, io::Result<()>> {
100 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
101
102 if matches!(
103 &*self.state.borrow(),
104 SocketState::Listening(_)
105 | SocketState::Connecting(_)
106 | SocketState::Connected(_)
107 | SocketState::ConnectionFailed(_)
108 ) {
109 ecx.machine.blocking_io.deregister(self_id, self)
112 };
113
114 interp_ok(Ok(()))
115 }
116
117 fn read<'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.recv(
126 communicate_allowed,
127 ptr,
128 len,
129 false,
130 false,
131 ecx,
132 finish,
133 )
134 }
135
136 fn write<'tcx>(
137 self: FileDescriptionRef<Self>,
138 communicate_allowed: bool,
139 ptr: Pointer,
140 len: usize,
141 ecx: &mut MiriInterpCx<'tcx>,
142 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
143 ) -> InterpResult<'tcx> {
144 self.send(communicate_allowed, ptr, len, false, ecx, finish)
145 }
146
147 fn short_fd_operations(&self) -> bool {
148 false
153 }
154
155 fn as_unix<'tcx>(
156 self: FileDescriptionRef<Self>,
157 _ecx: &MiriInterpCx<'tcx>,
158 ) -> FileDescriptionRef<dyn UnixFileDescription> {
159 self
160 }
161
162 fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
163 let mut flags = ecx.eval_libc_i32("O_RDWR");
164
165 if self.is_non_block.get() {
166 flags |= ecx.eval_libc_i32("O_NONBLOCK");
167 }
168
169 interp_ok(Scalar::from_i32(flags))
170 }
171
172 fn set_flags<'tcx>(
173 &self,
174 mut flag: i32,
175 ecx: &mut MiriInterpCx<'tcx>,
176 ) -> InterpResult<'tcx, Scalar> {
177 let o_nonblock = ecx.eval_libc_i32("O_NONBLOCK");
178
179 if flag & o_nonblock == o_nonblock {
181 self.is_non_block.set(true);
182 flag &= !o_nonblock;
183 } else {
184 self.is_non_block.set(false);
185 }
186
187 if flag != 0 {
189 throw_unsup_format!("fcntl: only O_NONBLOCK is supported for sockets")
190 }
191
192 interp_ok(Scalar::from_i32(0))
193 }
194
195 fn readiness<'tcx>(&self) -> InterpResult<'tcx, Readiness> {
196 interp_ok(self.io_readiness.borrow().clone())
197 }
198}
199
200impl UnixFileDescription for TcpSocket {
201 fn ioctl<'tcx>(
202 &self,
203 op: Scalar,
204 arg: Option<&OpTy<'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 if !matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android | Os::MacOs | Os::FreeBsd)
215 {
216 throw_unsup_format!(
221 "ioctl: setting FIONBIO on sockets is unsupported on target {}",
222 ecx.tcx.sess.target.os
223 );
224 }
225
226 let Some(value_ptr) = arg else {
227 throw_ub_format!("ioctl: setting FIONBIO on sockets requires a third argument");
228 };
229 let value = ecx.deref_pointer_as(value_ptr, ecx.machine.layouts.i32)?;
230 let non_block = ecx.read_scalar(&value)?.to_i32()? != 0;
231 self.is_non_block.set(non_block);
232 return interp_ok(0);
233 }
234
235 throw_unsup_format!("ioctl: unsupported operation {op:#x} on socket");
236 }
237
238 fn as_socket<'tcx>(
239 self: FileDescriptionRef<Self>,
240 _ecx: &MiriInterpCx<'tcx>,
241 ) -> Option<FileDescriptionRef<dyn UnixSocketFileDescription>> {
242 Some(self)
243 }
244}
245
246impl UnixSocketFileDescription for TcpSocket {
247 fn bind<'tcx>(
248 self: FileDescriptionRef<TcpSocket>,
249 communicate_allowed: bool,
250 address: SocketAddr,
251 ecx: &mut MiriInterpCx<'tcx>,
252 ) -> InterpResult<'tcx, Result<(), IoError>> {
253 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
254 ecx.ensure_not_failed(&self, "bind")?;
255
256 let mut state = self.state.borrow_mut();
257
258 match *state {
259 SocketState::Initial => {
260 let address_family = match &address {
261 SocketAddr::V4(_) => SocketFamily::IPv4,
262 SocketAddr::V6(_) => SocketFamily::IPv6,
263 };
264
265 if self.family != address_family {
266 let err = if matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android) {
269 LibcError("EINVAL")
272 } else {
273 LibcError("EAFNOSUPPORT")
277 };
278 return interp_ok(Err(err));
279 }
280
281 *state = SocketState::Bound(address);
282 }
283 SocketState::Connecting(_) | SocketState::Connected(_) =>
284 throw_unsup_format!(
285 "bind: tcp socket is already connected and binding a
286 connected socket is unsupported"
287 ),
288 SocketState::Bound(_) | SocketState::Listening(_) =>
289 throw_unsup_format!(
290 "bind: tcp socket is already bound and binding a socket \
291 multiple times is unsupported"
292 ),
293 SocketState::ConnectionFailed(_) => unreachable!(),
294 }
295
296 interp_ok(Ok(()))
297 }
298
299 fn listen<'tcx>(
300 self: FileDescriptionRef<TcpSocket>,
301 communicate_allowed: bool,
302 _backlog: i32,
304 ecx: &mut MiriInterpCx<'tcx>,
305 ) -> InterpResult<'tcx, Result<(), IoError>> {
306 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
307 ecx.ensure_not_failed(&self, "listen")?;
308
309 let mut state = self.state.borrow_mut();
310
311 match *state {
312 SocketState::Bound(socket_addr) =>
313 match TcpListener::bind(socket_addr) {
314 Ok(listener) => {
315 *state = SocketState::Listening(listener);
316 drop(state);
317 ecx.machine.blocking_io.register(self);
320 }
321 Err(e) => return interp_ok(Err(IoError::HostError(e))),
322 },
323 SocketState::Initial => {
324 throw_unsup_format!(
325 "listen: listening on a tcp socket which isn't bound is unsupported"
326 )
327 }
328 SocketState::Listening(_) => {
329 throw_unsup_format!(
330 "listen: listening on a tcp socket multiple times is unsupported"
331 )
332 }
333 SocketState::Connecting(_) | SocketState::Connected(_) => {
334 throw_unsup_format!("listen: listening on a connected tcp socket is unsupported")
335 }
336 SocketState::ConnectionFailed(_) => unreachable!(),
337 }
338
339 interp_ok(Ok(()))
340 }
341
342 fn accept<'tcx>(
343 self: FileDescriptionRef<Self>,
344 communicate_allowed: bool,
345 is_client_sock_non_block: bool,
346 ecx: &mut MiriInterpCx<'tcx>,
347 finish: DynMachineCallback<'tcx, Result<(FdNum, SocketAddr), IoError>>,
348 ) -> InterpResult<'tcx> {
349 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
350
351 if !matches!(*self.state.borrow(), SocketState::Listening(_)) {
352 throw_unsup_format!(
353 "accept: accepting incoming connections is only allowed when tcp socket is listening"
354 )
355 };
356
357 if self.is_non_block.get() {
358 let result = ecx.try_non_block_accept(&self, is_client_sock_non_block)?;
361 finish.call(ecx, result)
362 } else {
363 if self.read_timeout.get().is_some() {
367 throw_unsup_format!(
372 "accept: blocking tcp accept is not supported when SO_RCVTIMEO is non-zero"
373 )
374 }
375
376 ecx.block_for_accept(self, is_client_sock_non_block, finish)
377 }
378 }
379
380 fn connect<'tcx>(
381 self: FileDescriptionRef<Self>,
382 communicate_allowed: bool,
383 address: SocketAddr,
384 ecx: &mut MiriInterpCx<'tcx>,
385 finish: DynMachineCallback<'tcx, Result<(), IoError>>,
386 ) -> InterpResult<'tcx> {
387 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
388 ecx.ensure_not_failed(&self, "connect")?;
389
390 match &*self.state.borrow() {
391 SocketState::Initial => { }
392 SocketState::Connecting(_) => return finish.call(ecx, Err(LibcError("EALREADY"))),
394 _ =>
398 throw_unsup_format!(
399 "connect: connecting is only supported for tcp sockets which are neither \
400 bound, listening nor already connected"
401 ),
402 }
403
404 match TcpStream::connect(address) {
407 Ok(stream) => {
408 *self.state.borrow_mut() = SocketState::Connecting(stream);
409 ecx.machine.blocking_io.register(self.clone());
412 }
413 Err(e) => return finish.call(ecx, Err(IoError::HostError(e))),
414 };
415
416 if self.is_non_block.get() {
417 finish.call(ecx, Err(LibcError("EINPROGRESS")))
424 } else {
425 if self.write_timeout.get().is_some() {
429 throw_unsup_format!(
434 "connect: blocking connect is not supported when SO_SNDTIMEO is non-zero"
435 )
436 }
437
438 let socket = self;
439 ecx.ensure_connected(
440 socket.clone(),
441 None,
442 "connect",
443 callback!(
444 @capture<'tcx> {
445 socket: FileDescriptionRef<TcpSocket>,
446 finish: DynMachineCallback<'tcx, Result<(), IoError>>,
447 } |this, result: Result<(), ()>| {
448 if result.is_err() {
449 let err = socket.error.take().unwrap();
453 finish.call(this, Err(IoError::HostError(err)))
454 } else {
455 finish.call(this, Ok(()))
456 }
457 }
458 ),
459 )
460 }
461 }
462
463 fn send<'tcx>(
464 self: FileDescriptionRef<Self>,
465 communicate_allowed: bool,
466 ptr: Pointer,
467 len: usize,
468 is_non_block: bool,
469 ecx: &mut MiriInterpCx<'tcx>,
470 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
471 ) -> InterpResult<'tcx> {
472 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
473
474 let is_non_block = is_non_block || self.is_non_block.get();
475 let deadline = ecx.action_deadline(is_non_block, self.write_timeout.get());
476
477 let socket = self;
478 ecx.ensure_connected(
479 socket.clone(),
480 deadline.clone(),
481 "send",
482 callback!(
483 @capture<'tcx> {
484 socket: FileDescriptionRef<TcpSocket>,
485 deadline: Option<Deadline>,
486 ptr: Pointer,
487 len: usize,
488 is_non_block: bool,
489 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
490 } |this, result: Result<(), ()>| {
491 if result.is_err() {
492 return finish.call(this, Err(LibcError("ENOTCONN")))
493 }
494
495 if is_non_block {
496 let result = this.try_non_block_send(&socket, ptr, len)?;
499 finish.call(this, result)
500 } else {
501 this.block_for_send(socket, deadline, ptr, len, finish)
504 }
505 }
506 ),
507 )
508 }
509
510 fn recv<'tcx>(
511 self: FileDescriptionRef<Self>,
512 communicate_allowed: bool,
513 ptr: Pointer,
514 len: usize,
515 is_peek: bool,
516 is_non_block: bool,
517 ecx: &mut MiriInterpCx<'tcx>,
518 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
519 ) -> InterpResult<'tcx> {
520 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
521
522 let is_non_block = is_non_block || self.is_non_block.get();
523 let deadline = ecx.action_deadline(is_non_block, self.read_timeout.get());
524
525 let socket = self;
526 ecx.ensure_connected(
527 socket.clone(),
528 deadline.clone(),
529 "recv",
530 callback!(
531 @capture<'tcx> {
532 socket: FileDescriptionRef<TcpSocket>,
533 deadline: Option<Deadline>,
534 ptr: Pointer,
535 len: usize,
536 is_peek: bool,
537 is_non_block: bool,
538 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
539 } |this, result: Result<(), ()>| {
540 if result.is_err() {
541 return finish.call(this, Err(LibcError("ENOTCONN")))
542 }
543
544 if is_non_block {
545 let result = this.try_non_block_recv(&socket, ptr, len, is_peek)?;
548 finish.call(this, result)
549 } else {
550 this.block_for_recv(socket, deadline, ptr, len, is_peek, finish)
553 }
554 }
555 ),
556 )
557 }
558
559 fn setsockopt<'tcx>(
560 self: FileDescriptionRef<Self>,
561 level: i32,
562 option: i32,
563 value_ptr: Pointer,
564 value_len: u64,
565 ecx: &mut MiriInterpCx<'tcx>,
566 ) -> InterpResult<'tcx, Result<(), IoError>> {
567 if level == ecx.eval_libc_i32("SOL_SOCKET") {
568 let opt_so_rcvtimeo = ecx.eval_libc_i32("SO_RCVTIMEO");
569 let opt_so_sndtimeo = ecx.eval_libc_i32("SO_SNDTIMEO");
570 let opt_so_reuseaddr = ecx.eval_libc_i32("SO_REUSEADDR");
571
572 if matches!(ecx.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::NetBsd) {
573 let opt_so_nosigpipe = ecx.eval_libc_i32("SO_NOSIGPIPE");
575
576 if option == opt_so_nosigpipe {
577 if value_len != 4 {
578 return interp_ok(Err(LibcError("EINVAL")));
580 }
581 let option_value = ecx.ptr_to_mplace(value_ptr, ecx.machine.layouts.i32);
582 let _val = ecx.read_scalar(&option_value)?.to_i32()?;
583 return interp_ok(Ok(()));
586 }
587 }
588
589 if option == opt_so_rcvtimeo || option == opt_so_sndtimeo {
590 let timeval_layout = ecx.libc_ty_layout("timeval");
591 let option_value = ecx.ptr_to_mplace(value_ptr, timeval_layout);
592
593 let timeout = match ecx.read_timeval(&option_value)? {
594 None => return interp_ok(Err(LibcError("EINVAL"))),
595 Some(Duration::ZERO) => None,
596 Some(duration) => Some(duration),
597 };
598
599 if option == opt_so_rcvtimeo {
600 self.read_timeout.set(timeout);
601 } else {
602 self.write_timeout.set(timeout);
603 }
604
605 return interp_ok(Ok(()));
606 }
607
608 if option == opt_so_reuseaddr {
609 if value_len != 4 {
610 return interp_ok(Err(LibcError("EINVAL")));
612 }
613 let option_value = ecx.ptr_to_mplace(value_ptr, ecx.machine.layouts.i32);
614 let _val = ecx.read_scalar(&option_value)?.to_i32()?;
615 return interp_ok(Ok(()));
618 } else {
619 throw_unsup_format!(
620 "setsockopt: option {option:#x} is unsupported for level SOL_SOCKET",
621 );
622 }
623 } else if level == ecx.eval_libc_i32("IPPROTO_IP") {
624 let opt_ip_ttl = ecx.eval_libc_i32("IP_TTL");
625
626 if option == opt_ip_ttl {
627 if value_len != 4 {
628 return interp_ok(Err(LibcError("EINVAL")));
630 }
631 let option_value = ecx.ptr_to_mplace(value_ptr, ecx.machine.layouts.u32);
632 let ttl = ecx.read_scalar(&option_value)?.to_u32()?;
633
634 let result = match &*self.state.borrow() {
635 SocketState::Initial | SocketState::Bound(_) =>
636 throw_unsup_format!(
637 "setsockopt: setting option IP_TTL on level IPPROTO_IP is only supported \
638 on connected and listening tcp sockets"
639 ),
640 SocketState::Listening(listener) => listener.set_ttl(ttl),
641 SocketState::Connecting(stream) | SocketState::Connected(stream) =>
642 stream.set_ttl(ttl),
643 SocketState::ConnectionFailed(_) => unreachable!(),
644 };
645
646 return match result {
647 Ok(_) => interp_ok(Ok(())),
648 Err(e) => interp_ok(Err(IoError::HostError(e))),
649 };
650 } else {
651 throw_unsup_format!(
652 "setsockopt: option {option:#x} is unsupported for level IPPROTO_IP",
653 );
654 }
655 } else if level == ecx.eval_libc_i32("IPPROTO_TCP") {
656 let opt_tcp_nodelay = ecx.eval_libc_i32("TCP_NODELAY");
657
658 if option == opt_tcp_nodelay {
659 if value_len != 4 {
660 return interp_ok(Err(LibcError("EINVAL")));
662 }
663 let option_value = ecx.ptr_to_mplace(value_ptr, ecx.machine.layouts.i32);
664 let nodelay = ecx.read_scalar(&option_value)?.to_i32()? != 0;
665
666 let result = match &*self.state.borrow() {
667 SocketState::Initial | SocketState::Bound(_) | SocketState::Listening(_) =>
668 throw_unsup_format!(
669 "setsockopt: setting option TCP_NODELAY on level IPPROTO_TCP is only supported \
670 on connected tcp sockets"
671 ),
672 SocketState::Connecting(stream) | SocketState::Connected(stream) =>
673 stream.set_nodelay(nodelay),
674 SocketState::ConnectionFailed(_) => unreachable!(),
675 };
676
677 return match result {
678 Ok(_) => interp_ok(Ok(())),
679 Err(e) => interp_ok(Err(IoError::HostError(e))),
680 };
681 } else {
682 throw_unsup_format!(
683 "setsockopt: option {option:#x} is unsupported for level IPPROTO_TCP"
684 );
685 }
686 }
687
688 throw_unsup_format!(
689 "setsockopt: level {level:#x} is unsupported, only SOL_SOCKET, IPPROTO_IP \
690 and IPPROTO_TCP are allowed"
691 );
692 }
693
694 fn getsockopt<'tcx>(
695 self: FileDescriptionRef<Self>,
696 level: i32,
697 option: i32,
698 ecx: &mut MiriInterpCx<'tcx>,
699 ) -> InterpResult<'tcx, Result<MPlaceTy<'tcx>, IoError>> {
700 if level == ecx.eval_libc_i32("SOL_SOCKET") {
701 let opt_so_error = ecx.eval_libc_i32("SO_ERROR");
702 let opt_so_rcvtimeo = ecx.eval_libc_i32("SO_RCVTIMEO");
703 let opt_so_sndtimeo = ecx.eval_libc_i32("SO_SNDTIMEO");
704
705 if option == opt_so_error {
706 ecx.update_last_error(&self);
709
710 let return_value = match self.error.take() {
711 Some(err) => ecx.io_error_to_errnum(err)?.to_i32()?,
712 None => 0,
714 };
715
716 self.error.replace(None);
718
719 self.io_readiness.borrow_mut().error = false;
722 ecx.update_fd_readiness(self, false)?;
723
724 let value_buffer = ecx.allocate(ecx.machine.layouts.i32, MemoryKind::Stack)?;
726 ecx.write_int(return_value, &value_buffer)?;
727 interp_ok(Ok(value_buffer))
728 } else if option == opt_so_rcvtimeo || option == opt_so_sndtimeo {
729 let timeout = if option == opt_so_rcvtimeo {
730 self.read_timeout.get()
731 } else {
732 self.write_timeout.get()
733 }
734 .unwrap_or_default();
735
736 let secs = timeout.as_secs();
737 let usecs = timeout.subsec_micros();
738
739 let timeval_layout = ecx.libc_ty_layout("timeval");
740 let timeval_buffer = ecx.allocate(timeval_layout, MemoryKind::Stack)?;
742
743 let sec_field = ecx.project_field_named(&timeval_buffer, "tv_sec")?;
744 ecx.write_int(secs, &sec_field)?;
745
746 let usec_field = ecx.project_field_named(&timeval_buffer, "tv_usec")?;
747 ecx.write_int(usecs, &usec_field)?;
748
749 interp_ok(Ok(timeval_buffer))
750 } else {
751 throw_unsup_format!(
752 "getsockopt: option {option:#x} is unsupported for level SOL_SOCKET",
753 );
754 }
755 } else if level == ecx.eval_libc_i32("IPPROTO_IP") {
756 let opt_ip_ttl = ecx.eval_libc_i32("IP_TTL");
757
758 if option == opt_ip_ttl {
759 let ttl = match &*self.state.borrow() {
760 SocketState::Initial | SocketState::Bound(_) =>
761 throw_unsup_format!(
762 "getsockopt: reading option IP_TTL on level IPPROTO_IP is only supported \
763 on connected and listening tcp sockets"
764 ),
765 SocketState::Listening(listener) => listener.ttl(),
766 SocketState::Connecting(stream) | SocketState::Connected(stream) =>
767 stream.ttl(),
768 SocketState::ConnectionFailed(_) => unreachable!(),
769 };
770
771 let ttl = match ttl {
772 Ok(ttl) => ttl,
773 Err(e) => return interp_ok(Err(IoError::HostError(e))),
774 };
775
776 let value_buffer = ecx.allocate(ecx.machine.layouts.u32, MemoryKind::Stack)?;
778 ecx.write_int(ttl, &value_buffer)?;
779 interp_ok(Ok(value_buffer))
780 } else {
781 throw_unsup_format!(
782 "getsockopt: option {option:#x} is unsupported for level IPPROTO_IP",
783 );
784 }
785 } else if level == ecx.eval_libc_i32("IPPROTO_TCP") {
786 let opt_tcp_nodelay = ecx.eval_libc_i32("TCP_NODELAY");
787
788 if option == opt_tcp_nodelay {
789 let nodelay = match &*self.state.borrow() {
790 SocketState::Initial | SocketState::Bound(_) | SocketState::Listening(_) =>
791 throw_unsup_format!(
792 "getsockopt: reading option TCP_NODELAY on level IPPROTO_TCP is only supported \
793 on connected tcp sockets"
794 ),
795 SocketState::Connecting(stream) | SocketState::Connected(stream) =>
796 stream.nodelay(),
797 SocketState::ConnectionFailed(_) => unreachable!(),
798 };
799
800 let nodelay = match nodelay {
801 Ok(nodelay) => nodelay,
802 Err(e) => return interp_ok(Err(IoError::HostError(e))),
803 };
804
805 let value_buffer = ecx.allocate(ecx.machine.layouts.i32, MemoryKind::Stack)?;
807 ecx.write_int(i32::from(nodelay), &value_buffer)?;
808 interp_ok(Ok(value_buffer))
809 } else {
810 throw_unsup_format!(
811 "getsockopt: option {option:#x} is unsupported for level IPPROTO_TCP"
812 );
813 }
814 } else {
815 throw_unsup_format!(
816 "getsockopt: level {level:#x} is unsupported, only SOL_SOCKET, IPPROTO_IP \
817 and IPPROTO_TCP are allowed"
818 )
819 }
820 }
821
822 fn getsockname<'tcx>(
823 self: FileDescriptionRef<Self>,
824 communicate_allowed: bool,
825 ecx: &mut MiriInterpCx<'tcx>,
826 ) -> InterpResult<'tcx, Result<SocketAddr, IoError>> {
827 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
828 ecx.ensure_not_failed(&self, "getsockname")?;
829
830 let state = self.state.borrow();
831
832 let address = match &*state {
833 SocketState::Bound(address) => {
834 if address.port() == 0 {
835 throw_unsup_format!(
839 "getsockname: when the port is 0, getting the tcp socket address before \
840 calling `listen` or `connect` is unsupported"
841 )
842 }
843
844 *address
845 }
846 SocketState::Listening(listener) =>
847 match listener.local_addr() {
848 Ok(address) => address,
849 Err(e) => return interp_ok(Err(IoError::HostError(e))),
850 },
851 SocketState::Connecting(stream) | SocketState::Connected(stream) => {
852 if cfg!(windows) && matches!(&*state, SocketState::Connecting(_)) {
853 static DEDUP: AtomicBool = AtomicBool::new(false);
860 if !DEDUP.swap(true, std::sync::atomic::Ordering::Relaxed) {
861 ecx.emit_diagnostic(NonHaltingDiagnostic::ConnectingSocketGetsockname);
862 }
863 }
864 match stream.local_addr() {
865 Ok(address) => address,
866 Err(e) => return interp_ok(Err(IoError::HostError(e))),
867 }
868 }
869 SocketState::Initial => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)),
872 SocketState::ConnectionFailed(_) => unreachable!(),
873 };
874
875 interp_ok(Ok(address))
876 }
877
878 fn getpeername<'tcx>(
879 self: FileDescriptionRef<Self>,
880 communicate_allowed: bool,
881 ecx: &mut MiriInterpCx<'tcx>,
882 finish: DynMachineCallback<'tcx, Result<SocketAddr, IoError>>,
883 ) -> InterpResult<'tcx> {
884 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
885
886 let socket = self;
887 ecx.ensure_connected(
890 socket.clone(),
891 Some(ecx.machine.monotonic_clock.now().into()),
893 "getpeername",
894 callback!(
895 @capture<'tcx> {
896 socket: FileDescriptionRef<TcpSocket>,
897 finish: DynMachineCallback<'tcx, Result<SocketAddr, IoError>>,
898 } |this, result: Result<(), ()>| {
899 if result.is_err() {
900 return finish.call(this, Err(LibcError("ENOTCONN")))
901 };
902
903 let SocketState::Connected(stream) = &*socket.state.borrow() else {
904 unreachable!()
905 };
906
907 let result = stream.peer_addr().map_err(IoError::HostError);
908 finish.call(this, result)
909 }
910 ),
911 )
912 }
913
914 fn shutdown<'tcx>(
915 self: FileDescriptionRef<Self>,
916 communicate_allowed: bool,
917 how: Shutdown,
918 ecx: &mut MiriInterpCx<'tcx>,
919 ) -> InterpResult<'tcx, Result<(), IoError>> {
920 assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");
921 ecx.ensure_not_failed(&self, "shutdown")?;
922
923 let state = self.state.borrow();
924
925 let (SocketState::Connecting(stream) | SocketState::Connected(stream)) = &*state else {
926 return interp_ok(Err(LibcError("ENOTCONN")));
927 };
928
929 if let Err(e) = stream.shutdown(how) {
930 return interp_ok(Err(IoError::HostError(e)));
931 };
932
933 drop(state);
934
935 let mut readiness = self.io_readiness.borrow_mut();
941 readiness.read_closed |= matches!(how, Shutdown::Read | Shutdown::Both);
943 readiness.write_closed |= matches!(how, Shutdown::Both);
946 readiness.readable |= matches!(how, Shutdown::Read | Shutdown::Both);
949
950 drop(readiness);
951
952 ecx.update_fd_readiness(self, false)?;
954
955 interp_ok(Ok(()))
956 }
957}
958
959impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
960trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
961 fn action_deadline(
968 &self,
969 is_non_block: bool,
970 action_timeout: Option<Duration>,
971 ) -> Option<Deadline> {
972 let this = self.eval_context_ref();
973
974 if is_non_block {
975 Some(this.machine.monotonic_clock.now().into())
977 } else {
978 action_timeout
979 .map(|duration| this.machine.monotonic_clock.now().add_lossy(duration).into())
980 }
981 }
982
983 fn block_for_accept(
992 &mut self,
993 socket: FileDescriptionRef<TcpSocket>,
994 is_client_sock_nonblock: bool,
995 finish: DynMachineCallback<'tcx, Result<(FdNum, SocketAddr), IoError>>,
996 ) -> InterpResult<'tcx> {
997 let this = self.eval_context_mut();
998 this.block_thread_for_io(
999 socket.clone(),
1000 BlockingIoInterest::Read,
1001 None,
1002 callback!(@capture<'tcx> {
1003 socket: FileDescriptionRef<TcpSocket>,
1004 is_client_sock_nonblock: bool,
1005 finish: DynMachineCallback<'tcx, Result<(FdNum, SocketAddr), IoError>>,
1006 } |this, kind: UnblockKind| {
1007 this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
1009
1010 match kind {
1011 UnblockKind::Ready => { },
1012 UnblockKind::TimedOut => return finish.call(this, Err(LibcError("EWOULDBLOCK")))
1014 }
1015
1016 match this.try_non_block_accept(&socket, is_client_sock_nonblock)? {
1017 Ok((sockfd, addr)) => finish.call(this, Ok((sockfd, addr))),
1018 Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {
1019 this.block_for_accept(socket, is_client_sock_nonblock, finish)
1021 }
1022 Err(e) => finish.call(this, Err(e)),
1023 }
1024 }),
1025 )
1026 }
1027
1028 fn try_non_block_accept(
1035 &mut self,
1036 socket: &FileDescriptionRef<TcpSocket>,
1037 is_client_sock_nonblock: bool,
1038 ) -> InterpResult<'tcx, Result<(FdNum, SocketAddr), IoError>> {
1039 let this = self.eval_context_mut();
1040
1041 let state = socket.state.borrow();
1042 let SocketState::Listening(listener) = &*state else {
1043 panic!(
1044 "try_non_block_accept must only be called when socket is in `SocketState::Listening`"
1045 )
1046 };
1047
1048 let (stream, addr) = match listener.accept() {
1049 Ok(peer) => peer,
1050 Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
1051 socket.io_readiness.borrow_mut().readable = false;
1053 this.update_fd_readiness(socket.clone(), false)?;
1054
1055 return interp_ok(Err(IoError::HostError(e)));
1056 }
1057 Err(e) => return interp_ok(Err(IoError::HostError(e))),
1058 };
1059
1060 let family = match addr {
1061 SocketAddr::V4(_) => SocketFamily::IPv4,
1062 SocketAddr::V6(_) => SocketFamily::IPv6,
1063 };
1064
1065 let fd = this.machine.fds.new_ref(TcpSocket {
1066 family,
1067 state: RefCell::new(SocketState::Connected(stream)),
1068 is_non_block: Cell::new(is_client_sock_nonblock),
1069 io_readiness: RefCell::new(Readiness::EMPTY),
1070 error: RefCell::new(None),
1071 read_timeout: Cell::new(None),
1072 write_timeout: Cell::new(None),
1073 });
1074 this.machine.blocking_io.register(fd.clone());
1077 let sockfd = this.machine.fds.insert(fd);
1078 interp_ok(Ok((sockfd, addr)))
1079 }
1080
1081 fn block_for_send(
1089 &mut self,
1090 socket: FileDescriptionRef<TcpSocket>,
1091 deadline: Option<Deadline>,
1092 buffer_ptr: Pointer,
1093 length: usize,
1094 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1095 ) -> InterpResult<'tcx> {
1096 let this = self.eval_context_mut();
1097 this.block_thread_for_io(
1098 socket.clone(),
1099 BlockingIoInterest::Write,
1100 deadline.clone(),
1101 callback!(@capture<'tcx> {
1102 socket: FileDescriptionRef<TcpSocket>,
1103 deadline: Option<Deadline>,
1104 buffer_ptr: Pointer,
1105 length: usize,
1106 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1107 } |this, kind: UnblockKind| {
1108 this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
1110
1111 match kind {
1112 UnblockKind::Ready => { },
1113 UnblockKind::TimedOut => return finish.call(this, Err(LibcError("EWOULDBLOCK")))
1115 }
1116
1117 match this.try_non_block_send(&socket, buffer_ptr, length)? {
1118 Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {
1119 this.block_for_send(socket, deadline, buffer_ptr, length, finish)
1121 },
1122 result => finish.call(this, result)
1123 }
1124 }),
1125 )
1126 }
1127
1128 fn try_non_block_send(
1133 &mut self,
1134 socket: &FileDescriptionRef<TcpSocket>,
1135 buffer_ptr: Pointer,
1136 length: usize,
1137 ) -> InterpResult<'tcx, Result<usize, IoError>> {
1138 let this = self.eval_context_mut();
1139
1140 let mut state = socket.state.borrow_mut();
1141 let SocketState::Connected(stream) = &mut *state else {
1142 panic!("try_non_block_send must only be called when the socket is connected")
1143 };
1144
1145 let result = this.write_to_host(stream, length, buffer_ptr)?;
1147
1148 drop(state);
1149
1150 if result.is_ok() {
1152 assert!(!socket.io_readiness.borrow().write_closed, "successful write after close");
1153 }
1154
1155 match result {
1156 Err(IoError::HostError(e))
1157 if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::WouldBlock) =>
1158 {
1159 socket.io_readiness.borrow_mut().writable = false;
1161 this.update_fd_readiness(socket.clone(), false)?;
1162
1163 interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into())))
1166 }
1167 Ok(bytes_written) if bytes_written < length => {
1168 if cfg!(any(
1174 target_os = "android",
1176 target_os = "illumos",
1177 target_os = "linux",
1178 target_os = "redox",
1179 target_os = "dragonfly",
1181 target_os = "freebsd",
1182 target_os = "ios",
1183 target_os = "macos",
1184 target_os = "netbsd",
1185 target_os = "openbsd",
1186 target_os = "tvos",
1187 target_os = "visionos",
1188 target_os = "watchos",
1189 )) {
1190 socket.io_readiness.borrow_mut().writable = false;
1191 this.update_fd_readiness(socket.clone(), false)?;
1192 } else {
1193 this.update_fd_readiness(socket.clone(), true)?;
1203 }
1204 interp_ok(result)
1205 }
1206 result => interp_ok(result),
1207 }
1208 }
1209
1210 fn block_for_recv(
1218 &mut self,
1219 socket: FileDescriptionRef<TcpSocket>,
1220 deadline: Option<Deadline>,
1221 buffer_ptr: Pointer,
1222 length: usize,
1223 should_peek: bool,
1224 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1225 ) -> InterpResult<'tcx> {
1226 let this = self.eval_context_mut();
1227 this.block_thread_for_io(
1228 socket.clone(),
1229 BlockingIoInterest::Read,
1230 deadline.clone(),
1231 callback!(@capture<'tcx> {
1232 socket: FileDescriptionRef<TcpSocket>,
1233 deadline: Option<Deadline>,
1234 buffer_ptr: Pointer,
1235 length: usize,
1236 should_peek: bool,
1237 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1238 } |this, kind: UnblockKind| {
1239 this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
1241
1242 match kind {
1243 UnblockKind::Ready => { },
1244 UnblockKind::TimedOut => return finish.call(this, Err(LibcError("EWOULDBLOCK")))
1246 }
1247
1248 match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? {
1249 Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {
1250 this.block_for_recv(socket, deadline, buffer_ptr, length, should_peek, finish)
1252 },
1253 result => finish.call(this, result)
1254 }
1255 }),
1256 )
1257 }
1258
1259 fn try_non_block_recv(
1264 &mut self,
1265 socket: &FileDescriptionRef<TcpSocket>,
1266 buffer_ptr: Pointer,
1267 length: usize,
1268 should_peek: bool,
1269 ) -> InterpResult<'tcx, Result<usize, IoError>> {
1270 let this = self.eval_context_mut();
1271
1272 let mut state = socket.state.borrow_mut();
1273 let SocketState::Connected(stream) = &mut *state else {
1274 panic!("try_non_block_recv must only be called when the socket is connected")
1275 };
1276
1277 let result = this.read_from_host(
1279 |buf| {
1280 if should_peek { stream.peek(buf) } else { stream.read(buf) }
1281 },
1282 length,
1283 buffer_ptr,
1284 )?;
1285
1286 drop(state);
1287
1288 match result {
1289 Err(IoError::HostError(e))
1290 if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::WouldBlock) =>
1291 {
1292 socket.io_readiness.borrow_mut().readable = false;
1294 this.update_fd_readiness(socket.clone(), false)?;
1295
1296 interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into())))
1299 }
1300 Ok(bytes_read)
1301 if !should_peek
1302 && bytes_read < length
1303 && bytes_read > 0
1304 && !socket.io_readiness.borrow().read_closed =>
1305 {
1306 if cfg!(any(
1316 target_os = "android",
1318 target_os = "illumos",
1319 target_os = "linux",
1320 target_os = "redox",
1321 target_os = "dragonfly",
1323 target_os = "freebsd",
1324 target_os = "ios",
1325 target_os = "macos",
1326 target_os = "netbsd",
1327 target_os = "openbsd",
1328 target_os = "tvos",
1329 target_os = "visionos",
1330 target_os = "watchos",
1331 )) {
1332 socket.io_readiness.borrow_mut().readable = false;
1333 this.update_fd_readiness(socket.clone(), false)?;
1334 } else {
1335 this.update_fd_readiness(socket.clone(), true)?;
1345 }
1346 interp_ok(result)
1347 }
1348 result => interp_ok(result),
1349 }
1350 }
1351
1352 fn ensure_connected(
1365 &mut self,
1366 socket: FileDescriptionRef<TcpSocket>,
1367 deadline: Option<Deadline>,
1368 foreign_name: &'static str,
1369 action: DynMachineCallback<'tcx, Result<(), ()>>,
1370 ) -> InterpResult<'tcx> {
1371 let this = self.eval_context_mut();
1372
1373 let state = socket.state.borrow();
1374 match &*state {
1375 SocketState::Connecting(_) => { }
1376 SocketState::Connected(_) => {
1377 drop(state);
1378 return action.call(this, Ok(()));
1379 }
1380 _ => {
1381 drop(state);
1382 this.ensure_not_failed(&socket, foreign_name)?;
1383 return action.call(this, Err(()));
1384 }
1385 };
1386
1387 drop(state);
1388
1389 this.block_thread_for_io(
1393 socket.clone(),
1394 BlockingIoInterest::Write,
1395 deadline,
1396 callback!(
1397 @capture<'tcx> {
1398 socket: FileDescriptionRef<TcpSocket>,
1399 foreign_name: &'static str,
1400 action: DynMachineCallback<'tcx, Result<(), ()>>,
1401 } |this, kind: UnblockKind| {
1402 this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
1404
1405 if UnblockKind::TimedOut == kind {
1406 return action.call(this, Err(()))
1408 }
1409
1410 let state = socket.state.borrow();
1413 match &*state {
1414 SocketState::Connecting(_) => { },
1415 SocketState::Connected(_) => {
1416 drop(state);
1417 return action.call(this, Ok(()))
1420 },
1421 _ => {
1422 drop(state);
1423 this.ensure_not_failed(&socket, foreign_name)?;
1428 return action.call(this, Err(()))
1429 }
1430 };
1431
1432 drop(state);
1433
1434 this.update_last_error(&socket);
1436
1437 if socket.error.borrow().is_some() {
1438 return action.call(this, Err(()))
1441 }
1442
1443 let mut state = socket.state.borrow_mut();
1462 let SocketState::Connecting(stream) = std::mem::replace(&mut*state, SocketState::Initial) else {
1463 unreachable!()
1465 };
1466 *state = SocketState::Connected(stream);
1467 drop(state);
1468 action.call(this, Ok(()))
1469 }
1470 ),
1471 )
1472 }
1473
1474 fn ensure_not_failed(
1478 &self,
1479 socket: &FileDescriptionRef<TcpSocket>,
1480 foreign_name: &'static str,
1481 ) -> InterpResult<'tcx> {
1482 if let SocketState::ConnectionFailed(_) = &*socket.state.borrow() {
1483 throw_unsup_format!(
1484 "{foreign_name}: sockets are in an unspecified state after a failed `connect`; \
1485 any operation on such a socket is thus unsupported"
1486 );
1487 } else {
1488 interp_ok(())
1489 }
1490 }
1491
1492 fn update_last_error(&self, socket: &FileDescriptionRef<TcpSocket>) {
1500 let mut state = socket.state.borrow_mut();
1501
1502 let new_error = match &*state {
1503 SocketState::Listening(listener) =>
1504 listener.take_error().expect("Reading SO_ERROR should not fail"),
1505 SocketState::Connecting(stream) | SocketState::Connected(stream) =>
1506 stream.take_error().expect("Reading SO_ERROR should not fail"),
1507 SocketState::Initial | SocketState::Bound(_) | SocketState::ConnectionFailed(_) => None,
1508 };
1509
1510 let Some(new_error) = new_error else { return };
1511
1512 socket.error.replace(Some(new_error));
1515
1516 if matches!(&*state, SocketState::Connecting(_)) {
1517 let SocketState::Connecting(stream) =
1524 std::mem::replace(&mut *state, SocketState::Initial)
1525 else {
1526 unreachable!()
1527 };
1528 *state = SocketState::ConnectionFailed(stream);
1529 }
1530 }
1531}
1532
1533impl VisitProvenance for FileDescriptionRef<TcpSocket> {
1534 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
1537}
1538
1539impl SourceFileDescription for TcpSocket {
1540 fn with_source(&self, f: &mut dyn FnMut(&mut dyn Source) -> io::Result<()>) -> io::Result<()> {
1541 let mut state = self.state.borrow_mut();
1542 match &mut *state {
1543 SocketState::Listening(listener) => f(listener),
1544 SocketState::Connecting(stream)
1545 | SocketState::Connected(stream)
1546 | SocketState::ConnectionFailed(stream) => f(stream),
1547 _ => unreachable!(),
1549 }
1550 }
1551
1552 fn get_readiness_mut(&self) -> RefMut<'_, Readiness> {
1553 self.io_readiness.borrow_mut()
1554 }
1555}