use std::time::Duration;
use iroh::{TransportAddr, Watcher, endpoint::Side};
use n0_error::{Result, StackResultExt};
use n0_tracing_test::traced_test;
use patchbay::{IpSupport, RouterPreset};
use testdir::testdir;
use tracing::info;
use crate::util::{Pair, PathWatcherExt, lab_with_relay, ping_accept, ping_open};
async fn run_switch_uplink(switching_side: Side, from: IpSupport, to: IpSupport) -> Result {
let (lab, relay_map, _relay_guard, guard) = lab_with_relay(testdir!()).await?;
let timeout = Duration::from_secs(30);
let observer_id = lab
.add_router("observer")
.preset(RouterPreset::Home)
.ip_support(IpSupport::DualStack)
.build()
.await?
.id();
let from_id = lab
.add_router("from")
.preset(router_preset(from))
.ip_support(from)
.build()
.await?
.id();
let to_id = lab
.add_router("to")
.preset(router_preset(to))
.ip_support(to)
.build()
.await?
.id();
let switcher = lab.add_device("switcher").uplink(from_id).build().await?;
let observer = lab
.add_device("observer")
.uplink(observer_id)
.build()
.await?;
info!(?switching_side, ?from, ?to, "switch uplink test start");
Pair::new(relay_map)
.left(switching_side, switcher, async move |dev, _ep, conn| {
let mut paths = conn.paths();
paths.wait_ip(timeout).await.context("initial holepunch")?;
ping_accept(&conn, timeout)
.await
.context("ping_accept before switch")?;
dev.iface("eth0").unwrap().replug(to_id).await?;
ping_accept(&conn, timeout)
.await
.context("ping_accept after switch")?;
conn.closed().await;
Ok(())
})
.right(observer, async move |_dev, _ep, conn| {
let mut paths = conn.paths();
paths.wait_ip(timeout).await.context("initial holepunch")?;
let previous: Vec<TransportAddr> = paths
.get()
.iter()
.map(|p| p.remote_addr().clone())
.collect();
ping_open(&conn, timeout)
.await
.context("ping_open before switch")?;
paths
.wait_selected(timeout, |p| path_switched(to, &previous, p.remote_addr()))
.await
.context("path did not switch")?;
ping_open(&conn, timeout)
.await
.context("ping_open after switch")?;
conn.close(0u32.into(), b"bye");
Ok(())
})
.run()
.await?;
guard.ok();
Ok(())
}
fn router_preset(ip: IpSupport) -> RouterPreset {
match ip {
IpSupport::V4Only => RouterPreset::Home,
IpSupport::V6Only => RouterPreset::IspV6,
IpSupport::DualStack => RouterPreset::Home,
}
}
fn path_switched(to: IpSupport, previous: &[TransportAddr], new: &TransportAddr) -> bool {
if previous.contains(new) {
return false;
}
match to {
IpSupport::V4Only => matches!(new, TransportAddr::Ip(a) if a.ip().is_ipv4()),
IpSupport::V6Only => matches!(new, TransportAddr::Ip(a) if a.ip().is_ipv6()),
IpSupport::DualStack => matches!(new, TransportAddr::Ip(_)),
}
}
#[tokio::test]
#[traced_test]
async fn switch_client_v4_to_v4() -> Result {
run_switch_uplink(Side::Client, IpSupport::V4Only, IpSupport::V4Only).await
}
#[tokio::test]
#[traced_test]
async fn switch_client_v4_to_v6() -> Result {
run_switch_uplink(Side::Client, IpSupport::V4Only, IpSupport::V6Only).await
}
#[tokio::test]
#[traced_test]
async fn switch_client_v4_to_dual() -> Result {
run_switch_uplink(Side::Client, IpSupport::V4Only, IpSupport::DualStack).await
}
#[tokio::test]
#[traced_test]
async fn switch_client_v6_to_v4() -> Result {
run_switch_uplink(Side::Client, IpSupport::V6Only, IpSupport::V4Only).await
}
#[tokio::test]
#[traced_test]
async fn switch_client_v6_to_v6() -> Result {
run_switch_uplink(Side::Client, IpSupport::V6Only, IpSupport::V6Only).await
}
#[tokio::test]
#[traced_test]
async fn switch_client_v6_to_dual() -> Result {
run_switch_uplink(Side::Client, IpSupport::V6Only, IpSupport::DualStack).await
}
#[tokio::test]
#[traced_test]
async fn switch_client_dual_to_v4() -> Result {
run_switch_uplink(Side::Client, IpSupport::DualStack, IpSupport::V4Only).await
}
#[tokio::test]
#[traced_test]
async fn switch_client_dual_to_v6() -> Result {
run_switch_uplink(Side::Client, IpSupport::DualStack, IpSupport::V6Only).await
}
#[tokio::test]
#[traced_test]
async fn switch_client_dual_to_dual() -> Result {
run_switch_uplink(Side::Client, IpSupport::DualStack, IpSupport::DualStack).await
}
#[tokio::test]
#[traced_test]
async fn switch_server_v4_to_v4() -> Result {
run_switch_uplink(Side::Server, IpSupport::V4Only, IpSupport::V4Only).await
}
#[tokio::test]
#[traced_test]
async fn switch_server_v4_to_v6() -> Result {
run_switch_uplink(Side::Server, IpSupport::V4Only, IpSupport::V6Only).await
}
#[tokio::test]
#[traced_test]
async fn switch_server_v4_to_dual() -> Result {
run_switch_uplink(Side::Server, IpSupport::V4Only, IpSupport::DualStack).await
}
#[tokio::test]
#[traced_test]
async fn switch_server_v6_to_v4() -> Result {
run_switch_uplink(Side::Server, IpSupport::V6Only, IpSupport::V4Only).await
}
#[tokio::test]
#[traced_test]
async fn switch_server_v6_to_v6() -> Result {
run_switch_uplink(Side::Server, IpSupport::V6Only, IpSupport::V6Only).await
}
#[tokio::test]
#[traced_test]
async fn switch_server_v6_to_dual() -> Result {
run_switch_uplink(Side::Server, IpSupport::V6Only, IpSupport::DualStack).await
}
#[tokio::test]
#[traced_test]
async fn switch_server_dual_to_v4() -> Result {
run_switch_uplink(Side::Server, IpSupport::DualStack, IpSupport::V4Only).await
}
#[tokio::test]
#[traced_test]
async fn switch_server_dual_to_v6() -> Result {
run_switch_uplink(Side::Server, IpSupport::DualStack, IpSupport::V6Only).await
}
#[tokio::test]
#[traced_test]
async fn switch_server_dual_to_dual() -> Result {
run_switch_uplink(Side::Server, IpSupport::DualStack, IpSupport::DualStack).await
}