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