use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
time::Duration,
};
#[doc(inline)]
pub use config::Config;
#[doc(inline)]
pub use error::{Error, InternalErrorKind};
#[doc(inline)]
pub use ts_control::Node as NodeInfo;
use ts_netstack_smoltcp::{CreateSocket, netcore::Channel};
#[cfg(feature = "axum")]
pub mod axum;
pub mod config;
mod error;
#[cfg(feature = "ssh")]
pub mod ssh;
pub struct Device {
runtime: ts_runtime::Runtime,
channel: Channel,
}
impl Device {
pub async fn new(config: &Config, auth_key: Option<String>) -> Result<Self, Error> {
check_magic_env()?;
let rt =
ts_runtime::Runtime::spawn(config.into(), auth_key, (&config.key_state).into()).await?;
let channel = rt.channel().await?;
Ok(Self {
runtime: rt,
channel,
})
}
pub async fn ipv4_addr(&self) -> Result<Ipv4Addr, Error> {
self.runtime
.control
.ask(ts_runtime::control_runner::Ipv4)
.await
.map_err(ts_runtime::Error::from)?
.ok_or(Error::Internal(InternalErrorKind::Actor))
}
pub async fn ipv6_addr(&self) -> Result<Ipv6Addr, Error> {
self.runtime
.control
.ask(ts_runtime::control_runner::Ipv6)
.await
.map_err(ts_runtime::Error::from)?
.ok_or(Error::Internal(InternalErrorKind::Actor))
}
pub async fn udp_bind(&self, socket_addr: SocketAddr) -> Result<netstack::UdpSocket, Error> {
self.channel.udp_bind(socket_addr).await.map_err(Into::into)
}
pub async fn tcp_listen(
&self,
socket_addr: SocketAddr,
) -> Result<netstack::TcpListener, Error> {
self.channel
.tcp_listen(socket_addr)
.await
.map_err(Into::into)
}
pub async fn tcp_connect(&self, remote: SocketAddr) -> Result<netstack::TcpStream, Error> {
let ip: IpAddr = match remote.is_ipv4() {
true => self.ipv4_addr().await?.into(),
false => self.ipv6_addr().await?.into(),
};
let ephemeral_port = rand::random_range(49152..=u16::MAX);
self.channel
.tcp_connect((ip, ephemeral_port).into(), remote)
.await
.map_err(Into::into)
}
pub async fn self_node(&self) -> Result<NodeInfo, Error> {
self.runtime
.control
.ask(ts_runtime::control_runner::SelfNode)
.await
.map_err(ts_runtime::Error::from)?
.ok_or(Error::Internal(InternalErrorKind::Actor))
}
pub async fn peer_by_name(&self, name: &str) -> Result<Option<NodeInfo>, Error> {
let pt = self
.runtime
.peer_tracker
.upgrade()
.ok_or(Error::Internal(InternalErrorKind::Actor))?;
pt.ask(ts_runtime::peer_tracker::PeerByName {
name: name.to_string(),
})
.await
.map_err(ts_runtime::Error::from)
.map_err(Into::into)
}
pub async fn peer_by_tailnet_ip(&self, ip: IpAddr) -> Result<Option<NodeInfo>, Error> {
let pt = self
.runtime
.peer_tracker
.upgrade()
.ok_or(Error::Internal(InternalErrorKind::Actor))?;
pt.ask(ts_runtime::peer_tracker::PeerByTailnetIp { ip })
.await
.map_err(ts_runtime::Error::from)
.map_err(Into::into)
}
pub async fn peers_with_route(&self, ip: IpAddr) -> Result<Vec<NodeInfo>, Error> {
let pt = self
.runtime
.peer_tracker
.upgrade()
.ok_or(Error::Internal(InternalErrorKind::Actor))?;
pt.ask(ts_runtime::peer_tracker::PeerByAcceptedRoute { ip })
.await
.map_err(ts_runtime::Error::from)
.map_err(Into::into)
}
pub async fn shutdown(self, timeout: Option<Duration>) -> bool {
self.runtime.graceful_shutdown(timeout).await
}
}
pub mod netstack {
#[doc(inline)]
pub use ts_netstack_smoltcp::netcore::Error;
#[doc(inline)]
pub use ts_netstack_smoltcp::netcore::InternalErrorKind;
#[doc(inline)]
pub use ts_netstack_smoltcp::netsock::{TcpListener, TcpStream, UdpSocket};
}
pub mod keys {
#[doc(inline)]
pub use ts_keys::{
DiscoKeyPair, DiscoPrivateKey, DiscoPublicKey, MachineKeyPair, MachinePrivateKey,
MachinePublicKey, NetworkLockKeyPair, NetworkLockPrivateKey, NetworkLockPublicKey,
NodeKeyPair, NodePrivateKey, NodePublicKey, NodeState, PersistState,
};
}
const ENV_MAGIC_VAR: &str = "TS_RS_EXPERIMENT";
const ENV_MAGIC_VALUE: &str = "this_is_unstable_software";
fn check_magic_env() -> Result<(), Error> {
if std::env::var(ENV_MAGIC_VAR).as_deref() != Ok(ENV_MAGIC_VALUE) {
let warning = format!(
"
check failed: set {ENV_MAGIC_VAR}={ENV_MAGIC_VALUE} to acknowledge that tailscale-rs is early-days
experimental software containing bugs, unvalidated cryptography, and no stability or compatibility
guarantees.
"
);
eprintln!("{}", warning.trim());
return Err(Error::UnstableEnvVar);
};
Ok(())
}