use std::ffi::{CStr, CString};
use std::iter::FusedIterator;
use std::net::{IpAddr, TcpStream};
use std::os::fd::FromRawFd;
use std::os::raw::c_int;
use libtailscale_sys::*;
#[derive(Debug)]
pub struct Tailscale {
inner: tailscale,
}
unsafe impl Send for Tailscale {}
unsafe impl Sync for Tailscale {}
#[derive(Debug)]
pub struct Listener<'a> {
tailscale: &'a Tailscale,
listener: tailscale_listener,
}
impl Tailscale {
pub fn new() -> Self {
let inner = unsafe { tailscale_new() };
Self { inner }
}
pub fn start(&mut self) -> Result<(), String> {
let ret = unsafe { tailscale_start(self.inner) };
if ret != 0 {
Err(self.last_error())
} else {
Ok(())
}
}
pub fn up(&mut self) -> Result<(), String> {
let ret = unsafe { tailscale_up(self.inner) };
if ret != 0 {
Err(self.last_error())
} else {
Ok(())
}
}
fn close(&mut self) -> Result<(), ()> {
let ret = unsafe { tailscale_close(self.inner) };
if ret != 0 {
Err(())
} else {
Ok(())
}
}
pub fn set_dir(&mut self, dir: &str) -> Result<(), String> {
let dir = CString::new(dir).unwrap();
let ret = unsafe { tailscale_set_dir(self.inner, dir.as_ptr()) };
if ret != 0 {
Err(self.last_error())
} else {
Ok(())
}
}
pub fn set_hostname(&mut self, hostname: &str) -> Result<(), String> {
let hostname = CString::new(hostname).unwrap();
let ret = unsafe { tailscale_set_hostname(self.inner, hostname.as_ptr()) };
if ret != 0 {
Err(self.last_error())
} else {
Ok(())
}
}
pub fn set_authkey(&mut self, authkey: &str) -> Result<(), String> {
let authkey = CString::new(authkey).unwrap();
let ret = unsafe { tailscale_set_authkey(self.inner, authkey.as_ptr()) };
if ret != 0 {
Err(self.last_error())
} else {
Ok(())
}
}
pub fn set_control_url(&mut self, control_url: &str) -> Result<(), String> {
let control_url = CString::new(control_url).unwrap();
let ret = unsafe { tailscale_set_control_url(self.inner, control_url.as_ptr()) };
if ret != 0 {
Err(self.last_error())
} else {
Ok(())
}
}
pub fn set_ephemeral(&mut self, ephemeral: bool) -> Result<(), String> {
let ret = unsafe { tailscale_set_ephemeral(self.inner, ephemeral as _) };
if ret != 0 {
Err(self.last_error())
} else {
Ok(())
}
}
pub fn set_logfd(&mut self, logfd: c_int) -> Result<(), String> {
let ret = unsafe { tailscale_set_logfd(self.inner, logfd) };
if ret != 0 {
Err(self.last_error())
} else {
Ok(())
}
}
pub fn get_ips(&self) -> Result<Vec<IpAddr>, String> {
let mut buf = [0i8; 1024];
let ret = unsafe { tailscale_getips(self.inner, buf.as_mut_ptr(), buf.len()) };
if ret != 0 {
Err(self.last_error())
} else {
let cstr = unsafe { CStr::from_ptr(buf.as_ptr()) };
let s = cstr.to_str().map_err(|e| e.to_string())?;
if s.is_empty() {
return Ok(Vec::new());
}
s.split(',')
.map(|ip| ip.parse::<IpAddr>().map_err(|e| e.to_string()))
.collect()
}
}
pub fn dial(&self, network: &str, address: &str) -> Result<TcpStream, String> {
let c_network = CString::new(network).unwrap();
let c_address = CString::new(address).unwrap();
let mut conn = 0;
let ret = unsafe {
tailscale_dial(
self.inner,
c_network.as_ptr(),
c_address.as_ptr(),
&mut conn,
)
};
if ret != 0 {
Err(self.last_error())
} else {
Ok(unsafe { TcpStream::from_raw_fd(conn) })
}
}
pub fn listen(&self, network: &str, address: &str) -> Result<Listener<'_>, String> {
let c_network = CString::new(network).unwrap();
let c_address = CString::new(address).unwrap();
let mut listener = 0;
let ret = unsafe {
tailscale_listen(
self.inner,
c_network.as_ptr(),
c_address.as_ptr(),
&mut listener,
)
};
if ret != 0 {
Err(self.last_error())
} else {
Ok(Listener {
tailscale: self,
listener,
})
}
}
pub fn loopback(&mut self) -> Result<Loopback, String> {
let mut addr = [0; 1024];
let mut cred = [0; 33];
let mut proxy_cred = [0; 33];
let ret = unsafe {
tailscale_loopback(
self.inner,
addr.as_mut_ptr(),
addr.len(),
proxy_cred.as_mut_ptr(),
cred.as_mut_ptr(),
)
};
if ret != 0 {
Err(self.last_error())
} else {
let addr = unsafe { CStr::from_ptr(addr.as_ptr()) };
let cred = unsafe { CStr::from_ptr(cred.as_ptr()) };
let proxy_cred = unsafe { CStr::from_ptr(proxy_cred.as_ptr()) };
Ok(Loopback {
address: addr.to_str().unwrap().to_owned(),
credential: cred.to_str().unwrap().to_owned(),
proxy_username: "tsnet",
proxy_credential: proxy_cred.to_str().unwrap().to_owned(),
})
}
}
pub fn enable_funnel_to_localhost_plaintext_http1(
&self,
localhost_port: u16,
) -> Result<(), String> {
let ret = unsafe {
tailscale_enable_funnel_to_localhost_plaintext_http1(self.inner, localhost_port as _)
};
if ret != 0 {
Err(self.last_error())
} else {
Ok(())
}
}
fn last_error(&self) -> String {
let mut buffer = [0; 256];
let ret = unsafe { tailscale_errmsg(self.inner, buffer.as_mut_ptr(), buffer.len() as _) };
if ret != 0 {
return "tailscale internal error: failed to get error message".to_string();
}
let cstr = unsafe { CStr::from_ptr(buffer.as_ptr()) };
cstr.to_string_lossy().into_owned()
}
}
impl Drop for Tailscale {
fn drop(&mut self) {
let _ret = self.close();
}
}
impl Default for Tailscale {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct Loopback {
pub address: String,
pub credential: String,
pub proxy_username: &'static str,
pub proxy_credential: String,
}
impl<'a> Listener<'a> {
pub fn accept(&self) -> Result<TcpStream, String> {
let mut conn = 0;
let ret = unsafe { tailscale_accept(self.listener, &mut conn) };
if ret != 0 {
Err(self.tailscale.last_error())
} else {
Ok(unsafe { TcpStream::from_raw_fd(conn) })
}
}
pub fn get_remote_addr(&self, conn: tailscale_conn) -> Result<IpAddr, String> {
let mut buf = [0i8; 256];
let ret =
unsafe { tailscale_getremoteaddr(self.listener, conn, buf.as_mut_ptr(), buf.len()) };
if ret != 0 {
Err(self.tailscale.last_error())
} else {
let cstr = unsafe { CStr::from_ptr(buf.as_ptr()) };
let s = cstr.to_str().map_err(|e| e.to_string())?;
s.parse::<IpAddr>().map_err(|e| e.to_string())
}
}
pub fn accept_with_addr(&self) -> Result<(TcpStream, IpAddr), String> {
let mut conn = 0;
let ret = unsafe { tailscale_accept(self.listener, &mut conn) };
if ret != 0 {
return Err(self.tailscale.last_error());
}
let addr = self.get_remote_addr(conn)?;
let stream = unsafe { TcpStream::from_raw_fd(conn) };
Ok((stream, addr))
}
pub fn incoming(&self) -> Incoming<'_> {
Incoming { listener: self }
}
fn close(&mut self) -> Result<(), String> {
let ret = unsafe { libc::close(self.listener) };
if ret != 0 {
Err(self.tailscale.last_error())
} else {
Ok(())
}
}
}
impl<'a> Drop for Listener<'a> {
fn drop(&mut self) {
let _ret = self.close();
}
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
#[derive(Debug)]
pub struct Incoming<'a> {
listener: &'a Listener<'a>,
}
impl<'a> Iterator for Incoming<'a> {
type Item = Result<TcpStream, String>;
fn next(&mut self) -> Option<Result<TcpStream, String>> {
Some(self.listener.accept())
}
}
impl FusedIterator for Incoming<'_> {}