blob: 7262fdcb703d4c42adf50d185568778b021483a0 [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Extension crate for `fuchsia.posix.socket` and `fuchsia.posix.socket.packet`.
#![deny(missing_docs)]
use fidl_fuchsia_posix_socket as fposix_socket;
use fidl_fuchsia_posix_socket_packet as fpacket;
use fuchsia_zircon as zx;
/// Creates a datagram socket using the given provider.
pub async fn datagram_socket(
provider: &fposix_socket::ProviderProxy,
domain: fposix_socket::Domain,
protocol: fposix_socket::DatagramSocketProtocol,
) -> Result<Result<socket2::Socket, std::io::Error>, fidl::Error> {
let result = provider.datagram_socket(domain, protocol).await?;
Ok(async move {
let response =
result.map_err(|errno| std::io::Error::from_raw_os_error(errno.into_primitive()))?;
let fd = match response {
fposix_socket::ProviderDatagramSocketResponse::DatagramSocket(client_end) => {
fdio::create_fd(client_end.into()).map_err(zx::Status::into_io_error)
}
fposix_socket::ProviderDatagramSocketResponse::SynchronousDatagramSocket(
client_end,
) => fdio::create_fd(client_end.into()).map_err(zx::Status::into_io_error),
}?;
Ok(fd.into())
}
.await)
}
/// Creates a packet socket using the given provider.
pub async fn packet_socket(
provider: &fpacket::ProviderProxy,
kind: fpacket::Kind,
) -> Result<Result<socket2::Socket, std::io::Error>, fidl::Error> {
let result = provider.socket(kind).await?;
Ok(async move {
let client_end =
result.map_err(|errno| std::io::Error::from_raw_os_error(errno.into_primitive()))?;
Ok(fdio::create_fd(client_end.into()).map_err(zx::Status::into_io_error)?.into())
}
.await)
}
#[cfg(test)]
mod test {
use super::*;
use fidl_fuchsia_net_ext as fnet_ext;
use fidl_fuchsia_netemul_network as fnetemul_network;
use fidl_fuchsia_posix_socket as fposix_socket;
use net_declare::std_socket_addr;
use netstack_testing_common::realms::{Netstack, TestSandboxExt as _};
use netstack_testing_macros::netstack_test;
use sockaddr::{IntoSockAddr as _, TryToSockaddrLl as _};
#[netstack_test]
async fn datagram_socket_send_receive<N: Netstack>(name: &str) {
let sandbox: netemul::TestSandbox = netemul::TestSandbox::new().unwrap();
let network =
sandbox.create_network(format!("{name}-test-network")).await.expect("create network");
let realm_a: netemul::TestRealm<'_> = sandbox
.create_netstack_realm::<N, _>(format!("{name}-test-realm-a"))
.expect("create realm");
let realm_b: netemul::TestRealm<'_> = sandbox
.create_netstack_realm::<N, _>(format!("{name}-test-realm-b"))
.expect("create realm");
const MAC_A: net_types::ethernet::Mac = net_declare::net_mac!("00:00:00:00:00:01");
const MAC_B: net_types::ethernet::Mac = net_declare::net_mac!("00:00:00:00:00:02");
const FIDL_SUBNET_A: fidl_fuchsia_net::Subnet = net_declare::fidl_subnet!("192.0.2.1/24");
const SOCKET_ADDR_A: std::net::SocketAddr = std_socket_addr!("192.0.2.1:1111");
const FIDL_SUBNET_B: fidl_fuchsia_net::Subnet = net_declare::fidl_subnet!("192.0.2.2/24");
const SOCKET_ADDR_B: std::net::SocketAddr = std_socket_addr!("192.0.2.2:2222");
let iface_a = realm_a
.join_network_with(
&network,
"iface_a",
fnetemul_network::EndpointConfig {
mtu: netemul::DEFAULT_MTU,
mac: Some(Box::new(fnet_ext::MacAddress { octets: MAC_A.bytes() }.into())),
port_class: fidl_fuchsia_hardware_network::PortClass::Virtual,
},
netemul::InterfaceConfig { name: Some("iface_a".into()), ..Default::default() },
)
.await
.expect("join network with realm_a");
let iface_b = realm_b
.join_network_with(
&network,
"iface_b",
fnetemul_network::EndpointConfig {
mtu: netemul::DEFAULT_MTU,
mac: Some(Box::new(fnet_ext::MacAddress { octets: MAC_B.bytes() }.into())),
port_class: fidl_fuchsia_hardware_network::PortClass::Virtual,
},
netemul::InterfaceConfig { name: Some("iface_b".into()), ..Default::default() },
)
.await
.expect("join network with realm_b");
iface_a
.add_address_and_subnet_route(FIDL_SUBNET_A)
.await
.expect("add address should succeed");
iface_b
.add_address_and_subnet_route(FIDL_SUBNET_B)
.await
.expect("add address should succeed");
let socket_a = datagram_socket(
&realm_a
.connect_to_protocol::<fposix_socket::ProviderMarker>()
.expect("connect should succeed"),
fposix_socket::Domain::Ipv4,
fposix_socket::DatagramSocketProtocol::Udp,
)
.await
.expect("should not have FIDL error")
.expect("should not have io Error");
socket_a.bind(&SOCKET_ADDR_A.into()).expect("should succeed");
let socket_b = datagram_socket(
&realm_b
.connect_to_protocol::<fposix_socket::ProviderMarker>()
.expect("connect should succeed"),
fposix_socket::Domain::Ipv4,
fposix_socket::DatagramSocketProtocol::Udp,
)
.await
.expect("should not have FIDL error")
.expect("should not have io Error");
socket_b.bind(&SOCKET_ADDR_B.into()).expect("should succeed");
let mut buf = [std::mem::MaybeUninit::new(0u8); netemul::DEFAULT_MTU as usize];
let payload = b"hello world!";
let n = socket_a
.send_to(payload.as_ref(), &SOCKET_ADDR_B.into())
.expect("send_to should succeed");
assert_eq!(n, payload.len());
let (n, address) = socket_b.recv_from(&mut buf[..]).expect("recv_from should succeed");
let buf = buf[..n].iter().map(|byte| unsafe { byte.assume_init() }).collect::<Vec<_>>();
assert_eq!(&buf[..], payload.as_ref());
assert_eq!(address.as_socket().expect("should be SocketAddr"), SOCKET_ADDR_A);
}
#[netstack_test]
async fn packet_socket_send_receive<N: Netstack>(name: &str) {
let sandbox: netemul::TestSandbox = netemul::TestSandbox::new().unwrap();
let network =
sandbox.create_network(format!("{name}-test-network")).await.expect("create network");
let realm_a: netemul::TestRealm<'_> = sandbox
.create_netstack_realm::<N, _>(format!("{name}-test-realm-a"))
.expect("create realm");
let realm_b: netemul::TestRealm<'_> = sandbox
.create_netstack_realm::<N, _>(format!("{name}-test-realm-b"))
.expect("create realm");
const MAC_A: net_types::ethernet::Mac = net_declare::net_mac!("00:00:00:00:00:01");
const MAC_B: net_types::ethernet::Mac = net_declare::net_mac!("00:00:00:00:00:02");
let iface_a = realm_a
.join_network_with(
&network,
"iface_a",
fnetemul_network::EndpointConfig {
mtu: netemul::DEFAULT_MTU,
mac: Some(Box::new(fnet_ext::MacAddress { octets: MAC_A.bytes() }.into())),
port_class: fidl_fuchsia_hardware_network::PortClass::Virtual,
},
netemul::InterfaceConfig { name: Some("iface_a".into()), ..Default::default() },
)
.await
.expect("join network with realm_a");
let iface_b = realm_b
.join_network_with(
&network,
"iface_b",
fnetemul_network::EndpointConfig {
mtu: netemul::DEFAULT_MTU,
mac: Some(Box::new(fnet_ext::MacAddress { octets: MAC_B.bytes() }.into())),
port_class: fidl_fuchsia_hardware_network::PortClass::Virtual,
},
netemul::InterfaceConfig { name: Some("iface_b".into()), ..Default::default() },
)
.await
.expect("join network with realm_b");
let socket_a = packet_socket(
&realm_a
.connect_to_protocol::<fpacket::ProviderMarker>()
.expect("connect should succeed"),
fpacket::Kind::Network,
)
.await
.expect("should not have FIDL error")
.expect("should not have io Error");
let socket_b = packet_socket(
&realm_b
.connect_to_protocol::<fpacket::ProviderMarker>()
.expect("connect should succeed"),
fpacket::Kind::Network,
)
.await
.expect("should not have FIDL error")
.expect("should not have io Error");
let sockaddr_a = libc::sockaddr_ll::from(sockaddr::EthernetSockaddr {
interface_id: Some(iface_a.id().try_into().expect("nonzero")),
addr: MAC_A,
protocol: packet_formats::ethernet::EtherType::Ipv4,
});
let sockaddr_b = libc::sockaddr_ll::from(sockaddr::EthernetSockaddr {
interface_id: Some(iface_b.id().try_into().expect("nonzero")),
addr: MAC_B,
protocol: packet_formats::ethernet::EtherType::Ipv4,
});
socket_a.bind(&sockaddr_a.into_sockaddr()).expect("should succeed");
socket_b.bind(&sockaddr_b.into_sockaddr()).expect("should succeed");
let mut buf = [std::mem::MaybeUninit::new(0u8); netemul::DEFAULT_MTU as usize];
let payload = b"hello world!";
let n = socket_a
.send_to(
payload.as_ref(),
&libc::sockaddr_ll::from(sockaddr::EthernetSockaddr {
interface_id: Some(iface_a.id().try_into().expect("nonzero")),
addr: MAC_B,
protocol: packet_formats::ethernet::EtherType::Ipv4,
})
.into_sockaddr(),
)
.expect("send_to should succeed");
assert_eq!(n, payload.len());
// We make multiple attempts because there's no guarantee that we're the
// exclusive traffic over this interface. In particular, this is being
// introduced because we're seeing IGMP reports over the interface
// (https://g-issues.fuchsia.dev/issues/324591565#comment13), but even
// without that this is a source of flakiness.
const NUM_ATTEMPTS: i32 = 5;
for attempt in 1..=NUM_ATTEMPTS {
let (n, address) = socket_b.recv_from(&mut buf[..]).expect("recv_from should succeed");
let buf = buf[..n].iter().map(|byte| unsafe { byte.assume_init() }).collect::<Vec<_>>();
if &buf[..] != payload.as_ref() {
println!("got buf={buf:?} didn't match wanted={payload:?} in attempt {attempt}");
continue;
}
let got_address = match address.try_to_sockaddr_ll() {
Some(addr) => addr,
None => {
println!("could not convert {address:?} to sockaddr_ll in attempt {attempt}");
continue;
}
};
let want_address = {
let mut addr = libc::sockaddr_ll::from(sockaddr::EthernetSockaddr {
interface_id: Some(iface_b.id().try_into().expect("nonzero")),
addr: MAC_A,
protocol: packet_formats::ethernet::EtherType::Ipv4,
});
const ARPHRD_ETHER: libc::c_ushort = 1;
addr.sll_hatype = ARPHRD_ETHER;
addr
};
if got_address != want_address {
println!(
"got_address {got_address:?} didn't match \
want_address {want_address:?} in attempt {attempt}"
);
continue;
}
println!("succeeded on attempt {attempt}");
return;
}
panic!("failed to receive expected frame in all {NUM_ATTEMPTS} attempts");
}
}