//! An example chat application using the iroh-net endpoint and //! pkarr node discovery. //! //! Starting the example without args creates a server that publishes its //! address to the DHT. Starting the example with a node id as argument //! looks up the address of the node id in the DHT and connects to it. //! //! You can look at the published pkarr DNS record using . //! //! To see what is going on, run with `RUST_LOG=iroh_pkarr_node_discovery=debug`. use std::str::FromStr; use clap::Parser; use iroh_net::{endpoint::get_remote_node_id, Endpoint, NodeId}; use tracing::warn; use url::Url; const CHAT_ALPN: &[u8] = b"pkarr-discovery-demo-chat"; #[derive(Parser)] struct Args { /// The node id to connect to. If not set, the program will start a server. node_id: Option, /// Disable using the mainline DHT for discovery and publishing. #[clap(long)] disable_dht: bool, /// Pkarr relay to use. #[clap(long, default_value = "iroh")] pkarr_relay: PkarrRelay, } #[derive(Debug, Clone)] enum PkarrRelay { /// Disable pkarr relay. Disabled, /// Use the iroh pkarr relay. Iroh, /// Use a custom pkarr relay. Custom(Url), } impl FromStr for PkarrRelay { type Err = anyhow::Error; fn from_str(s: &str) -> Result { match s { "disabled" => Ok(Self::Disabled), "iroh" => Ok(Self::Iroh), s => Ok(Self::Custom(Url::parse(s)?)), } } } fn build_discovery(args: Args) -> iroh_net::discovery::pkarr::dht::Builder { let builder = iroh_net::discovery::pkarr::dht::DhtDiscovery::builder().dht(!args.disable_dht); match args.pkarr_relay { PkarrRelay::Disabled => builder, PkarrRelay::Iroh => builder.n0_dns_pkarr_relay(), PkarrRelay::Custom(url) => builder.pkarr_relay(url), } } async fn chat_server(args: Args) -> anyhow::Result<()> { let secret_key = iroh_net::key::SecretKey::generate(); let node_id = secret_key.public(); let discovery = build_discovery(args) .secret_key(secret_key.clone()) .build()?; let endpoint = Endpoint::builder() .alpns(vec![CHAT_ALPN.to_vec()]) .secret_key(secret_key) .discovery(Box::new(discovery)) .bind() .await?; let zid = pkarr::PublicKey::try_from(node_id.as_bytes())?.to_z32(); println!("Listening on {}", node_id); println!("pkarr z32: {}", zid); println!("see https://app.pkarr.org/?pk={}", zid); while let Some(incoming) = endpoint.accept().await { let connecting = match incoming.accept() { Ok(connecting) => connecting, Err(err) => { warn!("incoming connection failed: {err:#}"); // we can carry on in these cases: // this can be caused by retransmitted datagrams continue; } }; tokio::spawn(async move { let connection = connecting.await?; let remote_node_id = get_remote_node_id(&connection)?; println!("got connection from {}", remote_node_id); // just leave the tasks hanging. this is just an example. let (mut writer, mut reader) = connection.accept_bi().await?; let _copy_to_stdout = tokio::spawn(async move { tokio::io::copy(&mut reader, &mut tokio::io::stdout()).await }); let _copy_from_stdin = tokio::spawn( async move { tokio::io::copy(&mut tokio::io::stdin(), &mut writer).await }, ); anyhow::Ok(()) }); } Ok(()) } async fn chat_client(args: Args) -> anyhow::Result<()> { let remote_node_id = args.node_id.unwrap(); let secret_key = iroh_net::key::SecretKey::generate(); let node_id = secret_key.public(); // note: we don't pass a secret key here, because we don't need to publish our address, don't spam the DHT let discovery = build_discovery(args).build()?; // we do not need to specify the alpn here, because we are not going to accept connections let endpoint = Endpoint::builder() .secret_key(secret_key) .discovery(Box::new(discovery)) .bind() .await?; println!("We are {} and connecting to {}", node_id, remote_node_id); let connection = endpoint.connect(remote_node_id, CHAT_ALPN).await?; println!("connected to {}", remote_node_id); let (mut writer, mut reader) = connection.open_bi().await?; let _copy_to_stdout = tokio::spawn(async move { tokio::io::copy(&mut reader, &mut tokio::io::stdout()).await }); let _copy_from_stdin = tokio::spawn(async move { tokio::io::copy(&mut tokio::io::stdin(), &mut writer).await }); _copy_to_stdout.await??; _copy_from_stdin.await??; Ok(()) } #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); let args = Args::parse(); if args.node_id.is_some() { chat_client(args).await?; } else { chat_server(args).await?; } Ok(()) }