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