mirror of
https://github.com/Drop-OSS/libtailscale-drop.git
synced 2026-02-03 22:41:18 +01:00
Build it out
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -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
65
Cargo.lock
generated
Normal 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
2
Cargo.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[workspace]
|
||||
members = ["rust"]
|
||||
@@ -4,3 +4,4 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0.39"
|
||||
|
||||
23
rust/examples/echo_client.rs
Normal file
23
rust/examples/echo_client.rs
Normal 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();
|
||||
}
|
||||
@@ -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}"),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
235
rust/src/lib.rs
235
rust/src/lib.rs
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user