| // Copyright 2020 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. |
| |
| #![cfg(test)] |
| |
| use fidl_fuchsia_net_stack_ext::FidlReturn as _; |
| use fuchsia_async::TimeoutExt as _; |
| |
| use anyhow::Context as _; |
| use futures::{ |
| io::AsyncReadExt as _, io::AsyncWriteExt as _, FutureExt as _, TryFutureExt as _, |
| TryStreamExt as _, |
| }; |
| use net_declare::{fidl_ip_v4, fidl_ip_v6, fidl_subnet}; |
| use netemul::{EnvironmentTcpListener as _, EnvironmentTcpStream as _, EnvironmentUdpSocket as _}; |
| use netstack_testing_common::environments::{Netstack2, TestSandboxExt as _}; |
| use netstack_testing_common::Result; |
| use netstack_testing_macros::variants_test; |
| use packet::Serializer; |
| use packet_formats; |
| use packet_formats::ipv4::Ipv4Header; |
| |
| async fn run_udp_socket_test( |
| server: &netemul::TestEnvironment<'_>, |
| server_addr: fidl_fuchsia_net::IpAddress, |
| client: &netemul::TestEnvironment<'_>, |
| client_addr: fidl_fuchsia_net::IpAddress, |
| ) -> Result { |
| let fidl_fuchsia_net_ext::IpAddress(client_addr) = |
| fidl_fuchsia_net_ext::IpAddress::from(client_addr); |
| let client_addr = std::net::SocketAddr::new(client_addr, 1234); |
| |
| let fidl_fuchsia_net_ext::IpAddress(server_addr) = |
| fidl_fuchsia_net_ext::IpAddress::from(server_addr); |
| let server_addr = std::net::SocketAddr::new(server_addr, 8080); |
| |
| let client_sock = fuchsia_async::net::UdpSocket::bind_in_env(client, client_addr) |
| .await |
| .context("failed to create client socket")?; |
| |
| let server_sock = fuchsia_async::net::UdpSocket::bind_in_env(server, server_addr) |
| .await |
| .context("failed to create server socket")?; |
| |
| const PAYLOAD: &'static str = "Hello World"; |
| |
| let client_fut = async move { |
| let r = |
| client_sock.send_to(PAYLOAD.as_bytes(), server_addr).await.context("sendto failed")?; |
| assert_eq!(r, PAYLOAD.as_bytes().len()); |
| Result::Ok(()) |
| }; |
| let server_fut = async move { |
| let mut buf = [0u8; 1024]; |
| let (r, from) = server_sock.recv_from(&mut buf[..]).await.context("recvfrom failed")?; |
| assert_eq!(r, PAYLOAD.as_bytes().len()); |
| assert_eq!(&buf[..r], PAYLOAD.as_bytes()); |
| assert_eq!(from, client_addr); |
| Result::Ok(()) |
| }; |
| |
| let ((), ()) = futures::future::try_join(client_fut, server_fut).await?; |
| Ok(()) |
| } |
| |
| #[variants_test] |
| async fn test_udp_socket<E: netemul::Endpoint>(name: &str) -> Result { |
| let sandbox = netemul::TestSandbox::new().context("failed to create sandbox")?; |
| let net = sandbox.create_network("net").await.context("failed to create network")?; |
| |
| let client = sandbox |
| .create_netstack_environment::<Netstack2, _>(format!("{}_client", name)) |
| .context("failed to create client environment")?; |
| |
| const CLIENT_SUBNET: fidl_fuchsia_net::Subnet = fidl_subnet!("192.168.0.2/24"); |
| const SERVER_SUBNET: fidl_fuchsia_net::Subnet = fidl_subnet!("192.168.0.1/24"); |
| |
| let _client_ep = client |
| .join_network::<E, _>(&net, "client", &netemul::InterfaceConfig::StaticIp(CLIENT_SUBNET)) |
| .await |
| .context("client failed to join network")?; |
| let server = sandbox |
| .create_netstack_environment::<Netstack2, _>(format!("{}_server", name)) |
| .context("failed to create server environment")?; |
| let _server_ep = server |
| .join_network::<E, _>(&net, "server", &netemul::InterfaceConfig::StaticIp(SERVER_SUBNET)) |
| .await |
| .context("server failed to join network")?; |
| |
| run_udp_socket_test(&server, SERVER_SUBNET.addr, &client, CLIENT_SUBNET.addr).await |
| } |
| |
| async fn run_tcp_socket_test( |
| server: &netemul::TestEnvironment<'_>, |
| server_addr: fidl_fuchsia_net::IpAddress, |
| client: &netemul::TestEnvironment<'_>, |
| client_addr: fidl_fuchsia_net::IpAddress, |
| ) -> Result { |
| let fidl_fuchsia_net_ext::IpAddress(client_addr) = client_addr.into(); |
| let client_addr = std::net::SocketAddr::new(client_addr, 1234); |
| |
| let fidl_fuchsia_net_ext::IpAddress(server_addr) = server_addr.into(); |
| let server_addr = std::net::SocketAddr::new(server_addr, 8080); |
| |
| // We pick a payload that is small enough to be guaranteed to fit in a TCP segment so both the |
| // client and server can read the entire payload in a single `read`. |
| const PAYLOAD: &'static str = "Hello World"; |
| |
| let listener = fuchsia_async::net::TcpListener::listen_in_env(server, server_addr) |
| .await |
| .context("failed to create server socket")?; |
| |
| let server_fut = async { |
| let (_, mut stream, from) = listener.accept().await.context("accept failed")?; |
| |
| let mut buf = [0u8; 1024]; |
| let read_count = |
| stream.read(&mut buf).await.context("read from tcp server stream failed")?; |
| |
| assert_eq!(from.ip(), client_addr.ip()); |
| assert_eq!(read_count, PAYLOAD.as_bytes().len()); |
| assert_eq!(&buf[..read_count], PAYLOAD.as_bytes()); |
| |
| let write_count = |
| stream.write(PAYLOAD.as_bytes()).await.context("write to tcp server stream failed")?; |
| assert_eq!(write_count, PAYLOAD.as_bytes().len()); |
| |
| Result::Ok(()) |
| }; |
| |
| let client_fut = async { |
| let mut stream = fuchsia_async::net::TcpStream::connect_in_env(client, server_addr) |
| .await |
| .context("failed to create client socket")?; |
| |
| let write_count = |
| stream.write(PAYLOAD.as_bytes()).await.context("write to tcp client stream failed")?; |
| |
| assert_eq!(write_count, PAYLOAD.as_bytes().len()); |
| |
| let mut buf = [0u8; 1024]; |
| let read_count = |
| stream.read(&mut buf).await.context("read from tcp client stream failed")?; |
| |
| assert_eq!(read_count, PAYLOAD.as_bytes().len()); |
| assert_eq!(&buf[..read_count], PAYLOAD.as_bytes()); |
| |
| Result::Ok(()) |
| }; |
| |
| let ((), ()) = futures::future::try_join(client_fut, server_fut).await?; |
| Ok(()) |
| } |
| |
| #[variants_test] |
| async fn test_tcp_socket<E: netemul::Endpoint>(name: &str) -> Result { |
| const CLIENT_SUBNET: fidl_fuchsia_net::Subnet = fidl_subnet!("192.168.0.2/24"); |
| const SERVER_SUBNET: fidl_fuchsia_net::Subnet = fidl_subnet!("192.168.0.1/24"); |
| |
| let sandbox = netemul::TestSandbox::new().context("failed to create sandbox")?; |
| let net = sandbox.create_network("net").await.context("failed to create network")?; |
| |
| let client = sandbox |
| .create_netstack_environment::<Netstack2, _>(format!("{}_client", name)) |
| .context("failed to create client environment")?; |
| let _client_ep = client |
| .join_network::<E, _>(&net, "client", &netemul::InterfaceConfig::StaticIp(CLIENT_SUBNET)) |
| .await |
| .context("client failed to join network")?; |
| |
| let server = sandbox |
| .create_netstack_environment::<Netstack2, _>(format!("{}_server", name)) |
| .context("failed to create server environment")?; |
| let _server_ep = server |
| .join_network::<E, _>(&net, "server", &netemul::InterfaceConfig::StaticIp(SERVER_SUBNET)) |
| .await |
| .context("server failed to join network")?; |
| |
| run_tcp_socket_test(&server, SERVER_SUBNET.addr, &client, CLIENT_SUBNET.addr).await |
| } |
| |
| // Helper function to add ip device to stack. |
| async fn install_ip_device( |
| env: &netemul::TestEnvironment<'_>, |
| device: fidl::endpoints::ClientEnd<fidl_fuchsia_hardware_network::DeviceMarker>, |
| addrs: &mut [fidl_fuchsia_net::Subnet], |
| ) -> Result<u64> { |
| let stack = env.connect_to_service::<fidl_fuchsia_net_stack::StackMarker>()?; |
| let interface_state = env.connect_to_service::<fidl_fuchsia_net_interfaces::StateMarker>()?; |
| let id = stack |
| .add_interface( |
| fidl_fuchsia_net_stack::InterfaceConfig { |
| name: None, |
| topopath: None, |
| metric: None, |
| ..fidl_fuchsia_net_stack::InterfaceConfig::EMPTY |
| }, |
| &mut fidl_fuchsia_net_stack::DeviceDefinition::Ip(device), |
| ) |
| .await |
| .squash_result() |
| .context("failed to add to stack")?; |
| let () = |
| stack.enable_interface(id).await.squash_result().context("failed to enable interface")?; |
| for addr in addrs.iter_mut() { |
| let () = stack |
| .add_interface_address(id, addr) |
| .await |
| .squash_result() |
| .with_context(|| format!("failed to add interface address {:?}", addr))?; |
| } |
| // Wait for addresses to be assigned. Necessary for IPv6 addresses |
| // since DAD must be performed. |
| let () = fidl_fuchsia_net_interfaces_ext::wait_interface_with_id( |
| fidl_fuchsia_net_interfaces_ext::event_stream_from_state(&interface_state)?, |
| &mut fidl_fuchsia_net_interfaces_ext::InterfaceState::Unknown(id), |
| |fidl_fuchsia_net_interfaces_ext::Properties { addresses, .. }| { |
| // TODO(https://github.com/rust-lang/rust/issues/80967): use bool::then_some. |
| addrs |
| .iter() |
| .all(|want| { |
| addresses |
| .iter() |
| .any(|&fidl_fuchsia_net_interfaces_ext::Address { addr }| addr == *want) |
| }) |
| .then(|| ()) |
| }, |
| ) |
| .await |
| .context("failed to observe addresses")?; |
| Ok(id) |
| } |
| |
| /// Creates default base config for an IP tun device. |
| fn base_ip_device_config() -> fidl_fuchsia_net_tun::BaseConfig { |
| fidl_fuchsia_net_tun::BaseConfig { |
| mtu: Some(1500), |
| rx_types: Some(vec![ |
| fidl_fuchsia_hardware_network::FrameType::Ipv4, |
| fidl_fuchsia_hardware_network::FrameType::Ipv6, |
| ]), |
| tx_types: Some(vec![ |
| fidl_fuchsia_hardware_network::FrameTypeSupport { |
| type_: fidl_fuchsia_hardware_network::FrameType::Ipv4, |
| features: fidl_fuchsia_hardware_network::FRAME_FEATURES_RAW, |
| supported_flags: fidl_fuchsia_hardware_network::TxFlags::empty(), |
| }, |
| fidl_fuchsia_hardware_network::FrameTypeSupport { |
| type_: fidl_fuchsia_hardware_network::FrameType::Ipv6, |
| features: fidl_fuchsia_hardware_network::FRAME_FEATURES_RAW, |
| supported_flags: fidl_fuchsia_hardware_network::TxFlags::empty(), |
| }, |
| ]), |
| report_metadata: None, |
| min_tx_buffer_length: None, |
| ..fidl_fuchsia_net_tun::BaseConfig::EMPTY |
| } |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn test_ip_endpoints_socket() -> Result { |
| let sandbox = netemul::TestSandbox::new().context("failed to create sandbox")?; |
| let client = sandbox |
| .create_netstack_environment::<Netstack2, _>("test_ip_endpoints_socket_client") |
| .context("failed to create client environment")?; |
| let server = sandbox |
| .create_netstack_environment::<Netstack2, _>("test_ip_endpoints_socket_server") |
| .context("failed to create server environment")?; |
| |
| let tun = client |
| .connect_to_service::<fidl_fuchsia_net_tun::ControlMarker>() |
| .context("failed to connect to tun service")?; |
| |
| let (tun_pair, req) = |
| fidl::endpoints::create_proxy::<fidl_fuchsia_net_tun::DevicePairMarker>()?; |
| let () = tun |
| .create_pair( |
| fidl_fuchsia_net_tun::DevicePairConfig { |
| base: Some(base_ip_device_config()), |
| fallible_transmit_left: None, |
| fallible_transmit_right: None, |
| mac_left: None, |
| mac_right: None, |
| ..fidl_fuchsia_net_tun::DevicePairConfig::EMPTY |
| }, |
| req, |
| ) |
| .context("failed to create tun pair")?; |
| |
| let (client_device, client_req) = |
| fidl::endpoints::create_endpoints::<fidl_fuchsia_hardware_network::DeviceMarker>()?; |
| let (server_device, server_req) = |
| fidl::endpoints::create_endpoints::<fidl_fuchsia_hardware_network::DeviceMarker>()?; |
| let () = tun_pair |
| .connect_protocols(fidl_fuchsia_net_tun::DevicePairEnds { |
| left: Some(fidl_fuchsia_net_tun::Protocols { |
| network_device: Some(client_req), |
| mac_addressing: None, |
| ..fidl_fuchsia_net_tun::Protocols::EMPTY |
| }), |
| right: Some(fidl_fuchsia_net_tun::Protocols { |
| network_device: Some(server_req), |
| mac_addressing: None, |
| ..fidl_fuchsia_net_tun::Protocols::EMPTY |
| }), |
| ..fidl_fuchsia_net_tun::DevicePairEnds::EMPTY |
| }) |
| .context("connect protocols failed")?; |
| |
| // Addresses must be in the same subnet. |
| const SERVER_ADDR_V4: fidl_fuchsia_net::Subnet = fidl_subnet!("192.168.0.1/24"); |
| const SERVER_ADDR_V6: fidl_fuchsia_net::Subnet = fidl_subnet!("2001::1/120"); |
| const CLIENT_ADDR_V4: fidl_fuchsia_net::Subnet = fidl_subnet!("192.168.0.2/24"); |
| const CLIENT_ADDR_V6: fidl_fuchsia_net::Subnet = fidl_subnet!("2001::2/120"); |
| |
| // We install both devices in parallel because a DevicePair will only have |
| // its link signal set to up once both sides have sessions attached. This |
| // way both devices will be configured "at the same time" and DAD will be |
| // able to complete for IPv6 addresses. |
| let (_client_id, _server_id) = futures::future::try_join( |
| install_ip_device(&client, client_device, &mut [CLIENT_ADDR_V4, CLIENT_ADDR_V6]) |
| .map(|r| r.context("client setup failed")), |
| install_ip_device(&server, server_device, &mut [SERVER_ADDR_V4, SERVER_ADDR_V6]) |
| .map(|r| r.context("server setup failed")), |
| ) |
| .await |
| .context("setup failed")?; |
| |
| // Run socket test for both IPv4 and IPv6. |
| let () = run_udp_socket_test(&server, SERVER_ADDR_V4.addr, &client, CLIENT_ADDR_V4.addr) |
| .await |
| .context("v4 socket test failed")?; |
| let () = run_udp_socket_test(&server, SERVER_ADDR_V6.addr, &client, CLIENT_ADDR_V6.addr) |
| .await |
| .context("v6 socket test failed")?; |
| |
| Ok(()) |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn test_ip_endpoint_packets() -> Result { |
| let sandbox = netemul::TestSandbox::new().context("failed to create sandbox")?; |
| let env = sandbox |
| .create_netstack_environment::<Netstack2, _>("test_ip_endpoint_packets") |
| .context("failed to create client environment")?; |
| |
| let tun = env |
| .connect_to_service::<fidl_fuchsia_net_tun::ControlMarker>() |
| .context("failed to connect to tun service")?; |
| |
| let (tun_dev, req) = fidl::endpoints::create_proxy::<fidl_fuchsia_net_tun::DeviceMarker>()?; |
| let () = tun |
| .create_device( |
| fidl_fuchsia_net_tun::DeviceConfig { |
| base: Some(base_ip_device_config()), |
| online: Some(true), |
| blocking: Some(true), |
| mac: None, |
| ..fidl_fuchsia_net_tun::DeviceConfig::EMPTY |
| }, |
| req, |
| ) |
| .context("failed to create tun pair")?; |
| |
| let (device, device_req) = |
| fidl::endpoints::create_endpoints::<fidl_fuchsia_hardware_network::DeviceMarker>()?; |
| let () = tun_dev |
| .connect_protocols(fidl_fuchsia_net_tun::Protocols { |
| network_device: Some(device_req), |
| mac_addressing: None, |
| ..fidl_fuchsia_net_tun::Protocols::EMPTY |
| }) |
| .context("connect protocols failed")?; |
| |
| // Declare addresses in the same subnet. Alice is Netstack, and Bob is our |
| // end of the tun device that we'll use to inject frames. |
| const PREFIX_V4: u8 = 24; |
| const PREFIX_V6: u8 = 120; |
| const ALICE_ADDR_V4: fidl_fuchsia_net::Ipv4Address = fidl_ip_v4!("192.168.0.1"); |
| const ALICE_ADDR_V6: fidl_fuchsia_net::Ipv6Address = fidl_ip_v6!("2001::1"); |
| const BOB_ADDR_V4: fidl_fuchsia_net::Ipv4Address = fidl_ip_v4!("192.168.0.2"); |
| const BOB_ADDR_V6: fidl_fuchsia_net::Ipv6Address = fidl_ip_v6!("2001::2"); |
| |
| let _device_id = install_ip_device( |
| &env, |
| device, |
| &mut [ |
| fidl_fuchsia_net::Subnet { |
| addr: fidl_fuchsia_net::IpAddress::Ipv4(ALICE_ADDR_V4), |
| prefix_len: PREFIX_V4, |
| }, |
| fidl_fuchsia_net::Subnet { |
| addr: fidl_fuchsia_net::IpAddress::Ipv6(ALICE_ADDR_V6), |
| prefix_len: PREFIX_V6, |
| }, |
| ], |
| ) |
| .await |
| .context("setup failed")?; |
| |
| use net_types::ip::{Ipv4, Ipv4Addr, Ipv6, Ipv6Addr}; |
| use packet::ParsablePacket; |
| use packet_formats::{ |
| icmp::{ |
| IcmpEchoRequest, IcmpPacketBuilder, IcmpUnusedCode, Icmpv4Packet, Icmpv6Packet, |
| MessageBody, |
| }, |
| igmp::messages::IgmpPacket, |
| ipv4::{Ipv4Packet, Ipv4PacketBuilder}, |
| ipv6::{Ipv6Header, Ipv6Packet, Ipv6PacketBuilder}, |
| }; |
| |
| let read_frame = futures::stream::try_unfold(tun_dev.clone(), |tun_dev| async move { |
| let frame = tun_dev |
| .read_frame() |
| .await |
| .context("read_frame_failed")? |
| .map_err(fuchsia_zircon::Status::from_raw) |
| .context("read_frame returned error")?; |
| Ok(Some((frame, tun_dev))) |
| }) |
| .try_filter_map(|frame| async move { |
| let frame_type = frame.frame_type.context("missing frame type in frame")?; |
| let frame_data = frame.data.context("missing data in frame")?; |
| match frame_type { |
| fidl_fuchsia_hardware_network::FrameType::Ipv6 => { |
| // Ignore all NDP and MLD IPv6 frames. |
| let mut bv = &frame_data[..]; |
| let ipv6 = Ipv6Packet::parse(&mut bv, ()) |
| .with_context(|| format!("failed to parse IPv6 packet {:?}", frame_data))?; |
| if ipv6.proto() == packet_formats::ip::IpProto::Icmpv6 { |
| let parse_args = |
| packet_formats::icmp::IcmpParseArgs::new(ipv6.src_ip(), ipv6.dst_ip()); |
| match Icmpv6Packet::parse(&mut bv, parse_args) |
| .context("failed to parse ICMP packet")? |
| { |
| Icmpv6Packet::Ndp(p) => { |
| println!("ignoring NDP packet {:?}", p); |
| return Ok(None); |
| } |
| Icmpv6Packet::Mld(p) => { |
| println!("ignoring MLD packet {:?}", p); |
| return Ok(None); |
| } |
| Icmpv6Packet::DestUnreachable(_) |
| | Icmpv6Packet::PacketTooBig(_) |
| | Icmpv6Packet::TimeExceeded(_) |
| | Icmpv6Packet::ParameterProblem(_) |
| | Icmpv6Packet::EchoRequest(_) |
| | Icmpv6Packet::EchoReply(_) => {} |
| } |
| } |
| } |
| fidl_fuchsia_hardware_network::FrameType::Ipv4 => { |
| // Ignore all IGMP frames. |
| let mut bv = &frame_data[..]; |
| let ipv4 = Ipv4Packet::parse(&mut bv, ()) |
| .with_context(|| format!("failed to parse IPv4 packet {:?}", frame_data))?; |
| if ipv4.proto() == packet_formats::ip::IpProto::Igmp { |
| let p = |
| IgmpPacket::parse(&mut bv, ()).context("failed to parse IGMP packet")?; |
| println!("ignoring IGMP packet {:?}", p); |
| return Ok(None); |
| } |
| } |
| fidl_fuchsia_hardware_network::FrameType::Ethernet => {} |
| } |
| Ok(Some((frame_type, frame_data))) |
| }); |
| pin_utils::pin_mut!(read_frame); |
| |
| async fn write_frame_and_read_with_timeout<S>( |
| tun_dev: &fidl_fuchsia_net_tun::DeviceProxy, |
| frame: fidl_fuchsia_net_tun::Frame, |
| read_frame: &mut S, |
| ) -> Result<Option<S::Ok>> |
| where |
| S: futures::stream::TryStream<Error = anyhow::Error> + std::marker::Unpin, |
| { |
| let () = tun_dev |
| .write_frame(frame) |
| .await |
| .context("write_frame failed")? |
| .map_err(fuchsia_zircon::Status::from_raw) |
| .context("write_frame returned error")?; |
| Ok(read_frame |
| .try_next() |
| .and_then(|f| { |
| futures::future::ready(f.context("frame stream ended unexpectedly").map(Some)) |
| }) |
| .on_timeout( |
| fuchsia_async::Time::after(fuchsia_zircon::Duration::from_millis(50)), |
| || Ok(None), |
| ) |
| .await |
| .context("failed to read frame")?) |
| } |
| |
| const ICMP_ID: u16 = 10; |
| const SEQ_NUM: u16 = 1; |
| let mut payload = [1u8, 2, 3, 4]; |
| |
| // Manually build a ping frame and see it come back out of the stack. |
| let src_ip = Ipv4Addr::new(BOB_ADDR_V4.addr); |
| let dst_ip = Ipv4Addr::new(ALICE_ADDR_V4.addr); |
| let packet = packet::Buf::new(&mut payload[..], ..) |
| .encapsulate(IcmpPacketBuilder::<Ipv4, &[u8], _>::new( |
| src_ip, |
| dst_ip, |
| IcmpUnusedCode, |
| IcmpEchoRequest::new(ICMP_ID, SEQ_NUM), |
| )) |
| .encapsulate(Ipv4PacketBuilder::new(src_ip, dst_ip, 1, packet_formats::ip::IpProto::Icmp)) |
| .serialize_vec_outer() |
| .expect("serialization failed") |
| .as_ref() |
| .to_vec(); |
| |
| // Send v4 ping request. |
| let () = tun_dev |
| .write_frame(fidl_fuchsia_net_tun::Frame { |
| frame_type: Some(fidl_fuchsia_hardware_network::FrameType::Ipv4), |
| data: Some(packet.clone()), |
| meta: None, |
| ..fidl_fuchsia_net_tun::Frame::EMPTY |
| }) |
| .await |
| .context("write_frame failed")? |
| .map_err(fuchsia_zircon::Status::from_raw) |
| .context("write_frame returned error")?; |
| |
| // Read ping response. |
| let (frame_type, data) = read_frame |
| .try_next() |
| .await |
| .context("failed to read ping response")? |
| .context("frame stream ended unexpectedly")?; |
| assert_eq!(frame_type, fidl_fuchsia_hardware_network::FrameType::Ipv4); |
| let mut bv = &data[..]; |
| let ipv4_packet = Ipv4Packet::parse(&mut bv, ()).context("failed to parse IPv4 packet")?; |
| assert_eq!(ipv4_packet.src_ip(), dst_ip); |
| assert_eq!(ipv4_packet.dst_ip(), src_ip); |
| assert_eq!(ipv4_packet.proto(), packet_formats::ip::IpProto::Icmp); |
| |
| let parse_args = |
| packet_formats::icmp::IcmpParseArgs::new(ipv4_packet.src_ip(), ipv4_packet.dst_ip()); |
| let icmp_packet = |
| match Icmpv4Packet::parse(&mut bv, parse_args).context("failed to parse ICMP packet")? { |
| Icmpv4Packet::EchoReply(reply) => reply, |
| p => return Err(anyhow::anyhow!("got ICMP packet {:?}, want EchoReply", p)), |
| }; |
| assert_eq!(icmp_packet.message().id(), ICMP_ID); |
| assert_eq!(icmp_packet.message().seq(), SEQ_NUM); |
| assert_eq!(icmp_packet.body().bytes(), &payload[..]); |
| |
| // Send the same data again, but with an IPv6 frame type, expect that it'll |
| // fail parsing and no response will be generated. |
| assert_eq!( |
| write_frame_and_read_with_timeout( |
| &tun_dev, |
| fidl_fuchsia_net_tun::Frame { |
| frame_type: Some(fidl_fuchsia_hardware_network::FrameType::Ipv6), |
| data: Some(packet), |
| meta: None, |
| ..fidl_fuchsia_net_tun::Frame::EMPTY |
| }, |
| &mut read_frame, |
| ) |
| .await |
| .context("IPv4 frame with IPv6 frame type failure")?, |
| None |
| ); |
| |
| // Manually build a V6 ping frame and see it come back out of the stack. |
| let src_ip = Ipv6Addr::new(BOB_ADDR_V6.addr); |
| let dst_ip = Ipv6Addr::new(ALICE_ADDR_V6.addr); |
| let packet = packet::Buf::new(&mut payload[..], ..) |
| .encapsulate(IcmpPacketBuilder::<Ipv6, &[u8], _>::new( |
| src_ip, |
| dst_ip, |
| IcmpUnusedCode, |
| IcmpEchoRequest::new(ICMP_ID, SEQ_NUM), |
| )) |
| .encapsulate(Ipv6PacketBuilder::new(src_ip, dst_ip, 1, packet_formats::ip::IpProto::Icmpv6)) |
| .serialize_vec_outer() |
| .expect("serialization failed") |
| .as_ref() |
| .to_vec(); |
| |
| // Send v6 ping request. |
| let () = tun_dev |
| .write_frame(fidl_fuchsia_net_tun::Frame { |
| frame_type: Some(fidl_fuchsia_hardware_network::FrameType::Ipv6), |
| data: Some(packet.clone()), |
| meta: None, |
| ..fidl_fuchsia_net_tun::Frame::EMPTY |
| }) |
| .await |
| .context("write_frame failed")? |
| .map_err(fuchsia_zircon::Status::from_raw) |
| .context("write_frame returned error")?; |
| |
| // Read ping response. |
| let (frame_type, data) = read_frame |
| .try_next() |
| .await |
| .context("failed to read ping response")? |
| .context("frame stream ended unexpectedly")?; |
| assert_eq!(frame_type, fidl_fuchsia_hardware_network::FrameType::Ipv6); |
| let mut bv = &data[..]; |
| let ipv6_packet = Ipv6Packet::parse(&mut bv, ()).context("failed to parse IPv6 packet")?; |
| assert_eq!(ipv6_packet.src_ip(), dst_ip); |
| assert_eq!(ipv6_packet.dst_ip(), src_ip); |
| assert_eq!(ipv6_packet.proto(), packet_formats::ip::IpProto::Icmpv6); |
| |
| let parse_args = |
| packet_formats::icmp::IcmpParseArgs::new(ipv6_packet.src_ip(), ipv6_packet.dst_ip()); |
| let icmp_packet = |
| match Icmpv6Packet::parse(&mut bv, parse_args).context("failed to parse ICMPv6 packet")? { |
| Icmpv6Packet::EchoReply(reply) => reply, |
| p => return Err(anyhow::anyhow!("got ICMPv6 packet {:?}, want EchoReply", p)), |
| }; |
| assert_eq!(icmp_packet.message().id(), ICMP_ID); |
| assert_eq!(icmp_packet.message().seq(), SEQ_NUM); |
| assert_eq!(icmp_packet.body().bytes(), &payload[..]); |
| |
| // Send the same data again, but with an IPv4 frame type, expect that it'll |
| // fail parsing and no response will be generated. |
| assert_eq!( |
| write_frame_and_read_with_timeout( |
| &tun_dev, |
| fidl_fuchsia_net_tun::Frame { |
| frame_type: Some(fidl_fuchsia_hardware_network::FrameType::Ipv4), |
| data: Some(packet), |
| meta: None, |
| ..fidl_fuchsia_net_tun::Frame::EMPTY |
| }, |
| &mut read_frame, |
| ) |
| .await |
| .context("IPv6 frame with IPv4 frame type failure")?, |
| None |
| ); |
| Ok(()) |
| } |