From 90617ff606b6d4c89d1a3f166dcee6a2b7d5461a Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 27 Feb 2024 10:14:41 +0100 Subject: [PATCH] Add possibility to binding on the interface (#755) * Add test binding * Add binding to the interface * Add UDP support, fix windows/macos build * Add tests, fix windows build * Fix windows/macos builds * Move platfrom-related code to the zenoh-util * Remove unused dependencies * Add interface binding on connection * String usage refactoring --- commons/zenoh-util/src/std_only/net/mod.rs | 76 ++++++++- io/zenoh-link-commons/src/lib.rs | 3 + io/zenoh-link-commons/src/listener.rs | 8 +- io/zenoh-links/zenoh-link-tcp/src/unicast.rs | 21 ++- io/zenoh-links/zenoh-link-udp/src/unicast.rs | 21 ++- io/zenoh-links/zenoh-link-ws/src/unicast.rs | 4 +- io/zenoh-transport/tests/unicast_openclose.rs | 145 ++++++++++++++++-- 7 files changed, 246 insertions(+), 32 deletions(-) diff --git a/commons/zenoh-util/src/std_only/net/mod.rs b/commons/zenoh-util/src/std_only/net/mod.rs index 67e732d3ee..8658e24bbc 100644 --- a/commons/zenoh-util/src/std_only/net/mod.rs +++ b/commons/zenoh-util/src/std_only/net/mod.rs @@ -11,7 +11,7 @@ // Contributors: // ZettaScale Zenoh Team, // -use async_std::net::TcpStream; +use async_std::net::{TcpListener, TcpStream, UdpSocket}; use std::net::{IpAddr, Ipv6Addr}; use std::time::Duration; use zenoh_core::zconfigurable; @@ -210,12 +210,19 @@ pub fn get_multicast_interfaces() -> Vec { } } -pub fn get_local_addresses() -> ZResult> { +pub fn get_local_addresses(interface: Option<&str>) -> ZResult> { #[cfg(unix)] { Ok(pnet_datalink::interfaces() .into_iter() - .filter(|iface| iface.is_up() && iface.is_running()) + .filter(|iface| { + if let Some(interface) = interface.as_ref() { + if iface.name != *interface { + return false; + } + } + iface.is_up() && iface.is_running() + }) .flat_map(|iface| iface.ips) .map(|ipnet| ipnet.ip()) .collect()) @@ -232,6 +239,11 @@ pub fn get_local_addresses() -> ZResult> { let mut result = vec![]; let mut next_iface = (buffer.as_ptr() as *mut IP_ADAPTER_ADDRESSES_LH).as_ref(); while let Some(iface) = next_iface { + if let Some(interface) = interface.as_ref() { + if ffi::pstr_to_string(iface.AdapterName) != *interface { + continue; + } + } let mut next_ucast_addr = iface.FirstUnicastAddress.as_ref(); while let Some(ucast_addr) = next_ucast_addr { if let Ok(ifaddr) = ffi::win::sockaddr_to_addr(ucast_addr.Address) { @@ -412,8 +424,8 @@ pub fn get_interface_names_by_addr(addr: IpAddr) -> ZResult> { } } -pub fn get_ipv4_ipaddrs() -> Vec { - get_local_addresses() +pub fn get_ipv4_ipaddrs(interface: Option<&str>) -> Vec { + get_local_addresses(interface) .unwrap_or_else(|_| vec![]) .drain(..) .filter_map(|x| match x { @@ -425,12 +437,12 @@ pub fn get_ipv4_ipaddrs() -> Vec { .collect() } -pub fn get_ipv6_ipaddrs() -> Vec { +pub fn get_ipv6_ipaddrs(interface: Option<&str>) -> Vec { const fn is_unicast_link_local(addr: &Ipv6Addr) -> bool { (addr.segments()[0] & 0xffc0) == 0xfe80 } - let ipaddrs = get_local_addresses().unwrap_or_else(|_| vec![]); + let ipaddrs = get_local_addresses(interface).unwrap_or_else(|_| vec![]); // Get first all IPv4 addresses let ipv4_iter = ipaddrs @@ -479,3 +491,53 @@ pub fn get_ipv6_ipaddrs() -> Vec { .chain(priv_ipv4_addrs) .collect() } + +#[cfg(target_os = "linux")] +fn set_bind_to_device(socket: std::os::raw::c_int, iface: Option<&str>) { + if let Some(iface) = iface { + // @TODO: switch to bind_device after tokio porting + log::debug!("Listen at the interface: {}", iface); + unsafe { + libc::setsockopt( + socket, + libc::SOL_SOCKET, + libc::SO_BINDTODEVICE, + iface.as_ptr() as *const std::os::raw::c_void, + iface.len() as libc::socklen_t, + ); + } + } +} + +#[cfg(target_os = "linux")] +pub fn set_bind_to_device_tcp_listener(socket: &TcpListener, iface: Option<&str>) { + use std::os::fd::AsRawFd; + set_bind_to_device(socket.as_raw_fd(), iface); +} + +#[cfg(target_os = "linux")] +pub fn set_bind_to_device_tcp_stream(socket: &TcpStream, iface: Option<&str>) { + use std::os::fd::AsRawFd; + set_bind_to_device(socket.as_raw_fd(), iface); +} + +#[cfg(target_os = "linux")] +pub fn set_bind_to_device_udp_socket(socket: &UdpSocket, iface: Option<&str>) { + use std::os::fd::AsRawFd; + set_bind_to_device(socket.as_raw_fd(), iface); +} + +#[cfg(any(target_os = "macos", target_os = "windows"))] +pub fn set_bind_to_device_tcp_listener(_socket: &TcpListener, _iface: Option<&str>) { + log::warn!("Listen at the interface is not supported for this platform"); +} + +#[cfg(any(target_os = "macos", target_os = "windows"))] +pub fn set_bind_to_device_tcp_stream(_socket: &TcpStream, _iface: Option<&str>) { + log::warn!("Listen at the interface is not supported for this platform"); +} + +#[cfg(any(target_os = "macos", target_os = "windows"))] +pub fn set_bind_to_device_udp_socket(_socket: &UdpSocket, _iface: Option<&str>) { + log::warn!("Listen at the interface is not supported for this platform"); +} diff --git a/io/zenoh-link-commons/src/lib.rs b/io/zenoh-link-commons/src/lib.rs index 2ee28c3f08..0a43aac3d9 100644 --- a/io/zenoh-link-commons/src/lib.rs +++ b/io/zenoh-link-commons/src/lib.rs @@ -36,6 +36,9 @@ use zenoh_result::ZResult; /*************************************/ /* GENERAL */ /*************************************/ + +pub const BIND_INTERFACE: &str = "iface"; + #[derive(Clone, Debug, Serialize, Hash, PartialEq, Eq)] pub struct Link { pub src: Locator, diff --git a/io/zenoh-link-commons/src/listener.rs b/io/zenoh-link-commons/src/listener.rs index 1d5d7bb172..7cf294de8a 100644 --- a/io/zenoh-link-commons/src/listener.rs +++ b/io/zenoh-link-commons/src/listener.rs @@ -23,6 +23,8 @@ use zenoh_protocol::core::{EndPoint, Locator}; use zenoh_result::{zerror, ZResult}; use zenoh_sync::Signal; +use crate::BIND_INTERFACE; + pub struct ListenerUnicastIP { endpoint: EndPoint, active: Arc, @@ -109,12 +111,14 @@ impl ListenersUnicastIP { let guard = zread!(self.listeners); for (key, value) in guard.iter() { let (kip, kpt) = (key.ip(), key.port()); + let config = value.endpoint.config(); + let iface = config.get(BIND_INTERFACE); // Either ipv4/0.0.0.0 or ipv6/[::] if kip.is_unspecified() { let mut addrs = match kip { - IpAddr::V4(_) => zenoh_util::net::get_ipv4_ipaddrs(), - IpAddr::V6(_) => zenoh_util::net::get_ipv6_ipaddrs(), + IpAddr::V4(_) => zenoh_util::net::get_ipv4_ipaddrs(iface), + IpAddr::V6(_) => zenoh_util::net::get_ipv6_ipaddrs(iface), }; let iter = addrs.drain(..).map(|x| { Locator::new( diff --git a/io/zenoh-links/zenoh-link-tcp/src/unicast.rs b/io/zenoh-links/zenoh-link-tcp/src/unicast.rs index 551f4c8c97..b01d8be22e 100644 --- a/io/zenoh-links/zenoh-link-tcp/src/unicast.rs +++ b/io/zenoh-links/zenoh-link-tcp/src/unicast.rs @@ -23,7 +23,7 @@ use std::sync::Arc; use std::time::Duration; use zenoh_link_commons::{ get_ip_interface_names, LinkManagerUnicastTrait, LinkUnicast, LinkUnicastTrait, - ListenersUnicastIP, NewLinkChannelSender, + ListenersUnicastIP, NewLinkChannelSender, BIND_INTERFACE, }; use zenoh_protocol::core::{EndPoint, Locator}; use zenoh_result::{bail, zerror, Error as ZError, ZResult}; @@ -199,6 +199,7 @@ impl LinkManagerUnicastTcp { async fn new_link_inner( &self, dst_addr: &SocketAddr, + iface: Option<&str>, ) -> ZResult<(TcpStream, SocketAddr, SocketAddr)> { let stream = TcpStream::connect(dst_addr) .await @@ -212,15 +213,23 @@ impl LinkManagerUnicastTcp { .peer_addr() .map_err(|e| zerror!("{}: {}", dst_addr, e))?; + zenoh_util::net::set_bind_to_device_tcp_stream(&stream, iface); + Ok((stream, src_addr, dst_addr)) } - async fn new_listener_inner(&self, addr: &SocketAddr) -> ZResult<(TcpListener, SocketAddr)> { + async fn new_listener_inner( + &self, + addr: &SocketAddr, + iface: Option<&str>, + ) -> ZResult<(TcpListener, SocketAddr)> { // Bind the TCP socket let socket = TcpListener::bind(addr) .await .map_err(|e| zerror!("{}: {}", addr, e))?; + zenoh_util::net::set_bind_to_device_tcp_listener(&socket, iface); + let local_addr = socket .local_addr() .map_err(|e| zerror!("{}: {}", addr, e))?; @@ -233,10 +242,12 @@ impl LinkManagerUnicastTcp { impl LinkManagerUnicastTrait for LinkManagerUnicastTcp { async fn new_link(&self, endpoint: EndPoint) -> ZResult { let dst_addrs = get_tcp_addrs(endpoint.address()).await?; + let config = endpoint.config(); + let iface = config.get(BIND_INTERFACE); let mut errs: Vec = vec![]; for da in dst_addrs { - match self.new_link_inner(&da).await { + match self.new_link_inner(&da, iface).await { Ok((stream, src_addr, dst_addr)) => { let link = Arc::new(LinkUnicastTcp::new(stream, src_addr, dst_addr)); return Ok(LinkUnicast(link)); @@ -260,10 +271,12 @@ impl LinkManagerUnicastTrait for LinkManagerUnicastTcp { async fn new_listener(&self, mut endpoint: EndPoint) -> ZResult { let addrs = get_tcp_addrs(endpoint.address()).await?; + let config = endpoint.config(); + let iface = config.get(BIND_INTERFACE); let mut errs: Vec = vec![]; for da in addrs { - match self.new_listener_inner(&da).await { + match self.new_listener_inner(&da, iface).await { Ok((socket, local_addr)) => { // Update the endpoint locator address endpoint = EndPoint::new( diff --git a/io/zenoh-links/zenoh-link-udp/src/unicast.rs b/io/zenoh-links/zenoh-link-udp/src/unicast.rs index a5bd3c7726..d5214510be 100644 --- a/io/zenoh-links/zenoh-link-udp/src/unicast.rs +++ b/io/zenoh-links/zenoh-link-udp/src/unicast.rs @@ -28,7 +28,7 @@ use std::time::Duration; use zenoh_core::{zasynclock, zlock}; use zenoh_link_commons::{ get_ip_interface_names, ConstructibleLinkManagerUnicast, LinkManagerUnicastTrait, LinkUnicast, - LinkUnicastTrait, ListenersUnicastIP, NewLinkChannelSender, + LinkUnicastTrait, ListenersUnicastIP, NewLinkChannelSender, BIND_INTERFACE, }; use zenoh_protocol::core::{EndPoint, Locator}; use zenoh_result::{bail, zerror, Error as ZError, ZResult}; @@ -261,6 +261,7 @@ impl LinkManagerUnicastUdp { async fn new_link_inner( &self, dst_addr: &SocketAddr, + iface: Option<&str>, ) -> ZResult<(UdpSocket, SocketAddr, SocketAddr)> { // Establish a UDP socket let socket = UdpSocket::bind(SocketAddr::new( @@ -278,6 +279,8 @@ impl LinkManagerUnicastUdp { e })?; + zenoh_util::net::set_bind_to_device_udp_socket(&socket, iface); + // Connect the socket to the remote address socket.connect(dst_addr).await.map_err(|e| { let e = zerror!("Can not create a new UDP link bound to {}: {}", dst_addr, e); @@ -301,7 +304,11 @@ impl LinkManagerUnicastUdp { Ok((socket, src_addr, dst_addr)) } - async fn new_listener_inner(&self, addr: &SocketAddr) -> ZResult<(UdpSocket, SocketAddr)> { + async fn new_listener_inner( + &self, + addr: &SocketAddr, + iface: Option<&str>, + ) -> ZResult<(UdpSocket, SocketAddr)> { // Bind the UDP socket let socket = UdpSocket::bind(addr).await.map_err(|e| { let e = zerror!("Can not create a new UDP listener on {}: {}", addr, e); @@ -309,6 +316,8 @@ impl LinkManagerUnicastUdp { e })?; + zenoh_util::net::set_bind_to_device_udp_socket(&socket, iface); + let local_addr = socket.local_addr().map_err(|e| { let e = zerror!("Can not create a new UDP listener on {}: {}", addr, e); log::warn!("{}", e); @@ -325,10 +334,12 @@ impl LinkManagerUnicastTrait for LinkManagerUnicastUdp { let dst_addrs = get_udp_addrs(endpoint.address()) .await? .filter(|a| !a.ip().is_multicast()); + let config = endpoint.config(); + let iface = config.get(BIND_INTERFACE); let mut errs: Vec = vec![]; for da in dst_addrs { - match self.new_link_inner(&da).await { + match self.new_link_inner(&da, iface).await { Ok((socket, src_addr, dst_addr)) => { // Create UDP link let link = Arc::new(LinkUnicastUdp::new( @@ -362,10 +373,12 @@ impl LinkManagerUnicastTrait for LinkManagerUnicastUdp { let addrs = get_udp_addrs(endpoint.address()) .await? .filter(|a| !a.ip().is_multicast()); + let config = endpoint.config(); + let iface = config.get(BIND_INTERFACE); let mut errs: Vec = vec![]; for da in addrs { - match self.new_listener_inner(&da).await { + match self.new_listener_inner(&da, iface).await { Ok((socket, local_addr)) => { // Update the endpoint locator address endpoint = EndPoint::new( diff --git a/io/zenoh-links/zenoh-link-ws/src/unicast.rs b/io/zenoh-links/zenoh-link-ws/src/unicast.rs index 4276e2bfaf..0ff1b1ab46 100644 --- a/io/zenoh-links/zenoh-link-ws/src/unicast.rs +++ b/io/zenoh-links/zenoh-link-ws/src/unicast.rs @@ -416,7 +416,7 @@ impl LinkManagerUnicastTrait for LinkManagerUnicastWs { for (key, value) in guard.iter() { let listener_locator = value.endpoint.to_locator(); if key.ip() == default_ipv4 { - match zenoh_util::net::get_local_addresses() { + match zenoh_util::net::get_local_addresses(None) { Ok(ipaddrs) => { for ipaddr in ipaddrs { if !ipaddr.is_loopback() && !ipaddr.is_multicast() && ipaddr.is_ipv4() { @@ -433,7 +433,7 @@ impl LinkManagerUnicastTrait for LinkManagerUnicastWs { Err(err) => log::error!("Unable to get local addresses: {}", err), } } else if key.ip() == default_ipv6 { - match zenoh_util::net::get_local_addresses() { + match zenoh_util::net::get_local_addresses(None) { Ok(ipaddrs) => { for ipaddr in ipaddrs { if !ipaddr.is_loopback() && !ipaddr.is_multicast() && ipaddr.is_ipv6() { diff --git a/io/zenoh-transport/tests/unicast_openclose.rs b/io/zenoh-transport/tests/unicast_openclose.rs index 76a63cc6e0..56b686947a 100644 --- a/io/zenoh-transport/tests/unicast_openclose.rs +++ b/io/zenoh-transport/tests/unicast_openclose.rs @@ -24,7 +24,11 @@ use zenoh_transport::{ TransportMulticastEventHandler, TransportPeer, TransportPeerEventHandler, }; +#[cfg(target_os = "linux")] +use zenoh_util::net::get_ipv4_ipaddrs; + const TIMEOUT: Duration = Duration::from_secs(60); +const TIMEOUT_EXPECTED: Duration = Duration::from_secs(5); const SLEEP: Duration = Duration::from_millis(100); macro_rules! ztimeout { @@ -33,6 +37,12 @@ macro_rules! ztimeout { }; } +macro_rules! ztimeout_expected { + ($f:expr) => { + $f.timeout(TIMEOUT_EXPECTED).await.unwrap() + }; +} + #[cfg(test)] #[derive(Default)] struct SHRouterOpenClose; @@ -80,7 +90,11 @@ impl TransportEventHandler for SHClientOpenClose { } } -async fn openclose_transport(endpoint: &EndPoint, lowlatency_transport: bool) { +async fn openclose_transport( + listen_endpoint: &EndPoint, + connect_endpoint: &EndPoint, + lowlatency_transport: bool, +) { /* [ROUTER] */ let router_id = ZenohId::try_from([1]).unwrap(); @@ -140,7 +154,7 @@ async fn openclose_transport(endpoint: &EndPoint, lowlatency_transport: bool) { /* [1] */ println!("\nTransport Open Close [1a1]"); // Add the locator on the router - let res = ztimeout!(router_manager.add_listener(endpoint.clone())); + let res = ztimeout!(router_manager.add_listener(listen_endpoint.clone())); println!("Transport Open Close [1a1]: {res:?}"); assert!(res.is_ok()); println!("Transport Open Close [1a2]"); @@ -153,10 +167,11 @@ async fn openclose_transport(endpoint: &EndPoint, lowlatency_transport: bool) { let mut links_num = 1; println!("Transport Open Close [1c1]"); - let res = ztimeout!(client01_manager.open_transport_unicast(endpoint.clone())); + let open_res = + ztimeout_expected!(client01_manager.open_transport_unicast(connect_endpoint.clone())); println!("Transport Open Close [1c2]: {res:?}"); - assert!(res.is_ok()); - let c_ses1 = res.unwrap(); + assert!(open_res.is_ok()); + let c_ses1 = open_res.unwrap(); println!("Transport Open Close [1d1]"); let transports = ztimeout!(client01_manager.get_transports_unicast()); println!("Transport Open Close [1d2]: {transports:?}"); @@ -195,7 +210,7 @@ async fn openclose_transport(endpoint: &EndPoint, lowlatency_transport: bool) { links_num = 2; println!("\nTransport Open Close [2a1]"); - let res = ztimeout!(client01_manager.open_transport_unicast(endpoint.clone())); + let res = ztimeout!(client01_manager.open_transport_unicast(connect_endpoint.clone())); println!("Transport Open Close [2a2]: {res:?}"); assert!(res.is_ok()); let c_ses2 = res.unwrap(); @@ -235,7 +250,7 @@ async fn openclose_transport(endpoint: &EndPoint, lowlatency_transport: bool) { // Open transport -> This should be rejected because // of the maximum limit of links per transport println!("\nTransport Open Close [3a1]"); - let res = ztimeout!(client01_manager.open_transport_unicast(endpoint.clone())); + let res = ztimeout!(client01_manager.open_transport_unicast(connect_endpoint.clone())); println!("Transport Open Close [3a2]: {res:?}"); assert!(res.is_err()); println!("Transport Open Close [3b1]"); @@ -294,7 +309,7 @@ async fn openclose_transport(endpoint: &EndPoint, lowlatency_transport: bool) { links_num = 1; println!("\nTransport Open Close [5a1]"); - let res = ztimeout!(client01_manager.open_transport_unicast(endpoint.clone())); + let res = ztimeout!(client01_manager.open_transport_unicast(connect_endpoint.clone())); println!("Transport Open Close [5a2]: {res:?}"); assert!(res.is_ok()); let c_ses3 = res.unwrap(); @@ -326,7 +341,7 @@ async fn openclose_transport(endpoint: &EndPoint, lowlatency_transport: bool) { // Open transport -> This should be rejected because // of the maximum limit of transports println!("\nTransport Open Close [6a1]"); - let res = ztimeout!(client02_manager.open_transport_unicast(endpoint.clone())); + let res = ztimeout!(client02_manager.open_transport_unicast(connect_endpoint.clone())); println!("Transport Open Close [6a2]: {res:?}"); assert!(res.is_err()); println!("Transport Open Close [6b1]"); @@ -377,7 +392,7 @@ async fn openclose_transport(endpoint: &EndPoint, lowlatency_transport: bool) { links_num = 1; println!("\nTransport Open Close [8a1]"); - let res = ztimeout!(client02_manager.open_transport_unicast(endpoint.clone())); + let res = ztimeout!(client02_manager.open_transport_unicast(connect_endpoint.clone())); println!("Transport Open Close [8a2]: {res:?}"); assert!(res.is_ok()); let c_ses4 = res.unwrap(); @@ -435,7 +450,7 @@ async fn openclose_transport(endpoint: &EndPoint, lowlatency_transport: bool) { /* [10] */ // Perform clean up of the open locators println!("\nTransport Open Close [10a1]"); - let res = ztimeout!(router_manager.del_listener(endpoint)); + let res = ztimeout!(router_manager.del_listener(listen_endpoint)); println!("Transport Open Close [10a2]: {res:?}"); assert!(res.is_ok()); @@ -457,11 +472,11 @@ async fn openclose_transport(endpoint: &EndPoint, lowlatency_transport: bool) { } async fn openclose_universal_transport(endpoint: &EndPoint) { - openclose_transport(endpoint, false).await + openclose_transport(endpoint, endpoint, false).await } async fn openclose_lowlatency_transport(endpoint: &EndPoint) { - openclose_transport(endpoint, true).await + openclose_transport(endpoint, endpoint, true).await } #[cfg(feature = "transport_tcp")] @@ -786,3 +801,107 @@ R+IdLiXcyIkg0m9N8I17p0ljCSkbrgGMD3bbePRTfg== task::block_on(openclose_universal_transport(&endpoint)); } + +#[cfg(feature = "transport_tcp")] +#[cfg(target_os = "linux")] +#[test] +#[should_panic(expected = "TimeoutError")] +fn openclose_tcp_only_connect_with_interface_restriction() { + let addrs = get_ipv4_ipaddrs(None); + + let _ = env_logger::try_init(); + task::block_on(async { + zasync_executor_init!(); + }); + + let listen_endpoint: EndPoint = format!("tcp/{}:{}", addrs[0], 13001).parse().unwrap(); + + let connect_endpoint: EndPoint = format!("tcp/{}:{}#iface=lo", addrs[0], 13001) + .parse() + .unwrap(); + + // should not connect to local interface and external address + task::block_on(openclose_transport( + &listen_endpoint, + &connect_endpoint, + false, + )); +} + +#[cfg(feature = "transport_tcp")] +#[cfg(target_os = "linux")] +#[test] +#[should_panic(expected = "assertion failed: open_res.is_ok()")] +fn openclose_tcp_only_listen_with_interface_restriction() { + let addrs = get_ipv4_ipaddrs(None); + + let _ = env_logger::try_init(); + task::block_on(async { + zasync_executor_init!(); + }); + + let listen_endpoint: EndPoint = format!("tcp/{}:{}#iface=lo", addrs[0], 13002) + .parse() + .unwrap(); + + let connect_endpoint: EndPoint = format!("tcp/{}:{}", addrs[0], 13002).parse().unwrap(); + + // should not connect to local interface and external address + task::block_on(openclose_transport( + &listen_endpoint, + &connect_endpoint, + false, + )); +} + +#[cfg(feature = "transport_udp")] +#[cfg(target_os = "linux")] +#[test] +#[should_panic(expected = "TimeoutError")] +fn openclose_udp_only_connect_with_interface_restriction() { + let addrs = get_ipv4_ipaddrs(None); + + let _ = env_logger::try_init(); + task::block_on(async { + zasync_executor_init!(); + }); + + let listen_endpoint: EndPoint = format!("udp/{}:{}", addrs[0], 13003).parse().unwrap(); + + let connect_endpoint: EndPoint = format!("udp/{}:{}#iface=lo", addrs[0], 13003) + .parse() + .unwrap(); + + // should not connect to local interface and external address + task::block_on(openclose_transport( + &listen_endpoint, + &connect_endpoint, + false, + )); +} + +#[cfg(feature = "transport_udp")] +#[cfg(target_os = "linux")] +#[test] +#[should_panic(expected = "assertion failed: open_res.is_ok()")] +fn openclose_udp_onlyi_listen_with_interface_restriction() { + let addrs = get_ipv4_ipaddrs(None); + + let _ = env_logger::try_init(); + task::block_on(async { + zasync_executor_init!(); + }); + + let listen_endpoint: EndPoint = format!("udp/{}:{}#iface=lo", addrs[0], 13004) + .parse() + .unwrap(); + + let connect_endpoint: EndPoint = format!("udp/{}:{}", addrs[0], 13004).parse().unwrap(); + + // should not connect to local interface and external address + task::block_on(openclose_transport( + &listen_endpoint, + &connect_endpoint, + false, + )); +}