1use 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 Some(errno) => match errno {
26 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 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 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 E::Econnrefused => {
75 panic!("got ECONNREFUSED, but we aren't using connection-mode sockets: {:?}", e)
76 }
77
78 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
191const 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 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, 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 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 .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}