diff options
author | Himbeer <himbeer@disroot.org> | 2025-03-17 14:45:37 +0100 |
---|---|---|
committer | Himbeer <himbeer@disroot.org> | 2025-03-17 15:58:19 +0100 |
commit | 928cc84d24903871b3bb04eaa2ccb8de1c3c3ee5 (patch) | |
tree | 55d9163b3b93e0a313afae3ad79d6c6729d51148 | |
parent | 083e9e9f93db9c8ef488a2aec3ab171b0591c1f3 (diff) |
Parse configuration file at /data/wg.peers
-rw-r--r-- | Cargo.lock | 409 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/main.rs | 307 |
3 files changed, 716 insertions, 1 deletions
@@ -3,5 +3,414 @@ version = 3 [[package]] +name = "anyhow" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "netlink-packet-core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" +dependencies = [ + "anyhow", + "byteorder", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-generic" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd7eb8ad331c84c6b8cb7f685b448133e5ad82e1ffd5acafac374af4a5a308b" +dependencies = [ + "anyhow", + "byteorder", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" +dependencies = [ + "anyhow", + "bitflags", + "byteorder", + "libc", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-utils" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +dependencies = [ + "anyhow", + "byteorder", + "paste", + "thiserror", +] + +[[package]] +name = "netlink-packet-wireguard" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b25b050ff1f6a1e23c6777b72db22790fe5b6b5ccfd3858672587a79876c8f" +dependencies = [ + "anyhow", + "byteorder", + "libc", + "log", + "netlink-packet-generic", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-request" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "890da3a441eac11fbc621eaf5da8186032dbd047c37e5cf2f49b80319b381a2f" +dependencies = [ + "netlink-packet-core", + "netlink-packet-generic", + "netlink-packet-route", + "netlink-packet-utils", + "netlink-sys", + "nix", + "once_cell", +] + +[[package]] +name = "netlink-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16c903aa70590cb93691bf97a767c8d1d6122d2cc9070433deb3bbf36ce8bd23" +dependencies = [ + "bytes", + "libc", + "log", +] + +[[package]] +name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + +[[package]] +name = "once_cell" +version = "1.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] name = "rsdsl_wg" version = "0.1.0" +dependencies = [ + "wireguard-control", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wireguard-control" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248fb4e222045eb93d2a5269bb45472c2dadadce2c093a9e717847c0be72e611" +dependencies = [ + "base64", + "hex", + "libc", + "log", + "netlink-packet-core", + "netlink-packet-generic", + "netlink-packet-route", + "netlink-packet-utils", + "netlink-packet-wireguard", + "netlink-request", + "netlink-sys", + "rand_core", + "x25519-dalek", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] @@ -6,3 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +wireguard-control = "1.6.1" diff --git a/src/main.rs b/src/main.rs index e7a11a9..4db82e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,308 @@ +use std::collections::HashMap; +use std::fmt; +use std::fs::File; +use std::io; +use std::net::IpAddr; + +const CONFIG_PATH: &str = "/data/wg.peers"; + +#[derive(Debug)] +enum ConfigError { + AddrParse(std::net::AddrParseError), + DuplicateLink(String), + Eof, + ExpectedLinkStanza(String), + IncompleteLinkHead, + InvalidAllowedIp(String), + InvalidCidr, + InvalidKey, + Io(io::Error), + NoEndpoint(String), + NoPrivateKey(String), + NoPublicKey(String), + NoPresharedKey(String), + NoAddresses(String), + NoAllowedIps(String), + NoKeepaliveInterval(String), + ParseInt(std::num::ParseIntError), +} + +impl fmt::Display for ConfigError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "parse config: ")?; + + match self { + Self::AddrParse(e) => write!(f, "parse IP address: {}", e), + Self::DuplicateLink(name) => write!(f, "duplicate link {}", name), + Self::Eof => write!(f, "EOF"), + Self::ExpectedLinkStanza(kw) => { + write!(f, "expected \"link\" or \"delete\", got {}", kw) + } + Self::IncompleteLinkHead => write!(f, "incomplete link head (want \"link <name>\")"), + Self::InvalidAllowedIp(allowed_ip) => write!(f, "invalid AllowedIP: {}", allowed_ip), + Self::InvalidCidr => write!(f, "invalid CIDR (want exactly one /)"), + Self::InvalidKey => write!(f, "invalid WireGuard key"), + Self::Io(e) => write!(f, "io: {}", e), + Self::NoEndpoint(link) => write!(f, "missing endpoint for link {}", link), + Self::NoPrivateKey(link) => write!(f, "missing private key for link {}", link), + Self::NoPublicKey(link) => write!(f, "missing public key for link {}", link), + Self::NoPresharedKey(link) => write!(f, "missing preshared key for link {}", link), + Self::NoAddresses(link) => write!(f, "missing addresses for link {}", link), + Self::NoAllowedIps(link) => write!(f, "missing AllowedIPs for link {}", link), + Self::NoKeepaliveInterval(link) => { + write!(f, "missing keepalive interval for link {}", link) + } + Self::ParseInt(e) => write!(f, "parse int: {}", e), + } + } +} + +impl From<std::net::AddrParseError> for ConfigError { + fn from(e: std::net::AddrParseError) -> ConfigError { + ConfigError::AddrParse(e) + } +} + +impl From<wireguard_control::InvalidKey> for ConfigError { + fn from(e: wireguard_control::InvalidKey) -> ConfigError { + ConfigError::InvalidKey + } +} + +impl From<io::Error> for ConfigError { + fn from(e: io::Error) -> ConfigError { + ConfigError::Io(e) + } +} + +impl From<std::num::ParseIntError> for ConfigError { + fn from(e: std::num::ParseIntError) -> ConfigError { + ConfigError::ParseInt(e) + } +} + +impl std::error::Error for ConfigError {} + +#[derive(Debug)] +enum Error { + Config(ConfigError), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Config(e) => e.fmt(f), + } + } +} + +impl From<ConfigError> for Error { + fn from(e: ConfigError) -> Error { + Error::Config(e) + } +} + +impl std::error::Error for Error {} + +#[derive(Debug)] +struct Link { + endpoint: SocketAddr, + private_key: wireguard_control::Key, + public_key: wireguard_control::Key, + preshared_key: wireguard_control::Key, + addresses: Vec<(IpAddr, u8)>, + allowed_ips: Vec<wireguard_control::AllowedIp>, + keepalive_seconds: u16, +} + +#[derive(Debug)] +enum LinkStanza { + Link(Link), + Delete, +} + +#[derive(Debug)] +struct LinkConfig { + name: String, + link: LinkStanza, +} + +impl LinkConfig { + fn parse<R: io::BufRead>(r: &mut R) -> Result<Self, ConfigError> { + let mut head = String::new(); + let mut n = r.read_line(&mut head)?; + if n == 0 { + return Err(ConfigError::Eof); + } + + let head: Vec<&str> = head.split(' ').collect(); + if head.len() < 2 { + return Err(ConfigError::IncompleteLinkHead); + } + + let name = head[1].to_string(); + + let link_keyword = head[0]; + if link_keyword == "delete" { + return Ok(Self { + name, + link: LinkStanza::Delete, + }); + } else if link_keyword != "link" { + return Err(ConfigError::ExpectedLinkStanza(link_keyword.to_string())); + } + + let mut endpoint = String::new(); + n = r.read_line(&mut endpoint)?; + if n == 0 { + return Err(ConfigError::NoEndpoint(name)); + } + let endpoint: SocketAddr = endpoint.parse()?; + + let mut private_key = String::new(); + n = r.read_line(&mut private_key)?; + if n == 0 { + return Err(ConfigError::NoPrivateKey(name)); + } + let private_key = wireguard_control::Key::from_base64(&private_key)?; + + let mut public_key = String::new(); + n = r.read_line(&mut public_key)?; + if n == 0 { + return Err(ConfigError::NoPublicKey(name)); + } + let public_key = wireguard_control::Key::from_base64(&public_key)?; + + let mut preshared_key = String::new(); + n = r.read_line(&mut preshared_key)?; + if n == 0 { + return Err(ConfigError::NoPresharedKey(name)); + } + let preshared_key = wireguard_control::Key::from_base64(&preshared_key)?; + + let mut addresses = String::new(); + n = r.read_line(&mut addresses)?; + if n == 0 { + return Err(ConfigError::NoAddresses(name)); + } + + let address_strs = addresses.split(' '); + let mut addresses = Vec::new(); + + for address_str in address_strs { + let parts: Vec<&str> = address_str.split('/').collect(); + if parts.len() != 2 { + return Err(ConfigError::InvalidCidr); + } + + let address: IpAddr = parts[0].parse()?; + let prefix_length: u8 = parts[1].parse()?; + + addresses.push((address, prefix_length)); + } + + let mut allowed_ips = String::new(); + n = r.read_line(&mut allowed_ips)?; + if n == 0 { + return Err(ConfigError::NoAllowedIps(name)); + } + + let allowed_ip_strs = allowed_ips.split(' '); + let mut allowed_ips = Vec::new(); + + for allowed_ip_str in allowed_ip_strs { + let allowed_ip: wireguard_control::AllowedIp = allowed_ip_str + .parse() + .map_err(|_| ConfigError::InvalidAllowedIp(allowed_ip_str.to_string()))?; + allowed_ips.push(allowed_ip); + } + + let mut keepalive_seconds = String::new(); + n = r.read_line(&mut keepalive_seconds)?; + if n == 0 { + return Err(ConfigError::NoKeepaliveInterval(name)); + } + + let keepalive_seconds: u16 = keepalive_seconds.parse()?; + + Ok(Self { + name, + link: LinkStanza::Link(Link { + endpoint, + private_key, + public_key, + preshared_key, + addresses, + allowed_ips, + keepalive_seconds, + }), + }) + } +} + +#[derive(Debug)] +struct Config { + links: HashMap<String, LinkStanza>, +} + +impl Config { + fn parse<R: io::BufRead>(r: &mut R) -> Result<Self, ConfigError> { + let mut links = HashMap::new(); + loop { + let eof = Self::skip_blank_lines(r)?; + if eof { + break; + } + + let link_config = LinkConfig::parse(r); + if let Err(ConfigError::Eof) = link_config { + break; + } + let link_config = link_config?; + + if links + .insert(link_config.name.clone(), link_config.link) + .is_some() + { + return Err(ConfigError::DuplicateLink(link_config.name)); + } + } + + Ok(Self { links }) + } + + fn skip_blank_lines<R: io::BufRead>(r: &mut R) -> Result<bool, ConfigError> { + let mut s = String::new(); + while r.read_line(&mut s)? > 0 { + if s != "\n" { + return Ok(false); + } + + s.clear(); + } + + Ok(true) + } +} + fn main() { - println!("Hello, world!"); + println!("[info] init"); + match run() { + Ok(_) => loop { + std::thread::park() + }, + Err(e) => { + eprintln!("[warn] {}", e); + std::process::exit(1); + } + } +} + +fn run() -> Result<(), Error> { + let f = match File::open(CONFIG_PATH) { + Ok(f) => f, + Err(e) => return Err(ConfigError::Io(e).into()), + }; + let mut br = io::BufReader::new(f); + let config = Config::parse(&mut br)?; } |