diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 05f4641..6abda5d 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2825,9 +2825,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.169" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" @@ -5290,6 +5290,7 @@ version = "0.1.0" dependencies = [ "abs-file-macro", "bindgen", + "libc", ] [[package]] diff --git a/src-tauri/tailscale/Cargo.lock b/src-tauri/tailscale/Cargo.lock index ae7b7f6..2716b57 100644 --- a/src-tauri/tailscale/Cargo.lock +++ b/src-tauri/tailscale/Cargo.lock @@ -220,6 +220,7 @@ version = "0.1.0" dependencies = [ "abs-file-macro", "bindgen", + "libc", ] [[package]] diff --git a/src-tauri/tailscale/Cargo.toml b/src-tauri/tailscale/Cargo.toml index 3b42cd0..e7d3323 100644 --- a/src-tauri/tailscale/Cargo.toml +++ b/src-tauri/tailscale/Cargo.toml @@ -3,8 +3,9 @@ name = "tailscale" version = "0.1.0" edition = "2024" -[dependencies] - [build-dependencies] bindgen = "*" abs-file-macro = "0.1.2" + +[dependencies] +libc = "0.2.172" diff --git a/src-tauri/tailscale/src/bindings.rs b/src-tauri/tailscale/src/bindings.rs index 8091375..406961b 100644 --- a/src-tauri/tailscale/src/bindings.rs +++ b/src-tauri/tailscale/src/bindings.rs @@ -6,27 +6,49 @@ pub struct __BindgenComplex { pub re: T, pub im: T, } -pub const __has_safe_buffers: u32 = 1; -pub const __DARWIN_ONLY_64_BIT_INO_T: u32 = 1; -pub const __DARWIN_ONLY_UNIX_CONFORMANCE: u32 = 1; -pub const __DARWIN_ONLY_VERS_1050: u32 = 1; -pub const __DARWIN_UNIX03: u32 = 1; -pub const __DARWIN_64_BIT_INO_T: u32 = 1; -pub const __DARWIN_VERS_1050: u32 = 1; -pub const __DARWIN_NON_CANCELABLE: u32 = 0; -pub const __DARWIN_SUF_EXTSN: &[u8; 14] = b"$DARWIN_EXTSN\0"; -pub const __DARWIN_C_ANSI: u32 = 4096; -pub const __DARWIN_C_FULL: u32 = 900000; -pub const __DARWIN_C_LEVEL: u32 = 900000; -pub const __STDC_WANT_LIB_EXT1__: u32 = 1; -pub const __DARWIN_NO_LONG_LONG: u32 = 0; -pub const _DARWIN_FEATURE_64_BIT_INODE: u32 = 1; -pub const _DARWIN_FEATURE_ONLY_64_BIT_INODE: u32 = 1; -pub const _DARWIN_FEATURE_ONLY_VERS_1050: u32 = 1; -pub const _DARWIN_FEATURE_ONLY_UNIX_CONFORMANCE: u32 = 1; -pub const _DARWIN_FEATURE_UNIX_CONFORMANCE: u32 = 3; -pub const __has_ptrcheck: u32 = 0; -pub const __has_bounds_safety_attributes: u32 = 0; +pub const _ERRNO_H: u32 = 1; +pub const _FEATURES_H: u32 = 1; +pub const _DEFAULT_SOURCE: u32 = 1; +pub const __GLIBC_USE_ISOC2Y: u32 = 0; +pub const __GLIBC_USE_ISOC23: u32 = 0; +pub const __USE_ISOC11: u32 = 1; +pub const __USE_ISOC99: u32 = 1; +pub const __USE_ISOC95: u32 = 1; +pub const __USE_POSIX_IMPLICITLY: u32 = 1; +pub const _POSIX_SOURCE: u32 = 1; +pub const _POSIX_C_SOURCE: u32 = 200809; +pub const __USE_POSIX: u32 = 1; +pub const __USE_POSIX2: u32 = 1; +pub const __USE_POSIX199309: u32 = 1; +pub const __USE_POSIX199506: u32 = 1; +pub const __USE_XOPEN2K: u32 = 1; +pub const __USE_XOPEN2K8: u32 = 1; +pub const _ATFILE_SOURCE: u32 = 1; +pub const __WORDSIZE: u32 = 64; +pub const __WORDSIZE_TIME64_COMPAT32: u32 = 1; +pub const __SYSCALL_WORDSIZE: u32 = 64; +pub const __TIMESIZE: u32 = 64; +pub const __USE_TIME_BITS64: u32 = 1; +pub const __USE_MISC: u32 = 1; +pub const __USE_ATFILE: u32 = 1; +pub const __USE_FORTIFY_LEVEL: u32 = 0; +pub const __GLIBC_USE_DEPRECATED_GETS: u32 = 0; +pub const __GLIBC_USE_DEPRECATED_SCANF: u32 = 0; +pub const __GLIBC_USE_C23_STRTOL: u32 = 0; +pub const _STDC_PREDEF_H: u32 = 1; +pub const __STDC_IEC_559__: u32 = 1; +pub const __STDC_IEC_60559_BFP__: u32 = 201404; +pub const __STDC_IEC_559_COMPLEX__: u32 = 1; +pub const __STDC_IEC_60559_COMPLEX__: u32 = 201404; +pub const __STDC_ISO_10646__: u32 = 201706; +pub const __GNU_LIBRARY__: u32 = 6; +pub const __GLIBC__: u32 = 2; +pub const __GLIBC_MINOR__: u32 = 41; +pub const _SYS_CDEFS_H: u32 = 1; +pub const __glibc_c99_flexarr_available: u32 = 1; +pub const __LDOUBLE_REDIRECTS_TO_FLOAT128_ABI: u32 = 0; +pub const __HAVE_GENERIC_SELECTION: u32 = 1; +pub const _BITS_ERRNO_H: u32 = 1; pub const EPERM: u32 = 1; pub const ENOENT: u32 = 2; pub const ESRCH: u32 = 3; @@ -37,7 +59,7 @@ pub const E2BIG: u32 = 7; pub const ENOEXEC: u32 = 8; pub const EBADF: u32 = 9; pub const ECHILD: u32 = 10; -pub const EDEADLK: u32 = 11; +pub const EAGAIN: u32 = 11; pub const ENOMEM: u32 = 12; pub const EACCES: u32 = 13; pub const EFAULT: u32 = 14; @@ -61,82 +83,124 @@ pub const EMLINK: u32 = 31; pub const EPIPE: u32 = 32; pub const EDOM: u32 = 33; pub const ERANGE: u32 = 34; -pub const EAGAIN: u32 = 35; -pub const EWOULDBLOCK: u32 = 35; -pub const EINPROGRESS: u32 = 36; -pub const EALREADY: u32 = 37; -pub const ENOTSOCK: u32 = 38; -pub const EDESTADDRREQ: u32 = 39; -pub const EMSGSIZE: u32 = 40; -pub const EPROTOTYPE: u32 = 41; -pub const ENOPROTOOPT: u32 = 42; -pub const EPROTONOSUPPORT: u32 = 43; -pub const ESOCKTNOSUPPORT: u32 = 44; -pub const ENOTSUP: u32 = 45; -pub const EPFNOSUPPORT: u32 = 46; -pub const EAFNOSUPPORT: u32 = 47; -pub const EADDRINUSE: u32 = 48; -pub const EADDRNOTAVAIL: u32 = 49; -pub const ENETDOWN: u32 = 50; -pub const ENETUNREACH: u32 = 51; -pub const ENETRESET: u32 = 52; -pub const ECONNABORTED: u32 = 53; -pub const ECONNRESET: u32 = 54; -pub const ENOBUFS: u32 = 55; -pub const EISCONN: u32 = 56; -pub const ENOTCONN: u32 = 57; -pub const ESHUTDOWN: u32 = 58; -pub const ETOOMANYREFS: u32 = 59; -pub const ETIMEDOUT: u32 = 60; -pub const ECONNREFUSED: u32 = 61; -pub const ELOOP: u32 = 62; -pub const ENAMETOOLONG: u32 = 63; -pub const EHOSTDOWN: u32 = 64; -pub const EHOSTUNREACH: u32 = 65; -pub const ENOTEMPTY: u32 = 66; -pub const EPROCLIM: u32 = 67; -pub const EUSERS: u32 = 68; -pub const EDQUOT: u32 = 69; -pub const ESTALE: u32 = 70; -pub const EREMOTE: u32 = 71; -pub const EBADRPC: u32 = 72; -pub const ERPCMISMATCH: u32 = 73; -pub const EPROGUNAVAIL: u32 = 74; -pub const EPROGMISMATCH: u32 = 75; -pub const EPROCUNAVAIL: u32 = 76; -pub const ENOLCK: u32 = 77; -pub const ENOSYS: u32 = 78; -pub const EFTYPE: u32 = 79; -pub const EAUTH: u32 = 80; -pub const ENEEDAUTH: u32 = 81; -pub const EPWROFF: u32 = 82; -pub const EDEVERR: u32 = 83; -pub const EOVERFLOW: u32 = 84; -pub const EBADEXEC: u32 = 85; -pub const EBADARCH: u32 = 86; -pub const ESHLIBVERS: u32 = 87; -pub const EBADMACHO: u32 = 88; -pub const ECANCELED: u32 = 89; -pub const EIDRM: u32 = 90; -pub const ENOMSG: u32 = 91; -pub const EILSEQ: u32 = 92; -pub const ENOATTR: u32 = 93; -pub const EBADMSG: u32 = 94; -pub const EMULTIHOP: u32 = 95; -pub const ENODATA: u32 = 96; -pub const ENOLINK: u32 = 97; -pub const ENOSR: u32 = 98; -pub const ENOSTR: u32 = 99; -pub const EPROTO: u32 = 100; -pub const ETIME: u32 = 101; -pub const EOPNOTSUPP: u32 = 102; -pub const ENOPOLICY: u32 = 103; -pub const ENOTRECOVERABLE: u32 = 104; -pub const EOWNERDEAD: u32 = 105; -pub const EQFULL: u32 = 106; -pub const ELAST: u32 = 106; +pub const EDEADLK: u32 = 35; +pub const ENAMETOOLONG: u32 = 36; +pub const ENOLCK: u32 = 37; +pub const ENOSYS: u32 = 38; +pub const ENOTEMPTY: u32 = 39; +pub const ELOOP: u32 = 40; +pub const EWOULDBLOCK: u32 = 11; +pub const ENOMSG: u32 = 42; +pub const EIDRM: u32 = 43; +pub const ECHRNG: u32 = 44; +pub const EL2NSYNC: u32 = 45; +pub const EL3HLT: u32 = 46; +pub const EL3RST: u32 = 47; +pub const ELNRNG: u32 = 48; +pub const EUNATCH: u32 = 49; +pub const ENOCSI: u32 = 50; +pub const EL2HLT: u32 = 51; +pub const EBADE: u32 = 52; +pub const EBADR: u32 = 53; +pub const EXFULL: u32 = 54; +pub const ENOANO: u32 = 55; +pub const EBADRQC: u32 = 56; +pub const EBADSLT: u32 = 57; +pub const EDEADLOCK: u32 = 35; +pub const EBFONT: u32 = 59; +pub const ENOSTR: u32 = 60; +pub const ENODATA: u32 = 61; +pub const ETIME: u32 = 62; +pub const ENOSR: u32 = 63; +pub const ENONET: u32 = 64; +pub const ENOPKG: u32 = 65; +pub const EREMOTE: u32 = 66; +pub const ENOLINK: u32 = 67; +pub const EADV: u32 = 68; +pub const ESRMNT: u32 = 69; +pub const ECOMM: u32 = 70; +pub const EPROTO: u32 = 71; +pub const EMULTIHOP: u32 = 72; +pub const EDOTDOT: u32 = 73; +pub const EBADMSG: u32 = 74; +pub const EOVERFLOW: u32 = 75; +pub const ENOTUNIQ: u32 = 76; +pub const EBADFD: u32 = 77; +pub const EREMCHG: u32 = 78; +pub const ELIBACC: u32 = 79; +pub const ELIBBAD: u32 = 80; +pub const ELIBSCN: u32 = 81; +pub const ELIBMAX: u32 = 82; +pub const ELIBEXEC: u32 = 83; +pub const EILSEQ: u32 = 84; +pub const ERESTART: u32 = 85; +pub const ESTRPIPE: u32 = 86; +pub const EUSERS: u32 = 87; +pub const ENOTSOCK: u32 = 88; +pub const EDESTADDRREQ: u32 = 89; +pub const EMSGSIZE: u32 = 90; +pub const EPROTOTYPE: u32 = 91; +pub const ENOPROTOOPT: u32 = 92; +pub const EPROTONOSUPPORT: u32 = 93; +pub const ESOCKTNOSUPPORT: u32 = 94; +pub const EOPNOTSUPP: u32 = 95; +pub const EPFNOSUPPORT: u32 = 96; +pub const EAFNOSUPPORT: u32 = 97; +pub const EADDRINUSE: u32 = 98; +pub const EADDRNOTAVAIL: u32 = 99; +pub const ENETDOWN: u32 = 100; +pub const ENETUNREACH: u32 = 101; +pub const ENETRESET: u32 = 102; +pub const ECONNABORTED: u32 = 103; +pub const ECONNRESET: u32 = 104; +pub const ENOBUFS: u32 = 105; +pub const EISCONN: u32 = 106; +pub const ENOTCONN: u32 = 107; +pub const ESHUTDOWN: u32 = 108; +pub const ETOOMANYREFS: u32 = 109; +pub const ETIMEDOUT: u32 = 110; +pub const ECONNREFUSED: u32 = 111; +pub const EHOSTDOWN: u32 = 112; +pub const EHOSTUNREACH: u32 = 113; +pub const EALREADY: u32 = 114; +pub const EINPROGRESS: u32 = 115; +pub const ESTALE: u32 = 116; +pub const EUCLEAN: u32 = 117; +pub const ENOTNAM: u32 = 118; +pub const ENAVAIL: u32 = 119; +pub const EISNAM: u32 = 120; +pub const EREMOTEIO: u32 = 121; +pub const EDQUOT: u32 = 122; +pub const ENOMEDIUM: u32 = 123; +pub const EMEDIUMTYPE: u32 = 124; +pub const ECANCELED: u32 = 125; +pub const ENOKEY: u32 = 126; +pub const EKEYEXPIRED: u32 = 127; +pub const EKEYREVOKED: u32 = 128; +pub const EKEYREJECTED: u32 = 129; +pub const EOWNERDEAD: u32 = 130; +pub const ENOTRECOVERABLE: u32 = 131; +pub const ERFKILL: u32 = 132; +pub const EHWPOISON: u32 = 133; +pub const ENOTSUP: u32 = 95; pub type wchar_t = ::std::os::raw::c_int; -pub type max_align_t = f64; +#[repr(C)] +#[repr(align(16))] +#[derive(Debug, Copy, Clone)] +pub struct max_align_t { + pub __clang_max_align_nonce1: ::std::os::raw::c_longlong, + pub __bindgen_padding_0: u64, + pub __clang_max_align_nonce2: u128, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of max_align_t"][::std::mem::size_of::() - 32usize]; + ["Alignment of max_align_t"][::std::mem::align_of::() - 16usize]; + ["Offset of field: max_align_t::__clang_max_align_nonce1"] + [::std::mem::offset_of!(max_align_t, __clang_max_align_nonce1) - 0usize]; + ["Offset of field: max_align_t::__clang_max_align_nonce2"] + [::std::mem::offset_of!(max_align_t, __clang_max_align_nonce2) - 16usize]; +}; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _GoString_ { @@ -150,9 +214,8 @@ const _: () = { ["Offset of field: _GoString_::p"][::std::mem::offset_of!(_GoString_, p) - 0usize]; ["Offset of field: _GoString_::n"][::std::mem::offset_of!(_GoString_, n) - 8usize]; }; -pub type errno_t = ::std::os::raw::c_int; unsafe extern "C" { - pub fn __error() -> *mut ::std::os::raw::c_int; + pub fn __errno_location() -> *mut ::std::os::raw::c_int; } pub type GoInt8 = ::std::os::raw::c_schar; pub type GoUint8 = ::std::os::raw::c_uchar; diff --git a/src-tauri/tailscale/src/lib.rs b/src-tauri/tailscale/src/lib.rs index 4540762..98602a2 100644 --- a/src-tauri/tailscale/src/lib.rs +++ b/src-tauri/tailscale/src/lib.rs @@ -2,4 +2,207 @@ #![allow(non_camel_case_types)] #![allow(non_snake_case)] -pub mod bindings; +use std::os::raw::{c_int, c_char}; +use std::ffi::{CStr, CString}; +mod bindings; + +type GoInt = i64; + + +use bindings::*; +use libc; + + + +#[derive(Debug)] +pub enum TailscaleError { + ApiError(c_int, String), + BadFileDescriptor, + BufferTooSmall, + NulError(std::ffi::NulError), + InvalidUtf8(std::str::Utf8Error), + IoError(std::io::Error), + InvalidHandle, +} + +impl From for TailscaleError { + fn from(err: std::ffi::NulError) -> Self { + TailscaleError::NulError(err) + } +} + +impl From for TailscaleError { + fn from(err: std::str::Utf8Error) -> Self { + TailscaleError::InvalidUtf8(err) + } +} + +impl From for TailscaleError { + fn from(err: std::io::Error) -> Self { + TailscaleError::IoError(err) + } +} + + +// Helper function to get error message from the server handle +// This helper is needed because TsnetErrmsg requires the server handle (sd) +fn get_tsnet_errmsg(sd: c_int) -> String { + let mut buf = [0u8; 256]; // Choose a reasonable buffer size + let message = unsafe { TsnetErrmsg(sd, buf.as_mut_ptr() as *mut c_char, buf.len()) }; + + if message == 0 { + // Success, buf contains the null-terminated string + let c_str = unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) }; + c_str.to_string_lossy().into_owned() + } else { + // If errmsg itself failed + format!("(Failed to get error message, TsnetErrmsg returned {})", message) + } +} + + +fn parse_tsnet_result(sd: c_int, ret: c_int) -> Result<(), TailscaleError> { + match ret { + 0 => Ok(()), + code if code == libc::EBADF => Err(TailscaleError::BadFileDescriptor), + code if code == libc::ERANGE => Err(TailscaleError::BufferTooSmall), + _ => { + let message = get_tsnet_errmsg(sd); + Err(TailscaleError::ApiError(ret, message)) + } + } +} + + +pub struct Tailscale(c_int); + +// NEEDS REVIEW. CANNOT BE BADLY DONE +impl Drop for Tailscale { + fn drop(&mut self) { + let ret = unsafe { TsnetClose(self.0) }; + if ret != 0 && ret != libc::EBADF { + eprintln!("Error closing Tailscale server {}: {}", self.0, ret); + } + } +} + +impl Tailscale { + pub fn new() -> Self { + Tailscale(unsafe { TsnetNewServer() }) + } + + pub fn start(&self) -> Result<(), TailscaleError> { + let ret = unsafe { TsnetStart(self.0) }; + parse_tsnet_result(self.0, ret) + } + + pub fn up(&self) -> Result<(), TailscaleError> { + let ret = unsafe { TsnetUp(self.0) }; + parse_tsnet_result(self.0, ret) + } + + pub fn close(&self) -> Result<(), TailscaleError> { + let ret = unsafe { TsnetClose(self.0) }; + parse_tsnet_result(self.0, ret) + } + + pub fn set_dir(&self, dir: &str) -> Result<(), TailscaleError> { + let c_dir = CString::new(dir)?; + let ret = unsafe { TsnetSetDir(self.0, c_dir.as_ptr() as *mut c_char) }; + parse_tsnet_result(self.0, ret) + } + + pub fn set_hostname>(&self, hostname: T) -> Result<(), TailscaleError> { + let c_hostname = CString::new(hostname.as_ref())?; + let ret = unsafe { TsnetSetHostname(self.0, c_hostname.as_ptr() as *mut c_char) }; + parse_tsnet_result(self.0, ret) + } + + pub fn set_authkey(&self, authkey: &str) -> Result<(), TailscaleError> { + let c_authkey = CString::new(authkey)?; + let ret = unsafe { TsnetSetAuthKey(self.0, c_authkey.as_ptr() as *mut c_char) }; + parse_tsnet_result(self.0, ret) + } + + pub fn set_control_url(&self, control_url: &str) -> Result<(), TailscaleError> { + let c_control_url = CString::new(control_url)?; + let ret = unsafe { TsnetSetControlURL(self.0, c_control_url.as_ptr() as *mut c_char) }; + parse_tsnet_result(self.0, ret) + } + + pub fn set_ephemeral(&self, ephemeral: bool) -> Result<(), TailscaleError> { + let e: GoInt = if ephemeral { 1 } else { 0 }; + // Use GoInt (i64) based on bindgen output + let ret = unsafe { TsnetSetEphemeral(self.0, e) }; + parse_tsnet_result(self.0, ret) + } + + pub fn set_log_fd(&self, fd: i32) -> Result<(), TailscaleError> { + let ret = unsafe { TsnetSetLogFD(self.0, fd) }; + parse_tsnet_result(self.0, ret) + } + + pub fn get_ips<'a>(&self, buf: &'a mut [u8]) -> Result<&'a str, TailscaleError> { + let ret = unsafe { TsnetGetIps(self.0, buf.as_mut_ptr() as *mut c_char, buf.len()) }; + match ret { + 0 => { + let c_str = unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) }; + c_str.to_str().map_err(TailscaleError::from) // Convert Utf8Error + }, + code if code == libc::EBADF => Err(TailscaleError::BadFileDescriptor), + code if code == libc::ERANGE => Err(TailscaleError::BufferTooSmall), + _ => { + let err_msg = get_tsnet_errmsg(self.0); + Err(TailscaleError::ApiError(ret, err_msg)) + } + } + } + + pub fn loopback( + &self, + addr_buf: &mut [u8], + proxy_buf: &mut [u8], + local_buf: &mut [u8], + ) -> Result<(), TailscaleError> { + // C header says proxy_cred_out and local_api_cred_out must hold 33 bytes. + if proxy_buf.len() < 33 || local_buf.len() < 33 { + return Err(TailscaleError::BufferTooSmall); // Custom check based on docs + } + + let ret = unsafe { + TsnetLoopback( + self.0, + addr_buf.as_mut_ptr() as *mut c_char, + addr_buf.len(), + proxy_buf.as_mut_ptr() as *mut c_char, + local_buf.as_mut_ptr() as *mut c_char, + ) + }; + + parse_tsnet_result(self.0, ret) + } + + /// Configures Funnel to route requests from the public web to a local plaintext HTTP/1 server. + pub fn enable_funnel_to_localhost_plaintext_http1( + &self, + localhost_port: i32, + ) -> Result<(), TailscaleError> { + let ret = unsafe { TsnetEnableFunnelToLocalhostPlaintextHttp1(self.0, localhost_port as c_int) }; + // Returns 0 on success or -1 on error. + parse_tsnet_result(self.0, ret) + } + + pub fn get_last_error_message<'a>(&self, buf: &'a mut [u8]) -> Result<&'a str, TailscaleError> { + let ret = unsafe { TsnetErrmsg(self.0, buf.as_mut_ptr() as *mut c_char, buf.len()) }; + match ret { + 0 => { + let c_str = unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) }; + c_str.to_str().map_err(TailscaleError::from) // Convert Utf8Error + } + code if code == libc::EBADF => Err(TailscaleError::BadFileDescriptor), + code if code == libc::ERANGE => Err(TailscaleError::BufferTooSmall), + // TsnetErrmsg should ideally not return other codes, but handle defensively + _ => Err(TailscaleError::ApiError(ret, format!("TsnetErrmsg returned unknown code {}", ret))), + } + } +} \ No newline at end of file