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