dhcp_client/
udpsocket.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::num::NonZeroU64;
6use std::os::fd::AsRawFd;
7
8use dhcp_client_core::deps::UdpSocketProvider;
9use {fidl_fuchsia_posix as fposix, fuchsia_async as fasync};
10
11pub(crate) struct UdpSocket {
12    inner: fasync::net::UdpSocket,
13}
14
15fn translate_io_error(e: std::io::Error) -> dhcp_client_core::deps::SocketError {
16    use fposix::Errno as E;
17    match e.raw_os_error().and_then(fposix::Errno::from_primitive) {
18        None => dhcp_client_core::deps::SocketError::Other(e),
19        Some(errno) => match errno {
20            // ============
21            // These errors are documented in `man 7 udp`.
22            E::Econnrefused => dhcp_client_core::deps::SocketError::Other(e),
23
24            // ============
25            // These errors are documented in `man 7 ip`.
26            E::Eagain | E::Ealready => panic!(
27                "unexpected error {e:?}; nonblocking logic should be
28                 handled by fuchsia_async UDP socket wrapper"
29            ),
30            E::Econnaborted => panic!("unexpected error {e:?}; not using stream sockets"),
31            E::Ehostunreach => dhcp_client_core::deps::SocketError::HostUnreachable,
32            E::Enetunreach => dhcp_client_core::deps::SocketError::NetworkUnreachable,
33            E::Enobufs | E::Enomem => panic!("out of memory: {e:?}"),
34            E::Eacces
35            | E::Eaddrinuse
36            | E::Eaddrnotavail
37            | E::Einval
38            | E::Eisconn
39            | E::Emsgsize
40            | E::Enoent
41            | E::Enopkg
42            | E::Enoprotoopt
43            | E::Eopnotsupp
44            | E::Enotconn
45            | E::Eperm
46            | E::Epipe
47            | E::Esocktnosupport => dhcp_client_core::deps::SocketError::Other(e),
48
49            // TODO(https://fxbug.dev/42077996): Revisit whether we should
50            // actually panic on this error once we establish whether this error
51            // is set for UDP sockets.
52            E::Ehostdown => dhcp_client_core::deps::SocketError::Other(e),
53
54            // ============
55            // These errors are documented in `man bind`.
56            E::Enotsock => {
57                panic!("tried to perform socket operation with non-socket file descriptor: {:?}", e)
58            }
59            E::Ebadf => {
60                panic!("tried to perform socket operation with bad file descriptor: {:?}", e)
61            }
62
63            // ============
64            // These errors can be returned from `sendto()` at the socket layer.
65            E::Eintr => panic!("got EINTR, should be handled by lower-level library: {:?}", e),
66            E::Econnreset => panic!("got ECONNRESET, but we aren't using TCP: {:?}", e),
67
68            // ============
69            // The following errors aren't expected to be returned by any of the
70            // socket operations we use.
71            E::Esrch
72            | E::Eio
73            | E::E2Big
74            | E::Enoexec
75            | E::Echild
76            | E::Enotblk
77            | E::Ebusy
78            | E::Eexist
79            | E::Exdev
80            | E::Enotdir
81            | E::Eisdir
82            | E::Enfile
83            | E::Emfile
84            | E::Enotty
85            | E::Etxtbsy
86            | E::Efbig
87            | E::Enospc
88            | E::Espipe
89            | E::Erofs
90            | E::Emlink
91            | E::Edom
92            | E::Erange
93            | E::Edeadlk
94            | E::Enametoolong
95            | E::Enolck
96            | E::Enosys
97            | E::Enotempty
98            | E::Eloop
99            | E::Enomsg
100            | E::Eidrm
101            | E::Echrng
102            | E::El2Nsync
103            | E::El3Hlt
104            | E::El3Rst
105            | E::Elnrng
106            | E::Eunatch
107            | E::Enocsi
108            | E::El2Hlt
109            | E::Ebade
110            | E::Ebadr
111            | E::Exfull
112            | E::Enoano
113            | E::Ebadrqc
114            | E::Ebadslt
115            | E::Ebfont
116            | E::Enostr
117            | E::Enodata
118            | E::Etime
119            | E::Enosr
120            | E::Enonet
121            | E::Eremote
122            | E::Enolink
123            | E::Eadv
124            | E::Esrmnt
125            | E::Ecomm
126            | E::Eproto
127            | E::Emultihop
128            | E::Edotdot
129            | E::Ebadmsg
130            | E::Eoverflow
131            | E::Enotuniq
132            | E::Ebadfd
133            | E::Eremchg
134            | E::Elibacc
135            | E::Elibbad
136            | E::Elibscn
137            | E::Elibmax
138            | E::Elibexec
139            | E::Eilseq
140            | E::Erestart
141            | E::Estrpipe
142            | E::Eusers
143            | E::Eprototype
144            | E::Eprotonosupport
145            | E::Epfnosupport
146            | E::Eafnosupport
147            | E::Enetreset
148            | E::Eshutdown
149            | E::Etoomanyrefs
150            | E::Etimedout
151            | E::Einprogress
152            | E::Estale
153            | E::Euclean
154            | E::Enotnam
155            | E::Enavail
156            | E::Eisnam
157            | E::Eremoteio
158            | E::Edquot
159            | E::Enomedium
160            | E::Emediumtype
161            | E::Ecanceled
162            | E::Enokey
163            | E::Ekeyexpired
164            | E::Ekeyrevoked
165            | E::Ekeyrejected
166            | E::Eownerdead
167            | E::Enotrecoverable
168            | E::Erfkill
169            | E::Enxio
170            | E::Efault
171            | E::Enodev
172            | E::Edestaddrreq
173            | E::Enetdown
174            | E::Ehwpoison => panic!("unexpected error from socket: {:?}", e),
175        },
176    }
177}
178
179impl dhcp_client_core::deps::Socket<std::net::SocketAddr> for UdpSocket {
180    async fn send_to(
181        &self,
182        buf: &[u8],
183        addr: std::net::SocketAddr,
184    ) -> Result<(), dhcp_client_core::deps::SocketError> {
185        let Self { inner } = self;
186
187        let n = inner.send_to(buf, addr).await.map_err(translate_io_error)?;
188        // UDP sockets never have short sends.
189        assert_eq!(n, buf.len());
190        Ok(())
191    }
192
193    async fn recv_from(
194        &self,
195        buf: &mut [u8],
196    ) -> Result<
197        dhcp_client_core::deps::DatagramInfo<std::net::SocketAddr>,
198        dhcp_client_core::deps::SocketError,
199    > {
200        let Self { inner } = self;
201        let (length, address) = inner.recv_from(buf).await.map_err(translate_io_error)?;
202        Ok(dhcp_client_core::deps::DatagramInfo { length, address })
203    }
204}
205
206fn set_bindtoifindex(
207    socket: &impl AsRawFd,
208    interface_id: NonZeroU64,
209) -> Result<(), dhcp_client_core::deps::SocketError> {
210    let interface_id =
211        libc::c_int::try_from(interface_id.get()).expect("interface_id should fit in c_int");
212
213    // SAFETY: `setsockopt` does not take ownership of anything passed to it.
214    if unsafe {
215        libc::setsockopt(
216            socket.as_raw_fd(),
217            libc::SOL_SOCKET,
218            libc::SO_BINDTOIFINDEX,
219            &interface_id as *const _ as *const libc::c_void,
220            std::mem::size_of_val(&interface_id) as libc::socklen_t,
221        )
222    } != 0
223    {
224        return Err(translate_io_error(std::io::Error::last_os_error()));
225    }
226
227    Ok(())
228}
229
230pub(crate) struct LibcUdpSocketProvider {
231    pub(crate) interface_id: NonZeroU64,
232}
233
234impl UdpSocketProvider for LibcUdpSocketProvider {
235    type Sock = UdpSocket;
236
237    async fn bind_new_udp_socket(
238        &self,
239        bound_addr: std::net::SocketAddr,
240    ) -> Result<Self::Sock, dhcp_client_core::deps::SocketError> {
241        let socket = std::net::UdpSocket::bind(&bound_addr).map_err(translate_io_error)?;
242        set_bindtoifindex(&socket, self.interface_id)?;
243
244        let socket = fasync::net::UdpSocket::from_socket(socket).map_err(translate_io_error)?;
245        socket.set_broadcast(true).map_err(translate_io_error)?;
246        Ok(UdpSocket { inner: socket })
247    }
248}
249
250#[cfg(test)]
251mod testutil {
252    use super::*;
253    use {
254        fidl_fuchsia_posix_socket as fposix_socket,
255        fidl_fuchsia_posix_socket_ext as fposix_socket_ext,
256    };
257
258    pub(crate) struct TestUdpSocketProvider {
259        provider: fposix_socket::ProviderProxy,
260        interface_id: NonZeroU64,
261    }
262
263    impl TestUdpSocketProvider {
264        pub(crate) fn new(
265            provider: fposix_socket::ProviderProxy,
266            interface_id: NonZeroU64,
267        ) -> Self {
268            Self { provider, interface_id }
269        }
270    }
271
272    impl UdpSocketProvider for TestUdpSocketProvider {
273        type Sock = UdpSocket;
274
275        async fn bind_new_udp_socket(
276            &self,
277            bound_addr: std::net::SocketAddr,
278        ) -> Result<Self::Sock, dhcp_client_core::deps::SocketError> {
279            let Self { provider, interface_id } = self;
280
281            let socket = fposix_socket_ext::datagram_socket(
282                provider,
283                fposix_socket::Domain::Ipv4,
284                fposix_socket::DatagramSocketProtocol::Udp,
285            )
286            .await
287            .map_err(|e: fidl::Error| dhcp_client_core::deps::SocketError::FailedToOpen(e.into()))?
288            .map_err(translate_io_error)?;
289
290            socket.bind(&bound_addr.into()).map_err(translate_io_error)?;
291            socket.set_broadcast(true).map_err(translate_io_error)?;
292
293            set_bindtoifindex(&socket, *interface_id)?;
294
295            let socket =
296                fasync::net::UdpSocket::from_socket(socket.into()).map_err(translate_io_error)?;
297
298            Ok(UdpSocket { inner: socket })
299        }
300    }
301}
302
303#[cfg(test)]
304mod test {
305    use super::*;
306    use crate::udpsocket::testutil::TestUdpSocketProvider;
307    use dhcp_client_core::deps::{DatagramInfo, Socket as _};
308    use futures::{join, FutureExt as _};
309    use net_declare::std_socket_addr;
310    use netstack_testing_common::realms::TestSandboxExt as _;
311    use {
312        fidl_fuchsia_net_ext as fnet_ext, fidl_fuchsia_netemul_network as fnetemul_network,
313        fidl_fuchsia_posix_socket as fposix_socket, fuchsia_async as fasync,
314    };
315
316    #[fasync::run_singlethreaded(test)]
317    async fn udp_socket_provider_impl_send_receive() {
318        let sandbox: netemul::TestSandbox = netemul::TestSandbox::new().unwrap();
319
320        let network = sandbox.create_network("dhcp-test-network").await.expect("create network");
321        let realm_a: netemul::TestRealm<'_> = sandbox
322            .create_netstack_realm::<netstack_testing_common::realms::Netstack2, _>(
323                "dhcp-test-realm-a",
324            )
325            .expect("create realm");
326        let realm_b: netemul::TestRealm<'_> = sandbox
327            .create_netstack_realm::<netstack_testing_common::realms::Netstack2, _>(
328                "dhcp-test-realm-b",
329            )
330            .expect("create realm");
331
332        const MAC_A: net_types::ethernet::Mac = net_declare::net_mac!("00:00:00:00:00:01");
333        const MAC_B: net_types::ethernet::Mac = net_declare::net_mac!("00:00:00:00:00:02");
334        const FIDL_SUBNET_A: fidl_fuchsia_net::Subnet = net_declare::fidl_subnet!("1.1.1.1/24");
335        const SOCKET_ADDR_A: std::net::SocketAddr = std_socket_addr!("1.1.1.1:1111");
336        const FIDL_SUBNET_B: fidl_fuchsia_net::Subnet = net_declare::fidl_subnet!("1.1.1.2/24");
337        const SOCKET_ADDR_B: std::net::SocketAddr = std_socket_addr!("1.1.1.2:2222");
338
339        let iface_a = realm_a
340            .join_network_with(
341                &network,
342                "iface_a",
343                fnetemul_network::EndpointConfig {
344                    mtu: netemul::DEFAULT_MTU,
345                    mac: Some(Box::new(fnet_ext::MacAddress { octets: MAC_A.bytes() }.into())),
346                    port_class: fidl_fuchsia_hardware_network::PortClass::Virtual,
347                },
348                netemul::InterfaceConfig { name: Some("iface_a".into()), ..Default::default() },
349            )
350            .await
351            .expect("join network with realm_a");
352        let iface_b = realm_b
353            .join_network_with(
354                &network,
355                "iface_b",
356                fnetemul_network::EndpointConfig {
357                    mtu: netemul::DEFAULT_MTU,
358                    mac: Some(Box::new(fnet_ext::MacAddress { octets: MAC_B.bytes() }.into())),
359                    port_class: fidl_fuchsia_hardware_network::PortClass::Virtual,
360                },
361                netemul::InterfaceConfig { name: Some("iface_b".into()), ..Default::default() },
362            )
363            .await
364            .expect("join network with realm_b");
365
366        iface_a
367            .add_address_and_subnet_route(FIDL_SUBNET_A)
368            .await
369            .expect("add address should succeed");
370        iface_b
371            .add_address_and_subnet_route(FIDL_SUBNET_B)
372            .await
373            .expect("add address should succeed");
374
375        let socket_a = TestUdpSocketProvider::new(
376            realm_a.connect_to_protocol::<fposix_socket::ProviderMarker>().unwrap(),
377            NonZeroU64::new(iface_a.id()).unwrap(),
378        )
379        .bind_new_udp_socket(SOCKET_ADDR_A)
380        .await
381        .expect("get udp socket");
382
383        let socket_b = TestUdpSocketProvider::new(
384            realm_b.connect_to_protocol::<fposix_socket::ProviderMarker>().unwrap(),
385            NonZeroU64::new(iface_b.id()).unwrap(),
386        )
387        .bind_new_udp_socket(SOCKET_ADDR_B)
388        .await
389        .expect("get udp socket");
390
391        let mut buf = [0u8; netemul::DEFAULT_MTU as usize];
392
393        let payload = b"hello world!";
394
395        let DatagramInfo { length, address } = {
396            let send_fut = async {
397                socket_a.send_to(payload.as_ref(), SOCKET_ADDR_B).await.expect("send_to");
398            }
399            .fuse();
400
401            let receive_fut =
402                async { socket_b.recv_from(&mut buf).await.expect("recv_from") }.fuse();
403
404            let ((), datagram_info) = join!(send_fut, receive_fut);
405            datagram_info
406        };
407
408        assert_eq!(&buf[..length], payload.as_ref());
409        assert_eq!(address, SOCKET_ADDR_A);
410    }
411}