2023-06-26 02:00:36 +00:00
//! # nebula-ffi
2023-08-05 02:59:27 +00:00
//! nebula-ffi is a crate for interacting with the Nebula project via a CGo compatability layer.
//! It provides support for running a Nebula VPN directly from a Rust binary, liken to how the default
//! `nebula` binary functions.
//! ## Versioning
//! `nebula-ffi` is automatically updated for every single release or commit made on the slackhq/nebula repository.
//! To build against a **specific release:**
//! ```toml
//! [dependencies]
//! nebula-ffi = { version = "1.7.2" } # for Nebula 1.7.2
//! ```
//! To build against a **specific commit:**
//! ```toml
//! [dependencies]
//! nebula-ffi = { version = "1.7.2+83b6dc7" } # for commit 83b6dc7, which happened *after* the 1.7.2 release
//! ```
//! This versioning is an artifact of the build process for nebula-ffi and how Cargo versioning works.
2023-06-26 02:00:36 +00:00
#![ warn(clippy::pedantic) ]
#![ warn(clippy::nursery) ]
#![ deny(clippy::unwrap_used) ]
#![ deny(clippy::expect_used) ]
#![ deny(missing_docs) ]
#![ deny(clippy::missing_errors_doc) ]
#![ deny(clippy::missing_panics_doc) ]
#![ deny(clippy::missing_safety_doc) ]
#[ allow(non_upper_case_globals) ]
#[ allow(non_camel_case_types) ]
#[ allow(non_snake_case) ]
#[ allow(missing_docs) ]
#[ allow(unused) ]
#[ allow(clippy::derive_partial_eq_without_eq) ]
pub mod generated {
include! ( concat! ( env! ( " OUT_DIR " ) , " /bindings.rs " ) ) ;
}
use std ::error ::Error ;
use std ::ffi ::{ c_char , CString } ;
use std ::fmt ::{ Display , Formatter } ;
use std ::path ::{ Path } ;
use generated ::GoString ;
impl From < & str > for GoString {
#[ allow(clippy::cast_possible_wrap) ]
#[ allow(clippy::expect_used) ]
fn from ( value : & str ) -> Self {
let c_str = CString ::new ( value ) . expect ( " CString::new() failed " ) ;
let ptr = c_str . as_ptr ( ) ;
let go_string = GoString {
p : ptr ,
n : c_str . as_bytes ( ) . len ( ) as isize
} ;
go_string
}
}
#[ allow(clippy::expect_used) ]
fn cstring_to_string ( cstr_ptr : * mut c_char ) -> String {
let cstr = unsafe { CString ::from_raw ( cstr_ptr ) } ;
cstr . to_string_lossy ( ) . to_string ( )
}
/// An instance of a Nebula Control and Config object. This struct does not actually store any information - it is stored in globals in the Go wrapper, however this struct is used to provide typesafety.
pub struct NebulaInstance { }
impl NebulaInstance {
/// Creates a new `NebulaInstance` by calling `NebulaSetup` with the given config path and configTest parameters.
/// # Errors
/// This function will return errors in many, many circumstances. It is not fully documented the scope of errors that may occur.
/// # Panics
/// This function will panic if memory is corrupted while communicating with Go.
pub fn new ( config_path : & Path , config_test : bool ) -> Result < Self , Box < dyn Error > > {
let mut config_path_bytes = unsafe { config_path . display ( ) . to_string ( ) . as_bytes_mut ( ) . to_vec ( ) } ;
config_path_bytes . push ( 0 u8 ) ;
let config_test_u8 = u8 ::from ( config_test ) ;
let res ;
unsafe {
2023-06-26 16:38:19 +00:00
res = generated ::NebulaSetup ( config_path_bytes . as_mut_ptr ( ) . cast ::< c_char > ( ) , config_test_u8 ) ;
2023-06-26 02:00:36 +00:00
}
let res = cstring_to_string ( res ) ;
if res = = " NO_FAILURE " {
Ok ( Self { } )
} else {
Err ( NebulaError ::from_string ( & res ) . into ( ) )
}
}
/// Reloads the Nebula configuration file. Not all configuration values can be reloaded, some may require a full stop and re-setup of Nebula to be applied.
/// # Errors
/// This function will return an error if an error is returned by the CGOFFI layer.
pub fn reload_config ( & self ) -> Result < ( ) , Box < dyn Error > > {
let res ;
unsafe {
res = generated ::NebulaReloadConfig ( ) ;
}
let res = cstring_to_string ( res ) ;
if res = = " NO_FAILURE " {
Ok ( ( ) )
} else {
Err ( NebulaError ::from_string ( & res ) . into ( ) )
}
}
/// Starts the Nebula packet listener server if it is not already running.
/// # Errors
/// This function will return an error if an error is returned by the CGOFFI layer.
pub fn start ( & self ) -> Result < ( ) , Box < dyn Error > > {
let res ;
unsafe {
res = generated ::NebulaStart ( ) ;
}
let res = cstring_to_string ( res ) ;
if res = = " NO_FAILURE " {
Ok ( ( ) )
} else {
Err ( NebulaError ::from_string ( & res ) . into ( ) )
}
}
/// Signals nebula to shutdown, returns after the shutdown is complete
/// # Errors
/// This function will return an error if an error is returned by the CGOFFI layer.
pub fn stop ( & self ) -> Result < ( ) , Box < dyn Error > > {
let res ;
unsafe {
res = generated ::NebulaStop ( ) ;
}
let res = cstring_to_string ( res ) ;
if res = = " NO_FAILURE " {
Ok ( ( ) )
} else {
Err ( NebulaError ::from_string ( & res ) . into ( ) )
}
}
/// This function blocks until a SIGTERM or SIGINT is received, then stops the Nebula packet listener server if it is not already stopped.
/// # Errors
/// This function will return an error if an error is returned by the CGOFFI layer.
pub fn block_and_stop ( & self ) -> Result < ( ) , Box < dyn Error > > {
let res ;
unsafe {
res = generated ::NebulaShutdownBlock ( ) ;
}
let res = cstring_to_string ( res ) ;
if res = = " NO_FAILURE " {
Ok ( ( ) )
} else {
Err ( NebulaError ::from_string ( & res ) . into ( ) )
}
}
/// Asks the UDP listener to rebind it's listener. Mainly used on mobile clients when interfaces change
/// # Errors
/// This function will return an error if an error is returned by the CGOFFI layer.
pub fn rebind_udp ( & self ) -> Result < ( ) , Box < dyn Error > > {
let res ;
unsafe {
res = generated ::NebulaRebindUDP ( ) ;
}
let res = cstring_to_string ( res ) ;
if res = = " NO_FAILURE " {
Ok ( ( ) )
} else {
Err ( NebulaError ::from_string ( & res ) . into ( ) )
}
}
}
#[ derive(Debug) ]
/// An enumeration of all known errors that can be returned by Nebula. As errors are passed by string, this can be a little bit janky, but it works most of the time.
pub enum NebulaError {
/// Returned by nebula when the TUN/TAP device already exists
DeviceOrResourceBusy {
/// The complete error string returned by the Nebula wrapper
error_str : String
} ,
/// An unknown error that the error parser couldn't figure out how to parse.
Unknown {
/// The complete error string returned by the Nebula wrapper
error_str : String
} ,
/// Occurs if you call a function before NebulaSetup has been called
NebulaNotSetup {
/// The complete error string returned by the Nebula wrapper
error_str : String
}
}
impl Display for NebulaError {
fn fmt ( & self , f : & mut Formatter < '_ > ) -> std ::fmt ::Result {
match self {
Self ::DeviceOrResourceBusy { error_str } = > write! ( f , " device or resource busy ({error_str}) " ) ,
Self ::Unknown { error_str } = > write! ( f , " unknown error ({error_str}) " ) ,
Self ::NebulaNotSetup { error_str } = > write! ( f , " attempted to call a function depending on global variable before NebulaSetup ({error_str}) " )
}
}
}
impl Error for NebulaError { }
impl NebulaError {
/// Converts a string error from the Nebula CGO wrapper into a Rust strongtyped error.
#[ must_use ]
pub fn from_string ( string : & str ) -> Self {
if string . starts_with ( " device or resource busy " ) {
Self ::DeviceOrResourceBusy { error_str : string . to_string ( ) }
} else if string . starts_with ( " NebulaSetup has not yet been called " ) {
Self ::NebulaNotSetup { error_str : string . to_string ( ) }
} else {
Self ::Unknown { error_str : string . to_string ( ) }
}
}
}