diff --git a/Cargo.toml b/Cargo.toml index 33b19f5..c929885 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,3 +32,4 @@ default = [ "bin" ] bin = [ "clap", "pretty_env_logger", "pcap", "tokio/rt-multi-thread" ] [lib] +crate-type = [ "rlib", "staticlib" ] \ No newline at end of file diff --git a/cbindgen.toml b/cbindgen.toml new file mode 100644 index 0000000..1f096a6 --- /dev/null +++ b/cbindgen.toml @@ -0,0 +1,144 @@ +language = "C" + + + +############## Options for Wrapping the Contents of the Header ################# + +# header = "/* Text to put at the beginning of the generated file. Probably a license. */" +# trailer = "/* Text to put at the end of the generated file */" +# include_guard = "my_bindings_h" +# pragma_once = true +# autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" +include_version = false +# namespace = "my_namespace" +namespaces = [] +using_namespaces = [] +sys_includes = [] +includes = [] +no_includes = false +after_includes = "" + + + + +############################ Code Style Options ################################ + +braces = "SameLine" +line_length = 100 +tab_width = 2 +documentation = true +documentation_style = "auto" +documentation_length = "full" +line_endings = "LF" # also "CR", "CRLF", "Native" + + + + +############################# Codegen Options ################################## + +style = "both" +sort_by = "Name" # default for `fn.sort_by` and `const.sort_by` +usize_is_size_t = true + + + +[defines] +# "target_os = freebsd" = "DEFINE_FREEBSD" +# "feature = serde" = "DEFINE_SERDE" + + + +[export] +include = [] +exclude = [] +# prefix = "CAPI_" +item_types = [] +renaming_overrides_prefixing = false + + + +[export.rename] + + + +[export.body] + + +[export.mangle] + + +[fn] +rename_args = "None" +# must_use = "MUST_USE_FUNC" +# no_return = "NO_RETURN" +# prefix = "START_FUNC" +# postfix = "END_FUNC" +args = "auto" +sort_by = "Name" + + + + +[struct] +rename_fields = "None" +# must_use = "MUST_USE_STRUCT" +derive_constructor = false +derive_eq = false +derive_neq = false +derive_lt = false +derive_lte = false +derive_gt = false +derive_gte = false + + + + +[enum] +rename_variants = "None" +# must_use = "MUST_USE_ENUM" +add_sentinel = false +prefix_with_name = false +derive_helper_methods = false +derive_const_casts = false +derive_mut_casts = false +# cast_assert_name = "ASSERT" +derive_tagged_enum_destructor = false +derive_tagged_enum_copy_constructor = false +enum_class = true +private_default_tagged_enum_constructor = false + + + + +[const] +allow_static_const = true +allow_constexpr = false +sort_by = "Name" + + + + +[macro_expansion] +bitflags = false + + + + + + +############## Options for How Your Rust library Should Be Parsed ############## + +[parse] +parse_deps = false +# include = [] +exclude = [] +clean = false +extra_bindings = [] + + + +[parse.expand] +crates = [] +all_features = false +default_features = true +features = [] \ No newline at end of file diff --git a/onetun.h b/onetun.h new file mode 100644 index 0000000..86ec10e --- /dev/null +++ b/onetun.h @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include + + +/** + * The capacity of the channel for received IP packets. + */ +#define DISPATCH_CAPACITY 1000 + +typedef struct Bus Bus; + +typedef struct Config Config; + +typedef struct PortForwardConfig PortForwardConfig; + +struct Bus *onetun_new_bus(void); + +struct Config *onetun_new_config(struct PortForwardConfig *const *port_forwards, + unsigned int port_forwards_len, + struct PortForwardConfig *const *remote_forwards, + unsigned int remote_forwards_len, + const char *private_key, + const char *public_key, + const char *endpoint_addr, + const char *endpoint_bind_addr, + const char *source_peer_ip, + int keepalive_seconds, + int max_transmission_unit, + const char *log, + const char *pcap_file); + +struct PortForwardConfig *onetun_new_port_forward(const char *source, + const char *destination, + const char *protocol, + unsigned int remote); + +int32_t onetun_start_tunnels(struct Config *config, struct Bus *bus); diff --git a/src/config.rs b/src/config.rs index 9d9732d..1db620a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -278,7 +278,7 @@ impl Config { } } -fn parse_addr(s: Option<&str>) -> anyhow::Result { +pub(crate) fn parse_addr(s: Option<&str>) -> anyhow::Result { s.with_context(|| "Missing address")? .to_socket_addrs() .with_context(|| "Invalid address")? @@ -286,7 +286,7 @@ fn parse_addr(s: Option<&str>) -> anyhow::Result { .with_context(|| "Could not lookup address") } -fn parse_ip(s: Option<&str>) -> anyhow::Result { +pub(crate) fn parse_ip(s: Option<&str>) -> anyhow::Result { s.with_context(|| "Missing IP")? .parse::() .with_context(|| "Invalid IP address") diff --git a/src/ffi.rs b/src/ffi.rs new file mode 100644 index 0000000..71541ab --- /dev/null +++ b/src/ffi.rs @@ -0,0 +1,241 @@ +// FFI bindings for use in other languages + +use std::{ + os::raw::{c_char, c_int, c_uint}, + str::FromStr, +}; + +use crate::{ + config::{self}, + events::Bus, + start_tunnels, +}; + +#[no_mangle] +pub extern "C" fn onetun_start_tunnels(config: *mut config::Config, bus: *mut Bus) -> i32 { + // Unbox the structs + let config = unsafe { *(std::boxed::Box::from_raw(config)) }; + let bus = unsafe { *(std::boxed::Box::from_raw(bus)) }; + + // Create a runtime for the future + let rt = match tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + { + Ok(rt) => rt, + Err(err) => { + error!("Failed to create runtime: {}", err); + return -1; + } + }; + + // Start the future + rt.block_on(async move { + match start_tunnels(config, bus).await { + Ok(_) => 0, + Err(err) => { + error!("Failed to start tunnels: {}", err); + -1 + } + } + }); + 0 +} + +#[no_mangle] +pub extern "C" fn onetun_new_config( + port_forwards: *const *mut config::PortForwardConfig, + port_forwards_len: c_uint, + remote_forwards: *const *mut config::PortForwardConfig, + remote_forwards_len: c_uint, + private_key: *const c_char, + public_key: *const c_char, + endpoint_addr: *const c_char, + endpoint_bind_addr: *const c_char, + source_peer_ip: *const c_char, + keepalive_seconds: c_int, + max_transmission_unit: c_int, + log: *const c_char, + pcap_file: *const c_char, +) -> *mut config::Config { + // Convert the port configs to a vector of PortForwardConfigs ending with a null pointer + let port_forwards = unsafe { + std::slice::from_raw_parts(port_forwards, port_forwards_len as usize) + .iter() + .filter(|&&x| (!x.is_null() || x != 0 as *mut _)) + .map(|&x| *(std::boxed::Box::from_raw(x))) + .map(|x| x.clone()) + .collect::>() + }; + let remote_forwards = unsafe { + std::slice::from_raw_parts(remote_forwards, remote_forwards_len as usize) + .iter() + .filter(|&&x| (!x.is_null() || x != 0 as *mut _)) + .map(|&x| *(std::boxed::Box::from_raw(x))) + .map(|x| x.clone()) + .collect::>() + }; + + // Convert the c_chars to &str's + let private_key = unsafe { + match std::ffi::CStr::from_ptr(private_key).to_str() { + Ok(x) => match config::X25519SecretKey::from_str(x) { + Ok(x) => x, + Err(e) => { + println!("Error parsing private key: {}", e); + return std::ptr::null_mut(); + } + }, + Err(_) => return std::ptr::null_mut(), + } + }; + let public_key = unsafe { + match std::ffi::CStr::from_ptr(public_key).to_str() { + Ok(x) => match config::X25519PublicKey::from_str(x) { + Ok(x) => x, + Err(e) => { + println!("Error parsing public key: {}", e); + return std::ptr::null_mut(); + } + }, + Err(_) => return std::ptr::null_mut(), + } + }; + let endpoint_addr = unsafe { + match std::ffi::CStr::from_ptr(endpoint_addr).to_str() { + Ok(x) => match config::parse_addr(Some(x)) { + Ok(x) => x, + Err(e) => { + println!("Error parsing endpoint address: {}", e); + return std::ptr::null_mut(); + } + }, + Err(_) => return std::ptr::null_mut(), + } + }; + let endpoint_bind_addr = unsafe { + match std::ffi::CStr::from_ptr(endpoint_bind_addr).to_str() { + Ok(x) => match config::parse_addr(Some(x)) { + Ok(x) => x, + Err(e) => { + println!("Error parsing endpoint bind address: {}", e); + return std::ptr::null_mut(); + } + }, + Err(_) => return std::ptr::null_mut(), + } + }; + let source_peer_ip = unsafe { + match std::ffi::CStr::from_ptr(source_peer_ip).to_str() { + Ok(x) => match config::parse_ip(Some(x)) { + Ok(x) => x, + Err(e) => { + println!("Error parsing source peer IP: {}", e); + return std::ptr::null_mut(); + } + }, + Err(_) => return std::ptr::null_mut(), + } + }; + let log = unsafe { + match std::ffi::CStr::from_ptr(log).to_str() { + Ok(x) => x.to_string(), + Err(_) => return std::ptr::null_mut(), + } + }; + let pcap_file = unsafe { + match std::ffi::CStr::from_ptr(pcap_file).to_str() { + Ok(x) => { + if x == "" { + None + } else { + Some(x.to_string()) + } + } + Err(_) => return std::ptr::null_mut(), + } + }; + + let keepalive_seconds = if keepalive_seconds == -1 { + None + } else { + Some(keepalive_seconds as u16) + }; + + // Create the config + let config = config::Config { + port_forwards, + remote_port_forwards: remote_forwards, + private_key: std::sync::Arc::new(private_key), + endpoint_public_key: std::sync::Arc::new(public_key), + endpoint_addr, + endpoint_bind_addr, + source_peer_ip, + keepalive_seconds, + max_transmission_unit: max_transmission_unit as usize, + log, + pcap_file, + warnings: vec![], + }; + + // Return a pointer to the config + Box::into_raw(Box::new(config)) as *mut config::Config +} + +#[no_mangle] +pub extern "C" fn onetun_new_bus() -> *mut Bus { + let bus = Bus::new(); + Box::into_raw(Box::new(bus)) as *mut Bus +} + +#[no_mangle] +pub extern "C" fn onetun_new_port_forward( + source: *const c_char, + destination: *const c_char, + protocol: *const c_char, + remote: c_uint, +) -> *mut config::PortForwardConfig { + // Create strings from pointers + let source = unsafe { + match std::ffi::CStr::from_ptr(source).to_str() { + Ok(s) => s, + Err(_) => return std::ptr::null_mut(), + } + }; + let destination = unsafe { + match std::ffi::CStr::from_ptr(destination).to_str() { + Ok(s) => s, + Err(_) => return std::ptr::null_mut(), + } + }; + let protocol = unsafe { + match std::ffi::CStr::from_ptr(protocol).to_str() { + Ok(s) => s.to_lowercase(), + Err(_) => return std::ptr::null_mut(), + } + }; + + // Create config + let config = config::PortForwardConfig { + source: std::net::SocketAddr::V4(match std::net::SocketAddrV4::from_str(source) { + Ok(s) => s, + Err(_) => return std::ptr::null_mut(), + }), + destination: std::net::SocketAddr::V4( + match std::net::SocketAddrV4::from_str(destination) { + Ok(s) => s, + Err(_) => return std::ptr::null_mut(), + }, + ), + protocol: match protocol.as_str() { + "tcp" => config::PortProtocol::Tcp, + "udp" => config::PortProtocol::Udp, + _ => return std::ptr::null_mut(), + }, + remote: remote == 0, + }; + + // Create pointer to config + let config = Box::new(config); + Box::into_raw(config) as *mut config::PortForwardConfig +} diff --git a/src/lib.rs b/src/lib.rs index 57a3fc4..181a982 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ use crate::wg::WireGuardTunnel; pub mod config; pub mod events; +pub mod ffi; #[cfg(feature = "pcap")] pub mod pcap; pub mod tunnel;