refactor: rename to gpcommon

This commit is contained in:
Kevin Yue
2023-05-24 10:07:42 +08:00
parent ec0bff1e36
commit f42f0d248e
28 changed files with 30 additions and 30 deletions

56
gpcommon/src/vpn/ffi.rs Normal file
View File

@@ -0,0 +1,56 @@
use log::{debug, info, trace, warn};
use std::ffi::c_void;
use tokio::sync::mpsc;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(crate) struct Options {
pub server: *const ::std::os::raw::c_char,
pub cookie: *const ::std::os::raw::c_char,
pub script: *const ::std::os::raw::c_char,
pub user_data: *mut c_void,
}
#[link(name = "vpn")]
extern "C" {
#[link_name = "vpn_connect"]
pub(crate) fn connect(options: *const Options) -> ::std::os::raw::c_int;
#[link_name = "vpn_disconnect"]
pub(crate) fn disconnect();
}
#[no_mangle]
extern "C" fn on_vpn_connected(value: i32, sender: *mut c_void) {
let sender = unsafe { &*(sender as *const mpsc::Sender<i32>) };
sender
.blocking_send(value)
.expect("Failed to send VPN connection code");
}
// 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 ::std::os::raw::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);
}
}

153
gpcommon/src/vpn/mod.rs Normal file
View File

@@ -0,0 +1,153 @@
use log::{warn, info, debug};
use serde::{Deserialize, Serialize};
use std::ffi::{c_void, CString};
use std::sync::Arc;
use std::thread;
use tokio::sync::watch;
use tokio::sync::{mpsc, Mutex};
mod ffi;
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum VpnStatus {
Disconnected,
Connecting,
Connected,
Disconnecting,
}
#[derive(Debug)]
struct StatusHolder {
status: VpnStatus,
status_tx: watch::Sender<VpnStatus>,
status_rx: watch::Receiver<VpnStatus>,
}
impl Default for StatusHolder {
fn default() -> Self {
let (status_tx, status_rx) = watch::channel(VpnStatus::Disconnected);
Self {
status: VpnStatus::Disconnected,
status_tx,
status_rx,
}
}
}
impl StatusHolder {
fn set(&mut self, status: VpnStatus) {
self.status = status;
if let Err(err) = self.status_tx.send(status) {
warn!("Failed to send VPN status: {}", err);
}
}
fn status_rx(&self) -> watch::Receiver<VpnStatus> {
self.status_rx.clone()
}
}
#[derive(Debug)]
pub(crate) struct VpnOptions {
server: CString,
cookie: CString,
script: CString,
}
impl VpnOptions {
fn as_oc_options(&self, user_data: *mut c_void) -> ffi::Options {
ffi::Options {
server: self.server.as_ptr(),
cookie: self.cookie.as_ptr(),
script: self.script.as_ptr(),
user_data,
}
}
fn to_cstr(value: &str) -> CString {
CString::new(value.to_string()).expect("Failed to convert to CString")
}
}
#[derive(Debug, Default)]
pub(crate) struct Vpn {
status_holder: Arc<Mutex<StatusHolder>>,
vpn_options: Arc<Mutex<Option<VpnOptions>>>,
}
impl Vpn {
pub async fn status_rx(&self) -> watch::Receiver<VpnStatus> {
self.status_holder.lock().await.status_rx()
}
pub async fn connect(
&self,
server: &str,
cookie: &str,
) -> Result<(), Box<dyn std::error::Error>> {
// Save the VPN options so we can use them later, e.g. reconnect
*self.vpn_options.lock().await = Some(VpnOptions {
server: VpnOptions::to_cstr(server),
cookie: VpnOptions::to_cstr(cookie),
script: VpnOptions::to_cstr("/usr/share/vpnc-scripts/vpnc-script")
});
let vpn_options = self.vpn_options.clone();
let status_holder = self.status_holder.clone();
let (vpn_tx, mut vpn_rx) = mpsc::channel::<i32>(1);
thread::spawn(move || {
let vpn_tx = &vpn_tx as *const _ as *mut c_void;
let oc_options = vpn_options
.blocking_lock()
.as_ref()
.expect("Failed to unwrap vpn_options")
.as_oc_options(vpn_tx);
// Start the VPN connection, this will block until the connection is closed
status_holder.blocking_lock().set(VpnStatus::Connecting);
let ret = unsafe { ffi::connect(&oc_options) };
info!("VPN connection closed with code: {}", ret);
status_holder.blocking_lock().set(VpnStatus::Disconnected);
});
info!("Waiting for the VPN connection...");
if let Some(cmd_pipe_fd) = vpn_rx.recv().await {
info!("VPN connection started, cmd_pipe_fd: {}", cmd_pipe_fd);
self.status_holder.lock().await.set(VpnStatus::Connected);
} else {
warn!("VPN connection failed to start");
}
Ok(())
}
pub async fn disconnect(&self) {
if self.status().await == VpnStatus::Disconnected {
info!("VPN is not connected, nothing to do");
return;
}
info!("Disconnecting VPN...");
unsafe { ffi::disconnect() };
let mut status_rx = self.status_rx().await;
debug!("Waiting for the VPN to disconnect...");
while status_rx.changed().await.is_ok() {
if *status_rx.borrow() == VpnStatus::Disconnected {
info!("VPN disconnected");
break;
}
}
}
pub async fn status(&self) -> VpnStatus {
self.status_holder.lock().await.status
}
}

120
gpcommon/src/vpn/vpn.c Normal file
View File

@@ -0,0 +1,120 @@
#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;
const char *g_vpnc_script;
/* 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)
{
openconnect_setup_tun_device(_vpninfo, g_vpnc_script, NULL);
on_vpn_connected(g_cmd_pipe_fd, g_user_data);
}
/* Initialize VPN connection */
int vpn_connect(const vpn_options *options)
{
struct openconnect_info *vpninfo;
struct utsname utsbuf;
g_user_data = options->user_data;
g_vpnc_script = options->script;
vpninfo = openconnect_vpninfo_new("PAN GlobalProtect", validate_peer_cert, NULL, NULL, print_progress, NULL);
if (!vpninfo)
{
ERROR("openconnect_vpninfo_new failed");
return 1;
}
openconnect_set_loglevel(vpninfo, 1);
openconnect_init_ssl();
openconnect_set_protocol(vpninfo, "gp");
openconnect_set_hostname(vpninfo, options->server);
openconnect_set_cookie(vpninfo, options->cookie);
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");
}
}

62
gpcommon/src/vpn/vpn.h Normal file
View File

@@ -0,0 +1,62 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <openconnect.h>
typedef struct vpn_options
{
const char *server;
const char *cookie;
const char *script;
void *user_data;
} vpn_options;
int vpn_connect(const vpn_options *options);
void vpn_disconnect();
extern void on_vpn_connected(int cmd_pipe_fd, void *user_data);
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__)