Add support for ini parsing

This commit is contained in:
Jett Chen 2024-05-27 13:58:11 +08:00
parent 3ba0004370
commit 59ddc36f2c
11 changed files with 300 additions and 32 deletions

4
.gitignore vendored
View file

@ -6,3 +6,7 @@ target/
.DS_STORE .DS_STORE
.idea/ .idea/
burrow.sock
burrow.db
tmp/

View file

@ -23,8 +23,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/jpsim/SourceKitten.git", "location" : "https://github.com/jpsim/SourceKitten.git",
"state" : { "state" : {
"revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56", "revision" : "fd4df99170f5e9d7cf9aa8312aa8506e0e7a44e7",
"version" : "0.34.1" "version" : "0.35.0"
} }
}, },
{ {
@ -32,8 +32,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser.git", "location" : "https://github.com/apple/swift-argument-parser.git",
"state" : { "state" : {
"revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531", "revision" : "fee6933f37fde9a5e12a1e4aeaa93fe60116ff2a",
"version" : "1.2.3" "version" : "1.2.2"
} }
}, },
{ {
@ -41,8 +41,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax.git", "location" : "https://github.com/apple/swift-syntax.git",
"state" : { "state" : {
"revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", "revision" : "303e5c5c36d6a558407d364878df131c3546fad8",
"version" : "509.1.1" "version" : "510.0.2"
} }
}, },
{ {
@ -51,7 +51,7 @@
"location" : "https://github.com/realm/SwiftLint.git", "location" : "https://github.com/realm/SwiftLint.git",
"state" : { "state" : {
"branch" : "main", "branch" : "main",
"revision" : "7595ad3fafc1a31086dc40ba01fd898bf6b42d5f" "revision" : "b42f6ffe77159aed1060bf607212a0410c7623b8"
} }
}, },
{ {

72
Cargo.lock generated
View file

@ -353,6 +353,7 @@ dependencies = [
"rand_core", "rand_core",
"ring", "ring",
"rusqlite", "rusqlite",
"rust-ini",
"schemars", "schemars",
"serde", "serde",
"serde_json", "serde_json",
@ -586,6 +587,26 @@ dependencies = [
"tracing-subscriber", "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]] [[package]]
name = "constant_time_eq" name = "constant_time_eq"
version = "0.1.5" version = "0.1.5"
@ -641,6 +662,12 @@ version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.6"
@ -699,6 +726,15 @@ dependencies = [
"subtle", "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]] [[package]]
name = "dyn-clone" name = "dyn-clone"
version = "1.0.16" version = "1.0.16"
@ -1601,6 +1637,16 @@ dependencies = [
"vcpkg", "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]] [[package]]
name = "overload" name = "overload"
version = "0.1.1" version = "0.1.1"
@ -1947,6 +1993,17 @@ dependencies = [
"smallvec", "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]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.23" version = "0.1.23"
@ -2347,6 +2404,15 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 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]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.6.0" version = "1.6.0"
@ -2627,6 +2693,12 @@ dependencies = [
"tracing-log 0.2.0", "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]] [[package]]
name = "try-lock" name = "try-lock"
version = "0.2.5" version = "0.2.5"

View file

@ -24,7 +24,7 @@ clap = { version = "4.4", features = ["derive"] }
tracing = "0.1" tracing = "0.1"
tracing-log = "0.1" tracing-log = "0.1"
tracing-oslog = { git = "https://github.com/Stormshield-robinc/tracing-oslog" } tracing-oslog = { git = "https://github.com/Stormshield-robinc/tracing-oslog" }
tracing-subscriber = { version = "0.3" , features = ["std", "env-filter"] } tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] }
log = "0.4" log = "0.4"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
@ -51,6 +51,7 @@ once_cell = "1.19"
console-subscriber = { version = "0.2.0", optional = true } console-subscriber = { version = "0.2.0", optional = true }
console = "0.15.8" console = "0.15.8"
toml = "0.8.12" toml = "0.8.12"
rust-ini = "0.21.0"
[dependencies.rusqlite] [dependencies.rusqlite]
version = "0.31.0" version = "0.31.0"

View file

@ -17,7 +17,7 @@ use crate::{
ServerConfig, ServerConfig,
ServerInfo, ServerInfo,
}, },
database::{get_connection, load_interface, dump_interface}, database::{dump_interface, get_connection, load_interface},
wireguard::{Config, Interface}, wireguard::{Config, Interface},
}; };
@ -116,10 +116,10 @@ impl DaemonInstance {
.await?; .await?;
Ok(DaemonResponseData::None) Ok(DaemonResponseData::None)
} }
DaemonCommand::AddConfigToml(config_toml) => { DaemonCommand::AddConfig(cfig_raw) => {
let conn = get_connection(self.db_path.as_deref())?; let conn = get_connection(self.db_path.as_deref())?;
let cfig = Config::from_toml(&config_toml)?; let cfig = Config::from_content_fmt(&cfig_raw.content, &cfig_raw.fmt)?;
let _if_id = dump_interface(&conn, &cfig)?; let _if_id = dump_interface(&conn, &cfig, cfig_raw.interface_id)?;
Ok(DaemonResponseData::None) Ok(DaemonResponseData::None)
} }
} }

View file

@ -3,14 +3,14 @@ use serde::{Deserialize, Serialize};
use tun::TunOptions; use tun::TunOptions;
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(tag="method", content="params")] #[serde(tag = "method", content = "params")]
pub enum DaemonCommand { pub enum DaemonCommand {
Start(DaemonStartOptions), Start(DaemonStartOptions),
ServerInfo, ServerInfo,
ServerConfig, ServerConfig,
Stop, Stop,
ReloadConfig(String), ReloadConfig(String),
AddConfigToml(String), AddConfig(AddConfigOptions),
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
@ -18,6 +18,13 @@ pub struct DaemonStartOptions {
pub tun: TunOptions, 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)] #[derive(Clone, Serialize, Deserialize)]
pub struct DaemonRequest { pub struct DaemonRequest {
pub id: u64, pub id: u64,

View file

@ -92,17 +92,36 @@ pub fn load_interface(conn: &Connection, interface_id: &str) -> Result<Config> {
Ok(Config { interface: iface, peers }) Ok(Config { interface: iface, peers })
} }
pub fn dump_interface(conn: &Connection, config: &Config) -> Result<i64> { pub fn dump_interface(
let mut stmt = conn.prepare("INSERT INTO wg_interface (private_key, dns, address, listen_port, mtu) VALUES (?, ?, ?, ?, ?)")?; conn: &Connection,
let cif = &config.interface; config: &Config,
stmt.execute(params![ interface_id: Option<i64>,
cif.private_key, ) -> Result<i64> {
to_lst(&cif.dns), let interface_id = if let Some(id) = interface_id {
to_lst(&cif.address), let mut stmt = conn.prepare("INSERT INTO wg_interface (private_key, dns, address, listen_port, mtu, id) VALUES (?, ?, ?, ?, ?, ?)")?;
cif.listen_port, let cif = &config.interface;
cif.mtu stmt.execute(params![
])?; cif.private_key,
let interface_id = conn.last_insert_rowid(); 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![
cif.private_key,
to_lst(&cif.dns),
to_lst(&cif.address),
cif.listen_port,
cif.mtu
])?;
conn.last_insert_rowid()
};
let mut stmt = conn.prepare("INSERT INTO wg_peer (interface_id, public_key, preshared_key, allowed_ips, endpoint) VALUES (?, ?, ?, ?, ?)")?; let mut stmt = conn.prepare("INSERT INTO wg_peer (interface_id, public_key, preshared_key, allowed_ips, endpoint) VALUES (?, ?, ?, ?, ?)")?;
for peer in &config.peers { for peer in &config.peers {
stmt.execute(params![ stmt.execute(params![
@ -121,7 +140,7 @@ pub fn get_connection(path: Option<&Path>) -> Result<Connection> {
if !p.exists() { if !p.exists() {
let conn = Connection::open(p)?; let conn = Connection::open(p)?;
initialize_tables(&conn)?; initialize_tables(&conn)?;
dump_interface(&conn, &Config::default())?; dump_interface(&conn, &Config::default(), None)?;
return Ok(conn); return Ok(conn);
} }
Ok(Connection::open(p)?) Ok(Connection::open(p)?)
@ -129,8 +148,6 @@ pub fn get_connection(path: Option<&Path>) -> Result<Connection> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::path::Path;
use super::*; use super::*;
#[test] #[test]
@ -138,7 +155,7 @@ mod tests {
let conn = Connection::open_in_memory().unwrap(); let conn = Connection::open_in_memory().unwrap();
initialize_tables(&conn).unwrap(); initialize_tables(&conn).unwrap();
let config = Config::default(); let config = Config::default();
dump_interface(&conn, &config).unwrap(); dump_interface(&conn, &config, None).unwrap();
let loaded = load_interface(&conn, "1").unwrap(); let loaded = load_interface(&conn, "1").unwrap();
assert_eq!(config, loaded); assert_eq!(config, loaded);
} }

View file

@ -1,6 +1,10 @@
use std::{borrow::Cow, path::PathBuf};
use anyhow::Result; use anyhow::Result;
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use crate::daemon::rpc::request::AddConfigOptions;
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
mod daemon; mod daemon;
pub(crate) mod tracing; pub(crate) mod tracing;
@ -47,6 +51,16 @@ enum Commands {
ServerConfig, ServerConfig,
/// Reload Config /// Reload Config
ReloadConfig(ReloadConfigArgs), 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)] #[derive(Args)]
@ -59,7 +73,12 @@ struct ReloadConfigArgs {
struct StartArgs {} struct StartArgs {}
#[derive(Args)] #[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"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
async fn try_start() -> Result<()> { async fn try_start() -> Result<()> {
@ -132,6 +151,24 @@ async fn try_reloadconfig(interface_id: String) -> Result<()> {
Ok(()) 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"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> { async fn main() -> Result<()> {
@ -139,12 +176,20 @@ async fn main() -> Result<()> {
let cli = Cli::parse(); let cli = Cli::parse();
match &cli.command { match &cli.command {
Commands::Start(..) => try_start().await?, Commands::Start(_) => try_start().await?,
Commands::Stop => try_stop().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::ServerInfo => try_serverinfo().await?,
Commands::ServerConfig => try_serverconfig().await?, Commands::ServerConfig => try_serverconfig().await?,
Commands::ReloadConfig(args) => try_reloadconfig(args.interface_id.clone()).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(()) Ok(())

View file

@ -3,10 +3,12 @@ use std::{net::ToSocketAddrs, str::FromStr};
use anyhow::{anyhow, Error, Result}; use anyhow::{anyhow, Error, Result};
use base64::{engine::general_purpose, Engine}; use base64::{engine::general_purpose, Engine};
use fehler::throws; use fehler::throws;
use ini::{Ini, Properties};
use ip_network::IpNetwork; use ip_network::IpNetwork;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use x25519_dalek::{PublicKey, StaticSecret}; use x25519_dalek::{PublicKey, StaticSecret};
use super::inifield::IniField;
use crate::wireguard::{Interface as WgInterface, Peer as WgPeer}; use crate::wireguard::{Interface as WgInterface, Peer as WgPeer};
#[throws] #[throws]
@ -53,6 +55,7 @@ pub struct Interface {
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Config { pub struct Config {
#[serde(rename = "Peer")]
pub peers: Vec<Peer>, pub peers: Vec<Peer>,
pub interface: Interface, // Support for multiple interfaces? 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 { impl Config {
pub fn from_toml(toml: &str) -> Result<Self> { pub fn from_toml(toml: &str) -> Result<Self> {
toml::from_str(toml).map_err(Into::into) 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)] #[cfg(test)]

View 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())
}
}

View file

@ -1,5 +1,6 @@
pub mod config; pub mod config;
mod iface; mod iface;
mod inifield;
mod noise; mod noise;
mod pcb; mod pcb;
mod peer; mod peer;