Add support for ini parsing
This commit is contained in:
parent
3ba0004370
commit
59ddc36f2c
11 changed files with 300 additions and 32 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -6,3 +6,7 @@ target/
|
|||
|
||||
.DS_STORE
|
||||
.idea/
|
||||
|
||||
burrow.sock
|
||||
burrow.db
|
||||
tmp/
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/jpsim/SourceKitten.git",
|
||||
"state" : {
|
||||
"revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56",
|
||||
"version" : "0.34.1"
|
||||
"revision" : "fd4df99170f5e9d7cf9aa8312aa8506e0e7a44e7",
|
||||
"version" : "0.35.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -32,8 +32,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-argument-parser.git",
|
||||
"state" : {
|
||||
"revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531",
|
||||
"version" : "1.2.3"
|
||||
"revision" : "fee6933f37fde9a5e12a1e4aeaa93fe60116ff2a",
|
||||
"version" : "1.2.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -41,8 +41,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-syntax.git",
|
||||
"state" : {
|
||||
"revision" : "64889f0c732f210a935a0ad7cda38f77f876262d",
|
||||
"version" : "509.1.1"
|
||||
"revision" : "303e5c5c36d6a558407d364878df131c3546fad8",
|
||||
"version" : "510.0.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -51,7 +51,7 @@
|
|||
"location" : "https://github.com/realm/SwiftLint.git",
|
||||
"state" : {
|
||||
"branch" : "main",
|
||||
"revision" : "7595ad3fafc1a31086dc40ba01fd898bf6b42d5f"
|
||||
"revision" : "b42f6ffe77159aed1060bf607212a0410c7623b8"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
72
Cargo.lock
generated
72
Cargo.lock
generated
|
|
@ -353,6 +353,7 @@ dependencies = [
|
|||
"rand_core",
|
||||
"ring",
|
||||
"rusqlite",
|
||||
"rust-ini",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
@ -586,6 +587,26 @@ dependencies = [
|
|||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.1.5"
|
||||
|
|
@ -641,6 +662,12 @@ version = "0.8.19"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
|
|
@ -699,6 +726,15 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlv-list"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
|
||||
dependencies = [
|
||||
"const-random",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.16"
|
||||
|
|
@ -1601,6 +1637,16 @@ dependencies = [
|
|||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-multimap"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
|
||||
dependencies = [
|
||||
"dlv-list",
|
||||
"hashbrown 0.14.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
|
|
@ -1947,6 +1993,17 @@ dependencies = [
|
|||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-ini"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d625ed57d8f49af6cfa514c42e1a71fadcff60eb0b1c517ff82fe41aa025b41"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ordered-multimap",
|
||||
"trim-in-place",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.23"
|
||||
|
|
@ -2347,6 +2404,15 @@ version = "0.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
|
|
@ -2627,6 +2693,12 @@ dependencies = [
|
|||
"tracing-log 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trim-in-place"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc"
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ once_cell = "1.19"
|
|||
console-subscriber = { version = "0.2.0", optional = true }
|
||||
console = "0.15.8"
|
||||
toml = "0.8.12"
|
||||
rust-ini = "0.21.0"
|
||||
|
||||
[dependencies.rusqlite]
|
||||
version = "0.31.0"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use crate::{
|
|||
ServerConfig,
|
||||
ServerInfo,
|
||||
},
|
||||
database::{get_connection, load_interface, dump_interface},
|
||||
database::{dump_interface, get_connection, load_interface},
|
||||
wireguard::{Config, Interface},
|
||||
};
|
||||
|
||||
|
|
@ -116,10 +116,10 @@ impl DaemonInstance {
|
|||
.await?;
|
||||
Ok(DaemonResponseData::None)
|
||||
}
|
||||
DaemonCommand::AddConfigToml(config_toml) => {
|
||||
DaemonCommand::AddConfig(cfig_raw) => {
|
||||
let conn = get_connection(self.db_path.as_deref())?;
|
||||
let cfig = Config::from_toml(&config_toml)?;
|
||||
let _if_id = dump_interface(&conn, &cfig)?;
|
||||
let cfig = Config::from_content_fmt(&cfig_raw.content, &cfig_raw.fmt)?;
|
||||
let _if_id = dump_interface(&conn, &cfig, cfig_raw.interface_id)?;
|
||||
Ok(DaemonResponseData::None)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ pub enum DaemonCommand {
|
|||
ServerConfig,
|
||||
Stop,
|
||||
ReloadConfig(String),
|
||||
AddConfigToml(String),
|
||||
AddConfig(AddConfigOptions),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||
|
|
@ -18,6 +18,13 @@ pub struct DaemonStartOptions {
|
|||
pub tun: TunOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct AddConfigOptions {
|
||||
pub content: String,
|
||||
pub fmt: String,
|
||||
pub interface_id: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct DaemonRequest {
|
||||
pub id: u64,
|
||||
|
|
|
|||
|
|
@ -92,7 +92,24 @@ pub fn load_interface(conn: &Connection, interface_id: &str) -> Result<Config> {
|
|||
Ok(Config { interface: iface, peers })
|
||||
}
|
||||
|
||||
pub fn dump_interface(conn: &Connection, config: &Config) -> Result<i64> {
|
||||
pub fn dump_interface(
|
||||
conn: &Connection,
|
||||
config: &Config,
|
||||
interface_id: Option<i64>,
|
||||
) -> Result<i64> {
|
||||
let interface_id = if let Some(id) = interface_id {
|
||||
let mut stmt = conn.prepare("INSERT INTO wg_interface (private_key, dns, address, listen_port, mtu, id) VALUES (?, ?, ?, ?, ?, ?)")?;
|
||||
let cif = &config.interface;
|
||||
stmt.execute(params![
|
||||
cif.private_key,
|
||||
to_lst(&cif.dns),
|
||||
to_lst(&cif.address),
|
||||
cif.listen_port,
|
||||
cif.mtu,
|
||||
id
|
||||
])?;
|
||||
id
|
||||
} else {
|
||||
let mut stmt = conn.prepare("INSERT INTO wg_interface (private_key, dns, address, listen_port, mtu) VALUES (?, ?, ?, ?, ?)")?;
|
||||
let cif = &config.interface;
|
||||
stmt.execute(params![
|
||||
|
|
@ -102,7 +119,9 @@ pub fn dump_interface(conn: &Connection, config: &Config) -> Result<i64> {
|
|||
cif.listen_port,
|
||||
cif.mtu
|
||||
])?;
|
||||
let interface_id = conn.last_insert_rowid();
|
||||
conn.last_insert_rowid()
|
||||
};
|
||||
|
||||
let mut stmt = conn.prepare("INSERT INTO wg_peer (interface_id, public_key, preshared_key, allowed_ips, endpoint) VALUES (?, ?, ?, ?, ?)")?;
|
||||
for peer in &config.peers {
|
||||
stmt.execute(params![
|
||||
|
|
@ -121,7 +140,7 @@ pub fn get_connection(path: Option<&Path>) -> Result<Connection> {
|
|||
if !p.exists() {
|
||||
let conn = Connection::open(p)?;
|
||||
initialize_tables(&conn)?;
|
||||
dump_interface(&conn, &Config::default())?;
|
||||
dump_interface(&conn, &Config::default(), None)?;
|
||||
return Ok(conn);
|
||||
}
|
||||
Ok(Connection::open(p)?)
|
||||
|
|
@ -129,8 +148,6 @@ pub fn get_connection(path: Option<&Path>) -> Result<Connection> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
|
@ -138,7 +155,7 @@ mod tests {
|
|||
let conn = Connection::open_in_memory().unwrap();
|
||||
initialize_tables(&conn).unwrap();
|
||||
let config = Config::default();
|
||||
dump_interface(&conn, &config).unwrap();
|
||||
dump_interface(&conn, &config, None).unwrap();
|
||||
let loaded = load_interface(&conn, "1").unwrap();
|
||||
assert_eq!(config, loaded);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
use std::{borrow::Cow, path::PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
|
||||
use crate::daemon::rpc::request::AddConfigOptions;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
mod daemon;
|
||||
pub(crate) mod tracing;
|
||||
|
|
@ -47,6 +51,16 @@ enum Commands {
|
|||
ServerConfig,
|
||||
/// Reload Config
|
||||
ReloadConfig(ReloadConfigArgs),
|
||||
/// Add Server Config
|
||||
AddConfig(AddServerConfigArgs),
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
struct AddServerConfigArgs {
|
||||
#[clap(short, long)]
|
||||
path: PathBuf,
|
||||
#[clap(short, long)]
|
||||
interface_id: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
|
@ -59,7 +73,12 @@ struct ReloadConfigArgs {
|
|||
struct StartArgs {}
|
||||
|
||||
#[derive(Args)]
|
||||
struct DaemonArgs {}
|
||||
struct DaemonArgs {
|
||||
#[clap(long, short)]
|
||||
socket_path: Option<PathBuf>,
|
||||
#[clap(long, short)]
|
||||
db_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
async fn try_start() -> Result<()> {
|
||||
|
|
@ -132,6 +151,24 @@ async fn try_reloadconfig(interface_id: String) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
async fn try_add_server_config(path: &PathBuf, interface_id: Option<i64>) -> Result<()> {
|
||||
let mut client = DaemonClient::new().await?;
|
||||
let ext = path
|
||||
.extension()
|
||||
.map(|e| e.to_string_lossy())
|
||||
.unwrap_or_else(|| Cow::Borrowed("toml"));
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
let res = client
|
||||
.send_command(DaemonCommand::AddConfig(AddConfigOptions {
|
||||
content,
|
||||
fmt: ext.to_string(),
|
||||
interface_id,
|
||||
}))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<()> {
|
||||
|
|
@ -139,12 +176,20 @@ async fn main() -> Result<()> {
|
|||
|
||||
let cli = Cli::parse();
|
||||
match &cli.command {
|
||||
Commands::Start(..) => try_start().await?,
|
||||
Commands::Start(_) => try_start().await?,
|
||||
Commands::Stop => try_stop().await?,
|
||||
Commands::Daemon(_) => daemon::daemon_main(None, None, None).await?,
|
||||
Commands::Daemon(daemon_args) => {
|
||||
daemon::daemon_main(
|
||||
daemon_args.socket_path.as_ref().map(|p| p.as_path()),
|
||||
daemon_args.db_path.as_ref().map(|p| p.as_path()),
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
Commands::ServerInfo => try_serverinfo().await?,
|
||||
Commands::ServerConfig => try_serverconfig().await?,
|
||||
Commands::ReloadConfig(args) => try_reloadconfig(args.interface_id.clone()).await?,
|
||||
Commands::AddConfig(args) => try_add_server_config(&args.path, args.interface_id).await?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ use std::{net::ToSocketAddrs, str::FromStr};
|
|||
use anyhow::{anyhow, Error, Result};
|
||||
use base64::{engine::general_purpose, Engine};
|
||||
use fehler::throws;
|
||||
use ini::{Ini, Properties};
|
||||
use ip_network::IpNetwork;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use x25519_dalek::{PublicKey, StaticSecret};
|
||||
|
||||
use super::inifield::IniField;
|
||||
use crate::wireguard::{Interface as WgInterface, Peer as WgPeer};
|
||||
|
||||
#[throws]
|
||||
|
|
@ -53,6 +55,7 @@ pub struct Interface {
|
|||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
#[serde(rename = "Peer")]
|
||||
pub peers: Vec<Peer>,
|
||||
pub interface: Interface, // Support for multiple interfaces?
|
||||
}
|
||||
|
|
@ -115,10 +118,69 @@ impl Default for Config {
|
|||
}
|
||||
}
|
||||
|
||||
fn props_get<T>(props: &Properties, key: &str) -> T
|
||||
where
|
||||
T: From<IniField>,
|
||||
{
|
||||
IniField::from(props.get(key)).into()
|
||||
}
|
||||
|
||||
impl TryFrom<&Properties> for Interface {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(props: &Properties) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
private_key: props_get(props, "PrivateKey"),
|
||||
address: props_get(props, "Address"),
|
||||
listen_port: props_get(props, "ListenPort"),
|
||||
dns: props_get(props, "DNS"),
|
||||
mtu: props_get(props, "MTU"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Properties> for Peer {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(props: &Properties) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
public_key: props_get(props, "PublicKey"),
|
||||
preshared_key: props_get(props, "PresharedKey"),
|
||||
allowed_ips: props_get(props, "AllowedIPs"),
|
||||
endpoint: props_get(props, "Endpoint"),
|
||||
persistent_keepalive: props_get(props, "PersistentKeepalive"),
|
||||
name: props_get(props, "Name"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn from_toml(toml: &str) -> Result<Self> {
|
||||
toml::from_str(toml).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn from_ini(ini: &str) -> Result<Self> {
|
||||
let ini = Ini::load_from_str(ini)?;
|
||||
let interface = ini
|
||||
.section(Some("Interface"))
|
||||
.ok_or(anyhow!("Interface section not found"))?;
|
||||
let peers = ini.section_all(Some("Peer"));
|
||||
Ok(Self {
|
||||
interface: Interface::try_from(interface)?,
|
||||
peers: peers
|
||||
.into_iter()
|
||||
.map(|v| Peer::try_from(v))
|
||||
.collect::<Result<Vec<Peer>>>()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_content_fmt(content: &str, fmt: &str) -> Result<Self> {
|
||||
match fmt {
|
||||
"toml" => Self::from_toml(content),
|
||||
"ini" | "conf" => Self::from_ini(content),
|
||||
_ => Err(anyhow::anyhow!("Unsupported format: {}", fmt)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
59
burrow/src/wireguard/inifield.rs
Normal file
59
burrow/src/wireguard/inifield.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
pub struct IniField(String);
|
||||
|
||||
impl FromStr for IniField {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(s.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IniField> for Vec<String> {
|
||||
fn from(field: IniField) -> Self {
|
||||
field.0.split(",").map(|s| s.to_string()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IniField> for u32 {
|
||||
fn from(value: IniField) -> Self {
|
||||
value.0.parse().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IniField> for Option<u32> {
|
||||
fn from(value: IniField) -> Self {
|
||||
Some(value.0.parse().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IniField> for String {
|
||||
fn from(value: IniField) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IniField> for Option<String> {
|
||||
fn from(value: IniField) -> Self {
|
||||
Some(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Option<T>> for IniField
|
||||
where
|
||||
T: ToString,
|
||||
{
|
||||
fn from(value: Option<T>) -> Self {
|
||||
match value {
|
||||
Some(v) => Self(v.to_string()),
|
||||
None => Self("".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IniField {
|
||||
fn new(value: &str) -> Self {
|
||||
Self(value.to_string())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
pub mod config;
|
||||
mod iface;
|
||||
mod inifield;
|
||||
mod noise;
|
||||
mod pcb;
|
||||
mod peer;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue