Build it out

This commit is contained in:
Jan-Erik Rediger
2023-03-12 14:59:30 +01:00
parent b7a68833fb
commit b834ef1c43
7 changed files with 279 additions and 93 deletions

2
.gitignore vendored
View File

@@ -8,4 +8,4 @@ libtailscale.h
/ruby/ext/libtailscale/go.mod
/ruby/ext/libtailscale/go.sum
/ruby/LICENSE
/rust/target
/target

65
Cargo.lock generated Normal file
View File

@@ -0,0 +1,65 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "libtailscale"
version = "0.1.0"
dependencies = [
"thiserror",
]
[[package]]
name = "proc-macro2"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"

2
Cargo.toml Normal file
View File

@@ -0,0 +1,2 @@
[workspace]
members = ["rust"]

View File

@@ -4,3 +4,4 @@ version = "0.1.0"
edition = "2021"
[dependencies]
thiserror = "1.0.39"

View File

@@ -0,0 +1,23 @@
use std::{env, io::Write};
use libtailscale::{ServerBuilder, Network};
fn main() {
let target = env::args()
.skip(1)
.next()
.expect("usage: echoclient host:port");
let srv = ServerBuilder::new()
.hostname("libtailscale-rs-echoclient")
.ephemeral()
.authkey(env::var("TS_AUTHKEY").expect("set TS_AUTHKEY in environment"))
.build()
.unwrap();
let mut conn = srv.connect(Network::Tcp, &target).unwrap();
write!(
conn,
"This is a test of the Tailscale connection service.\n"
)
.unwrap();
}

View File

@@ -1,24 +1,38 @@
use std::io::{Read, self, Write};
use std::{
io::{self, Read, Write},
net::TcpStream,
thread,
};
use libtailscale::*;
use libtailscale::{ServerBuilder, Network};
fn main() {
let ts = Tailscale::new();
ts.set_ephermal(true).unwrap();
ts.up().unwrap();
let ts = ServerBuilder::new().ephemeral().build().unwrap();
let ln = ts.listen(Network::Tcp, ":1999").unwrap();
loop {
let mut conn = ln.accept().unwrap();
let mut buf = [0; 2048];
loop {
let nread = conn.read(&mut buf).unwrap();
if nread > 0 {
io::stdout().write_all(&buf[0..nread]).unwrap();
} else {
break;
for conn in ln {
match conn {
Ok(conn) => {
thread::spawn(move || {
handle_client(conn);
});
}
Err(err) => panic!("{err}"),
}
}
}
fn handle_client(mut stream: TcpStream) {
let mut buf = [0; 2048];
loop {
match stream.read(&mut buf) {
Ok(n) if n == 0 => {
break;
}
Ok(n) => {
io::stdout().write_all(&buf[0..n]).unwrap();
}
Err(err) => panic!("{err}"),
}
}
}

View File

@@ -3,14 +3,33 @@ mod sys;
use std::{
ffi::{c_int, CStr, CString},
fmt::Display,
fs::File,
io::{Read, Write},
mem::ManuallyDrop,
net::TcpStream,
os::fd::FromRawFd,
path::PathBuf,
};
#[derive(Debug)]
pub struct Error(String);
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("can't convert this from an OsString to a String, invalid unicode?")]
CantConvertToString,
#[error("error fetching error from tsnet")]
FetchingErrorFromTSNet,
#[error("[unexpected] string from tsnet has invalid UTF-8: {0}")]
TSNetSentBadUTF8(#[from] std::string::FromUtf8Error),
#[error("tsnet: {0}")]
TSNet(String),
#[error("io error: {0}")]
IO(#[from] std::io::Error),
#[error("your string has NULL in it: {0}")]
NullInString(#[from] std::ffi::NulError),
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug)]
pub enum Network {
@@ -27,7 +46,7 @@ impl Display for Network {
}
}
pub struct Tailscale {
pub struct Server {
/// a handle onto a Tailscale serve
handle: sys::tailscale,
}
@@ -43,59 +62,41 @@ fn err(handle: c_int, code: c_int) -> Result<(), Error> {
let slice = CStr::from_ptr(&errmsg as *const _);
let errmsg = String::from_utf8_lossy(slice.to_bytes()).to_string();
Err(Error(errmsg))
Err(Error::TSNet(errmsg))
}
} else {
Ok(())
}
}
impl Tailscale {
/// Creates a tailscale server object.
///
/// No network connection is initialized until [`start`] is called.
pub fn new() -> Self {
unsafe {
Tailscale {
handle: sys::tailscale_new(),
}
}
}
impl Server {
pub fn connect(&self, network: Network, addr: &str) -> Result<TcpStream> {
let mut conn: sys::tailscale_conn = 0;
let network = CString::new(format!("{}", network)).unwrap();
let addr = CString::new(addr)?;
/// Connects the server to the tailnet.
///
/// Calling this function is optional as it will be called by the first use
/// of `listen` or `dial` on a server.
///
/// See also: `up`.
pub fn start(&self) -> Result<(), Error> {
unsafe { err(self.handle, sys::tailscale_start(self.handle)) }
}
pub fn set_ephermal(&self, ephemeral: bool) -> Result<(), Error> {
unsafe {
err(
self.handle,
sys::tailscale_set_ephemeral(self.handle, ephemeral as c_int),
)
sys::tailscale_dial(self.handle, network.as_ptr(), addr.as_ptr(), &mut conn),
)?
}
}
pub fn up(&self) -> Result<(), Error> {
unsafe { err(self.handle, sys::tailscale_up(self.handle)) }
let conn = conn as c_int;
Ok(unsafe { TcpStream::from_raw_fd(conn) })
}
pub fn listen(&self, network: Network, address: &str) -> Result<Listener, Error> {
unsafe {
let c_network = CString::new(format!("{}", network)).unwrap();
let c_addr = CString::new(address).unwrap();
let network = CString::new(format!("{}", network)).unwrap();
let addr = CString::new(address).unwrap();
let mut out = 0;
let res = err(
self.handle,
sys::tailscale_listen(
self.handle,
c_network.as_ptr(),
c_addr.as_ptr(),
network.as_ptr(),
addr.as_ptr(),
&mut out as *mut _,
),
);
@@ -108,7 +109,7 @@ impl Tailscale {
}
}
impl Drop for Tailscale {
impl Drop for Server {
fn drop(&mut self) {
unsafe {
sys::tailscale_close(self.handle);
@@ -116,20 +117,131 @@ impl Drop for Tailscale {
}
}
#[derive(Default)]
pub struct ServerBuilder {
dir: Option<PathBuf>,
hostname: Option<String>,
authkey: Option<String>,
control_url: Option<String>,
ephemeral: bool,
}
impl ServerBuilder {
/// Creates a server builder.
///
/// Call [`ServerBuilder::build`] to start the server.
pub fn new() -> ServerBuilder {
ServerBuilder::default()
}
pub fn dir(mut self, dir: PathBuf) -> Self {
self.dir = Some(dir);
self
}
pub fn hostname(mut self, hostname: &str) -> Self {
self.hostname = Some(hostname.to_owned());
self
}
pub fn authkey(mut self, authkey: String) -> Self {
self.authkey = Some(authkey);
self
}
pub fn control_url(mut self, control_url: String) -> Self {
self.control_url = Some(control_url);
self
}
pub fn ephemeral(mut self) -> Self {
self.ephemeral = true;
self
}
pub fn build(self) -> Result<Server> {
let result = unsafe {
Server {
handle: sys::tailscale_new(),
}
};
if let Some(dir) = self.dir {
let dir = dir.into_os_string();
let dir = dir.into_string().map_err(|_| Error::CantConvertToString)?;
let dir = CString::new(dir)?;
unsafe {
err(
result.handle,
sys::tailscale_set_dir(result.handle, dir.as_ptr()),
)?
}
}
if let Some(hostname) = self.hostname {
let hostname = CString::new(hostname)?;
unsafe {
err(
result.handle,
sys::tailscale_set_hostname(result.handle, hostname.as_ptr()),
)?
}
}
if let Some(authkey) = self.authkey {
let authkey = CString::new(authkey)?;
unsafe {
err(
result.handle,
sys::tailscale_set_authkey(result.handle, authkey.as_ptr()),
)?
}
}
if let Some(control_url) = self.control_url {
let control_url = CString::new(control_url)?;
unsafe {
err(
result.handle,
sys::tailscale_set_control_url(result.handle, control_url.as_ptr()),
)?
}
}
unsafe {
err(
result.handle,
sys::tailscale_set_ephemeral(result.handle, if self.ephemeral { 1 } else { 0 }),
)?
}
unsafe { err(result.handle, sys::tailscale_start(result.handle))? }
Ok(result)
}
}
pub struct Listener {
ts: sys::tailscale,
handle: sys::tailscale_listener,
}
impl Listener {
pub fn accept(&self) -> Result<Stream, Error> {
pub fn accept(&self) -> Result<TcpStream, Error> {
unsafe {
let mut conn = 0;
let res = err(self.ts, sys::tailscale_accept(self.handle, &mut conn as *mut _));
res.map(|_| Stream { handle: conn })
let res = err(
self.ts,
sys::tailscale_accept(self.handle, &mut conn as *mut _),
);
res.map(|_| TcpStream::from_raw_fd(conn))
}
}
pub fn incoming(&mut self) -> &Self {
self
}
}
impl Drop for Listener {
@@ -140,40 +252,9 @@ impl Drop for Listener {
}
}
#[derive(Debug)]
pub struct Stream {
handle: sys::tailscale_conn,
}
impl Drop for Stream {
fn drop(&mut self) {
unsafe {
drop(File::from_raw_fd(self.handle));
}
}
}
impl Write for Stream {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
unsafe {
let mut fd = ManuallyDrop::new(File::from_raw_fd(self.handle));
fd.write(buf)
}
}
fn flush(&mut self) -> std::io::Result<()> {
unsafe {
let mut fd = ManuallyDrop::new(File::from_raw_fd(self.handle));
fd.flush()
}
}
}
impl Read for Stream {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
unsafe {
let mut fd = ManuallyDrop::new(File::from_raw_fd(self.handle));
fd.read(buf)
}
impl Iterator for Listener {
type Item = Result<TcpStream>;
fn next(&mut self) -> Option<Result<TcpStream>> {
Some(self.accept())
}
}