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