Skip to main content

shadow_rs/host/descriptor/socket/inet/
legacy_tcp.rs

1use std::ffi::CStr;
2use std::net::{Ipv4Addr, SocketAddrV4};
3use std::sync::Arc;
4
5use atomic_refcell::AtomicRefCell;
6use linux_api::errno::Errno;
7use linux_api::ioctls::IoctlRequest;
8use linux_api::socket::Shutdown;
9use nix::sys::socket::{MsgFlags, SockaddrIn};
10use shadow_shim_helper_rs::emulated_time::EmulatedTime;
11use shadow_shim_helper_rs::syscall_types::ForeignPtr;
12
13use crate::core::worker::Worker;
14use crate::cshadow as c;
15use crate::host::descriptor::listener::{StateListenHandle, StateListenerFilter};
16use crate::host::descriptor::socket::inet::{self, InetSocket};
17use crate::host::descriptor::socket::{RecvmsgArgs, RecvmsgReturn, SendmsgArgs, Socket};
18use crate::host::descriptor::{
19    CompatFile, File, FileMode, FileSignals, FileState, FileStatus, OpenFile, SyscallResult,
20};
21use crate::host::host::Host;
22use crate::host::memory_manager::MemoryManager;
23use crate::host::network::interface::FifoPacketPriority;
24use crate::host::network::namespace::NetworkNamespace;
25use crate::host::syscall::io::{IoVec, write_partial};
26use crate::host::syscall::types::{ForeignArrayPtr, SyscallError};
27use crate::host::thread::ThreadId;
28use crate::network::packet::PacketRc;
29use crate::utility::callback_queue::CallbackQueue;
30use crate::utility::sockaddr::SockaddrStorage;
31use crate::utility::{HostTreePointer, ObjectCounter};
32
33pub struct LegacyTcpSocket {
34    socket: HostTreePointer<c::TCP>,
35    // should only be used by `OpenFile` to make sure there is only ever one `OpenFile` instance for
36    // this file
37    has_open_file: bool,
38    /// Did the last connect() call block, and if so what thread?
39    thread_of_blocked_connect: Option<ThreadId>,
40    _counter: ObjectCounter,
41}
42
43impl LegacyTcpSocket {
44    pub fn new(status: FileStatus, host: &Host) -> Arc<AtomicRefCell<Self>> {
45        let recv_buf_size = host.params.init_sock_recv_buf_size.try_into().unwrap();
46        let send_buf_size = host.params.init_sock_send_buf_size.try_into().unwrap();
47
48        let tcp = unsafe { c::tcp_new(host, recv_buf_size, send_buf_size) };
49        let tcp = unsafe { Self::new_from_legacy(tcp) };
50
51        tcp.borrow_mut().set_status(status);
52
53        tcp
54    }
55
56    /// Takes ownership of the [`TCP`](c::TCP) reference.
57    ///
58    /// # Safety
59    ///
60    /// `legacy_tcp` must be safely dereferenceable, and not directly accessed again.
61    pub unsafe fn new_from_legacy(legacy_tcp: *mut c::TCP) -> Arc<AtomicRefCell<Self>> {
62        assert!(!legacy_tcp.is_null());
63
64        let socket = Self {
65            socket: HostTreePointer::new(legacy_tcp),
66            has_open_file: false,
67            thread_of_blocked_connect: None,
68            _counter: ObjectCounter::new("LegacyTcpSocket"),
69        };
70
71        let rv = Arc::new(AtomicRefCell::new(socket));
72
73        let inet_socket = InetSocket::LegacyTcp(rv.clone());
74        let inet_socket = Box::into_raw(Box::new(inet_socket.downgrade()));
75        unsafe { c::tcp_setRustSocket(legacy_tcp, inet_socket) };
76
77        rv
78    }
79
80    /// Get a canonical handle for this socket. We use the address of the `TCP` object so that the
81    /// rust socket and legacy socket have the same handle.
82    pub fn canonical_handle(&self) -> usize {
83        self.as_legacy_tcp() as usize
84    }
85
86    /// Get the [`c::TCP`] pointer.
87    pub fn as_legacy_tcp(&self) -> *mut c::TCP {
88        unsafe { self.socket.ptr() }
89    }
90
91    /// Get the [`c::TCP`] pointer as a [`c::LegacySocket`] pointer.
92    pub fn as_legacy_socket(&self) -> *mut c::LegacySocket {
93        self.as_legacy_tcp() as *mut c::LegacySocket
94    }
95
96    /// Get the [`c::TCP`] pointer as a [`c::LegacyFile`] pointer.
97    pub fn as_legacy_file(&self) -> *mut c::LegacyFile {
98        self.as_legacy_tcp() as *mut c::LegacyFile
99    }
100
101    pub fn status(&self) -> FileStatus {
102        let o_flags = unsafe { c::legacyfile_getFlags(self.as_legacy_file()) };
103        let o_flags =
104            linux_api::fcntl::OFlag::from_bits(o_flags).expect("Not a valid OFlag: {o_flags:?}");
105        let (status, extra_flags) = FileStatus::from_o_flags(o_flags);
106        assert!(
107            extra_flags.is_empty(),
108            "Rust wrapper doesn't support {extra_flags:?} flags",
109        );
110        status
111    }
112
113    pub fn set_status(&mut self, status: FileStatus) {
114        let o_flags = status.as_o_flags().bits();
115        unsafe { c::legacyfile_setFlags(self.as_legacy_file(), o_flags) };
116    }
117
118    pub fn mode(&self) -> FileMode {
119        FileMode::READ | FileMode::WRITE
120    }
121
122    pub fn has_open_file(&self) -> bool {
123        self.has_open_file
124    }
125
126    pub fn supports_sa_restart(&self) -> bool {
127        // TODO: false if a timeout has been set via setsockopt
128        true
129    }
130
131    pub fn set_has_open_file(&mut self, val: bool) {
132        self.has_open_file = val;
133    }
134
135    pub fn push_in_packet(
136        &mut self,
137        packet: PacketRc,
138        _cb_queue: &mut CallbackQueue,
139        _recv_time: EmulatedTime,
140    ) {
141        Worker::with_active_host(|host| {
142            // Here we drop the `PacketRc`, and we transfer our ref to the inner `Packet` to C.
143            unsafe {
144                c::legacysocket_pushInPacket(self.as_legacy_socket(), host, packet.into_raw())
145            };
146        })
147        .unwrap();
148    }
149
150    pub fn pull_out_packet(&mut self, _cb_queue: &mut CallbackQueue) -> Option<PacketRc> {
151        // If we get a `Packet`, a ref to it is transfered to us from the C code.
152        let packet = Worker::with_active_host(|host| unsafe {
153            c::legacysocket_pullOutPacket(self.as_legacy_socket(), host)
154        })
155        .unwrap();
156
157        if packet.is_null() {
158            return None;
159        }
160
161        // We own the ref to the `Packet` now, let the C code borrow it.
162        Worker::with_active_host(|host| unsafe {
163            c::tcp_networkInterfaceIsAboutToSendPacket(self.as_legacy_tcp(), host, packet);
164        })
165        .unwrap();
166
167        // We own the ref to the `Packet`, track it in a `PacketRc` and return it to the caller.
168        Some(PacketRc::from_raw(packet))
169    }
170
171    fn peek_packet(&self) -> Option<PacketRc> {
172        // If we get a `Packet`, a ref to it is transfered to us from the C code.
173        let packet = unsafe { c::legacysocket_peekNextOutPacket(self.as_legacy_socket()) };
174
175        if packet.is_null() {
176            return None;
177        }
178
179        // We own the ref to the `Packet`, track it in a `PacketRc` and return it to the caller.
180        Some(PacketRc::from_raw(packet))
181    }
182
183    pub fn peek_next_packet_priority(&self) -> Option<FifoPacketPriority> {
184        self.peek_packet().map(|p| p.priority())
185    }
186
187    pub fn has_data_to_send(&self) -> bool {
188        self.peek_packet().is_some()
189    }
190
191    pub fn getsockname(&self) -> Result<Option<SockaddrIn>, Errno> {
192        let mut ip: libc::in_addr_t = 0;
193        let mut port: libc::in_port_t = 0;
194
195        // should return ip and port in network byte order
196        let okay =
197            unsafe { c::legacysocket_getSocketName(self.as_legacy_socket(), &mut ip, &mut port) };
198        if okay != 1 {
199            return Ok(Some(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0).into()));
200        }
201
202        let ip = Ipv4Addr::from(u32::from_be(ip));
203        let port = u16::from_be(port);
204        let addr = SocketAddrV4::new(ip, port);
205
206        Ok(Some(addr.into()))
207    }
208
209    pub fn getpeername(&self) -> Result<Option<SockaddrIn>, Errno> {
210        let mut ip: libc::in_addr_t = 0;
211        let mut port: libc::in_port_t = 0;
212
213        // should return ip and port in network byte order
214        let okay =
215            unsafe { c::legacysocket_getPeerName(self.as_legacy_socket(), &mut ip, &mut port) };
216        if okay != 1 {
217            return Err(Errno::ENOTCONN);
218        }
219
220        let ip = Ipv4Addr::from(u32::from_be(ip));
221        let port = u16::from_be(port);
222        let addr = SocketAddrV4::new(ip, port);
223
224        Ok(Some(addr.into()))
225    }
226
227    pub fn address_family(&self) -> linux_api::socket::AddressFamily {
228        linux_api::socket::AddressFamily::AF_INET
229    }
230
231    pub fn close(&mut self, _cb_queue: &mut CallbackQueue) -> Result<(), SyscallError> {
232        Worker::with_active_host(|h| {
233            unsafe { c::legacyfile_close(self.as_legacy_file(), h) };
234        })
235        .unwrap();
236        Ok(())
237    }
238
239    pub fn bind(
240        socket: &Arc<AtomicRefCell<Self>>,
241        addr: Option<&SockaddrStorage>,
242        net_ns: &NetworkNamespace,
243        rng: impl rand::Rng,
244    ) -> Result<(), SyscallError> {
245        // if the address pointer was NULL
246        let Some(addr) = addr else {
247            return Err(Errno::EFAULT.into());
248        };
249
250        // if not an inet socket address
251        let Some(addr) = addr.as_inet() else {
252            return Err(Errno::EINVAL.into());
253        };
254
255        let addr: SocketAddrV4 = (*addr).into();
256
257        // if the socket is already bound
258        {
259            let socket = socket.borrow();
260            let socket = socket.as_legacy_socket();
261            if unsafe { c::legacysocket_isBound(socket) } == 1 {
262                return Err(Errno::EINVAL.into());
263            }
264        }
265
266        // make sure the socket doesn't have a peer
267        {
268            // Since we're not bound, we're not connected and have no peer. We may have a peer in
269            // the future if `connect()` is called on this socket.
270            let socket = socket.borrow();
271            let socket = socket.as_legacy_socket();
272            assert_eq!(0, unsafe {
273                c::legacysocket_getPeerName(socket, std::ptr::null_mut(), std::ptr::null_mut())
274            });
275        }
276
277        // this will allow us to receive packets from any peer
278        let peer_addr = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0);
279
280        // associate the socket
281        let (addr, handle) = inet::associate_socket(
282            InetSocket::LegacyTcp(Arc::clone(socket)),
283            addr,
284            peer_addr,
285            /* check_generic_peer= */ true,
286            net_ns,
287            rng,
288        )?;
289
290        // the handle normally disassociates the socket when dropped, but the C TCP code does it's
291        // own manual disassociation, so we'll just let it do its own thing
292        std::mem::forget(handle);
293
294        // update the socket's local address
295        let socket = socket.borrow_mut();
296        let socket = socket.as_legacy_socket();
297        unsafe {
298            c::legacysocket_setSocketName(
299                socket,
300                u32::from(*addr.ip()).to_be(),
301                addr.port().to_be(),
302            )
303        };
304
305        Ok(())
306    }
307
308    pub fn readv(
309        &mut self,
310        _iovs: &[IoVec],
311        _offset: Option<libc::off_t>,
312        _flags: libc::c_int,
313        _mem: &mut MemoryManager,
314        _cb_queue: &mut CallbackQueue,
315    ) -> Result<libc::ssize_t, SyscallError> {
316        // we could call LegacyTcpSocket::recvmsg() here, but for now we expect that there are no
317        // code paths that would call LegacyTcpSocket::readv() since the readv() syscall handler
318        // should have called LegacyTcpSocket::recvmsg() instead
319        panic!("Called LegacyTcpSocket::readv() on a TCP socket.");
320    }
321
322    pub fn writev(
323        &mut self,
324        _iovs: &[IoVec],
325        _offset: Option<libc::off_t>,
326        _flags: libc::c_int,
327        _mem: &mut MemoryManager,
328        _cb_queue: &mut CallbackQueue,
329    ) -> Result<libc::ssize_t, SyscallError> {
330        // we could call LegacyTcpSocket::sendmsg() here, but for now we expect that there are no
331        // code paths that would call LegacyTcpSocket::writev() since the writev() syscall handler
332        // should have called LegacyTcpSocket::sendmsg() instead
333        panic!("Called LegacyTcpSocket::writev() on a TCP socket");
334    }
335
336    pub fn sendmsg(
337        socket: &Arc<AtomicRefCell<Self>>,
338        args: SendmsgArgs,
339        mem: &mut MemoryManager,
340        _net_ns: &NetworkNamespace,
341        _rng: impl rand::Rng,
342        _cb_queue: &mut CallbackQueue,
343    ) -> Result<libc::ssize_t, SyscallError> {
344        let socket_ref = socket.borrow_mut();
345        let tcp = socket_ref.as_legacy_tcp();
346
347        if socket_ref.state().contains(FileState::CLOSED) {
348            // A file that is referenced in the descriptor table should never be a closed file. File
349            // handles (fds) are handles to open files, so if we have a file handle to a closed
350            // file, then there's an error somewhere in Shadow. Shadow's TCP sockets do close
351            // themselves even if there are still file handles (see `_tcp_endOfFileSignalled`), so
352            // we can't make this a panic.
353            log::warn!("Sending on a closed TCP socket");
354            return Err(Errno::EBADF.into());
355        }
356
357        let Some(mut flags) = MsgFlags::from_bits(args.flags) else {
358            log::warn!("Unrecognized send flags: {:#b}", args.flags);
359            return Err(Errno::EINVAL.into());
360        };
361
362        if socket_ref.status().contains(FileStatus::NONBLOCK) {
363            flags.insert(MsgFlags::MSG_DONTWAIT);
364        }
365
366        // run in a closure so that an early return doesn't skip checking if we should block
367        let result = (|| {
368            let mut bytes_sent = 0;
369
370            for iov in args.iovs {
371                let errcode = unsafe { c::tcp_getConnectionError(tcp) };
372
373                log::trace!("Connection error state is currently {errcode}");
374
375                #[allow(clippy::if_same_then_else)]
376                if errcode > 0 {
377                    // connect() was not called yet
378                    // TODO: Can they can piggy back a connect() on sendto() if they provide an
379                    // address for the connection?
380                    if bytes_sent == 0 {
381                        return Err(Errno::EPIPE);
382                    } else {
383                        break;
384                    }
385                } else if errcode == 0 {
386                    // They connected, but never read the success code with a second call to
387                    // connect(). That's OK, proceed to send as usual.
388                } else if errcode == -libc::EISCONN {
389                    // they are connected, and we can send now
390                } else if errcode == -libc::EALREADY {
391                    // connection in progress
392                    // TODO: should we wait, or just return -EALREADY?
393                    if bytes_sent == 0 {
394                        return Err(Errno::EWOULDBLOCK);
395                    } else {
396                        break;
397                    }
398                }
399
400                // SAFETY: We're passing an immutable pointer to the memory manager. We should not
401                // have any other mutable references to the memory manager at this point.
402                let rv = Worker::with_active_host(|host| unsafe {
403                    c::tcp_sendUserData(
404                        tcp,
405                        host,
406                        iov.base.cast::<()>(),
407                        iov.len.try_into().unwrap(),
408                        0,
409                        0,
410                        mem,
411                    )
412                })
413                .unwrap();
414
415                if rv < 0 {
416                    if bytes_sent == 0 {
417                        return Err(Errno::try_from(-rv).unwrap());
418                    } else {
419                        break;
420                    }
421                }
422
423                bytes_sent += rv;
424
425                if usize::try_from(rv).unwrap() < iov.len {
426                    // stop if we didn't write all of the data in the iov
427                    break;
428                }
429            }
430
431            Ok(bytes_sent)
432        })();
433
434        // if the syscall would block and we don't have the MSG_DONTWAIT flag
435        if result == Err(Errno::EWOULDBLOCK) && !flags.contains(MsgFlags::MSG_DONTWAIT) {
436            return Err(SyscallError::new_blocked_on_file(
437                File::Socket(Socket::Inet(InetSocket::LegacyTcp(socket.clone()))),
438                FileState::WRITABLE,
439                socket_ref.supports_sa_restart(),
440            ));
441        }
442
443        Ok(result?.try_into().unwrap())
444    }
445
446    pub fn recvmsg(
447        socket: &Arc<AtomicRefCell<Self>>,
448        mut args: RecvmsgArgs,
449        mem: &mut MemoryManager,
450        _cb_queue: &mut CallbackQueue,
451    ) -> Result<RecvmsgReturn, SyscallError> {
452        let socket_ref = socket.borrow_mut();
453        let tcp = socket_ref.as_legacy_tcp();
454
455        if socket_ref.state().contains(FileState::CLOSED) {
456            // A file that is referenced in the descriptor table should never be a closed file. File
457            // handles (fds) are handles to open files, so if we have a file handle to a closed
458            // file, then there's an error somewhere in Shadow. Shadow's TCP sockets do close
459            // themselves even if there are still file handles (see `_tcp_endOfFileSignalled`), so
460            // we can't make this a panic.
461            if unsafe { c::tcp_getConnectionError(tcp) != -libc::EISCONN } {
462                // connection error will be -ENOTCONN when reading is done
463                log::warn!("Receiving on a closed TCP socket");
464                return Err(Errno::EBADF.into());
465            }
466        }
467
468        let Some(mut flags) = MsgFlags::from_bits(args.flags) else {
469            log::warn!("Unrecognized recv flags: {:#b}", args.flags);
470            return Err(Errno::EINVAL.into());
471        };
472
473        if socket_ref.status().contains(FileStatus::NONBLOCK) {
474            flags.insert(MsgFlags::MSG_DONTWAIT);
475        }
476
477        // run in a closure so that an early return doesn't skip checking if we should block
478        let result = (|| {
479            let mut bytes_read = 0;
480
481            // want to make sure we run the loop at least once so that we can return any errors
482            if args.iovs.is_empty() {
483                const EMPTY_IOV: IoVec = IoVec {
484                    base: ForeignPtr::null(),
485                    len: 0,
486                };
487                args.iovs = std::slice::from_ref(&EMPTY_IOV);
488            }
489
490            for iov in args.iovs {
491                let errcode = unsafe { c::tcp_getConnectionError(tcp) };
492
493                if errcode > 0 {
494                    // connect() was not called yet
495                    if bytes_read == 0 {
496                        return Err(Errno::ENOTCONN);
497                    } else {
498                        break;
499                    }
500                } else if errcode == -libc::EALREADY {
501                    // Connection in progress
502                    if bytes_read == 0 {
503                        return Err(Errno::EWOULDBLOCK);
504                    } else {
505                        break;
506                    }
507                }
508
509                // SAFETY: We're passing a mutable pointer to the memory manager. We should not have
510                // any other mutable references to the memory manager at this point.
511                let rv = Worker::with_active_host(|host| unsafe {
512                    c::tcp_receiveUserData(
513                        tcp,
514                        host,
515                        iov.base.cast::<()>(),
516                        iov.len.try_into().unwrap(),
517                        std::ptr::null_mut(),
518                        std::ptr::null_mut(),
519                        mem,
520                    )
521                })
522                .unwrap();
523
524                if rv < 0 {
525                    if bytes_read == 0 {
526                        return Err(Errno::try_from(-rv).unwrap());
527                    } else {
528                        break;
529                    }
530                }
531
532                bytes_read += rv;
533
534                if usize::try_from(rv).unwrap() < iov.len {
535                    // stop if we didn't receive all of the data in the iov
536                    break;
537                }
538            }
539
540            Ok(RecvmsgReturn {
541                return_val: bytes_read.try_into().unwrap(),
542                addr: None,
543                msg_flags: 0,
544                control_len: 0,
545            })
546        })();
547
548        // if the syscall would block and we don't have the MSG_DONTWAIT flag
549        if result.as_ref().err() == Some(&Errno::EWOULDBLOCK)
550            && !flags.contains(MsgFlags::MSG_DONTWAIT)
551        {
552            return Err(SyscallError::new_blocked_on_file(
553                File::Socket(Socket::Inet(InetSocket::LegacyTcp(socket.clone()))),
554                FileState::READABLE,
555                socket_ref.supports_sa_restart(),
556            ));
557        }
558
559        Ok(result?)
560    }
561
562    pub fn ioctl(
563        &mut self,
564        request: IoctlRequest,
565        arg_ptr: ForeignPtr<()>,
566        memory_manager: &mut MemoryManager,
567    ) -> SyscallResult {
568        match request {
569            // equivalent to SIOCINQ
570            IoctlRequest::FIONREAD => {
571                let len = unsafe { c::tcp_getInputBufferLength(self.as_legacy_tcp()) }
572                    .try_into()
573                    .unwrap();
574
575                let arg_ptr = arg_ptr.cast::<libc::c_int>();
576                memory_manager.write(arg_ptr, &len)?;
577
578                Ok(0.into())
579            }
580            // equivalent to SIOCOUTQ
581            IoctlRequest::TIOCOUTQ => {
582                let len = unsafe { c::tcp_getOutputBufferLength(self.as_legacy_tcp()) }
583                    .try_into()
584                    .unwrap();
585
586                let arg_ptr = arg_ptr.cast::<libc::c_int>();
587                memory_manager.write(arg_ptr, &len)?;
588
589                Ok(0.into())
590            }
591            IoctlRequest::SIOCOUTQNSD => {
592                let len = unsafe { c::tcp_getNotSentBytes(self.as_legacy_tcp()) }
593                    .try_into()
594                    .unwrap();
595
596                let arg_ptr = arg_ptr.cast::<libc::c_int>();
597                memory_manager.write(arg_ptr, &len)?;
598
599                Ok(0.into())
600            }
601            // this isn't supported by tcp
602            IoctlRequest::SIOCGSTAMP => Err(Errno::ENOENT.into()),
603            IoctlRequest::FIONBIO => {
604                panic!("This should have been handled by the ioctl syscall handler");
605            }
606            IoctlRequest::TCGETS
607            | IoctlRequest::TCSETS
608            | IoctlRequest::TCSETSW
609            | IoctlRequest::TCSETSF
610            | IoctlRequest::TCGETA
611            | IoctlRequest::TCSETA
612            | IoctlRequest::TCSETAW
613            | IoctlRequest::TCSETAF
614            | IoctlRequest::TIOCGWINSZ
615            | IoctlRequest::TIOCSWINSZ => {
616                // not a terminal
617                Err(Errno::ENOTTY.into())
618            }
619            request => {
620                warn_once_then_debug!(
621                    "We do not yet handle ioctl request {request:?} on tcp sockets"
622                );
623                Err(Errno::EINVAL.into())
624            }
625        }
626    }
627
628    pub fn stat(&self) -> Result<linux_api::stat::stat, SyscallError> {
629        warn_once_then_debug!("We do not yet handle stat calls on tcp sockets");
630        Err(Errno::EINVAL.into())
631    }
632
633    pub fn lseek(
634        &mut self,
635        _off: linux_api::posix_types::kernel_off_t,
636        _whence: linux_api::unistd::LSeekWhence,
637    ) -> Result<linux_api::posix_types::kernel_off_t, SyscallError> {
638        warn_once_then_debug!("We do not yet handle lseek calls on tcp sockets");
639        Err(Errno::EBADF.into())
640    }
641
642    pub fn listen(
643        socket: &Arc<AtomicRefCell<Self>>,
644        backlog: i32,
645        net_ns: &NetworkNamespace,
646        rng: impl rand::Rng,
647        _cb_queue: &mut CallbackQueue,
648    ) -> Result<(), Errno> {
649        let socket_ref = socket.borrow();
650
651        // only listen on the socket if it is not used for other functions
652        let is_listening_allowed =
653            unsafe { c::tcp_isListeningAllowed(socket_ref.as_legacy_tcp()) } == 1;
654        if !is_listening_allowed {
655            log::debug!("Cannot listen on previously used socket");
656            return Err(Errno::EOPNOTSUPP);
657        }
658
659        // if we are already listening, just update the backlog and return 0
660        let is_valid_listener = unsafe { c::tcp_isValidListener(socket_ref.as_legacy_tcp()) } == 1;
661        if is_valid_listener {
662            log::trace!("Socket already set up as a listener; updating backlog");
663            unsafe { c::tcp_updateServerBacklog(socket_ref.as_legacy_tcp(), backlog) };
664            return Ok(());
665        }
666
667        // a listening socket must be bound
668        let is_bound = unsafe { c::legacysocket_isBound(socket_ref.as_legacy_socket()) } == 1;
669        if !is_bound {
670            log::trace!("Implicitly binding listener socket");
671
672            // implicit bind: bind to all interfaces at an ephemeral port
673            let local_addr = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0);
674
675            // this will allow us to receive packets from any peer address
676            let peer_addr = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0);
677
678            // associate the socket
679            let (local_addr, handle) = super::associate_socket(
680                super::InetSocket::LegacyTcp(socket.clone()),
681                local_addr,
682                peer_addr,
683                /* check_generic_peer= */ true,
684                net_ns,
685                rng,
686            )?;
687
688            // the handle normally disassociates the socket when dropped, but the C TCP code does
689            // it's own manual disassociation, so we'll just let it do its own thing
690            std::mem::forget(handle);
691
692            unsafe {
693                c::legacysocket_setSocketName(
694                    socket_ref.as_legacy_socket(),
695                    u32::from(*local_addr.ip()).to_be(),
696                    local_addr.port().to_be(),
697                )
698            };
699        }
700
701        // we are allowed to listen but not already listening; start now
702        Worker::with_active_host(|host| {
703            unsafe {
704                c::tcp_enterServerMode(
705                    socket_ref.as_legacy_tcp(),
706                    host,
707                    Worker::active_process_id().unwrap().into(),
708                    backlog,
709                )
710            };
711        })
712        .unwrap();
713
714        Ok(())
715    }
716
717    pub fn connect(
718        socket: &Arc<AtomicRefCell<Self>>,
719        peer_addr: &SockaddrStorage,
720        net_ns: &NetworkNamespace,
721        rng: impl rand::Rng,
722        _cb_queue: &mut CallbackQueue,
723    ) -> Result<(), SyscallError> {
724        let mut socket_ref = socket.borrow_mut();
725
726        if let Some(tid) = socket_ref.thread_of_blocked_connect {
727            // check if there is already a blocking connect() call on another thread
728            if tid != Worker::active_thread_id().unwrap() {
729                // connect(2) says "Generally,  connection-based protocol sockets may successfully
730                // connect() only once", but the application is attempting to call connect() in two
731                // threads on a blocking socket at the same time. Let's just return an error and
732                // hope no one ever does this.
733                log::warn!("Two threads are attempting to connect() on a blocking socket");
734                return Err(Errno::EBADFD.into());
735            }
736        }
737
738        // if the socket is already listening, return EISCONN
739        let is_valid_listener = unsafe { c::tcp_isValidListener(socket_ref.as_legacy_tcp()) } == 1;
740        if is_valid_listener {
741            return Err(Errno::EISCONN.into());
742        }
743
744        let Some(peer_addr) = peer_addr.as_inet() else {
745            return Err(Errno::EINVAL.into());
746        };
747
748        let mut peer_addr: std::net::SocketAddrV4 = (*peer_addr).into();
749
750        // https://stackoverflow.com/a/22425796
751        if peer_addr.ip().is_unspecified() {
752            peer_addr.set_ip(std::net::Ipv4Addr::LOCALHOST);
753        }
754
755        let host_default_ip = net_ns.default_ip;
756
757        // NOTE: it would be nice to use `Ipv4Addr::is_loopback` in this code rather than comparing
758        // to `Ipv4Addr::LOCALHOST`, but the rest of Shadow probably can't handle other loopback
759        // addresses (ex: 127.0.0.2) and it's probably best not to change this behaviour
760
761        // make sure we will be able to route this later
762        // TODO: should we just send the SYN and let the connection fail normally?
763        if peer_addr.ip() != &std::net::Ipv4Addr::LOCALHOST {
764            let is_routable = Worker::is_routable(host_default_ip.into(), (*peer_addr.ip()).into());
765
766            if !is_routable {
767                // can't route it - there is no node with this address
768                log::warn!(
769                    "Attempting to connect to address '{peer_addr}' for which no host exists"
770                );
771                return Err(Errno::ECONNREFUSED.into());
772            }
773        }
774
775        // a connected tcp socket must be bound
776        let is_bound = unsafe { c::legacysocket_isBound(socket_ref.as_legacy_socket()) } == 1;
777        if !is_bound {
778            log::trace!("Implicitly binding listener socket");
779
780            // implicit bind: bind to an ephemeral port (use default interface unless the remote
781            // peer is on loopback)
782            let local_addr = if peer_addr.ip() == &std::net::Ipv4Addr::LOCALHOST {
783                SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0)
784            } else {
785                SocketAddrV4::new(host_default_ip, 0)
786            };
787
788            // associate the socket
789            let (local_addr, handle) = super::associate_socket(
790                super::InetSocket::LegacyTcp(socket.clone()),
791                local_addr,
792                peer_addr,
793                /* check_generic_peer= */ true,
794                net_ns,
795                rng,
796            )?;
797
798            // the handle normally disassociates the socket when dropped, but the C TCP code does
799            // it's own manual disassociation, so we'll just let it do its own thing
800            std::mem::forget(handle);
801
802            unsafe {
803                c::legacysocket_setSocketName(
804                    socket_ref.as_legacy_socket(),
805                    u32::from(*local_addr.ip()).to_be(),
806                    local_addr.port().to_be(),
807                )
808            };
809        } else if let Some(bound_addr) = socket_ref.getsockname()? {
810            // make sure the new peer address is connectable from the bound interface
811            if !bound_addr.ip().is_unspecified() {
812                // assume that a socket bound to 0.0.0.0 can connect anywhere, so only check
813                // localhost
814                match (
815                    bound_addr.ip() == Ipv4Addr::LOCALHOST,
816                    peer_addr.ip() == &Ipv4Addr::LOCALHOST,
817                ) {
818                    // bound and peer on loopback interface
819                    (true, true) => {}
820                    // neither bound nor peer on loopback interface (shadow treats any
821                    // non-127.0.0.1 address as an "internet" address)
822                    (false, false) => {}
823                    _ => return Err(Errno::EINVAL.into()),
824                }
825            }
826        }
827
828        unsafe {
829            c::legacysocket_setPeerName(
830                socket_ref.as_legacy_socket(),
831                u32::from(*peer_addr.ip()).to_be(),
832                peer_addr.port().to_be(),
833            )
834        };
835
836        // now we are ready to connect
837        let errcode = Worker::with_active_host(|host| unsafe {
838            c::legacysocket_connectToPeer(
839                socket_ref.as_legacy_socket(),
840                host,
841                u32::from(*peer_addr.ip()).to_be(),
842                peer_addr.port().to_be(),
843                libc::AF_INET as u16,
844            )
845        })
846        .unwrap();
847
848        assert!(errcode <= 0);
849
850        let mut errcode = if errcode < 0 {
851            Err(Errno::try_from(-errcode).unwrap())
852        } else {
853            Ok(())
854        };
855
856        if !socket_ref.status().contains(FileStatus::NONBLOCK) {
857            // this is a blocking connect call
858            if errcode == Err(Errno::EINPROGRESS) {
859                // This is the first time we ever called connect, and so we need to wait for the
860                // 3-way handshake to complete. We will wait indefinitely for a success or failure.
861
862                let err = SyscallError::new_blocked_on_file(
863                    File::Socket(Socket::Inet(InetSocket::LegacyTcp(Arc::clone(socket)))),
864                    FileState::ACTIVE | FileState::WRITABLE,
865                    socket_ref.supports_sa_restart(),
866                );
867
868                // block the current thread
869                socket_ref.thread_of_blocked_connect = Some(Worker::active_thread_id().unwrap());
870                return Err(err);
871            }
872
873            // if we were previously blocked (we checked the thread ID above) and are now connected
874            if socket_ref.thread_of_blocked_connect.is_some() && errcode == Err(Errno::EISCONN) {
875                // it was EINPROGRESS, but is now a successful blocking connect
876                errcode = Ok(());
877            }
878        }
879
880        // make sure we return valid error codes for connect
881        if errcode == Err(Errno::ECONNRESET) || errcode == Err(Errno::ENOTCONN) {
882            errcode = Err(Errno::EISCONN);
883        }
884        // EALREADY is well defined in man page, but Linux returns EINPROGRESS
885        if errcode == Err(Errno::EALREADY) {
886            errcode = Err(Errno::EINPROGRESS);
887        }
888
889        socket_ref.thread_of_blocked_connect = None;
890        errcode.map_err(Into::into)
891    }
892
893    pub fn accept(
894        &mut self,
895        net_ns: &NetworkNamespace,
896        rng: impl rand::Rng,
897        _cb_queue: &mut CallbackQueue,
898    ) -> Result<OpenFile, SyscallError> {
899        let is_valid_listener = unsafe { c::tcp_isValidListener(self.as_legacy_tcp()) } == 1;
900
901        // we must be listening in order to accept
902        if !is_valid_listener {
903            log::debug!("Socket is not listening");
904            return Err(Errno::EINVAL.into());
905        }
906
907        let mut peer_addr: libc::sockaddr_in = shadow_pod::zeroed();
908        peer_addr.sin_family = libc::AF_INET as u16;
909        let mut accepted_fd = -1;
910
911        // now we can check if we have anything to accept
912        let errcode = Worker::with_active_host(|host| unsafe {
913            c::tcp_acceptServerPeer(
914                self.as_legacy_tcp(),
915                host,
916                &mut peer_addr.sin_addr.s_addr,
917                &mut peer_addr.sin_port,
918                &mut accepted_fd,
919            )
920        })
921        .unwrap();
922
923        assert!(errcode <= 0);
924
925        if errcode < 0 {
926            log::trace!("TCP error when accepting connection");
927            return Err(Errno::try_from(-errcode).unwrap().into());
928        }
929
930        // we accepted something!
931        assert!(accepted_fd >= 0);
932
933        // The rust socket syscall interface expects us to return the socket object so that it can
934        // add it to the descriptor table, but the TCP code has already added it to the descriptor
935        // table (see https://github.com/shadow/shadow/issues/1780). We'll remove the socket from
936        // the descriptor table, return it to the syscall handler, and let the syscall handler
937        // re-add it to the descriptor table. It may end up with a different fd handle, but that
938        // should be fine since nothing should be relying on the socket having a specific/fixed fd
939        // handle.
940
941        let new_descriptor = Worker::with_active_host(|host| {
942            Worker::with_active_thread(|thread| {
943                thread
944                    .descriptor_table_borrow_mut(host)
945                    .deregister_descriptor(accepted_fd.try_into().unwrap())
946                    .unwrap()
947            })
948        })
949        .unwrap()
950        .unwrap();
951
952        let CompatFile::New(open_file) = new_descriptor.into_file() else {
953            panic!(
954                "The TCP code should have added the TCP socket to the descriptor table as a rust socket"
955            );
956        };
957
958        // Associate the new socket with the local:peer address pair. In
959        // previous versions of shadow, the new socket was never registered this
960        // way. Instead the packets continued to be routed to the parent
961        // listening-socket, which was responsible for routing them to this new
962        // "child" socket.  But that led to bugs such as
963        // https://github.com/shadow/shadow/issues/3563.
964        {
965            let File::Socket(Socket::Inet(InetSocket::LegacyTcp(new_socket))) =
966                open_file.inner_file()
967            else {
968                panic!("Expected this to be a LegacyTcpSocket");
969            };
970
971            // get and validate child peer and local addresses.
972            let child_peer_addr;
973            let child_local_addr;
974            {
975                let new_socket_ref = new_socket.borrow();
976                // sanity check: make sure new socket peer address matches address returned from
977                // tcp_acceptServerPeer() above
978                {
979                    let mut ip: libc::in_addr_t = 0;
980                    let mut port: libc::in_port_t = 0;
981
982                    // should return ip and port in network byte order
983                    let okay = unsafe {
984                        c::legacysocket_getPeerName(
985                            new_socket_ref.as_legacy_socket(),
986                            &mut ip,
987                            &mut port,
988                        )
989                    };
990
991                    assert_eq!(okay, 1);
992                    assert_eq!(ip, peer_addr.sin_addr.s_addr);
993                    assert_eq!(port, peer_addr.sin_port);
994                }
995                child_peer_addr = new_socket_ref
996                    .getpeername()
997                    .expect("error finding child peer address")
998                    .expect("missing child peer address");
999                child_local_addr = new_socket_ref
1000                    .getsockname()
1001                    .expect("error finding local address")
1002                    .expect("missing local address");
1003                let parent_local_addr = self
1004                    .getsockname()
1005                    .expect("error finding parent local address")
1006                    .expect("missing parent local address");
1007                // port should be the same as the listening socket.
1008                debug_assert_eq!(
1009                    child_local_addr.port(),
1010                    parent_local_addr.port(),
1011                    "local address of accept'ed socket doesn't match parent listening socket"
1012                );
1013                // address should be same as the parent address, unless it was unspecified.
1014                if !parent_local_addr.ip().is_unspecified() {
1015                    debug_assert_eq!(child_local_addr.ip(), parent_local_addr.ip());
1016                }
1017                // in any case, address of the child should *not* be unspecified.
1018                debug_assert!(!child_local_addr.ip().is_unspecified());
1019                debug_assert_ne!(!child_local_addr.port(), 0);
1020            }
1021
1022            let (_addr, handle) = inet::associate_socket(
1023                InetSocket::LegacyTcp(Arc::clone(new_socket)),
1024                SocketAddrV4::from(child_local_addr),
1025                SocketAddrV4::from(child_peer_addr),
1026                /* Allow the parent/listening socket to be bound to the same address,
1027                 * with a missing/generic peer. */
1028                /* check_generic_peer= */
1029                false,
1030                net_ns,
1031                rng,
1032            )?;
1033            // the handle normally disassociates the socket when dropped, but
1034            // the C TCP code does its own manual disassociation, so we'll just
1035            // let it do its own thing.
1036            std::mem::forget(handle);
1037        }
1038
1039        Ok(open_file)
1040    }
1041
1042    pub fn shutdown(
1043        &mut self,
1044        how: Shutdown,
1045        _cb_queue: &mut CallbackQueue,
1046    ) -> Result<(), SyscallError> {
1047        let how = match how {
1048            Shutdown::SHUT_RD => libc::SHUT_RD,
1049            Shutdown::SHUT_WR => libc::SHUT_WR,
1050            Shutdown::SHUT_RDWR => libc::SHUT_RDWR,
1051        };
1052
1053        let errcode = Worker::with_active_host(|host| unsafe {
1054            c::tcp_shutdown(self.as_legacy_tcp(), host, how)
1055        })
1056        .unwrap();
1057
1058        assert!(errcode <= 0);
1059
1060        if errcode < 0 {
1061            return Err(Errno::try_from(-errcode).unwrap().into());
1062        }
1063
1064        Ok(())
1065    }
1066
1067    pub fn getsockopt(
1068        &self,
1069        level: libc::c_int,
1070        optname: libc::c_int,
1071        optval_ptr: ForeignPtr<()>,
1072        optlen: libc::socklen_t,
1073        memory_manager: &mut MemoryManager,
1074        _cb_queue: &mut CallbackQueue,
1075    ) -> Result<libc::socklen_t, SyscallError> {
1076        match (level, optname) {
1077            (libc::SOL_TCP, libc::TCP_INFO) => {
1078                let mut info = shadow_pod::zeroed();
1079                unsafe { c::tcp_getInfo(self.as_legacy_tcp(), &mut info) };
1080
1081                let optval_ptr = optval_ptr.cast::<crate::cshadow::tcp_info>();
1082                let bytes_written =
1083                    write_partial(memory_manager, &info, optval_ptr, optlen as usize)?;
1084
1085                Ok(bytes_written as libc::socklen_t)
1086            }
1087            (libc::SOL_TCP, libc::TCP_NODELAY) => {
1088                // shadow doesn't support nagle's algorithm, so shadow always behaves as if
1089                // TCP_NODELAY is enabled
1090                let val = 1;
1091
1092                let optval_ptr = optval_ptr.cast::<libc::c_int>();
1093                let bytes_written =
1094                    write_partial(memory_manager, &val, optval_ptr, optlen as usize)?;
1095
1096                Ok(bytes_written as libc::socklen_t)
1097            }
1098            (libc::SOL_TCP, libc::TCP_CONGESTION) => {
1099                // the value of TCP_CA_NAME_MAX in linux
1100                const CONG_NAME_MAX: usize = 16;
1101
1102                if optval_ptr.is_null() {
1103                    return Err(Errno::EINVAL.into());
1104                }
1105
1106                let name: *const libc::c_char =
1107                    unsafe { c::tcpcong_nameStr(c::tcp_cong(self.as_legacy_tcp())) };
1108                assert!(!name.is_null(), "shadow's congestion type has no name");
1109                let name = unsafe { CStr::from_ptr(name) };
1110                let name = name.to_bytes_with_nul();
1111
1112                let bytes_to_copy = *[optlen as usize, CONG_NAME_MAX, name.len()]
1113                    .iter()
1114                    .min()
1115                    .unwrap();
1116
1117                let name = &name[..bytes_to_copy];
1118                let optval_ptr = optval_ptr.cast::<u8>();
1119                let optval_ptr = ForeignArrayPtr::new(optval_ptr, bytes_to_copy);
1120
1121                memory_manager.copy_to_ptr(optval_ptr, name)?;
1122
1123                // the len value returned by linux seems to be independent from the actual string length
1124                Ok(std::cmp::min(optlen as usize, CONG_NAME_MAX) as libc::socklen_t)
1125            }
1126            (libc::SOL_SOCKET, libc::SO_SNDBUF) => {
1127                let sndbuf_size: libc::c_int =
1128                    unsafe { c::legacysocket_getOutputBufferSize(self.as_legacy_socket()) }
1129                        .try_into()
1130                        .unwrap();
1131
1132                let optval_ptr = optval_ptr.cast::<libc::c_int>();
1133                let bytes_written =
1134                    write_partial(memory_manager, &sndbuf_size, optval_ptr, optlen as usize)?;
1135
1136                Ok(bytes_written as libc::socklen_t)
1137            }
1138            (libc::SOL_SOCKET, libc::SO_RCVBUF) => {
1139                let rcvbuf_size: libc::c_int =
1140                    unsafe { c::legacysocket_getInputBufferSize(self.as_legacy_socket()) }
1141                        .try_into()
1142                        .unwrap();
1143
1144                let optval_ptr = optval_ptr.cast::<libc::c_int>();
1145                let bytes_written =
1146                    write_partial(memory_manager, &rcvbuf_size, optval_ptr, optlen as usize)?;
1147
1148                Ok(bytes_written as libc::socklen_t)
1149            }
1150            (libc::SOL_SOCKET, libc::SO_ERROR) => {
1151                // return error for failed connect() attempts
1152                let conn_err = unsafe { c::tcp_getConnectionError(self.as_legacy_tcp()) };
1153
1154                let error = if conn_err == -libc::ECONNRESET || conn_err == -libc::ECONNREFUSED {
1155                    // result is a positive errcode
1156                    -conn_err
1157                } else {
1158                    0
1159                };
1160
1161                let optval_ptr = optval_ptr.cast::<libc::c_int>();
1162                let bytes_written =
1163                    write_partial(memory_manager, &error, optval_ptr, optlen as usize)?;
1164
1165                Ok(bytes_written as libc::socklen_t)
1166            }
1167            (libc::SOL_SOCKET, libc::SO_DOMAIN) => {
1168                let domain = libc::AF_INET;
1169
1170                let optval_ptr = optval_ptr.cast::<libc::c_int>();
1171                let bytes_written =
1172                    write_partial(memory_manager, &domain, optval_ptr, optlen as usize)?;
1173
1174                Ok(bytes_written as libc::socklen_t)
1175            }
1176            (libc::SOL_SOCKET, libc::SO_TYPE) => {
1177                let sock_type = libc::SOCK_STREAM;
1178
1179                let optval_ptr = optval_ptr.cast::<libc::c_int>();
1180                let bytes_written =
1181                    write_partial(memory_manager, &sock_type, optval_ptr, optlen as usize)?;
1182
1183                Ok(bytes_written as libc::socklen_t)
1184            }
1185            (libc::SOL_SOCKET, libc::SO_PROTOCOL) => {
1186                let protocol = libc::IPPROTO_TCP;
1187
1188                let optval_ptr = optval_ptr.cast::<libc::c_int>();
1189                let bytes_written =
1190                    write_partial(memory_manager, &protocol, optval_ptr, optlen as usize)?;
1191
1192                Ok(bytes_written as libc::socklen_t)
1193            }
1194            (libc::SOL_SOCKET, libc::SO_ACCEPTCONN) => {
1195                let is_listener = unsafe { c::tcp_isValidListener(self.as_legacy_tcp()) };
1196
1197                let optval_ptr = optval_ptr.cast::<libc::c_int>();
1198                let bytes_written =
1199                    write_partial(memory_manager, &is_listener, optval_ptr, optlen as usize)?;
1200
1201                Ok(bytes_written as libc::socklen_t)
1202            }
1203            (libc::SOL_SOCKET, libc::SO_BROADCAST) => {
1204                let optval_ptr = optval_ptr.cast::<libc::c_int>();
1205                // we don't support broadcast sockets, so just just return the default 0
1206                let bytes_written = write_partial(memory_manager, &0, optval_ptr, optlen as usize)?;
1207
1208                Ok(bytes_written as libc::socklen_t)
1209            }
1210            _ => {
1211                log_once_per_value_at_level!(
1212                    (level, optname),
1213                    (i32, i32),
1214                    log::Level::Warn,
1215                    log::Level::Debug,
1216                    "getsockopt called with unsupported level {level} and opt {optname}"
1217                );
1218                Err(Errno::ENOPROTOOPT.into())
1219            }
1220        }
1221    }
1222
1223    pub fn setsockopt(
1224        &mut self,
1225        level: libc::c_int,
1226        optname: libc::c_int,
1227        optval_ptr: ForeignPtr<()>,
1228        optlen: libc::socklen_t,
1229        memory_manager: &MemoryManager,
1230    ) -> Result<(), SyscallError> {
1231        match (level, optname) {
1232            (libc::SOL_TCP, libc::TCP_NODELAY) => {
1233                // Shadow doesn't support nagle's algorithm, so Shadow always behaves as if
1234                // TCP_NODELAY is enabled. Some programs will fail if `setsockopt(fd, SOL_TCP,
1235                // TCP_NODELAY, &1, sizeof(int))` returns an error, so we treat this as a no-op for
1236                // compatibility.
1237
1238                type OptType = libc::c_int;
1239
1240                if usize::try_from(optlen).unwrap() < std::mem::size_of::<OptType>() {
1241                    return Err(Errno::EINVAL.into());
1242                }
1243
1244                let optval_ptr = optval_ptr.cast::<OptType>();
1245                let enable = memory_manager.read(optval_ptr)?;
1246
1247                if enable != 0 {
1248                    // wants to enable TCP_NODELAY
1249                    log::debug!("Ignoring TCP_NODELAY");
1250                } else {
1251                    // wants to disable TCP_NODELAY
1252                    log::warn!(
1253                        "Cannot disable TCP_NODELAY since shadow does not implement Nagle's algorithm."
1254                    );
1255                    return Err(Errno::ENOPROTOOPT.into());
1256                }
1257            }
1258            (libc::SOL_TCP, libc::TCP_CONGESTION) => {
1259                // the value of TCP_CA_NAME_MAX in linux
1260                const CONG_NAME_MAX: usize = 16;
1261
1262                let mut name = [0u8; CONG_NAME_MAX];
1263
1264                let optlen = std::cmp::min(optlen as usize, CONG_NAME_MAX);
1265                let name = &mut name[..optlen];
1266
1267                let optval_ptr = optval_ptr.cast::<u8>();
1268                let optval_ptr = ForeignArrayPtr::new(optval_ptr, optlen);
1269                memory_manager.copy_from_ptr(name, optval_ptr)?;
1270
1271                // truncate the name at the first NUL character if there is one, but don't include
1272                // the NUL since in linux the strings don't need a NUL
1273                let name = name
1274                    .iter()
1275                    .position(|x| *x == 0)
1276                    .map(|x| &name[..x])
1277                    .unwrap_or(name);
1278
1279                let reno = unsafe { CStr::from_ptr(c::TCP_CONG_RENO_NAME) }.to_bytes();
1280
1281                if name != reno {
1282                    log::warn!("Shadow sockets only support '{reno:?}' for TCP_CONGESTION");
1283                    return Err(Errno::ENOENT.into());
1284                }
1285
1286                // shadow doesn't support other congestion types, so do nothing
1287            }
1288            (libc::SOL_SOCKET, libc::SO_SNDBUF) => {
1289                type OptType = libc::c_int;
1290
1291                if usize::try_from(optlen).unwrap() < std::mem::size_of::<OptType>() {
1292                    return Err(Errno::EINVAL.into());
1293                }
1294
1295                let optval_ptr = optval_ptr.cast::<OptType>();
1296                let val: u64 = memory_manager
1297                    .read(optval_ptr)?
1298                    .try_into()
1299                    .or(Err(Errno::EINVAL))?;
1300
1301                // linux kernel doubles this value upon setting
1302                let val = val * 2;
1303
1304                // Linux also has limits SOCK_MIN_SNDBUF (slightly greater than 4096) and the sysctl
1305                // max limit. We choose a reasonable lower limit for Shadow. The minimum limit in
1306                // man 7 socket is incorrect.
1307                let val = std::cmp::max(val, 4096);
1308
1309                // This upper limit was added as an arbitrarily high number so that we don't change
1310                // Shadow's behaviour, but also prevents an application from setting this to
1311                // something unnecessarily large like INT_MAX.
1312                let val = std::cmp::min(val, 268435456); // 2^28 = 256 MiB
1313
1314                unsafe { c::legacysocket_setOutputBufferSize(self.as_legacy_socket(), val) };
1315                unsafe { c::tcp_disableSendBufferAutotuning(self.as_legacy_tcp()) };
1316            }
1317            (libc::SOL_SOCKET, libc::SO_RCVBUF) => {
1318                type OptType = libc::c_int;
1319
1320                if usize::try_from(optlen).unwrap() < std::mem::size_of::<OptType>() {
1321                    return Err(Errno::EINVAL.into());
1322                }
1323
1324                let optval_ptr = optval_ptr.cast::<OptType>();
1325                let val: u64 = memory_manager
1326                    .read(optval_ptr)?
1327                    .try_into()
1328                    .or(Err(Errno::EINVAL))?;
1329
1330                // linux kernel doubles this value upon setting
1331                let val = val * 2;
1332
1333                // Linux also has limits SOCK_MIN_RCVBUF (slightly greater than 2048) and the sysctl
1334                // max limit. We choose a reasonable lower limit for Shadow. The minimum limit in
1335                // man 7 socket is incorrect.
1336                let val = std::cmp::max(val, 2048);
1337
1338                // This upper limit was added as an arbitrarily high number so that we don't change
1339                // Shadow's behaviour, but also prevents an application from setting this to
1340                // something unnecessarily large like INT_MAX.
1341                let val = std::cmp::min(val, 268435456); // 2^28 = 256 MiB
1342
1343                unsafe { c::legacysocket_setInputBufferSize(self.as_legacy_socket(), val) };
1344                unsafe { c::tcp_disableReceiveBufferAutotuning(self.as_legacy_tcp()) };
1345            }
1346            (libc::SOL_SOCKET, libc::SO_REUSEADDR) => {
1347                // TODO: implement this, tor and tgen use it
1348                log::trace!("setsockopt SO_REUSEADDR not yet implemented");
1349            }
1350            (libc::SOL_SOCKET, libc::SO_REUSEPORT) => {
1351                // TODO: implement this, tgen uses it
1352                log::trace!("setsockopt SO_REUSEPORT not yet implemented");
1353            }
1354            (libc::SOL_SOCKET, libc::SO_KEEPALIVE) => {
1355                // TODO: implement this, libevent uses it in
1356                // evconnlistener_new_bind()
1357                log::trace!("setsockopt SO_KEEPALIVE not yet implemented");
1358            }
1359            (libc::SOL_SOCKET, libc::SO_BROADCAST) => {
1360                type OptType = libc::c_int;
1361
1362                if usize::try_from(optlen).unwrap() < std::mem::size_of::<OptType>() {
1363                    return Err(Errno::EINVAL.into());
1364                }
1365
1366                let optval_ptr = optval_ptr.cast::<OptType>();
1367                let val = memory_manager.read(optval_ptr)?;
1368
1369                if val == 0 {
1370                    // we don't support broadcast sockets, so an attempt to disable is okay
1371                } else {
1372                    // TODO: implement this, pkg.go.dev/net uses it
1373                    warn_once_then_debug!(
1374                        "setsockopt SO_BROADCAST not yet implemented for tcp; ignoring and returning 0"
1375                    );
1376                }
1377            }
1378            _ => {
1379                log_once_per_value_at_level!(
1380                    (level, optname),
1381                    (i32, i32),
1382                    log::Level::Warn,
1383                    log::Level::Debug,
1384                    "setsockopt called with unsupported level {level} and opt {optname}"
1385                );
1386                return Err(Errno::ENOPROTOOPT.into());
1387            }
1388        }
1389
1390        Ok(())
1391    }
1392
1393    pub fn add_listener(
1394        &mut self,
1395        monitoring_state: FileState,
1396        monitoring_signals: FileSignals,
1397        filter: StateListenerFilter,
1398        notify_fn: impl Fn(FileState, FileState, FileSignals, &mut CallbackQueue)
1399        + Send
1400        + Sync
1401        + 'static,
1402    ) -> StateListenHandle {
1403        let event_source = unsafe { c::legacyfile_getEventSource(self.as_legacy_file()) };
1404        let event_source = unsafe { event_source.as_ref() }.unwrap();
1405
1406        Worker::with_active_host(|host| {
1407            let mut event_source = event_source.borrow_mut(host.root());
1408            event_source.add_listener(monitoring_state, monitoring_signals, filter, notify_fn)
1409        })
1410        .unwrap()
1411    }
1412
1413    pub fn add_legacy_listener(&mut self, ptr: HostTreePointer<c::StatusListener>) {
1414        unsafe { c::legacyfile_addListener(self.as_legacy_file(), ptr.ptr()) };
1415    }
1416
1417    pub fn remove_legacy_listener(&mut self, ptr: *mut c::StatusListener) {
1418        unsafe { c::legacyfile_removeListener(self.as_legacy_file(), ptr) };
1419    }
1420
1421    pub fn state(&self) -> FileState {
1422        unsafe { c::legacyfile_getStatus(self.as_legacy_file()) }
1423    }
1424}
1425
1426impl std::ops::Drop for LegacyTcpSocket {
1427    fn drop(&mut self) {
1428        unsafe { c::legacyfile_unref(self.socket.ptr() as *mut libc::c_void) };
1429    }
1430}