dhcp_client/
packetsocket.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;
6
7use dhcp_client_core::deps::PacketSocketProvider;
8use sockaddr::{EthernetSockaddr, IntoSockAddr as _, TryToSockaddrLl as _};
9use {
10    fidl_fuchsia_posix as fposix, fidl_fuchsia_posix_socket_ext as fposix_socket_ext,
11    fidl_fuchsia_posix_socket_packet as fpacket, fuchsia_async as fasync,
12};
13
14pub(crate) struct PacketSocket {
15    interface_id: NonZeroU64,
16    inner: fasync::net::DatagramSocket,
17}
18
19fn translate_io_error(e: std::io::Error) -> dhcp_client_core::deps::SocketError {
20    use fposix::Errno as E;
21    match e.raw_os_error().and_then(fposix::Errno::from_primitive) {
22        None => dhcp_client_core::deps::SocketError::Other(e),
23        // TODO(https://fxbug.dev/42075434): better indicate recoverable
24        // vs. non-recoverable error types.
25        Some(errno) => match errno {
26            // ============
27            // These errors are documented in `man packet`.
28            E::Eaddrnotavail => panic!(
29                "got EADDRNOTAVAIL, which shouldn't be possible because we \
30                 aren't sending traffic to multicast groups: {:?}",
31                e
32            ),
33            E::Efault => panic!("passed invalid memory address to socket call"),
34            E::Enobufs => panic!("out of memory: {:?}", e),
35            E::Enodev => dhcp_client_core::deps::SocketError::NoInterface,
36            E::Einval
37            | E::Emsgsize
38            | E::Enetdown
39            | E::Enoent
40            | E::Enotconn
41            | E::Enxio
42            | E::Eperm => dhcp_client_core::deps::SocketError::Other(e),
43
44            // ============
45            // These errors are documented in `man bind`.
46            E::Enotsock => {
47                panic!("tried to perform socket operation with non-socket file descriptor: {:?}", e)
48            }
49            E::Ebadf => {
50                panic!("tried to perform socket operation with bad file descriptor: {:?}", e)
51            }
52            E::Eacces | E::Eaddrinuse => dhcp_client_core::deps::SocketError::Other(e),
53
54            // ============
55            // These errors can be returned from `sendto()` at the socket layer.
56            E::Eagain => {
57                panic!("got EAGAIN, should be handled by lower-level library: {:?}", e)
58            }
59            E::Eintr => panic!("got EINTR, should be handled by lower-level library: {:?}", e),
60            E::Ealready => panic!("got EALREADY, but we aren't using TCP: {:?}", e),
61            E::Econnreset => panic!("got ECONNRESET, but we aren't using TCP: {:?}", e),
62            E::Edestaddrreq => panic!("got EDESTADDRREQ, but we only use sendto: {:?}", e),
63            E::Eisconn => {
64                panic!("got EISCONN, but we aren't using connection-mode sockets: {:?}", e)
65            }
66            E::Epipe => {
67                panic!("got EPIPE, but we aren't using connection-mode sockets: {:?}", e)
68            }
69            E::Enomem => panic!("out of memory: {:?}", e),
70            E::Eopnotsupp => dhcp_client_core::deps::SocketError::Other(e),
71
72            // ============
73            // These errors are documented in `man recvfrom`.
74            E::Econnrefused => {
75                panic!("got ECONNREFUSED, but we aren't using connection-mode sockets: {:?}", e)
76            }
77
78            // ============
79            // The following errors aren't expected to be returned by any of the
80            // socket operations we use.
81            E::Enetunreach
82            | E::Esrch
83            | E::Eio
84            | E::E2Big
85            | E::Enoexec
86            | E::Echild
87            | E::Enotblk
88            | E::Ebusy
89            | E::Eexist
90            | E::Exdev
91            | E::Enotdir
92            | E::Eisdir
93            | E::Enfile
94            | E::Emfile
95            | E::Enotty
96            | E::Etxtbsy
97            | E::Efbig
98            | E::Enospc
99            | E::Espipe
100            | E::Erofs
101            | E::Emlink
102            | E::Edom
103            | E::Erange
104            | E::Edeadlk
105            | E::Enametoolong
106            | E::Enolck
107            | E::Enosys
108            | E::Enotempty
109            | E::Eloop
110            | E::Enomsg
111            | E::Eidrm
112            | E::Echrng
113            | E::El2Nsync
114            | E::El3Hlt
115            | E::El3Rst
116            | E::Elnrng
117            | E::Eunatch
118            | E::Enocsi
119            | E::El2Hlt
120            | E::Ebade
121            | E::Ebadr
122            | E::Exfull
123            | E::Enoano
124            | E::Ebadrqc
125            | E::Ebadslt
126            | E::Ebfont
127            | E::Enostr
128            | E::Enodata
129            | E::Etime
130            | E::Enosr
131            | E::Enonet
132            | E::Enopkg
133            | E::Eremote
134            | E::Enolink
135            | E::Eadv
136            | E::Esrmnt
137            | E::Ecomm
138            | E::Eproto
139            | E::Emultihop
140            | E::Edotdot
141            | E::Ebadmsg
142            | E::Eoverflow
143            | E::Enotuniq
144            | E::Ebadfd
145            | E::Eremchg
146            | E::Elibacc
147            | E::Elibbad
148            | E::Elibscn
149            | E::Elibmax
150            | E::Elibexec
151            | E::Eilseq
152            | E::Erestart
153            | E::Estrpipe
154            | E::Eusers
155            | E::Eprototype
156            | E::Enoprotoopt
157            | E::Eprotonosupport
158            | E::Esocktnosupport
159            | E::Epfnosupport
160            | E::Eafnosupport
161            | E::Enetreset
162            | E::Econnaborted
163            | E::Eshutdown
164            | E::Etoomanyrefs
165            | E::Etimedout
166            | E::Ehostdown
167            | E::Ehostunreach
168            | E::Einprogress
169            | E::Estale
170            | E::Euclean
171            | E::Enotnam
172            | E::Enavail
173            | E::Eisnam
174            | E::Eremoteio
175            | E::Edquot
176            | E::Enomedium
177            | E::Emediumtype
178            | E::Ecanceled
179            | E::Enokey
180            | E::Ekeyexpired
181            | E::Ekeyrevoked
182            | E::Ekeyrejected
183            | E::Eownerdead
184            | E::Enotrecoverable
185            | E::Erfkill
186            | E::Ehwpoison => panic!("unexpected error from socket: {:?}", e),
187        },
188    }
189}
190
191// TODO(https://fxbug.dev/42075730): expose this from `libc` crate on fuchsia.
192const ARPHRD_ETHER: libc::c_ushort = 1;
193
194impl dhcp_client_core::deps::Socket<net_types::ethernet::Mac> for PacketSocket {
195    async fn send_to(
196        &self,
197        buf: &[u8],
198        addr: net_types::ethernet::Mac,
199    ) -> Result<(), dhcp_client_core::deps::SocketError> {
200        let Self { interface_id, inner } = self;
201
202        let sockaddr_ll = libc::sockaddr_ll::from(EthernetSockaddr {
203            interface_id: Some(*interface_id),
204            addr,
205            protocol: packet_formats::ethernet::EtherType::Ipv4,
206        });
207
208        let n =
209            inner.send_to(buf, sockaddr_ll.into_sockaddr()).await.map_err(translate_io_error)?;
210        // Packet sockets never have short sends.
211        assert_eq!(n, buf.len());
212        Ok(())
213    }
214
215    async fn recv_from(
216        &self,
217        buf: &mut [u8],
218    ) -> Result<
219        dhcp_client_core::deps::DatagramInfo<net_types::ethernet::Mac>,
220        dhcp_client_core::deps::SocketError,
221    > {
222        let Self { interface_id: _, inner } = self;
223        let (length, sock_addr) = inner.recv_from(buf).await.map_err(translate_io_error)?;
224        let libc::sockaddr_ll {
225            sll_family: _,
226            sll_ifindex: _,
227            sll_protocol: _,
228            sll_halen,
229            sll_addr,
230            sll_hatype,
231            sll_pkttype: _,
232        } = sock_addr.try_to_sockaddr_ll().expect("addr from packet socket must be sockaddr_ll");
233
234        if sll_hatype != ARPHRD_ETHER {
235            log::error!("unsupported sll_hatype: {}", sll_hatype);
236            return Err(dhcp_client_core::deps::SocketError::UnsupportedHardwareType);
237        }
238
239        assert_eq!(usize::from(sll_halen), net_types::ethernet::Mac::BYTES);
240        let address = {
241            let mut address = net_types::ethernet::Mac::new([0; net_types::ethernet::Mac::BYTES]);
242            address.as_mut().copy_from_slice(&sll_addr[..net_types::ethernet::Mac::BYTES]);
243            address
244        };
245        Ok(dhcp_client_core::deps::DatagramInfo { length, address })
246    }
247}
248
249pub(crate) struct PacketSocketProviderImpl {
250    provider: fpacket::ProviderProxy,
251    interface_id: NonZeroU64,
252}
253
254impl PacketSocketProviderImpl {
255    pub(crate) fn new(provider: fpacket::ProviderProxy, interface_id: NonZeroU64) -> Self {
256        PacketSocketProviderImpl { provider, interface_id }
257    }
258
259    pub(crate) async fn get_mac(
260        &self,
261    ) -> Result<net_types::ethernet::Mac, dhcp_client_core::deps::SocketError> {
262        let PacketSocket { interface_id: _, inner } = self.get_packet_socket().await?;
263        let sock_addr = inner.local_addr().map_err(translate_io_error)?;
264        let libc::sockaddr_ll {
265            sll_family: _,
266            sll_ifindex: _,
267            sll_protocol: _,
268            sll_halen,
269            sll_addr,
270            sll_hatype,
271            sll_pkttype: _,
272        } = sock_addr.try_to_sockaddr_ll().expect("addr from packet socket must be sockaddr_ll");
273        if sll_hatype != ARPHRD_ETHER {
274            log::error!("unsupported sll_hatype: {}", sll_hatype);
275            return Err(dhcp_client_core::deps::SocketError::UnsupportedHardwareType);
276        }
277
278        assert_eq!(usize::from(sll_halen), net_types::ethernet::Mac::BYTES);
279        let mut address = net_types::ethernet::Mac::new([0; net_types::ethernet::Mac::BYTES]);
280        address.as_mut().copy_from_slice(&sll_addr[..net_types::ethernet::Mac::BYTES]);
281        Ok(address)
282    }
283}
284
285impl PacketSocketProvider for PacketSocketProviderImpl {
286    type Sock = PacketSocket;
287
288    async fn get_packet_socket(&self) -> Result<Self::Sock, dhcp_client_core::deps::SocketError> {
289        let PacketSocketProviderImpl { provider, interface_id } = self;
290
291        let socket = fposix_socket_ext::packet_socket(provider, fpacket::Kind::Network)
292            .await
293            .map_err(|e: fidl::Error| dhcp_client_core::deps::SocketError::FailedToOpen(e.into()))?
294            .map_err(translate_io_error)?;
295
296        let sockaddr_ll = libc::sockaddr_ll::from(EthernetSockaddr {
297            interface_id: Some(*interface_id),
298            addr: net_types::ethernet::Mac::UNSPECIFIED, // unused by bind()
299            protocol: packet_formats::ethernet::EtherType::Ipv4,
300        });
301
302        socket.bind(&sockaddr_ll.into_sockaddr()).map_err(translate_io_error)?;
303        let socket = fasync::net::DatagramSocket::new_from_socket(socket)
304            .map_err(dhcp_client_core::deps::SocketError::Other)?;
305        Ok(PacketSocket { interface_id: *interface_id, inner: socket })
306    }
307}
308
309#[cfg(test)]
310mod test {
311    use super::*;
312    use dhcp_client_core::deps::{DatagramInfo, Socket as _};
313    use futures::{join, FutureExt as _};
314    use netstack_testing_common::realms::TestSandboxExt as _;
315    use packet::{InnerPacketBuilder as _, Serializer as _};
316    use {
317        fidl_fuchsia_net_ext as fnet_ext, fidl_fuchsia_netemul_network as fnetemul_network,
318        fidl_fuchsia_posix_socket_packet as fpacket, fuchsia_async as fasync,
319    };
320
321    #[fasync::run_singlethreaded(test)]
322    async fn packet_socket_provider_impl_send_receive() {
323        let sandbox: netemul::TestSandbox = netemul::TestSandbox::new().unwrap();
324
325        let network = sandbox.create_network("dhcp-test-network").await.expect("create network");
326        let realm_a: netemul::TestRealm<'_> = sandbox
327            .create_netstack_realm::<netstack_testing_common::realms::Netstack2, _>(
328                "dhcp-test-realm-a",
329            )
330            .expect("create realm");
331        let realm_b: netemul::TestRealm<'_> = sandbox
332            .create_netstack_realm::<netstack_testing_common::realms::Netstack2, _>(
333                "dhcp-test-realm-b",
334            )
335            .expect("create realm");
336
337        const MAC_A: net_types::ethernet::Mac = net_declare::net_mac!("00:00:00:00:00:01");
338        const MAC_B: net_types::ethernet::Mac = net_declare::net_mac!("00:00:00:00:00:02");
339
340        let iface_a = realm_a
341            .join_network_with(
342                &network,
343                "iface_a",
344                fnetemul_network::EndpointConfig {
345                    mtu: netemul::DEFAULT_MTU,
346                    mac: Some(Box::new(fnet_ext::MacAddress { octets: MAC_A.bytes() }.into())),
347                    port_class: fidl_fuchsia_hardware_network::PortClass::Virtual,
348                },
349                netemul::InterfaceConfig { name: Some("iface_a".into()), ..Default::default() },
350            )
351            .await
352            .expect("join network with realm_a");
353        let iface_b = realm_b
354            .join_network_with(
355                &network,
356                "iface_b",
357                fnetemul_network::EndpointConfig {
358                    mtu: netemul::DEFAULT_MTU,
359                    mac: Some(Box::new(fnet_ext::MacAddress { octets: MAC_B.bytes() }.into())),
360                    port_class: fidl_fuchsia_hardware_network::PortClass::Virtual,
361                },
362                netemul::InterfaceConfig { name: Some("iface_b".into()), ..Default::default() },
363            )
364            .await
365            .expect("join network with realm_b");
366
367        let socket_a = PacketSocketProviderImpl {
368            interface_id: NonZeroU64::new(iface_a.id()).expect("ID is nonzero"),
369            provider: realm_a.connect_to_protocol::<fpacket::ProviderMarker>().unwrap(),
370        }
371        .get_packet_socket()
372        .await
373        .expect("get packet socket");
374        let socket_b = PacketSocketProviderImpl {
375            interface_id: NonZeroU64::new(iface_b.id()).unwrap(),
376            provider: realm_b.connect_to_protocol::<fpacket::ProviderMarker>().unwrap(),
377        }
378        .get_packet_socket()
379        .await
380        .expect("get packet socket");
381
382        let mut buf = [0u8; netemul::DEFAULT_MTU as usize];
383
384        // We can only receive IPv4-formatted packets because our packet sockets
385        // only bind to the IPv4 protocol.
386        let payload = packet_formats::ipv4::Ipv4PacketBuilder::new(
387            net_types::ip::Ipv4Addr::default(),
388            net_types::ip::Ipv4Addr::default(),
389            0,
390            packet_formats::ip::Ipv4Proto::Other(0),
391        );
392        let payload = b"hello world!"
393            .into_serializer()
394            .encapsulate(payload)
395            .serialize_vec_outer()
396            .expect("serialize");
397
398        let DatagramInfo { length, address } = {
399            let send_fut = async {
400                socket_a
401                    // The packet sockets are all on the same link, so the
402                    // destination MAC address doesn't matter.
403                    .send_to(payload.as_ref(), net_declare::net_mac!("00:00:00:00:00:00"))
404                    .await
405                    .expect("send_to");
406            }
407            .fuse();
408
409            let receive_fut =
410                async { socket_b.recv_from(&mut buf).await.expect("recv_from") }.fuse();
411
412            let ((), datagram_info) = join!(send_fut, receive_fut);
413            datagram_info
414        };
415
416        assert_eq!(&buf[..length], payload.as_ref());
417        assert_eq!(address, MAC_A);
418    }
419
420    #[fasync::run_singlethreaded(test)]
421    async fn packet_socket_provider_impl_get_mac() {
422        let sandbox: netemul::TestSandbox = netemul::TestSandbox::new().unwrap();
423
424        let network = sandbox.create_network("dhcp-test-network").await.expect("create network");
425        let realm: netemul::TestRealm<'_> = sandbox
426            .create_netstack_realm::<netstack_testing_common::realms::Netstack2, _>(
427                "dhcp-test-realm-a",
428            )
429            .expect("create realm");
430
431        const MAC: net_types::ethernet::Mac = net_declare::net_mac!("00:00:00:00:00:01");
432        let iface = realm
433            .join_network_with(
434                &network,
435                "iface",
436                fnetemul_network::EndpointConfig {
437                    mtu: netemul::DEFAULT_MTU,
438                    mac: Some(Box::new(fnet_ext::MacAddress { octets: MAC.bytes() }.into())),
439                    port_class: fidl_fuchsia_hardware_network::PortClass::Virtual,
440                },
441                netemul::InterfaceConfig { name: Some("iface".into()), ..Default::default() },
442            )
443            .await
444            .expect("join network with realm");
445
446        let provider = PacketSocketProviderImpl {
447            interface_id: NonZeroU64::new(iface.id()).unwrap(),
448            provider: realm.connect_to_protocol::<fpacket::ProviderMarker>().unwrap(),
449        };
450        assert_eq!(provider.get_mac().await.expect("get mac"), MAC);
451    }
452}