Refactor using Tauri (#278)

This commit is contained in:
Kevin Yue
2024-01-16 22:18:20 +08:00
committed by GitHub
parent edc13ed14d
commit 04a916a3e1
199 changed files with 10153 additions and 7203 deletions

View File

@@ -0,0 +1,13 @@
[package]
name = "openconnect"
version.workspace = true
edition.workspace = true
license.workspace = true
links = "openconnect"
[dependencies]
log.workspace = true
is_executable.workspace = true
[build-dependencies]
cc = "1"

View File

@@ -0,0 +1,12 @@
fn main() {
// Link to the native openconnect library
println!("cargo:rustc-link-lib=openconnect");
println!("cargo:rerun-if-changed=src/ffi/vpn.c");
println!("cargo:rerun-if-changed=src/ffi/vpn.h");
// Compile the vpn.c file
cc::Build::new()
.file("src/ffi/vpn.c")
.include("src/ffi")
.compile("vpn");
}

View File

@@ -0,0 +1,71 @@
use crate::Vpn;
use log::{debug, info, trace, warn};
use std::ffi::{c_char, c_int, c_void};
#[repr(C)]
#[derive(Debug)]
pub(crate) struct ConnectOptions {
pub user_data: *mut c_void,
pub server: *const c_char,
pub cookie: *const c_char,
pub user_agent: *const c_char,
pub script: *const c_char,
pub os: *const c_char,
pub certificate: *const c_char,
pub servercert: *const c_char,
}
#[link(name = "vpn")]
extern "C" {
#[link_name = "vpn_connect"]
fn vpn_connect(
options: *const ConnectOptions,
callback: extern "C" fn(i32, *mut c_void),
) -> c_int;
#[link_name = "vpn_disconnect"]
fn vpn_disconnect();
}
pub(crate) fn connect(options: &ConnectOptions) -> i32 {
unsafe { vpn_connect(options, on_vpn_connected) }
}
pub(crate) fn disconnect() {
unsafe { vpn_disconnect() }
}
#[no_mangle]
extern "C" fn on_vpn_connected(pipe_fd: i32, vpn: *mut c_void) {
let vpn = unsafe { &*(vpn as *const Vpn) };
vpn.on_connected(pipe_fd);
}
// Logger used in the C code.
// level: 0 = error, 1 = info, 2 = debug, 3 = trace
// map the error level log in openconnect to the warning level
#[no_mangle]
extern "C" fn vpn_log(level: i32, message: *const c_char) {
let message = unsafe { std::ffi::CStr::from_ptr(message) };
let message = message.to_str().unwrap_or("Invalid log message");
// Strip the trailing newline
let message = message.trim_end_matches('\n');
if level == 0 {
warn!("{}", message);
} else if level == 1 {
info!("{}", message);
} else if level == 2 {
debug!("{}", message);
} else if level == 3 {
trace!("{}", message);
} else {
warn!(
"Unknown log level: {}, enable DEBUG log level to see more details",
level
);
debug!("{}", message);
}
}

View File

@@ -0,0 +1,144 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <openconnect.h>
#include "vpn.h"
void *g_user_data;
static int g_cmd_pipe_fd;
static const char *g_vpnc_script;
static vpn_connected_callback on_vpn_connected;
/* Validate the peer certificate */
static int validate_peer_cert(__attribute__((unused)) void *_vpninfo, const char *reason)
{
INFO("Validating peer cert: %s", reason);
return 0;
}
/* Print progress messages */
static void print_progress(__attribute__((unused)) void *_vpninfo, int level, const char *format, ...)
{
va_list args;
va_start(args, format);
char *message = format_message(format, args);
va_end(args);
if (message == NULL)
{
ERROR("Failed to format log message");
}
else
{
LOG(level, message);
free(message);
}
}
static void setup_tun_handler(void *_vpninfo)
{
int ret = openconnect_setup_tun_device(_vpninfo, g_vpnc_script, NULL);
if (!ret) {
on_vpn_connected(g_cmd_pipe_fd, g_user_data);
}
}
/* Initialize VPN connection */
int vpn_connect(const vpn_options *options, vpn_connected_callback callback)
{
INFO("openconnect version: %s", openconnect_get_version());
struct openconnect_info *vpninfo;
struct utsname utsbuf;
g_user_data = options->user_data;
g_vpnc_script = options->script;
on_vpn_connected = callback;
INFO("User agent: %s", options->user_agent);
INFO("VPNC script: %s", options->script);
INFO("OS: %s", options->os);
vpninfo = openconnect_vpninfo_new(options->user_agent, validate_peer_cert, NULL, NULL, print_progress, NULL);
if (!vpninfo)
{
ERROR("openconnect_vpninfo_new failed");
return 1;
}
openconnect_set_loglevel(vpninfo, PRG_TRACE);
openconnect_init_ssl();
openconnect_set_protocol(vpninfo, "gp");
openconnect_set_hostname(vpninfo, options->server);
openconnect_set_cookie(vpninfo, options->cookie);
if (options->os) {
openconnect_set_reported_os(vpninfo, options->os);
}
if (options->certificate)
{
INFO("Setting client certificate: %s", options->certificate);
openconnect_set_client_cert(vpninfo, options->certificate, NULL);
}
if (options->servercert) {
INFO("Setting server certificate: %s", options->servercert);
openconnect_set_system_trust(vpninfo, 0);
}
g_cmd_pipe_fd = openconnect_setup_cmd_pipe(vpninfo);
if (g_cmd_pipe_fd < 0)
{
ERROR("openconnect_setup_cmd_pipe failed");
return 1;
}
if (!uname(&utsbuf))
{
openconnect_set_localname(vpninfo, utsbuf.nodename);
}
// Essential step
if (openconnect_make_cstp_connection(vpninfo) != 0)
{
ERROR("openconnect_make_cstp_connection failed");
return 1;
}
if (openconnect_setup_dtls(vpninfo, 60) != 0)
{
openconnect_disable_dtls(vpninfo);
}
// Essential step
openconnect_set_setup_tun_handler(vpninfo, setup_tun_handler);
while (1)
{
int ret = openconnect_mainloop(vpninfo, 300, 10);
if (ret)
{
INFO("openconnect_mainloop returned %d, exiting", ret);
openconnect_vpninfo_free(vpninfo);
return ret;
}
INFO("openconnect_mainloop returned 0, reconnecting");
}
}
/* Stop the VPN connection */
void vpn_disconnect()
{
char cmd = OC_CMD_CANCEL;
if (write(g_cmd_pipe_fd, &cmd, 1) < 0)
{
ERROR("Failed to write to command pipe, VPN connection may not be stopped");
}
}

View File

@@ -0,0 +1,68 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <openconnect.h>
typedef void (*vpn_connected_callback)(int cmd_pipe_fd, void *user_data);
typedef struct vpn_options
{
void *user_data;
const char *server;
const char *cookie;
const char *user_agent;
const char *script;
const char *os;
const char *certificate;
const char *servercert;
} vpn_options;
int vpn_connect(const vpn_options *options, vpn_connected_callback callback);
void vpn_disconnect();
extern void vpn_log(int level, const char *msg);
static char *format_message(const char *format, va_list args)
{
va_list args_copy;
va_copy(args_copy, args);
int len = vsnprintf(NULL, 0, format, args_copy);
va_end(args_copy);
char *buffer = malloc(len + 1);
if (buffer == NULL)
{
return NULL;
}
vsnprintf(buffer, len + 1, format, args);
return buffer;
}
static void _log(int level, ...)
{
va_list args;
va_start(args, level);
char *format = va_arg(args, char *);
char *message = format_message(format, args);
va_end(args);
if (message == NULL)
{
vpn_log(PRG_ERR, "Failed to format log message");
}
else
{
vpn_log(level, message);
free(message);
}
}
#define LOG(level, ...) _log(level, __VA_ARGS__)
#define ERROR(...) LOG(PRG_ERR, __VA_ARGS__)
#define INFO(...) LOG(PRG_INFO, __VA_ARGS__)
#define DEBUG(...) LOG(PRG_DEBUG, __VA_ARGS__)
#define TRACE(...) LOG(PRG_TRACE, __VA_ARGS__)

View File

@@ -0,0 +1,5 @@
mod ffi;
mod vpn;
mod vpnc_script;
pub use vpn::*;

View File

@@ -0,0 +1,131 @@
use std::{
ffi::{c_char, CString},
sync::{Arc, RwLock},
};
use log::info;
use crate::{ffi, vpnc_script::find_default_vpnc_script};
type OnConnectedCallback = Arc<RwLock<Option<Box<dyn FnOnce() + 'static + Send + Sync>>>>;
pub struct Vpn {
server: CString,
cookie: CString,
user_agent: CString,
script: CString,
os: CString,
certificate: Option<CString>,
servercert: Option<CString>,
callback: OnConnectedCallback,
}
impl Vpn {
pub fn builder(server: &str, cookie: &str) -> VpnBuilder {
VpnBuilder::new(server, cookie)
}
pub fn connect(&self, on_connected: impl FnOnce() + 'static + Send + Sync) -> i32 {
self
.callback
.write()
.unwrap()
.replace(Box::new(on_connected));
let options = self.build_connect_options();
ffi::connect(&options)
}
pub(crate) fn on_connected(&self, pipe_fd: i32) {
info!("Connected to VPN, pipe_fd: {}", pipe_fd);
if let Some(callback) = self.callback.write().unwrap().take() {
callback();
}
}
pub fn disconnect(&self) {
ffi::disconnect();
}
fn build_connect_options(&self) -> ffi::ConnectOptions {
ffi::ConnectOptions {
user_data: self as *const _ as *mut _,
server: self.server.as_ptr(),
cookie: self.cookie.as_ptr(),
user_agent: self.user_agent.as_ptr(),
script: self.script.as_ptr(),
os: self.os.as_ptr(),
certificate: Self::option_to_ptr(&self.certificate),
servercert: Self::option_to_ptr(&self.servercert),
}
}
fn option_to_ptr(option: &Option<CString>) -> *const c_char {
match option {
Some(value) => value.as_ptr(),
None => std::ptr::null(),
}
}
}
pub struct VpnBuilder {
server: String,
cookie: String,
user_agent: Option<String>,
script: Option<String>,
os: Option<String>,
}
impl VpnBuilder {
fn new(server: &str, cookie: &str) -> Self {
Self {
server: server.to_string(),
cookie: cookie.to_string(),
user_agent: None,
script: None,
os: None,
}
}
pub fn user_agent<T: Into<Option<String>>>(mut self, user_agent: T) -> Self {
self.user_agent = user_agent.into();
self
}
pub fn script<T: Into<Option<String>>>(mut self, script: T) -> Self {
self.script = script.into();
self
}
pub fn os<T: Into<Option<String>>>(mut self, os: T) -> Self {
self.os = os.into();
self
}
pub fn build(self) -> Vpn {
let user_agent = self.user_agent.unwrap_or_default();
let script = self
.script
.or_else(find_default_vpnc_script)
.unwrap_or_default();
let os = self.os.unwrap_or("linux".to_string());
Vpn {
server: Self::to_cstring(&self.server),
cookie: Self::to_cstring(&self.cookie),
user_agent: Self::to_cstring(&user_agent),
script: Self::to_cstring(&script),
os: Self::to_cstring(&os),
certificate: None,
servercert: None,
callback: Default::default(),
}
}
fn to_cstring(value: &str) -> CString {
CString::new(value.to_string()).expect("Failed to convert to CString")
}
}

View File

@@ -0,0 +1,23 @@
use is_executable::IsExecutable;
use std::path::Path;
const VPNC_SCRIPT_LOCATIONS: [&str; 5] = [
"/usr/local/share/vpnc-scripts/vpnc-script",
"/usr/local/sbin/vpnc-script",
"/usr/share/vpnc-scripts/vpnc-script",
"/usr/sbin/vpnc-script",
"/etc/vpnc/vpnc-script",
];
pub(crate) fn find_default_vpnc_script() -> Option<String> {
for location in VPNC_SCRIPT_LOCATIONS.iter() {
let path = Path::new(location);
if path.is_executable() {
return Some(location.to_string());
}
}
log::warn!("vpnc-script not found");
None
}