| // 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. |
| |
| use net_declare::{fidl_ip, fidl_mac, fidl_subnet}; |
| |
| struct IpLayerConfig { |
| alice_subnet: fidl_fuchsia_net::Subnet, |
| bob_ip: fidl_fuchsia_net::IpAddress, |
| } |
| |
| struct EthernetLayerConfig { |
| alice_mac: fidl_fuchsia_net::MacAddress, |
| bob_mac: fidl_fuchsia_net::MacAddress, |
| ip_layer: IpLayerConfig, |
| } |
| |
| /// The network configuration used for TAP-like example. |
| /// |
| /// Note that the tests in this crate run in parallel against the same Netstack, |
| /// so the IP addresses between tests must be different to avoid interference. |
| const CONFIG_FOR_TAP_LIKE: EthernetLayerConfig = EthernetLayerConfig { |
| alice_mac: fidl_mac!("02:03:04:05:06:07"), |
| bob_mac: fidl_mac!("02:AA:BB:CC:DD:EE"), |
| ip_layer: IpLayerConfig { |
| alice_subnet: fidl_subnet!("192.168.0.1/24"), |
| bob_ip: fidl_ip!("192.168.0.2"), |
| }, |
| }; |
| |
| /// The network configuration used for TUN-like example. |
| /// |
| /// Note that the tests in this crate run in parallel against the same Netstack, |
| /// so the IP addresses between tests must be different to avoid interference. |
| const CONFIG_FOR_TUN_LIKE: IpLayerConfig = IpLayerConfig { |
| alice_subnet: fidl_subnet!("192.168.86.1/24"), |
| bob_ip: fidl_ip!("192.168.86.2"), |
| }; |
| |
| /// The port identifier used in examples. |
| const PORT_ID: u8 = 0; |
| |
| /// Creates a new [`fidl_fuchsia_net_tun::DeviceConfig`] and |
| /// [`fidl_fuchsia_net_tun::DevicePortConfig`] using the provided `frame_type` and `mac`. |
| fn new_device_and_port_config( |
| frame_types: Vec<fidl_fuchsia_hardware_network::FrameType>, |
| mac: Option<fidl_fuchsia_net::MacAddress>, |
| ) -> (fidl_fuchsia_net_tun::DeviceConfig, fidl_fuchsia_net_tun::DevicePortConfig) { |
| let rx_types = frame_types; |
| let tx_types = rx_types |
| .iter() |
| .map(|frame_type| fidl_fuchsia_hardware_network::FrameTypeSupport { |
| type_: *frame_type, |
| features: 0, |
| supported_flags: fidl_fuchsia_hardware_network::TxFlags::empty(), |
| }) |
| .collect(); |
| let device_config = fidl_fuchsia_net_tun::DeviceConfig { |
| base: Some(fidl_fuchsia_net_tun::BaseDeviceConfig { |
| // Discard frame metadata. It is reported through the `read_frame` |
| // method. |
| report_metadata: Some(false), |
| min_tx_buffer_length: None, |
| ..fidl_fuchsia_net_tun::BaseDeviceConfig::EMPTY |
| }), |
| // Blocking will cause the read and write frame methods to block. See |
| // the documentation on fuchsia.net.tun/DeviceConfig for more details. |
| blocking: Some(true), |
| |
| ..fidl_fuchsia_net_tun::DeviceConfig::EMPTY |
| }; |
| let port_config = fidl_fuchsia_net_tun::DevicePortConfig { |
| base: Some(fidl_fuchsia_net_tun::BasePortConfig { |
| // The port identifier that we'll use to refer to this port. |
| id: Some(PORT_ID), |
| // Device MTU reported to Netstack. |
| mtu: Some(1500), |
| // The frame types we're going to accept. TAP and TUN examples will |
| // request different frame types. |
| rx_types: Some(rx_types), |
| tx_types: Some(tx_types), |
| ..fidl_fuchsia_net_tun::BasePortConfig::EMPTY |
| }), |
| // Create the port with the link online signal. |
| online: Some(true), |
| // Use the MAC requested by the caller. TAP-like devices require a MAC |
| // address, while TUN-like devices don't. |
| mac, |
| ..fidl_fuchsia_net_tun::DevicePortConfig::EMPTY |
| }; |
| (device_config, port_config) |
| } |
| |
| /// An example of configuring and operating a TAP-like interface using |
| /// fuchsia.net.tun. |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn tap_like_over_network_tun() { |
| // Connect to the ambient fuchsia.net.tun/Control service. |
| let tun = |
| fuchsia_component::client::connect_to_protocol::<fidl_fuchsia_net_tun::ControlMarker>() |
| .expect("failed to connect to service"); |
| |
| // Define the tun device configuration we want to use. |
| // For a TAP-like device, the frame type must be Ethernet, and we must |
| // provide a MAC address for the virtual interface. |
| let (device_config, port_config) = new_device_and_port_config( |
| vec![fidl_fuchsia_hardware_network::FrameType::Ethernet], |
| Some(CONFIG_FOR_TAP_LIKE.alice_mac), |
| ); |
| |
| // Request device creation. |
| let (tun_device, server_end) = |
| fidl::endpoints::create_proxy::<fidl_fuchsia_net_tun::DeviceMarker>() |
| .expect("failed to create device endpoints"); |
| let () = tun.create_device(device_config, server_end).expect("failed to create device"); |
| // Add a port. |
| let (tun_port, server_end) = |
| fidl::endpoints::create_proxy::<fidl_fuchsia_net_tun::PortMarker>() |
| .expect("failed to create port endpoints"); |
| let () = tun_device.add_port(port_config, server_end).expect("failed to add port"); |
| |
| // Install the interface in the stack. |
| // |
| // _device_control must be kept alive because it encodes the device |
| // lifetime. |
| let (_device_control, control, interface_id) = |
| helpers::create_interface(&tun_device, &tun_port).await; |
| |
| // Add an IPv4 address. |
| let () = helpers::add_interface_address( |
| &control, |
| interface_id, |
| CONFIG_FOR_TAP_LIKE.ip_layer.alice_subnet.clone(), |
| ) |
| .await; |
| |
| // Wait for Netstack to report the interface online. Netstack may drop |
| // frames if they're sent before the online signal is observed. This is |
| // necessary to prevent this test from flaking since we're only going to |
| // send a single echo request. |
| let () = helpers::wait_interface_online(interface_id).await; |
| |
| // Now we can send frames. In this example we'll send an ICMP echo request |
| // and wait for the Netstack to respond. |
| let () = tun_device |
| .write_frame(fidl_fuchsia_net_tun::Frame { |
| port: Some(PORT_ID), |
| frame_type: Some(fidl_fuchsia_hardware_network::FrameType::Ethernet), |
| data: Some(helpers::bob_pings_alice_for_tap_like(&CONFIG_FOR_TAP_LIKE)), |
| meta: None, |
| ..fidl_fuchsia_net_tun::Frame::EMPTY |
| }) |
| .await |
| .expect("write_frame FIDL error") |
| .map_err(fuchsia_zircon::Status::from_raw) |
| .expect("write_frame failed"); |
| |
| // Read frames until we see the echo response. |
| loop { |
| let fidl_fuchsia_net_tun::Frame { |
| // Port over which the frame was sent. |
| port, |
| // The frame's type will always be Ethernet in this example. If multiple |
| // frame types are supported (like IPv4 and IPv6, for example) that |
| // information will be here. |
| frame_type, |
| // Frame payload. |
| data, |
| // Metadata associated with the frame. |
| meta: _, |
| .. |
| } = tun_device |
| .read_frame() |
| .await |
| .expect("read_frame FIDL error") |
| .map_err(fuchsia_zircon::Status::from_raw) |
| .expect("read_frame failed"); |
| let data = data.expect("received Frame with missing data field"); |
| assert_eq!(port, Some(PORT_ID)); |
| assert_eq!(frame_type, Some(fidl_fuchsia_hardware_network::FrameType::Ethernet)); |
| match helpers::get_icmp_response_for_tap_like(&data[..]) { |
| None => (), |
| Some(helpers::ParsedFrame::ArpRequest { target_ip, sender_mac, sender_ip }) => { |
| // When we get an ARP request for bob's IP, we need to send an |
| // ARP response back in order for the ICMP echo response to come |
| // in. |
| if target_ip == CONFIG_FOR_TAP_LIKE.ip_layer.bob_ip { |
| assert_eq!(sender_mac, CONFIG_FOR_TAP_LIKE.alice_mac); |
| assert_eq!(sender_ip, CONFIG_FOR_TAP_LIKE.ip_layer.alice_subnet.addr); |
| let () = tun_device |
| .write_frame(fidl_fuchsia_net_tun::Frame { |
| port: Some(PORT_ID), |
| frame_type: Some(fidl_fuchsia_hardware_network::FrameType::Ethernet), |
| data: Some(helpers::build_bob_arp_response(&CONFIG_FOR_TAP_LIKE)), |
| meta: None, |
| ..fidl_fuchsia_net_tun::Frame::EMPTY |
| }) |
| .await |
| .expect("write_frame FIDL error") |
| .map_err(fuchsia_zircon::Status::from_raw) |
| .expect("write_frame failed"); |
| } |
| } |
| Some(helpers::ParsedFrame::IcmpEchoResponse { src_mac, dst_mac, src_ip, dst_ip }) => { |
| assert_eq!(src_mac, CONFIG_FOR_TAP_LIKE.alice_mac); |
| assert_eq!(dst_mac, CONFIG_FOR_TAP_LIKE.bob_mac); |
| assert_eq!(src_ip, CONFIG_FOR_TAP_LIKE.ip_layer.alice_subnet.addr); |
| assert_eq!(dst_ip, CONFIG_FOR_TAP_LIKE.ip_layer.bob_ip); |
| break; |
| } |
| } |
| } |
| } |
| |
| /// An example of configuring and operating a TUN-like interface using |
| /// fuchsia.net.tun. |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn tun_like_over_network_tun() { |
| // Connect to the ambient fuchsia.net.tun/Control service. |
| let tun = |
| fuchsia_component::client::connect_to_protocol::<fidl_fuchsia_net_tun::ControlMarker>() |
| .expect("failed to connect to service"); |
| |
| // Define the tun device configuration we want to use. For a TUN-like |
| // device, the frame types must be IPv4 and IPv6, and we don't provide a MAC |
| // address. |
| let (device_config, port_config) = new_device_and_port_config( |
| vec![ |
| fidl_fuchsia_hardware_network::FrameType::Ipv4, |
| fidl_fuchsia_hardware_network::FrameType::Ipv6, |
| ], |
| None, |
| ); |
| |
| // Request device creation. |
| let (tun_device, server_end) = |
| fidl::endpoints::create_proxy::<fidl_fuchsia_net_tun::DeviceMarker>() |
| .expect("failed to create device endpoints"); |
| let () = tun.create_device(device_config, server_end).expect("failed to create device"); |
| // Add a port. |
| let (tun_port, server_end) = |
| fidl::endpoints::create_proxy::<fidl_fuchsia_net_tun::PortMarker>() |
| .expect("failed to create port endpoints"); |
| let () = tun_device.add_port(port_config, server_end).expect("failed to add port"); |
| |
| // Install the interface in the stack. |
| // |
| // _device_control must be kept alive because it encodes the device |
| // lifetime. |
| let (_device_control, control, interface_id) = |
| helpers::create_interface(&tun_device, &tun_port).await; |
| |
| // Add an IPv4 address. |
| let () = helpers::add_interface_address( |
| &control, |
| interface_id, |
| CONFIG_FOR_TUN_LIKE.alice_subnet.clone(), |
| ) |
| .await; |
| |
| // Wait for Netstack to report the interface online. Netstack may drop |
| // frames if they're sent before the online signal is observed. This is |
| // necessary to prevent this test from flaking since we're only going to |
| // send a single echo request. |
| let () = helpers::wait_interface_online(interface_id).await; |
| |
| // Now we can send frames. In this example we'll send an ICMP echo request |
| // and wait for the Netstack to respond. |
| let () = tun_device |
| .write_frame(fidl_fuchsia_net_tun::Frame { |
| port: Some(PORT_ID), |
| frame_type: Some(fidl_fuchsia_hardware_network::FrameType::Ipv4), |
| data: Some(helpers::bob_pings_alice_for_tun_like(&CONFIG_FOR_TUN_LIKE)), |
| meta: None, |
| ..fidl_fuchsia_net_tun::Frame::EMPTY |
| }) |
| .await |
| .expect("write_frame FIDL error") |
| .map_err(fuchsia_zircon::Status::from_raw) |
| .expect("write_frame failed"); |
| |
| // Read frames until we see the echo response. |
| loop { |
| let fidl_fuchsia_net_tun::Frame { |
| // Port over which the frame was sent. |
| port, |
| // A TUN-like device can receive IPv4 or IPv6 frames, in this |
| // example we're going to be looking only into IPv4 packets. |
| frame_type, |
| // Frame payload. |
| data, |
| // Metadata associated with the frame. |
| meta: _, |
| .. |
| } = tun_device |
| .read_frame() |
| .await |
| .expect("read_frame FIDL error") |
| .map_err(fuchsia_zircon::Status::from_raw) |
| .expect("read_frame failed"); |
| assert_eq!(port, Some(PORT_ID)); |
| match frame_type { |
| Some(fidl_fuchsia_hardware_network::FrameType::Ipv4) => (), |
| Some(fidl_fuchsia_hardware_network::FrameType::Ipv6) => continue, |
| unexpected => { |
| panic!("received unexpected frame_type: {:?}", unexpected); |
| } |
| } |
| let data = data.expect("received Frame with missing data field"); |
| |
| if let Some((src_ip, dst_ip)) = helpers::get_icmp_response_for_tun_like(&data[..]) { |
| assert_eq!(src_ip, CONFIG_FOR_TUN_LIKE.alice_subnet.addr); |
| assert_eq!(dst_ip, CONFIG_FOR_TUN_LIKE.bob_ip); |
| break; |
| } |
| } |
| } |
| |
| /// This module contains some helpers to remove noise from the examples in this |
| /// crate. |
| mod helpers { |
| use fidl::endpoints::Proxy as _; |
| use net_types::{ |
| ethernet::Mac, |
| ip::{Ipv4, Ipv4Addr}, |
| }; |
| use packet::{InnerPacketBuilder as _, ParsablePacket as _, Serializer as _}; |
| use packet_formats::{ |
| arp::{ArpOp, ArpPacket, ArpPacketBuilder}, |
| ethernet::{EtherType, EthernetFrame, EthernetFrameBuilder, EthernetFrameLengthCheck}, |
| icmp::{IcmpEchoRequest, IcmpPacketBuilder, IcmpParseArgs, IcmpUnusedCode, Icmpv4Packet}, |
| ip::Ipv4Proto, |
| ipv4::{Ipv4Header as _, Ipv4Packet, Ipv4PacketBuilder}, |
| }; |
| |
| fn ip_v4(fidl_ip: fidl_fuchsia_net::IpAddress) -> Ipv4Addr { |
| match fidl_ip { |
| fidl_fuchsia_net::IpAddress::Ipv4(ip) => Ipv4Addr::new(ip.addr), |
| fidl_fuchsia_net::IpAddress::Ipv6(v6) => { |
| panic!("can't convert from FIDL IPv6 {:?}", v6) |
| } |
| } |
| } |
| |
| const fn mac(fidl_mac: fidl_fuchsia_net::MacAddress) -> Mac { |
| Mac::new(fidl_mac.octets) |
| } |
| |
| fn fidl_mac(mac: Mac) -> fidl_fuchsia_net::MacAddress { |
| fidl_fuchsia_net::MacAddress { octets: mac.bytes() } |
| } |
| |
| fn fidl_ip(ip_v4: Ipv4Addr) -> fidl_fuchsia_net::IpAddress { |
| fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address { |
| addr: ip_v4.ipv4_bytes(), |
| }) |
| } |
| |
| const ECHO_PAYLOAD: [u8; 4] = [1, 2, 3, 4]; |
| const ICMP_ID: u16 = 1; |
| const ICMP_SEQNUM: u16 = 1; |
| |
| /// Creates the ICMP bytes for bob pinging alice to use in TAP-like |
| /// examples. |
| pub(super) fn bob_pings_alice_for_tap_like(config: &super::EthernetLayerConfig) -> Vec<u8> { |
| packet::Buf::new(&mut bob_pings_alice_for_tun_like(&config.ip_layer)[..], ..) |
| .encapsulate(EthernetFrameBuilder::new( |
| mac(config.bob_mac), |
| mac(config.alice_mac), |
| EtherType::Ipv4, |
| )) |
| .serialize_vec_outer() |
| .expect("serialization failed") |
| .as_ref() |
| .to_vec() |
| } |
| |
| /// Creates the ICMP bytes for bob pinging alice to use in TUN-like |
| /// examples. |
| pub(super) fn bob_pings_alice_for_tun_like(config: &super::IpLayerConfig) -> Vec<u8> { |
| let alice_ip = ip_v4(config.alice_subnet.addr); |
| let bob_ip = ip_v4(config.bob_ip); |
| packet::Buf::new(&mut ECHO_PAYLOAD.to_vec()[..], ..) |
| .encapsulate(IcmpPacketBuilder::<Ipv4, &[u8], _>::new( |
| bob_ip, |
| alice_ip, |
| IcmpUnusedCode, |
| IcmpEchoRequest::new(ICMP_ID, ICMP_SEQNUM), |
| )) |
| .encapsulate(Ipv4PacketBuilder::new(bob_ip, alice_ip, 1, Ipv4Proto::Icmp)) |
| .serialize_vec_outer() |
| .expect("serialization failed") |
| .as_ref() |
| .to_vec() |
| } |
| |
| /// A parsed frame for a tap-like interface. |
| pub(super) enum ParsedFrame { |
| /// An ARP request was observed, an ARP response should be sent. |
| ArpRequest { |
| target_ip: fidl_fuchsia_net::IpAddress, |
| sender_mac: fidl_fuchsia_net::MacAddress, |
| sender_ip: fidl_fuchsia_net::IpAddress, |
| }, |
| /// An ICMP echo response was observed. |
| IcmpEchoResponse { |
| src_mac: fidl_fuchsia_net::MacAddress, |
| dst_mac: fidl_fuchsia_net::MacAddress, |
| src_ip: fidl_fuchsia_net::IpAddress, |
| dst_ip: fidl_fuchsia_net::IpAddress, |
| }, |
| } |
| |
| /// Attempts to parse an ICMP echo response in an Ethernet frame contained |
| /// in `data`. |
| /// |
| /// Returns `None` if the frame is valid but it's not an ICMP response. |
| /// |
| /// Otherwise, returns either a [`ParsedFrame::IcmpEchoResponse`] with the |
| /// echo response or a [`ParsedFrame::ArpRequest`] indicating that an ARP |
| /// response must be sent for LL resolution. |
| /// |
| /// # Panics |
| /// |
| /// Panics if the frame can't be parsed. |
| pub(super) fn get_icmp_response_for_tap_like(data: &[u8]) -> Option<ParsedFrame> { |
| let mut bv = data; |
| let ethernet = EthernetFrame::parse(&mut bv, EthernetFrameLengthCheck::NoCheck) |
| .expect("failed to parse Ethernet frame"); |
| let src_mac = fidl_mac(ethernet.src_mac()); |
| let dst_mac = fidl_mac(ethernet.dst_mac()); |
| match ethernet.ethertype() { |
| Some(EtherType::Ipv4) => get_icmp_response_for_tun_like(bv).map(|(src_ip, dst_ip)| { |
| ParsedFrame::IcmpEchoResponse { src_mac, dst_mac, src_ip, dst_ip } |
| }), |
| Some(EtherType::Arp) => { |
| let arp = ArpPacket::<_, Mac, Ipv4Addr>::parse(&mut bv, ()) |
| .expect("failed to parse ARP packet"); |
| match arp.operation() { |
| ArpOp::Request => Some(ParsedFrame::ArpRequest { |
| target_ip: fidl_ip(arp.target_protocol_address()), |
| sender_mac: fidl_mac(arp.sender_hardware_address()), |
| sender_ip: fidl_ip(arp.sender_protocol_address()), |
| }), |
| ArpOp::Response | ArpOp::Other(_) => None, |
| } |
| } |
| _ => None, |
| } |
| } |
| |
| /// Attempts to parse an ICMP echo response in an Ethernet frame contained |
| /// in `data`. |
| /// |
| /// Returns `None` if the frame is valid but it's not an ICMP response. |
| /// |
| /// Otherwise, returns the `(src_ip, dst_ip)` addressing tuple. |
| /// |
| /// Note that this function is provided as a support to the examples in this |
| /// crate and it makes assumptions that may not be valid in a production |
| /// environment, such as assuming that no IP fragmentation happens, and the |
| /// ICMP echo responses are not validated. |
| /// |
| /// # Panics |
| /// |
| /// Panics if the frame can't be parsed. |
| pub(super) fn get_icmp_response_for_tun_like( |
| data: &[u8], |
| ) -> Option<(fidl_fuchsia_net::IpAddress, fidl_fuchsia_net::IpAddress)> { |
| let mut bv = data; |
| let ipv4 = Ipv4Packet::parse(&mut bv, ()).expect("failed to parse IPv4"); |
| if ipv4.proto() != Ipv4Proto::Icmp { |
| return None; |
| } |
| let src_ip = ipv4.src_ip(); |
| let dst_ip = ipv4.dst_ip(); |
| let icmp = Icmpv4Packet::parse(&mut bv, IcmpParseArgs::new(src_ip, dst_ip)) |
| .expect("failed to parse ICMP"); |
| if let Icmpv4Packet::EchoReply(_echo) = icmp { |
| Some((fidl_ip(src_ip), fidl_ip(dst_ip))) |
| } else { |
| None |
| } |
| } |
| |
| /// Creates an ARP response from bob to alice for use in tap-like tests. |
| pub(super) fn build_bob_arp_response(config: &super::EthernetLayerConfig) -> Vec<u8> { |
| ArpPacketBuilder::new( |
| ArpOp::Response, |
| mac(config.bob_mac), |
| ip_v4(config.ip_layer.bob_ip), |
| mac(config.alice_mac), |
| ip_v4(config.ip_layer.alice_subnet.addr), |
| ) |
| .into_serializer() |
| .encapsulate(EthernetFrameBuilder::new( |
| mac(config.bob_mac), |
| mac(config.alice_mac), |
| EtherType::Arp, |
| )) |
| .serialize_vec_outer() |
| .expect("serialization failed") |
| .as_ref() |
| .to_vec() |
| } |
| |
| /// Waits for interface with ID `interface_id` to come online. |
| pub(super) async fn wait_interface_online(interface_id: u64) { |
| let interface_state = fuchsia_component::client::connect_to_protocol::< |
| fidl_fuchsia_net_interfaces::StateMarker, |
| >() |
| .expect("failed to connect to interfaces state service"); |
| fidl_fuchsia_net_interfaces_ext::wait_interface_with_id( |
| fidl_fuchsia_net_interfaces_ext::event_stream_from_state(&interface_state) |
| .expect("failed to create event stream"), |
| &mut fidl_fuchsia_net_interfaces_ext::InterfaceState::Unknown(interface_id), |
| |&fidl_fuchsia_net_interfaces_ext::Properties { |
| id: _, |
| addresses: _, |
| online, |
| device_class: _, |
| has_default_ipv4_route: _, |
| has_default_ipv6_route: _, |
| name: _, |
| }| { |
| if online { |
| Some(()) |
| } else { |
| None |
| } |
| }, |
| ) |
| .await |
| .expect("failed to wait for interface online") |
| } |
| |
| /// Installs the provided port on the stack. |
| pub(super) async fn create_interface( |
| tun_device: &fidl_fuchsia_net_tun::DeviceProxy, |
| tun_port: &fidl_fuchsia_net_tun::PortProxy, |
| ) -> ( |
| fidl_fuchsia_net_interfaces_admin::DeviceControlProxy, |
| fidl_fuchsia_net_interfaces_ext::admin::Control, |
| u64, |
| ) { |
| // Get the information Installer needs to install the device. |
| let (network_device, mut port_id) = { |
| let (network_device, netdevice_server_end) = |
| fidl::endpoints::create_proxy::<fidl_fuchsia_hardware_network::DeviceMarker>() |
| .expect("failed to create netdevice proxy"); |
| let (port, port_server_end) = |
| fidl::endpoints::create_proxy::<fidl_fuchsia_hardware_network::PortMarker>() |
| .expect("failed to create port proxy"); |
| |
| let () = tun_device.get_device(netdevice_server_end).expect("get_device failed"); |
| let () = tun_port.get_port(port_server_end).expect("get_port failed"); |
| let port_id = port.get_info().await.expect("get_info failed").id.expect("missing id"); |
| let network_device: fidl::endpoints::ClientEnd<_> = network_device |
| .into_channel() |
| .expect("failed to get inner channel") |
| .into_zx_channel() |
| .into(); |
| (network_device, port_id) |
| }; |
| |
| // Connect to Installer and install the device. |
| let installer = fuchsia_component::client::connect_to_protocol::< |
| fidl_fuchsia_net_interfaces_admin::InstallerMarker, |
| >() |
| .expect("failed to connect to installer"); |
| let device_control = { |
| let (control, server_end) = fidl::endpoints::create_proxy::< |
| fidl_fuchsia_net_interfaces_admin::DeviceControlMarker, |
| >() |
| .expect("create proxy"); |
| let () = installer |
| .install_device(network_device, server_end) |
| .expect("install_device failed"); |
| control |
| }; |
| let control = { |
| let (control, server_end) = |
| fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints() |
| .expect("create endpoints"); |
| let () = device_control |
| .create_interface( |
| &mut port_id, |
| server_end, |
| fidl_fuchsia_net_interfaces_admin::Options::EMPTY, |
| ) |
| .expect("create_interface failed"); |
| control |
| }; |
| |
| // Enable the interface. |
| let did_enable = control.enable().await.expect("enable FIDL error").expect("enable failed"); |
| assert!(did_enable); |
| |
| // Retrieve the interface ID. |
| let interface_id = control.get_id().await.expect("get_id failed"); |
| (device_control, control, interface_id) |
| } |
| |
| /// Adds an address to an installed interface. |
| pub(super) async fn add_interface_address( |
| control: &fidl_fuchsia_net_interfaces_ext::admin::Control, |
| interface_id: u64, |
| subnet: fidl_fuchsia_net::Subnet, |
| ) { |
| let (address_state_provider, server_end) = fidl::endpoints::create_proxy::< |
| fidl_fuchsia_net_interfaces_admin::AddressStateProviderMarker, |
| >() |
| .expect("create proxy"); |
| // AddressStateProvider allows us to tie the lifetime of the address to |
| // this proxy, opt out by detaching. |
| let () = address_state_provider.detach().expect("detach failed"); |
| |
| let () = control |
| .add_address( |
| &mut subnet.clone(), |
| fidl_fuchsia_net_interfaces_admin::AddressParameters::EMPTY, |
| server_end, |
| ) |
| .expect("add_address failed"); |
| // Wait for the address to be assigned. |
| let () = fidl_fuchsia_net_interfaces_ext::admin::wait_assignment_state( |
| &mut fidl_fuchsia_net_interfaces_ext::admin::assignment_state_stream( |
| address_state_provider, |
| ), |
| fidl_fuchsia_net_interfaces_admin::AddressAssignmentState::Assigned, |
| ) |
| .await |
| .expect("failed to observe assigned address"); |
| |
| // NB: Adding an address does not add the subnet route, we still have to |
| // add that so we can exchange frames. |
| let stack = |
| fuchsia_component::client::connect_to_protocol::<fidl_fuchsia_net_stack::StackMarker>() |
| .expect("failed to connect to stack"); |
| let () = stack |
| .add_forwarding_entry(&mut fidl_fuchsia_net_stack::ForwardingEntry { |
| subnet: fidl_fuchsia_net_ext::apply_subnet_mask(subnet), |
| device_id: interface_id, |
| next_hop: None, |
| metric: 0, |
| }) |
| .await |
| .expect("add_forwarding_entry FIDL error") |
| .expect("add_forwarding_entry failed"); |
| } |
| } |