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