Skip to content

Instantly share code, notes, and snippets.

@Frando
Last active February 6, 2026 12:47
Show Gist options
  • Select an option

  • Save Frando/938fa6a7c2cfda4f469191c4cc7046ee to your computer and use it in GitHub Desktop.

Select an option

Save Frando/938fa6a7c2cfda4f469191c4cc7046ee to your computer and use it in GitHub Desktop.
iroh 0.95 to 0.96 connectivity test
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
[dependencies]
tokio = { version = "1.49.0", features = ["full"] }
tracing = "0.1.44"
tracing-subscriber = "0.3.22"
iroh = "0.96"
iroh_095 = { package = "iroh", version = "0.95" }
n0-error = "0.1.3"
use std::{str::FromStr, time::Duration};
use n0_error::Result;
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt::init();
println!("--- WITH RELAYS ---");
run_all(true).await?;
println!("\n\n--- WITHOUT RELAYS ---");
run_all(false).await?;
Ok(())
}
async fn run_all(use_relay: bool) -> Result<()> {
let new1 = iroh_new::bind(use_relay).await?;
let new2 = iroh_new::bind(use_relay).await?;
println!("\nnew->new start");
let r = new2.ping(new1.addr_or_id(use_relay), "hi").await;
println!("new->new: {r:?}");
tokio::time::sleep(Duration::from_secs(1)).await;
let old1 = iroh_old::bind(use_relay).await?;
let old2 = iroh_old::bind(use_relay).await?;
println!("\nold->old start");
let r = old2.ping(old1.addr_or_id(use_relay), "hi").await;
println!("old->old: {r:?}");
tokio::time::sleep(Duration::from_secs(1)).await;
println!("\nold->new start");
let r = old2
.ping(addr_to_old(new1.addr_or_id(use_relay)), "hi")
.await;
println!("old->new: {r:?}");
tokio::time::sleep(Duration::from_secs(1)).await;
println!("\nnew->old start");
let r = new2
.ping(addr_to_new(old1.addr_or_id(use_relay)), "hi")
.await;
println!("new->old: {r:?}");
tokio::try_join!(
old1.shutdown(),
old2.shutdown(),
new1.shutdown(),
new2.shutdown()
)?;
Ok(())
}
const ECHO_ALPN: &[u8] = b"/iroh/echo/1";
fn addr_to_old(addr: iroh::EndpointAddr) -> iroh_095::EndpointAddr {
iroh_095::EndpointAddr::from_parts(
id_to_old(addr.id),
addr.addrs.into_iter().map(transport_addr_to_old),
)
}
fn addr_to_new(addr: iroh_095::EndpointAddr) -> iroh::EndpointAddr {
iroh::EndpointAddr::from_parts(
id_to_new(addr.id),
addr.addrs.into_iter().map(transport_addr_to_new),
)
}
fn transport_addr_to_old(addr: iroh::TransportAddr) -> iroh_095::TransportAddr {
match addr {
iroh::TransportAddr::Relay(relay_url) => iroh_095::TransportAddr::Relay(
iroh_095::RelayUrl::from_str(relay_url.as_str()).unwrap(),
),
iroh::TransportAddr::Ip(socket_addr) => iroh_095::TransportAddr::Ip(socket_addr),
_ => todo!(),
}
}
fn transport_addr_to_new(addr: iroh_095::TransportAddr) -> iroh::TransportAddr {
match addr {
iroh_095::TransportAddr::Relay(relay_url) => {
iroh::TransportAddr::Relay(iroh::RelayUrl::from_str(relay_url.as_str()).unwrap())
}
iroh_095::TransportAddr::Ip(socket_addr) => iroh::TransportAddr::Ip(socket_addr),
_ => todo!(),
}
}
fn id_to_old(id: iroh::EndpointId) -> iroh_095::EndpointId {
iroh_095::EndpointId::from_bytes(id.as_bytes()).unwrap()
}
fn id_to_new(id: iroh_095::EndpointId) -> iroh::EndpointId {
iroh::EndpointId::from_bytes(id.as_bytes()).unwrap()
}
mod iroh_new {
use std::time::Duration;
use iroh::{
Endpoint, EndpointAddr, EndpointId, RelayMode, Watcher,
endpoint::Connection,
protocol::{AcceptError, ProtocolHandler, Router},
};
use n0_error::{Result, StdResultExt};
use tracing::{info, instrument};
use crate::ECHO_ALPN;
pub async fn bind(relay: bool) -> Result<EchoNode> {
let endpoint = if relay {
Endpoint::bind().await?
} else {
Endpoint::builder()
.relay_mode(RelayMode::Disabled)
.bind()
.await?
};
if relay {
endpoint.online().await;
}
let router = Router::builder(endpoint.clone())
.accept(ECHO_ALPN, EchoProtocol)
.spawn();
let node = EchoNode(router);
Ok(node)
}
#[derive(Debug)]
pub struct EchoNode(Router);
impl EchoNode {
pub async fn ping(&self, remote: EndpointAddr, message: &str) -> Result<String> {
let conn = self.0.endpoint().connect(remote, ECHO_ALPN).await?;
let (mut send, mut recv) = conn.open_bi().await.anyerr()?;
send.write_all(message.as_bytes()).await.anyerr()?;
send.finish().anyerr()?;
let r = recv.read_to_end(1024).await.anyerr()?;
let s = String::from_utf8(r).anyerr()?;
tokio::time::sleep(Duration::from_secs(1)).await;
println!("client done {}", paths(&conn));
Ok(s)
}
pub fn addr_or_id(&self, id_only: bool) -> EndpointAddr {
if id_only {
EndpointAddr::from(self.id())
} else {
self.addr()
}
}
pub fn id(&self) -> EndpointId {
self.0.endpoint().id()
}
pub fn addr(&self) -> EndpointAddr {
self.0.endpoint().addr()
}
pub async fn shutdown(&self) -> Result<()> {
self.0.shutdown().await.anyerr()
}
}
#[derive(Debug)]
pub struct EchoProtocol;
impl ProtocolHandler for EchoProtocol {
async fn accept(&self, connection: Connection) -> Result<(), AcceptError> {
let (mut send, mut recv) = connection.accept_bi().await?;
// Echo any bytes received back directly.
let _bytes_sent = tokio::io::copy(&mut recv, &mut send).await?;
send.finish()?;
connection.closed().await;
println!("server done {}", paths(&connection));
Ok(())
}
}
fn paths(c: &Connection) -> String {
let p = c.paths().get();
p.iter()
.map(|p| {
let selected = if p.is_selected() { "*" } else { "" };
format!("{selected}{:?}", p.remote_addr())
})
.collect::<Vec<_>>()
.join(", ")
}
}
mod iroh_old {
use std::time::Duration;
use iroh_095::{
Endpoint, EndpointAddr, EndpointId, RelayMode, Watcher,
endpoint::Connection,
protocol::{AcceptError, ProtocolHandler, Router},
};
use n0_error::{Result, StdResultExt};
use tracing::{info, instrument};
use crate::ECHO_ALPN;
pub async fn bind(relay: bool) -> Result<EchoNode> {
let endpoint = if relay {
Endpoint::bind().await?
} else {
Endpoint::builder()
.relay_mode(RelayMode::Disabled)
.bind()
.await?
};
if relay {
endpoint.online().await;
}
let router = Router::builder(endpoint.clone())
.accept(ECHO_ALPN, EchoProtocol(endpoint))
.spawn();
let node = EchoNode(router);
Ok(node)
}
#[derive(Debug)]
pub struct EchoNode(Router);
impl EchoNode {
pub async fn ping(&self, remote: EndpointAddr, message: &str) -> Result<String> {
let remote_id = remote.id;
let conn = self.0.endpoint().connect(remote, ECHO_ALPN).await?;
let (mut send, mut recv) = conn.open_bi().await.anyerr()?;
send.write_all(message.as_bytes()).await.anyerr()?;
send.finish().anyerr()?;
let r = recv.read_to_end(1024).await.anyerr()?;
let s = String::from_utf8(r).anyerr()?;
tokio::time::sleep(Duration::from_secs(1)).await;
println!(
"client done {:?}",
self.0.endpoint().conn_type(remote_id).unwrap().get()
);
Ok(s)
}
pub fn addr_or_id(&self, id_only: bool) -> EndpointAddr {
if id_only {
EndpointAddr::from(self.id())
} else {
self.addr()
}
}
pub fn id(&self) -> EndpointId {
self.0.endpoint().id()
}
pub fn addr(&self) -> EndpointAddr {
self.0.endpoint().addr()
}
pub async fn shutdown(&self) -> Result<()> {
self.0.shutdown().await.anyerr()
}
}
#[derive(Debug)]
pub struct EchoProtocol(Endpoint);
impl ProtocolHandler for EchoProtocol {
async fn accept(&self, connection: Connection) -> Result<(), AcceptError> {
let remote = connection.remote_id();
let (mut send, mut recv) = connection.accept_bi().await?;
// Echo any bytes received back directly.
let _bytes_sent = tokio::io::copy(&mut recv, &mut send).await?;
send.finish()?;
connection.closed().await;
println!("server done {:?}", self.0.conn_type(remote).unwrap().get());
Ok(())
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment