Skip to main content

fidl_fuchsia_posix_socket_ext/
lib.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
5//! Extension crate for `fuchsia.posix.socket` and `fuchsia.posix.socket.packet`.
6#![deny(missing_docs)]
7
8use fidl_fuchsia_posix_socket as fposix_socket;
9use fidl_fuchsia_posix_socket_packet as fpacket;
10use zx::StatusExt;
11
12/// Creates a datagram socket using the given provider.
13pub async fn datagram_socket(
14    provider: &fposix_socket::ProviderProxy,
15    domain: fposix_socket::Domain,
16    protocol: fposix_socket::DatagramSocketProtocol,
17) -> Result<Result<socket2::Socket, std::io::Error>, fidl::Error> {
18    let result = provider.datagram_socket(domain, protocol).await?;
19    Ok(async move {
20        let response =
21            result.map_err(|errno| std::io::Error::from_raw_os_error(errno.into_primitive()))?;
22        let fd = match response {
23            fposix_socket::ProviderDatagramSocketResponse::DatagramSocket(client_end) => {
24                fdio::create_fd(client_end.into()).map_err(|status| status.into_io_error())
25            }
26            fposix_socket::ProviderDatagramSocketResponse::SynchronousDatagramSocket(
27                client_end,
28            ) => fdio::create_fd(client_end.into()).map_err(|status| status.into_io_error()),
29        }?;
30        Ok(fd.into())
31    }
32    .await)
33}
34
35/// Creates a datagram socket using the given provider and configured with the
36/// specified options.
37pub async fn datagram_socket_with_options(
38    provider: &fposix_socket::ProviderProxy,
39    domain: fposix_socket::Domain,
40    protocol: fposix_socket::DatagramSocketProtocol,
41    options: fposix_socket::SocketCreationOptions,
42) -> Result<Result<socket2::Socket, std::io::Error>, fidl::Error> {
43    let result = provider.datagram_socket_with_options(domain, protocol, options).await?;
44    Ok(async move {
45        let response =
46            result.map_err(|errno| std::io::Error::from_raw_os_error(errno.into_primitive()))?;
47        let fd = match response {
48            fposix_socket::ProviderDatagramSocketWithOptionsResponse::DatagramSocket(
49                client_end,
50            ) => fdio::create_fd(client_end.into()).map_err(|status| status.into_io_error()),
51            fposix_socket::ProviderDatagramSocketWithOptionsResponse::SynchronousDatagramSocket(
52                client_end,
53            ) => fdio::create_fd(client_end.into()).map_err(|status| status.into_io_error()),
54        }?;
55        Ok(fd.into())
56    }
57    .await)
58}
59
60/// Creates a packet socket using the given provider.
61pub async fn packet_socket(
62    provider: &fpacket::ProviderProxy,
63    kind: fpacket::Kind,
64) -> Result<Result<socket2::Socket, std::io::Error>, fidl::Error> {
65    let result = provider.socket(kind).await?;
66    Ok(async move {
67        let client_end =
68            result.map_err(|errno| std::io::Error::from_raw_os_error(errno.into_primitive()))?;
69        Ok(fdio::create_fd(client_end.into()).map_err(|status| status.into_io_error())?.into())
70    }
71    .await)
72}
73
74#[cfg(test)]
75mod test {
76    use super::*;
77    use fidl_fuchsia_net_ext as fnet_ext;
78    use fidl_fuchsia_netemul_network as fnetemul_network;
79    use fidl_fuchsia_posix_socket as fposix_socket;
80    use net_declare::std_socket_addr;
81    use netstack_testing_common::realms::{Netstack, TestSandboxExt as _};
82    use netstack_testing_macros::netstack_test;
83    use sockaddr::{IntoSockAddr as _, TryToSockaddrLl as _};
84
85    #[netstack_test]
86    #[variant(N, Netstack)]
87    async fn datagram_socket_send_receive<N: Netstack>(name: &str) {
88        let sandbox: netemul::TestSandbox = netemul::TestSandbox::new().unwrap();
89
90        let network =
91            sandbox.create_network(format!("{name}-test-network")).await.expect("create network");
92        let realm_a: netemul::TestRealm<'_> = sandbox
93            .create_netstack_realm::<N, _>(format!("{name}-test-realm-a"))
94            .expect("create realm");
95        let realm_b: netemul::TestRealm<'_> = sandbox
96            .create_netstack_realm::<N, _>(format!("{name}-test-realm-b"))
97            .expect("create realm");
98
99        const MAC_A: net_types::ethernet::Mac = net_declare::net_mac!("00:00:00:00:00:01");
100        const MAC_B: net_types::ethernet::Mac = net_declare::net_mac!("00:00:00:00:00:02");
101        const FIDL_SUBNET_A: fidl_fuchsia_net::Subnet = net_declare::fidl_subnet!("192.0.2.1/24");
102        const SOCKET_ADDR_A: std::net::SocketAddr = std_socket_addr!("192.0.2.1:1111");
103        const FIDL_SUBNET_B: fidl_fuchsia_net::Subnet = net_declare::fidl_subnet!("192.0.2.2/24");
104        const SOCKET_ADDR_B: std::net::SocketAddr = std_socket_addr!("192.0.2.2:2222");
105
106        let iface_a = realm_a
107            .join_network_with(
108                &network,
109                "iface_a",
110                fnetemul_network::EndpointConfig {
111                    mtu: netemul::DEFAULT_MTU,
112                    mac: Some(Box::new(fnet_ext::MacAddress { octets: MAC_A.bytes() }.into())),
113                    port_class: fidl_fuchsia_hardware_network::PortClass::Virtual,
114                    checksum_offload: false,
115                },
116                netemul::InterfaceConfig { name: Some("iface_a".into()), ..Default::default() },
117            )
118            .await
119            .expect("join network with realm_a");
120        let iface_b = realm_b
121            .join_network_with(
122                &network,
123                "iface_b",
124                fnetemul_network::EndpointConfig {
125                    mtu: netemul::DEFAULT_MTU,
126                    mac: Some(Box::new(fnet_ext::MacAddress { octets: MAC_B.bytes() }.into())),
127                    port_class: fidl_fuchsia_hardware_network::PortClass::Virtual,
128                    checksum_offload: false,
129                },
130                netemul::InterfaceConfig { name: Some("iface_b".into()), ..Default::default() },
131            )
132            .await
133            .expect("join network with realm_b");
134
135        iface_a
136            .add_address_and_subnet_route(FIDL_SUBNET_A)
137            .await
138            .expect("add address should succeed");
139        iface_b
140            .add_address_and_subnet_route(FIDL_SUBNET_B)
141            .await
142            .expect("add address should succeed");
143
144        let socket_a = datagram_socket(
145            &realm_a
146                .connect_to_protocol::<fposix_socket::ProviderMarker>()
147                .expect("connect should succeed"),
148            fposix_socket::Domain::Ipv4,
149            fposix_socket::DatagramSocketProtocol::Udp,
150        )
151        .await
152        .expect("should not have FIDL error")
153        .expect("should not have io Error");
154
155        socket_a.bind(&SOCKET_ADDR_A.into()).expect("should succeed");
156
157        let socket_b = datagram_socket(
158            &realm_b
159                .connect_to_protocol::<fposix_socket::ProviderMarker>()
160                .expect("connect should succeed"),
161            fposix_socket::Domain::Ipv4,
162            fposix_socket::DatagramSocketProtocol::Udp,
163        )
164        .await
165        .expect("should not have FIDL error")
166        .expect("should not have io Error");
167
168        socket_b.bind(&SOCKET_ADDR_B.into()).expect("should succeed");
169
170        let mut buf = [std::mem::MaybeUninit::new(0u8); netemul::DEFAULT_MTU as usize];
171
172        let payload = b"hello world!";
173
174        let n = socket_a
175            .send_to(payload.as_ref(), &SOCKET_ADDR_B.into())
176            .expect("send_to should succeed");
177        assert_eq!(n, payload.len());
178
179        let (n, address) = socket_b.recv_from(&mut buf[..]).expect("recv_from should succeed");
180        let buf = buf[..n].iter().map(|byte| unsafe { byte.assume_init() }).collect::<Vec<_>>();
181
182        assert_eq!(&buf[..], payload.as_ref());
183        assert_eq!(address.as_socket().expect("should be SocketAddr"), SOCKET_ADDR_A);
184    }
185
186    #[netstack_test]
187    #[variant(N, Netstack)]
188    async fn packet_socket_send_receive<N: Netstack>(name: &str) {
189        let sandbox: netemul::TestSandbox = netemul::TestSandbox::new().unwrap();
190
191        let network =
192            sandbox.create_network(format!("{name}-test-network")).await.expect("create network");
193        let realm_a: netemul::TestRealm<'_> = sandbox
194            .create_netstack_realm::<N, _>(format!("{name}-test-realm-a"))
195            .expect("create realm");
196        let realm_b: netemul::TestRealm<'_> = sandbox
197            .create_netstack_realm::<N, _>(format!("{name}-test-realm-b"))
198            .expect("create realm");
199
200        const MAC_A: net_types::ethernet::Mac = net_declare::net_mac!("00:00:00:00:00:01");
201        const MAC_B: net_types::ethernet::Mac = net_declare::net_mac!("00:00:00:00:00:02");
202
203        let iface_a = realm_a
204            .join_network_with(
205                &network,
206                "iface_a",
207                fnetemul_network::EndpointConfig {
208                    mtu: netemul::DEFAULT_MTU,
209                    mac: Some(Box::new(fnet_ext::MacAddress { octets: MAC_A.bytes() }.into())),
210                    port_class: fidl_fuchsia_hardware_network::PortClass::Virtual,
211                    checksum_offload: false,
212                },
213                netemul::InterfaceConfig { name: Some("iface_a".into()), ..Default::default() },
214            )
215            .await
216            .expect("join network with realm_a");
217        let iface_b = realm_b
218            .join_network_with(
219                &network,
220                "iface_b",
221                fnetemul_network::EndpointConfig {
222                    mtu: netemul::DEFAULT_MTU,
223                    mac: Some(Box::new(fnet_ext::MacAddress { octets: MAC_B.bytes() }.into())),
224                    port_class: fidl_fuchsia_hardware_network::PortClass::Virtual,
225                    checksum_offload: false,
226                },
227                netemul::InterfaceConfig { name: Some("iface_b".into()), ..Default::default() },
228            )
229            .await
230            .expect("join network with realm_b");
231
232        let socket_a = packet_socket(
233            &realm_a
234                .connect_to_protocol::<fpacket::ProviderMarker>()
235                .expect("connect should succeed"),
236            fpacket::Kind::Network,
237        )
238        .await
239        .expect("should not have FIDL error")
240        .expect("should not have io Error");
241
242        let socket_b = packet_socket(
243            &realm_b
244                .connect_to_protocol::<fpacket::ProviderMarker>()
245                .expect("connect should succeed"),
246            fpacket::Kind::Network,
247        )
248        .await
249        .expect("should not have FIDL error")
250        .expect("should not have io Error");
251
252        let sockaddr_a = libc::sockaddr_ll::from(sockaddr::EthernetSockaddr {
253            interface_id: Some(iface_a.id().try_into().expect("nonzero")),
254            addr: MAC_A,
255            protocol: packet_formats::ethernet::EtherType::Ipv4,
256        });
257
258        let sockaddr_b = libc::sockaddr_ll::from(sockaddr::EthernetSockaddr {
259            interface_id: Some(iface_b.id().try_into().expect("nonzero")),
260            addr: MAC_B,
261            protocol: packet_formats::ethernet::EtherType::Ipv4,
262        });
263
264        socket_a.bind(&sockaddr_a.into_sockaddr()).expect("should succeed");
265        socket_b.bind(&sockaddr_b.into_sockaddr()).expect("should succeed");
266
267        let mut buf = [std::mem::MaybeUninit::new(0u8); netemul::DEFAULT_MTU as usize];
268
269        let payload = b"hello world!";
270
271        let n = socket_a
272            .send_to(
273                payload.as_ref(),
274                &libc::sockaddr_ll::from(sockaddr::EthernetSockaddr {
275                    interface_id: Some(iface_a.id().try_into().expect("nonzero")),
276                    addr: MAC_B,
277                    protocol: packet_formats::ethernet::EtherType::Ipv4,
278                })
279                .into_sockaddr(),
280            )
281            .expect("send_to should succeed");
282        assert_eq!(n, payload.len());
283
284        // We make multiple attempts because there's no guarantee that we're the
285        // exclusive traffic over this interface. In particular, this is being
286        // introduced because we're seeing IGMP reports over the interface
287        // (https://g-issues.fuchsia.dev/issues/324591565#comment13), but even
288        // without that this is a source of flakiness.
289        const NUM_ATTEMPTS: i32 = 5;
290
291        for attempt in 1..=NUM_ATTEMPTS {
292            let (n, address) = socket_b.recv_from(&mut buf[..]).expect("recv_from should succeed");
293            let buf = buf[..n].iter().map(|byte| unsafe { byte.assume_init() }).collect::<Vec<_>>();
294
295            if &buf[..] != payload.as_ref() {
296                println!("got buf={buf:?} didn't match wanted={payload:?} in attempt {attempt}");
297                continue;
298            }
299
300            let got_address = match address.try_to_sockaddr_ll() {
301                Some(addr) => addr,
302                None => {
303                    println!("could not convert {address:?} to sockaddr_ll in attempt {attempt}");
304                    continue;
305                }
306            };
307
308            let want_address = {
309                let mut addr = libc::sockaddr_ll::from(sockaddr::EthernetSockaddr {
310                    interface_id: Some(iface_b.id().try_into().expect("nonzero")),
311                    addr: MAC_A,
312                    protocol: packet_formats::ethernet::EtherType::Ipv4,
313                });
314                const ARPHRD_ETHER: libc::c_ushort = 1;
315                addr.sll_hatype = ARPHRD_ETHER;
316                addr
317            };
318
319            if got_address != want_address {
320                println!(
321                    "got_address {got_address:?} didn't match \
322                want_address {want_address:?} in attempt {attempt}"
323                );
324                continue;
325            }
326
327            println!("succeeded on attempt {attempt}");
328            return;
329        }
330
331        panic!("failed to receive expected frame in all {NUM_ATTEMPTS} attempts");
332    }
333}