Something went wrong. Try again.
Connects Rex clients and servers transparently via iroh. rex-iroh.wisp.place
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138//! This device as `rex-iroh` keeps it: its key, and the directory holding its//! allowlist, control socket and the peers it has joined.
use std::path::PathBuf;
use iroh::{ Endpoint, EndpointId, SecretKey, endpoint::{BindError, presets},};
use crate::{ control::{self, ClientError, Pairing, PeerList, Revoke}, joined::Joined, key::{self, KeyError}, pair::Policy,};
const SECRET_KEY: &str = "secret.key";const ALLOWLIST: &str = "allowlist";const CONTROL_SOCKET: &str = "control.sock";const PEERS: &str = "peers";const SOCKETS: &str = "sockets";
/// Set (to anything) to keep every endpoint on loopback, with no relays or/// address lookup, so the binary's tests can pair and tunnel in a sandbox/// without network. Not gated on debug builds: Nix runs the tests in release.const LOOPBACK_ONLY: &str = "REX_IROH_LOOPBACK_ONLY";
fn loopback_only() -> bool { std::env::var_os(LOOPBACK_ONLY).is_some()}
#[derive(Debug, thiserror::Error)]#[error("no data directory for this user")]pub struct NoDataDir;
/// Where this device's state lives unless told otherwise.pub fn default_dir() -> Result<PathBuf, NoDataDir> { dirs::data_dir() .map(|dir| dir.join("rex-iroh")) .ok_or(NoDataDir)}
/// This device's identity and the directory its state lives in.#[derive(Debug)]pub struct Node { dir: PathBuf, secret: SecretKey,}
impl Node { /// Open the state in `dir`, creating this device's key on first use. pub fn open(dir: PathBuf) -> Result<Self, KeyError> { let secret = key::load_or_create(&dir.join(SECRET_KEY))?; Ok(Self { dir, secret }) }
pub fn id(&self) -> EndpointId { self.secret.public() }
/// An endpoint with this device's key, reachable through n0's relays and /// address lookup. pub async fn bind(&self) -> Result<Endpoint, BindError> { if loopback_only() { return Endpoint::builder(presets::Minimal) .secret_key(self.secret.clone()) .clear_ip_transports() .bind_addr("127.0.0.1:0") .expect("a literal socket address") .bind() .await; }
Endpoint::builder(presets::N0) .secret_key(self.secret.clone()) .bind() .await }
/// Whether endpoints stay on loopback, with no relays or address lookup. pub fn loopback_only(&self) -> bool { loopback_only() }
/// The allowlist `serve` uses unless told otherwise. pub fn allowlist(&self) -> PathBuf { self.dir.join(ALLOWLIST) }
/// Where `serve` listens for the commands that reach it locally. pub fn control_socket(&self) -> PathBuf { self.dir.join(CONTROL_SOCKET) }
/// Ask the `serve` running from this state for an invite, to see its /// pairing through. pub async fn pair(&self, policy: Policy) -> Result<Pairing, ClientError> { control::request_invite(&self.dir.join(CONTROL_SOCKET), policy).await }
/// Who is allowed to connect, through `serve` if it's running. pub async fn peers(&self) -> Result<PeerList, ClientError> { control::list_peers(&self.dir.join(CONTROL_SOCKET), &self.dir.join(ALLOWLIST)).await }
/// Take the peer `peer` names off the allowlist, through `serve` if it's /// running so its connections close too. pub async fn revoke(&self, peer: &str) -> Result<Revoke, ClientError> { control::revoke_peer( &self.dir.join(CONTROL_SOCKET), &self.dir.join(ALLOWLIST), peer, ) .await }
/// The issuers this device has joined. pub fn joined(&self) -> Joined { Joined::new(self.dir.join(PEERS), self.dir.join(SOCKETS), self.id()) }}
#[cfg(test)]mod tests { use super::*;
#[test] fn reopening_keeps_the_identity() { let dir = tempfile::tempdir().unwrap(); let first = Node::open(dir.path().join("state")).unwrap(); let second = Node::open(dir.path().join("state")).unwrap();
assert_eq!(first.id(), second.id()); assert!(dir.path().join("state/secret.key").exists()); }}