Add C FFI bindings

This commit is contained in:
Jackson Coxson 2022-06-25 15:05:28 -06:00
parent 8cee210ccb
commit f1275fc0d8
6 changed files with 429 additions and 2 deletions

View file

@ -32,3 +32,4 @@ default = [ "bin" ]
bin = [ "clap", "pretty_env_logger", "pcap", "tokio/rt-multi-thread" ]
[lib]
crate-type = [ "rlib", "staticlib" ]

144
cbindgen.toml Normal file
View file

@ -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 = []

40
onetun.h Normal file
View file

@ -0,0 +1,40 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
/**
* 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);

View file

@ -278,7 +278,7 @@ impl Config {
}
}
fn parse_addr(s: Option<&str>) -> anyhow::Result<SocketAddr> {
pub(crate) fn parse_addr(s: Option<&str>) -> anyhow::Result<SocketAddr> {
s.with_context(|| "Missing address")?
.to_socket_addrs()
.with_context(|| "Invalid address")?
@ -286,7 +286,7 @@ fn parse_addr(s: Option<&str>) -> anyhow::Result<SocketAddr> {
.with_context(|| "Could not lookup address")
}
fn parse_ip(s: Option<&str>) -> anyhow::Result<IpAddr> {
pub(crate) fn parse_ip(s: Option<&str>) -> anyhow::Result<IpAddr> {
s.with_context(|| "Missing IP")?
.parse::<IpAddr>()
.with_context(|| "Invalid IP address")

241
src/ffi.rs Normal file
View file

@ -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::<Vec<_>>()
};
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::<Vec<_>>()
};
// 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
}

View file

@ -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;