Skip to main content

miri/shims/unix/
udp_socket.rs

1use std::cell::{Cell, RefCell, RefMut};
2use std::io;
3use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
4use std::time::Duration;
5
6use mio::event::Source;
7use rustc_target::spec::Os;
8
9use crate::shims::files::{FileDescription, FileDescriptionRef};
10use crate::shims::unix::UnixFileDescription;
11use crate::shims::unix::socket::{EvalContextExt, UnixSocketFileDescription};
12use crate::*;
13
14#[derive(Debug)]
15pub(super) struct UdpSocket {
16    /// Family of the socket, used to ensure that the socket only binds/connects to addresses
17    /// of the same family.
18    family: socket2::Domain,
19    /// The underlying host socket.
20    socket: RefCell<mio::net::UdpSocket>,
21    /// Whether this fd is non-blocking or not.
22    is_non_block: Cell<bool>,
23    /// Whether the socket is implicitly or explicitly bound to an address.
24    is_bound: Cell<bool>,
25    /// The current blocking I/O readiness of the file description.
26    io_readiness: RefCell<Readiness>,
27    /// Read timeout of the socket. [`None`] means that reads can block indefinitely.
28    /// The timeout is applied to the monotonic clock (the Unix specification doesn't
29    /// specify which clock to use, but the monotonic clock is more common for
30    /// relative timeouts).
31    /// This is ignored when the socket is non-blocking.
32    read_timeout: Cell<Option<Duration>>,
33    /// Write timeout of the socket. [`None`] means that writes can block indefinitely.
34    /// The timeout is applied to the monotonic clock (the Unix specification doesn't
35    /// specify which clock to use, but the monotonic clock is more common
36    /// for relative timeouts).
37    /// This is ignored when the socket is non-blocking.
38    write_timeout: Cell<Option<Duration>>,
39    /// State for being watched by epoll.
40    watched: ReadinessWatched,
41}
42
43impl UdpSocket {
44    pub fn new<'tcx>(
45        family: socket2::Domain,
46        is_non_block: bool,
47        ecx: &mut MiriInterpCx<'tcx>,
48    ) -> io::Result<FileDescriptionRef<Self>> {
49        let socket =
50            socket2::Socket::new(family, socket2::Type::DGRAM, Some(socket2::Protocol::UDP))?;
51        // The underlying host socket needs to be non-blocking. The actual
52        // blocking mode of the socket is stored in `is_non_block`.
53        socket.set_nonblocking(true)?;
54
55        let socket = cfg_select! {
56            // Turn the `socket2::Socket` into a `mio::UdpSocket`. We cannot construct the
57            // mio socket directly, since its only constructor is `UdpSocket::bind`.
58            unix => {{
59                use std::os::fd::OwnedFd;
60                mio::net::UdpSocket::from(OwnedFd::from(socket))
61            }},
62            windows => {{
63                use std::os::windows::io::OwnedSocket;
64                mio::net::UdpSocket::from(OwnedSocket::from(socket))
65            }},
66            _ => unreachable!("unsupported host platform"),
67        };
68
69        let udp_socket = UdpSocket {
70            family,
71            socket: RefCell::new(socket),
72            is_non_block: Cell::new(is_non_block),
73            is_bound: Cell::new(false),
74            // We can set the default readiness to the empty readiness
75            // since the socket will be registered to the blocking I/O
76            // directly after its creation.
77            io_readiness: RefCell::new(Readiness::EMPTY),
78            read_timeout: Cell::new(None),
79            write_timeout: Cell::new(None),
80            watched: ReadinessWatched::default(),
81        };
82
83        let fd = ecx.machine.fds.new_ref(udp_socket);
84
85        // Add the UDP socket to the blocking I/O manager as it's always
86        // backed by a mio UdpSocket.
87        ecx.machine.blocking_io.register(fd.clone());
88
89        Ok(fd)
90    }
91}
92
93impl FileDescription for UdpSocket {
94    fn name(&self) -> &'static str {
95        "udp socket"
96    }
97
98    fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
99        self
100    }
101
102    fn readiness_watched(&self) -> Option<&ReadinessWatched> {
103        Some(&self.watched)
104    }
105
106    fn readiness(&self) -> Readiness {
107        *self.io_readiness.borrow()
108    }
109}
110
111impl UnixFileDescription for UdpSocket {
112    fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
113        let mut flags = ecx.eval_libc_i32("O_RDWR");
114
115        if self.is_non_block.get() {
116            flags |= ecx.eval_libc_i32("O_NONBLOCK");
117        }
118
119        interp_ok(Scalar::from_i32(flags))
120    }
121
122    fn set_flags<'tcx>(
123        &self,
124        mut flag: i32,
125        ecx: &mut MiriInterpCx<'tcx>,
126    ) -> InterpResult<'tcx, Scalar> {
127        let o_nonblock = ecx.eval_libc_i32("O_NONBLOCK");
128
129        // O_NONBLOCK flag can be set / unset by user.
130        if flag & o_nonblock == o_nonblock {
131            self.is_non_block.set(true);
132            flag &= !o_nonblock;
133        } else {
134            self.is_non_block.set(false);
135        }
136
137        // Throw error if there is any unsupported flag.
138        if flag != 0 {
139            throw_unsup_format!("fcntl: only O_NONBLOCK is supported for sockets")
140        }
141
142        interp_ok(Scalar::from_i32(0))
143    }
144
145    fn as_socket(
146        self: FileDescriptionRef<Self>,
147    ) -> Option<FileDescriptionRef<dyn UnixSocketFileDescription>> {
148        Some(self)
149    }
150}
151
152impl UnixSocketFileDescription for UdpSocket {
153    fn bind<'tcx>(
154        self: FileDescriptionRef<Self>,
155        communicate_allowed: bool,
156        address: SocketAddr,
157        ecx: &mut MiriInterpCx<'tcx>,
158    ) -> InterpResult<'tcx, Result<(), IoError>> {
159        assert!(communicate_allowed, "cannot have `UdpSocket` with isolation enabled!");
160
161        let address_family = match &address {
162            SocketAddr::V4(_) => socket2::Domain::IPV4,
163            SocketAddr::V6(_) => socket2::Domain::IPV6,
164        };
165
166        if self.family != address_family {
167            // Attempted to bind an address from a family that doesn't match
168            // the family of the socket.
169            let err = if matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android | Os::MacOs) {
170                // Linux man page states that `EINVAL` is used when there is an address family mismatch.
171                // See <https://man7.org/linux/man-pages/man2/bind.2.html>
172                // macOS also returns `EINVAL` but their man page does not specify this.
173                LibcError("EINVAL")
174            } else {
175                // POSIX man page states that `EAFNOSUPPORT` should be used when there is an address
176                // family mismatch.
177                // See <https://man7.org/linux/man-pages/man3/bind.3p.html>
178                LibcError("EAFNOSUPPORT")
179            };
180            return interp_ok(Err(err));
181        }
182
183        // `bind` is a non-blocking operation for UDP sockets.
184        if let Err(e) = self.with_socket_ref(|s| s.bind(&socket2::SockAddr::from(address))).unwrap()
185        {
186            return interp_ok(Err(IoError::HostError(e)));
187        }
188
189        // The socket has been explicitly bound to a local address.
190        self.is_bound.set(true);
191
192        interp_ok(Ok(()))
193    }
194
195    fn connect<'tcx>(
196        self: FileDescriptionRef<Self>,
197        communicate_allowed: bool,
198        address: SocketAddr,
199        ecx: &mut MiriInterpCx<'tcx>,
200        finish: DynMachineCallback<'tcx, Result<(), IoError>>,
201    ) -> InterpResult<'tcx> {
202        assert!(communicate_allowed, "cannot have `UdpSocket` with isolation enabled!");
203
204        if let Err(e) = self.socket.borrow().connect(address) {
205            return finish.call(ecx, Err(IoError::HostError(e)));
206        }
207
208        // POSIX specifies that unbound sockets get bound to an unassigned
209        // local address after `connect` has been invoked on them.
210        // We thus treat the socket as implicitly bound.
211        self.is_bound.set(true);
212
213        finish.call(ecx, Ok(()))
214    }
215
216    fn setsockopt<'tcx>(
217        self: FileDescriptionRef<Self>,
218        level: i32,
219        option: i32,
220        value_ptr: Pointer,
221        value_len: u64,
222        ecx: &mut MiriInterpCx<'tcx>,
223    ) -> InterpResult<'tcx, Result<(), IoError>> {
224        if level == ecx.eval_libc_i32("SOL_SOCKET") {
225            return ecx.set_generic_socket_option(option, value_ptr, value_len, self);
226        } else if level == ecx.eval_libc_i32("IPPROTO_IP") {
227            let socket = self.socket.borrow();
228            return ecx.set_ip_socket_option(
229                option,
230                value_ptr,
231                value_len,
232                socket2::SockRef::from(&*socket),
233            );
234        }
235
236        throw_unsup_format!(
237            "setsockopt: level {level:#x} is unsupported, only SOL_SOCKET and IPPROTO_IP \
238            are allowed"
239        );
240    }
241
242    fn getsockopt<'tcx>(
243        self: FileDescriptionRef<Self>,
244        level: i32,
245        option: i32,
246        ecx: &mut MiriInterpCx<'tcx>,
247    ) -> InterpResult<'tcx, Result<MPlaceTy<'tcx>, IoError>> {
248        if level == ecx.eval_libc_i32("SOL_SOCKET") {
249            ecx.get_generic_socket_option(option, self)
250        } else if level == ecx.eval_libc_i32("IPPROTO_IP") {
251            let socket = self.socket.borrow();
252            ecx.get_ip_socket_option(option, socket2::SockRef::from(&*socket))
253        } else {
254            throw_unsup_format!(
255                "getsockopt: level {level:#x} is unsupported, only SOL_SOCKET and IPPROTO_IP \
256                are allowed"
257            )
258        }
259    }
260
261    fn getsockname<'tcx>(
262        self: FileDescriptionRef<Self>,
263        communicate_allowed: bool,
264        _ecx: &mut MiriInterpCx<'tcx>,
265    ) -> InterpResult<'tcx, Result<SocketAddr, IoError>> {
266        assert!(communicate_allowed, "cannot have `UdpSocket` with isolation enabled!");
267
268        if !self.is_bound.get() {
269            // Since Windows returns EINVAL when invoking `getsockname` on
270            // a socket which hasn't been bound yet, we need to manually
271            // return an unspecified address here.
272
273            let address = if self.family == socket2::Domain::IPV4 {
274                SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, /* port */ 0))
275            } else {
276                SocketAddr::V6(SocketAddrV6::new(
277                    Ipv6Addr::UNSPECIFIED,
278                    /* port */ 0,
279                    /* flowinfo */ 0,
280                    /* scope_id */ 0,
281                ))
282            };
283            return interp_ok(Ok(address));
284        }
285
286        interp_ok(self.socket.borrow().local_addr().map_err(IoError::HostError))
287    }
288
289    fn getpeername<'tcx>(
290        self: FileDescriptionRef<Self>,
291        communicate_allowed: bool,
292        ecx: &mut MiriInterpCx<'tcx>,
293        finish: DynMachineCallback<'tcx, Result<SocketAddr, IoError>>,
294    ) -> InterpResult<'tcx> {
295        assert!(communicate_allowed, "cannot have `UdpSocket` with isolation enabled!");
296
297        finish.call(ecx, self.socket.borrow().peer_addr().map_err(IoError::HostError))
298    }
299
300    fn set_read_timeout<'tcx>(
301        self: FileDescriptionRef<Self>,
302        timeout: Option<Duration>,
303    ) -> InterpResult<'tcx> {
304        self.read_timeout.set(timeout);
305        interp_ok(())
306    }
307
308    fn get_read_timeout<'tcx>(
309        self: FileDescriptionRef<Self>,
310    ) -> InterpResult<'tcx, Option<Duration>> {
311        interp_ok(self.read_timeout.get())
312    }
313
314    fn set_write_timeout<'tcx>(
315        self: FileDescriptionRef<Self>,
316        timeout: Option<Duration>,
317    ) -> InterpResult<'tcx> {
318        self.write_timeout.set(timeout);
319        interp_ok(())
320    }
321
322    fn get_write_timeout<'tcx>(
323        self: FileDescriptionRef<Self>,
324    ) -> InterpResult<'tcx, Option<Duration>> {
325        interp_ok(self.write_timeout.get())
326    }
327
328    fn with_socket_ref<T>(&self, cb: impl FnOnce(socket2::SockRef<'_>) -> T) -> Result<T, ()>
329    where
330        Self: Sized,
331    {
332        let socket = self.socket.borrow();
333        Ok(cb(socket2::SockRef::from(&*socket)))
334    }
335}
336
337impl SourceFileDescription for UdpSocket {
338    fn with_source(&self, f: &mut dyn FnMut(&mut dyn Source) -> io::Result<()>) -> io::Result<()> {
339        let mut socket = self.socket.borrow_mut();
340        f(&mut *socket)
341    }
342
343    fn get_readiness_mut(&self) -> RefMut<'_, Readiness> {
344        self.io_readiness.borrow_mut()
345    }
346}