1use 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 E::Econnrefused => dhcp_client_core::deps::SocketError::Other(e),
23
24 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 E::Ehostdown => dhcp_client_core::deps::SocketError::Other(e),
53
54 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 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 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 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 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}