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::Enodev => dhcp_client_core::deps::SocketError::NoInterface,
35 E::Einval
36 | E::Emsgsize
37 | E::Enetdown
38 | E::Enobufs
41 | E::Enoent
42 | E::Enotconn
43 | E::Enxio
44 | E::Eperm => dhcp_client_core::deps::SocketError::Other(e),
45
46 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 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 E::Econnrefused => {
77 panic!("got ECONNREFUSED, but we aren't using connection-mode sockets: {:?}", e)
78 }
79
80 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
193const 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 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, 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 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 .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}