aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Cargo.lock325
-rw-r--r--Cargo.toml16
-rw-r--r--src/bin.rs56
-rw-r--r--src/config.rs67
-rw-r--r--src/ip.rs403
-rw-r--r--src/main.rs203
-rw-r--r--src/proto.rs77
-rw-r--r--src/wg.rs231
9 files changed, 1380 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f0e3bca
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/target
+**/*.rs.bk \ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..cec1ebc
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,325 @@
+[[package]]
+name = "arrayref"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "cc"
+version = "1.0.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "chrono"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "curl"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "curl-sys 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "openssl-sys 0.9.42 (registry+https://github.com/rust-lang/crates.io-index)",
+ "schannel 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "socket2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "curl-sys"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
+ "openssl-sys 0.9.42 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libc"
+version = "0.2.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libz-sys"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "proc-macro2"
+version = "0.4.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "quote"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.1.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "rustc_version"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "ryu"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "schannel"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "semver"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "serde"
+version = "1.0.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "serde_derive"
+version = "1.0.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "socket2"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "syn"
+version = "0.15.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "time"
+version = "0.1.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "wgconfd"
+version = "0.1.0"
+dependencies = [
+ "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "curl 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "winapi"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
+"checksum cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "c9ce8bb087aacff865633f0bd5aeaed910fe2fe55b55f4739527f2e023a2e53d"
+"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4"
+"checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878"
+"checksum curl 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)" = "bed4741d1d4e1fc1ba6786c1313057c609259785cde2c45e34602acc45fd6ccc"
+"checksum curl-sys 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7b8d8e51964f58c8053337fcef48e1c4608c7ee70c6f2e457674a97dda5a5828"
+"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b"
+"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
+"checksum libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "aab692d7759f5cd8c859e169db98ae5b52c924add2af5fbbca11d12fefb567c1"
+"checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe"
+"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea"
+"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1"
+"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
+"checksum openssl-sys 0.9.42 (registry+https://github.com/rust-lang/crates.io-index)" = "cb534d752bf98cf363b473950659ac2546517f9c6be9723771614ab3f03bbc9e"
+"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c"
+"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915"
+"checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1"
+"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85"
+"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
+"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7"
+"checksum schannel 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f2f6abf258d99c3c1c5c2131d99d064e94b7b3dd5f416483057f308fea253339"
+"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
+"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+"checksum serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "92514fb95f900c9b5126e32d020f5c6d40564c27a5ea6d1d7d9f157a96623560"
+"checksum serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6eabf4b5914e88e24eea240bb7c9f9a2cbc1bbbe8d961d381975ec3c6b806c"
+"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d"
+"checksum socket2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c4d11a52082057d87cb5caa31ad812f4504b97ab44732cd8359df2e9ff9f48e7"
+"checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2"
+"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
+"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
+"checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d"
+"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
+"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
+"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
+"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..177c77f
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "wgconfd"
+version = "0.1.0"
+authors = ["Hristo Venev <hristo@venev.name>"]
+edition = "2018"
+
+[dependencies]
+arrayref = "*"
+serde = "*"
+serde_derive = "*"
+serde_json = "*"
+chrono = "*"
+curl = "*"
+
+[profile.release]
+panic = "abort"
diff --git a/src/bin.rs b/src/bin.rs
new file mode 100644
index 0000000..2df3856
--- /dev/null
+++ b/src/bin.rs
@@ -0,0 +1,56 @@
+#[inline]
+pub fn i64_to_be(v: i64) -> [u8; 8] {
+ u64_to_be(v as u64)
+}
+
+pub fn i64_from_be(v: [u8; 8]) -> i64 {
+ u64_from_be(v) as i64
+}
+
+pub fn u64_to_be(v: u64) -> [u8; 8] {
+ [
+ (v >> 56) as u8,
+ (v >> 48) as u8,
+ (v >> 40) as u8,
+ (v >> 32) as u8,
+ (v >> 24) as u8,
+ (v >> 16) as u8,
+ (v >> 8) as u8,
+ v as u8,
+ ]
+}
+
+pub fn u64_from_be(v: [u8; 8]) -> u64 {
+ (u64::from(v[0]) << 56) |
+ (u64::from(v[1]) << 48) |
+ (u64::from(v[2]) << 40) |
+ (u64::from(v[3]) << 32) |
+ (u64::from(v[4]) << 24) |
+ (u64::from(v[5]) << 16) |
+ (u64::from(v[6]) << 8) |
+ u64::from(v[7])
+}
+
+pub fn u32_to_be(v: u32) -> [u8; 4] {
+ [
+ (v >> 24) as u8,
+ (v >> 16) as u8,
+ (v >> 8) as u8,
+ v as u8,
+ ]
+}
+
+pub fn u32_from_be(v: [u8; 4]) -> u32 {
+ (u32::from(v[0]) << 24) |
+ (u32::from(v[1]) << 16) |
+ (u32::from(v[2]) << 8) |
+ u32::from(v[3])
+}
+
+pub fn u16_to_be(v: u16) -> [u8; 2] {
+ [(v >> 8) as u8, v as u8]
+}
+
+pub fn u16_from_be(v: [u8; 2]) -> u16 {
+ (u16::from(v[0]) << 8) | u16::from(v[1])
+}
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..6411b3a
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,67 @@
+use ::std::collections::HashSet;
+use ::serde_derive;
+use crate::ip::{Ipv4Set, Ipv6Set};
+
+#[serde(deny_unknown_fields)]
+#[derive(serde_derive::Serialize, serde_derive::Deserialize)]
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct Source {
+ pub url: String,
+ pub psk: Option<String>,
+ pub ipv4: Ipv4Set,
+ pub ipv6: Ipv6Set,
+}
+
+#[serde(deny_unknown_fields)]
+#[derive(serde_derive::Serialize, serde_derive::Deserialize)]
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct PeerConfig {
+ #[serde(default = "default_min_keepalive")]
+ pub min_keepalive: u32,
+ #[serde(default = "default_max_keepalive")]
+ pub max_keepalive: u32,
+
+ pub omit_peers: HashSet<String>,
+}
+
+#[serde(deny_unknown_fields)]
+#[derive(serde_derive::Serialize, serde_derive::Deserialize)]
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct UpdateConfig {
+ // Number of seconds between regular updates.
+ #[serde(default = "default_refresh")]
+ pub refresh_period: u32,
+}
+
+#[serde(deny_unknown_fields)]
+#[derive(serde_derive::Serialize, serde_derive::Deserialize)]
+#[derive(Clone, Debug)]
+pub struct Config {
+ pub ifname: String,
+ #[serde(default = "default_wg_command")]
+ pub wg_command: String,
+
+ #[serde(flatten)]
+ pub peers: PeerConfig,
+
+ #[serde(flatten)]
+ pub update: UpdateConfig,
+
+ pub sources: Vec<Source>,
+}
+
+fn default_wg_command() -> String {
+ "wg".to_owned()
+}
+
+fn default_min_keepalive() -> u32 {
+ 10
+}
+
+fn default_max_keepalive() -> u32 {
+ 0
+}
+
+fn default_refresh() -> u32 {
+ 1200
+}
diff --git a/src/ip.rs b/src/ip.rs
new file mode 100644
index 0000000..1b21e4c
--- /dev/null
+++ b/src/ip.rs
@@ -0,0 +1,403 @@
+use ::std::{error, fmt, iter, net};
+use ::std::net::{Ipv4Addr, Ipv6Addr};
+use ::std::iter::{IntoIterator, FromIterator};
+use ::std::str::{FromStr};
+use ::serde;
+use crate::bin;
+
+#[derive(Debug)]
+pub struct NetParseError {}
+
+impl error::Error for NetParseError {}
+impl fmt::Display for NetParseError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "Invalid IP network")
+ }
+}
+
+macro_rules! per_proto {
+ ($nett:ident ($addrt:ident; $expecting:expr); $intt:ident($bytes:expr); $sett:ident) => {
+ #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
+ pub struct $nett {
+ pub address: $addrt,
+ pub prefix_len: u8,
+ }
+
+ impl $nett {
+ const BITS: u8 = $bytes * 8;
+
+ pub fn contains(&self, other: &$nett) -> bool {
+ if self.prefix_len > other.prefix_len {
+ return false;
+ }
+ if self.prefix_len == other.prefix_len {
+ return self.address == other.address;
+ }
+ // self.prefix_len < other.prefix_len = BITS
+ let shift = Self::BITS - self.prefix_len;
+ let v1: $intt = self.address.into();
+ let v2: $intt = other.address.into();
+ v1 >> shift == v2 >> shift
+ }
+ }
+
+ impl fmt::Display for $nett {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}/{}", self.address, self.prefix_len)
+ }
+ }
+
+ impl FromStr for $nett {
+ type Err = NetParseError;
+ fn from_str(s: &str) -> Result<$nett, NetParseError> {
+ let (addr, pfx) = pfx_split(s)?;
+ let addr = $addrt::from_str(addr).map_err(|_| NetParseError {})?;
+
+ let r = $nett {
+ address: addr,
+ prefix_len: pfx,
+ };
+ if !r.is_valid() {
+ return Err(NetParseError {});
+ }
+ Ok(r)
+ }
+ }
+
+ impl serde::Serialize for $nett {
+ fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
+ if ser.is_human_readable() {
+ ser.serialize_str(&format!("{}", self))
+ } else {
+ let mut buf = [0u8; $bytes + 1];
+ *array_mut_ref![&mut buf, 0, $bytes] = self.address.octets();
+ buf[$bytes] = self.prefix_len;
+ ser.serialize_bytes(&buf)
+ }
+ }
+ }
+
+ impl<'de> serde::Deserialize<'de> for $nett {
+ fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
+ if de.is_human_readable() {
+ struct NetVisitor;
+ impl<'de> serde::de::Visitor<'de> for NetVisitor {
+ type Value = $nett;
+
+ fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str($expecting)
+ }
+
+ fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<Self::Value, E> {
+ s.parse().map_err(E::custom)
+ }
+ }
+ de.deserialize_str(NetVisitor)
+ } else {
+ let buf = <[u8; $bytes+1] as serde::Deserialize>::deserialize(de)?;
+ let r = $nett {
+ address: (*array_ref![&buf, 0, $bytes]).into(),
+ prefix_len: buf[$bytes],
+ };
+ if r.is_valid() {
+ return Err(serde::de::Error::custom(NetParseError {}));
+ }
+ Ok(r)
+ }
+ }
+ }
+
+ #[derive(Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
+ pub struct $sett {
+ nets: Vec<$nett>,
+ }
+
+ impl Default for $sett {
+ #[inline]
+ fn default() -> Self {
+ $sett::new()
+ }
+ }
+
+ impl $sett {
+ #[inline]
+ pub fn new() -> Self {
+ $sett { nets: vec![] }
+ }
+
+ #[inline]
+ fn siblings(a: &$nett, b: &$nett) -> bool {
+ let pfx = a.prefix_len;
+ if b.prefix_len != pfx || pfx == 0 {
+ return false;
+ }
+ let a: $intt = a.address.into();
+ let b: $intt = b.address.into();
+ a ^ b == 1 << ($nett::BITS - pfx)
+ }
+
+ pub fn insert(&mut self, mut net: $nett) {
+ let mut i = match self.nets.binary_search(&net) {
+ Err(i) => i,
+ Ok(_) => {
+ return;
+ }
+ };
+ let mut j = i;
+ if i != 0 && self.nets[i-1].contains(&net) {
+ net = self.nets[i-1];
+ i -= 1;
+ }
+ while j < self.nets.len() && net.contains(&self.nets[j]) {
+ j += 1;
+ }
+ loop {
+ if j < self.nets.len() && Self::siblings(&net, &self.nets[j]) {
+ j += 1;
+ } else if i != 0 && Self::siblings(&self.nets[i-1], &net) {
+ net = self.nets[i-1];
+ i -= 1;
+ } else {
+ break;
+ }
+ net.prefix_len -= 1;
+ }
+ self.nets.splice(i..j, iter::once(net));
+ }
+
+ pub fn contains(&self, net: &$nett) -> bool {
+ match self.nets.binary_search(&net) {
+ Err(i) => {
+ if i == 0 {
+ return false;
+ }
+ self.nets[i-1].contains(&net)
+ }
+ Ok(_) => true,
+ }
+ }
+
+ #[inline]
+ pub fn iter(&self) -> std::slice::Iter<$nett> {
+ self.nets.iter()
+ }
+ }
+
+ impl IntoIterator for $sett {
+ type Item = $nett;
+ type IntoIter = std::vec::IntoIter<$nett>;
+
+ #[inline]
+ fn into_iter(self) -> Self::IntoIter {
+ self.nets.into_iter()
+ }
+ }
+
+ impl FromIterator<$nett> for $sett {
+ fn from_iter<I: IntoIterator<Item=$nett>>(it: I) -> $sett {
+ let mut r = $sett::new();
+ for net in it {
+ r.insert(net);
+ }
+ r
+ }
+ }
+
+ impl<'a> From<$nett> for $sett {
+ #[inline]
+ fn from(v: $nett) -> $sett {
+ $sett { nets: vec![v] }
+ }
+ }
+
+ impl<'a> From<[$nett; 1]> for $sett {
+ #[inline]
+ fn from(v: [$nett; 1]) -> $sett {
+ $sett { nets: vec![v[0]] }
+ }
+ }
+
+ impl From<$sett> for Vec<$nett> {
+ fn from(v: $sett) -> Vec<$nett> {
+ v.nets
+ }
+ }
+
+ impl From<Vec<$nett>> for $sett {
+ fn from(nets: Vec<$nett>) -> $sett {
+ let mut s = $sett { nets };
+ let len = s.nets.len();
+ if len == 0 {
+ return s;
+ }
+ s.nets.sort();
+ let mut i = 1;
+ for j in 1..len {
+ let mut net = s.nets[j];
+ if i != 0 && s.nets[i-1].contains(&net) {
+ net = s.nets[i-1];
+ i -= 1;
+ }
+ while i != 0 && Self::siblings(&s.nets[i-1], &net) {
+ net = s.nets[i-1];
+ net.prefix_len -= 1;
+ i -= 1;
+ }
+ s.nets[i] = net;
+ i += 1;
+ }
+ s.nets.splice(i.., iter::empty());
+ s
+ }
+ }
+
+ impl<'a> From<&'a [$nett]> for $sett {
+ #[inline]
+ fn from(nets: &'a [$nett]) -> $sett {
+ Vec::from(nets).into()
+ }
+ }
+
+ impl<'a> From<&'a mut [$nett]> for $sett {
+ #[inline]
+ fn from(nets: &'a mut [$nett]) -> $sett {
+ Vec::from(nets).into()
+ }
+ }
+
+ impl serde::Serialize for $sett {
+ fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
+ <Vec<$nett> as serde::Serialize>::serialize(&self.nets, ser)
+ }
+ }
+
+ impl<'de> serde::Deserialize<'de> for $sett {
+ fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
+ <Vec<$nett> as serde::Deserialize>::deserialize(de).map($sett::from)
+ }
+ }
+ };
+}
+
+per_proto!(Ipv4Net(Ipv4Addr; "IPv4 network"); u32(4); Ipv4Set);
+per_proto!(Ipv6Net(Ipv6Addr; "IPv6 network"); u128(16); Ipv6Set);
+
+impl Ipv4Net {
+ pub fn is_valid(&self) -> bool {
+ let pfx = self.prefix_len;
+ if pfx > 32 {
+ return false;
+ }
+ if pfx == 32 {
+ return true;
+ }
+ let val: u32 = self.address.into();
+ val & (u32::max_value() >> pfx) == 0
+ }
+}
+
+impl Ipv6Net {
+ pub fn is_valid(&self) -> bool {
+ let pfx = self.prefix_len;
+ if pfx > 128 {
+ return false;
+ }
+ if pfx == 128 {
+ return true;
+ }
+
+ let val: u128 = self.address.into();
+ let val: [u64; 2] = [(val >> 64) as u64, val as u64];
+ if pfx >= 64 {
+ return val[1] & (u64::max_value() >> (pfx - 64)) == 0;
+ }
+ if val[1] != 0 {
+ return false;
+ }
+ val[0] & (u64::max_value() >> pfx) == 0
+ }
+}
+
+fn pfx_split(s: &str) -> Result<(&str, u8), NetParseError> {
+ let i = match s.find("/") {
+ Some(i) => i,
+ None => {
+ return Err(NetParseError {});
+ }
+ };
+ let (addr, pfx) = s.split_at(i);
+ let pfx = u8::from_str(&pfx[1..]).map_err(|_| NetParseError {})?;
+ Ok((addr, pfx))
+}
+
+#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
+pub struct Endpoint {
+ pub address: Ipv6Addr,
+ pub port: u16,
+}
+
+impl fmt::Display for Endpoint {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if self.address.segments()[5] == 0xffff {
+ write!(f, "{}:", self.address.to_ipv4().unwrap())?;
+ } else {
+ write!(f, "[{}]:", self.address)?;
+ }
+ write!(f, "{}", self.port)
+ }
+}
+
+impl FromStr for Endpoint {
+ type Err = net::AddrParseError;
+ fn from_str(s: &str) -> Result<Endpoint, net::AddrParseError> {
+ net::SocketAddr::from_str(s)
+ .map(|v| Endpoint {
+ address: match v.ip() {
+ net::IpAddr::V4(a) => a.to_ipv6_mapped(),
+ net::IpAddr::V6(a) => a,
+ },
+ port: v.port(),
+ })
+ }
+}
+
+impl serde::Serialize for Endpoint {
+ fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
+ if ser.is_human_readable() {
+ ser.serialize_str(&format!("{}", self))
+ } else {
+ let mut buf = [0u8; 16 + 2];
+ let (buf_addr, buf_port) = mut_array_refs![&mut buf, 16, 2];
+ *buf_addr = self.address.octets();
+ *buf_port = crate::bin::u16_to_be(self.port);
+ ser.serialize_bytes(&buf)
+ }
+ }
+}
+
+impl<'de> serde::Deserialize<'de> for Endpoint {
+ fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
+ if de.is_human_readable() {
+ struct EndpointVisitor;
+ impl<'de> serde::de::Visitor<'de> for EndpointVisitor {
+ type Value = Endpoint;
+
+ fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str("ip:port")
+ }
+
+ fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<Self::Value, E> {
+ s.parse().map_err(E::custom)
+ }
+ }
+ de.deserialize_str(EndpointVisitor)
+ } else {
+ let buf = <[u8; 16 + 2] as serde::Deserialize>::deserialize(de)?;
+ let (buf_addr, buf_port) = array_refs![&buf, 16, 2];
+ Ok(Endpoint {
+ address: (*buf_addr).into(),
+ port: bin::u16_from_be(*buf_port),
+ })
+ }
+ }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..ad57895
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,203 @@
+#[macro_use]
+extern crate arrayref;
+
+use ::std::io;
+use ::std::time::{Duration, SystemTime, Instant};
+
+mod bin;
+mod ip;
+mod proto;
+mod config;
+mod wg;
+
+struct Source {
+ config: config::Source,
+ data: Option<proto::Source>,
+ next_update: Instant,
+ backoff: Option<u32>,
+}
+
+impl Source {
+ fn new(config: config::Source) -> Source {
+ Source {
+ config,
+ data: None,
+ next_update: Instant::now(),
+ backoff: None,
+ }
+ }
+}
+
+pub struct Device {
+ dev: wg::Device,
+ peer_config: config::PeerConfig,
+ update_config: config::UpdateConfig,
+ sources: Vec<Source>,
+ current: wg::Config,
+}
+
+impl Device {
+ pub fn new(c: config::Config) -> Device {
+ Device {
+ dev: wg::Device::new(c.ifname, c.wg_command),
+ peer_config: c.peers,
+ update_config: c.update,
+ sources: c.sources.into_iter().map(Source::new).collect(),
+ current: wg::Config::new(),
+ }
+ }
+
+ fn make_config(&self, ts: SystemTime) -> (wg::Config, Vec<wg::ConfigError>, SystemTime) {
+ let mut cfg = wg::Config::new();
+ let mut next_update = ts + Duration::from_secs(3600);
+ let mut errs = vec![];
+ for src in self.sources.iter() {
+ if let Some(data) = &src.data {
+ let sc = data.next.as_ref().and_then(|next| {
+ if ts >= next.update_at {
+ Some(&next.config)
+ } else {
+ next_update = next_update.min(next.update_at);
+ None
+ }
+ }).unwrap_or(&data.config);
+ for peer in sc.peers.iter() {
+ cfg.add_peer(&mut errs, &self.peer_config, &src.config, peer);
+ }
+ }
+ }
+ (cfg, errs, next_update)
+ }
+
+ pub fn update(&mut self) -> io::Result<Instant> {
+ let now = Instant::now();
+ let refresh = self.update_config.refresh_period;
+ let after_refresh = now + Duration::from_secs(u64::from(refresh));
+ let mut next_update = after_refresh;
+ for src in self.sources.iter_mut() {
+ if now < src.next_update {
+ next_update = next_update.min(src.next_update);
+ continue;
+ }
+
+ let r = fetch_source(&src.config.url);
+ let r = match r {
+ Ok(r) => {
+ eprintln!("<6>Updated [{}]", &src.config.url);
+ src.data = Some(r);
+ src.backoff = None;
+ src.next_update = after_refresh;
+ continue;
+ }
+ Err(r) => r,
+ };
+
+ eprintln!("<3>Failed to update [{}]: {}", &src.config.url, &r);
+
+ let b = src.backoff.unwrap_or(if src.data.is_some() {
+ refresh / 3
+ } else {
+ u32::min(10, refresh / 10)
+ });
+ let b = (b + b / 3).min(refresh);
+ src.backoff = Some(b);
+ src.next_update = now + Duration::from_secs(u64::from(b));
+ next_update = next_update.min(src.next_update);
+ }
+
+ let sysnow = SystemTime::now();
+ let (config, errors, upd_time) = self.make_config(sysnow);
+ let time_to_upd = upd_time.duration_since(sysnow).unwrap_or(Duration::from_secs(0));
+ next_update = next_update.min(now + time_to_upd);
+
+ if config != self.current {
+ eprintln!("<5>Applying configuration update");
+ for err in errors.iter() {
+ eprintln!("<{}>{}", if err.important { '4' } else { '5' }, err);
+ }
+ self.dev.apply_diff(&self.current, &config)?;
+ self.current = config;
+ }
+ eprintln!("<6>Next configuration update after {:?}", time_to_upd);
+
+ Ok(next_update)
+ }
+}
+
+fn fetch_source(url: &str) -> io::Result<proto::Source> {
+ use ::curl::easy::Easy;
+
+ let mut res = Vec::<u8>::new();
+
+ {
+ let mut req = Easy::new();
+ req.url(url)?;
+
+ {
+ let mut tr = req.transfer();
+ tr.write_function(|data| {
+ res.extend_from_slice(data);
+ Ok(data.len())
+ })?;
+ tr.perform()?;
+ }
+
+ let code = req.response_code()?;
+ if code != 0 && code != 200 {
+ return Err(io::Error::new(io::ErrorKind::Other, format!("HTTP error {}", code)));
+ }
+ }
+
+ let mut de = serde_json::Deserializer::from_slice(&res);
+ let r = serde::Deserialize::deserialize(&mut de)?;
+ Ok(r)
+}
+
+fn load_config(path: &str) -> io::Result<config::Config> {
+ use std::fs;
+ use ::serde_json;
+
+ let config_file = fs::File::open(path)?;
+ let rd = io::BufReader::new(config_file);
+ let mut de = serde_json::Deserializer::from_reader(rd);
+ Ok(serde::Deserialize::deserialize(&mut de)?)
+}
+
+fn main() {
+ use ::std::{env, thread, process};
+
+ let args: Vec<String> = env::args().into_iter().collect();
+ if args.len() != 2 {
+ let arg0 = if args.len() >= 1 {
+ &args[0]
+ } else {
+ "wgconf"
+ };
+ eprintln!("<1>Usage:");
+ eprintln!("<1> {} CONFIG", arg0);
+ process::exit(1);
+ }
+
+ let config = match load_config(&args[1]) {
+ Ok(c) => c,
+ Err(e) => {
+ eprintln!("<1>Failed to load config: {}", e);
+ process::exit(1);
+ }
+ };
+
+ let mut dev = Device::new(config);
+ loop {
+ let tm = match dev.update() {
+ Ok(t) => t,
+ Err(e) => {
+ eprintln!("<1>{}", e);
+ process::exit(1);
+ }
+ };
+ let now = Instant::now();
+ let sleep = tm.duration_since(now);
+ println!("Sleeping for {:?}", sleep);
+ thread::sleep(sleep);
+ }
+}
diff --git a/src/proto.rs b/src/proto.rs
new file mode 100644
index 0000000..e6759a1
--- /dev/null
+++ b/src/proto.rs
@@ -0,0 +1,77 @@
+use ::std::time::SystemTime;
+use ::serde_derive;
+
+use crate::ip::{Ipv4Net, Ipv6Net, Endpoint};
+
+#[serde(deny_unknown_fields)]
+#[derive(serde_derive::Serialize, serde_derive::Deserialize)]
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct Peer {
+ pub public_key: String,
+ pub endpoint: Endpoint,
+ #[serde(default = "default_peer_keepalive")]
+ pub keepalive: u32,
+ pub ipv4: Vec<Ipv4Net>,
+ pub ipv6: Vec<Ipv6Net>,
+}
+
+fn default_peer_keepalive() -> u32 {
+ 0
+}
+
+#[derive(serde_derive::Serialize, serde_derive::Deserialize)]
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct SourceConfig {
+ pub peers: Vec<Peer>,
+}
+
+#[derive(serde_derive::Serialize, serde_derive::Deserialize)]
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct SourceNextConfig {
+ #[serde(with = "serde_utc")]
+ pub update_at: SystemTime,
+ #[serde(flatten)]
+ pub config: SourceConfig,
+}
+
+#[derive(serde_derive::Serialize, serde_derive::Deserialize)]
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct Source {
+ #[serde(flatten)]
+ pub config: SourceConfig,
+ pub next: Option<SourceNextConfig>,
+}
+
+mod serde_utc {
+ use ::std::time::SystemTime;
+ use ::chrono::{DateTime, TimeZone, Utc, SecondsFormat};
+ use ::serde::*;
+ use crate::bin;
+
+ pub fn serialize<S: Serializer>(t: &SystemTime, ser: S) -> Result<S::Ok, S::Error> {
+ let t = DateTime::<Utc>::from(*t);
+ if ser.is_human_readable() {
+ ser.serialize_str(&t.to_rfc3339_opts(SecondsFormat::Nanos, true))
+ } else {
+ let mut buf = [0u8; 12];
+ let (buf_secs, buf_nanos) = mut_array_refs![&mut buf, 8, 4];
+ *buf_secs = bin::i64_to_be(t.timestamp());
+ *buf_nanos = bin::u32_to_be(t.timestamp_subsec_nanos());
+ ser.serialize_bytes(&buf)
+ }
+ }
+
+ pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result<SystemTime, D::Error> {
+ if de.is_human_readable() {
+ let s: String = String::deserialize(de)?;
+ let t = DateTime::parse_from_rfc3339(&s).map_err(de::Error::custom)?;
+ Ok(t.into())
+ } else {
+ let mut buf = <[u8; 12]>::deserialize(de)?;
+ let (buf_secs, buf_nanos) = array_refs![&mut buf, 8, 4];
+ let secs = bin::i64_from_be(*buf_secs);
+ let nanos = bin::u32_from_be(*buf_nanos);
+ Ok(Utc.timestamp(secs, nanos).into())
+ }
+ }
+}
diff --git a/src/wg.rs b/src/wg.rs
new file mode 100644
index 0000000..d5a03ff
--- /dev/null
+++ b/src/wg.rs
@@ -0,0 +1,231 @@
+use ::std::{error, io, fmt};
+use ::std::collections::hash_map;
+use hash_map::HashMap;
+use crate::ip::{Ipv4Net, Ipv6Net, Endpoint};
+use crate::{proto, config};
+
+#[derive(Clone, PartialEq, Eq, Debug)]
+struct Peer {
+ endpoint: Endpoint,
+ psk: Option<String>,
+ keepalive: u32,
+ ipv4: Vec<Ipv4Net>,
+ ipv6: Vec<Ipv6Net>,
+}
+
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct Config {
+ peers: HashMap<String, Peer>,
+}
+
+#[derive(Debug)]
+pub struct ConfigError {
+ pub url: String,
+ pub peer: String,
+ pub important: bool,
+ err: &'static str,
+}
+
+impl error::Error for ConfigError {}
+impl fmt::Display for ConfigError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "Invalid peer [{}] from [{}]: {}", self.peer, self.url, self.err)
+ }
+}
+
+impl Config {
+ pub fn new() -> Config {
+ Config {
+ peers: HashMap::new(),
+ }
+ }
+
+ pub fn add_peer(&mut self, errors: &mut Vec<ConfigError>, c: &config::PeerConfig, s: &config::Source, p: &proto::Peer) {
+ if !valid_key(&p.public_key) {
+ errors.push(ConfigError {
+ url: s.url.clone(),
+ peer: p.public_key.clone(),
+ important: true,
+ err: "Invalid public key",
+ });
+ return;
+ }
+
+ if let Some(ref psk) = s.psk {
+ if !valid_key(psk) {
+ errors.push(ConfigError {
+ url: s.url.clone(),
+ peer: p.public_key.clone(),
+ important: true,
+ err: "Invalid preshared key",
+ });
+ return;
+ }
+ }
+
+ if c.omit_peers.contains(&p.public_key) {
+ return;
+ }
+
+ let ent = match self.peers.entry(p.public_key.clone()) {
+ hash_map::Entry::Occupied(_) => {
+ errors.push(ConfigError {
+ url: s.url.clone(),
+ peer: p.public_key.clone(),
+ important: true,
+ err: "Duplicate public key",
+ });
+ return;
+ },
+ hash_map::Entry::Vacant(ent) => ent,
+ };
+
+ let mut keepalive = p.keepalive;
+ if c.max_keepalive != 0 && (keepalive == 0 || keepalive > c.max_keepalive) {
+ keepalive = c.max_keepalive;
+ }
+ if keepalive != 0 && keepalive < c.min_keepalive {
+ keepalive = c.min_keepalive;
+ }
+
+ let mut removed = false;
+
+ let mut ipv4 = p.ipv4.clone();
+ ipv4.retain(|i| {
+ let r = s.ipv4.contains(i);
+ if !r { removed = true; }
+ r
+ });
+
+ let mut ipv6 = p.ipv6.clone();
+ ipv6.retain(|i| {
+ let r = s.ipv6.contains(i);
+ if !r { removed = true; }
+ r
+ });
+
+ let r = ent.insert(Peer {
+ endpoint: p.endpoint.clone(),
+ psk: s.psk.clone(),
+ keepalive, ipv4, ipv6,
+ });
+
+ if removed {
+ let all = r.ipv4.is_empty() && r.ipv6.is_empty();
+ errors.push(ConfigError {
+ url: s.url.clone(),
+ peer: p.public_key.clone(),
+ important: all,
+ err: if all { "All IPs removed" } else {"Some IPs removed"},
+ });
+ }
+ }
+}
+
+impl Default for Config {
+ #[inline]
+ fn default() -> Self {
+ Config::new()
+ }
+}
+
+pub struct Device {
+ ifname: String,
+ wg_command: String,
+}
+
+impl Device {
+ pub fn new(ifname: String, wg_command: String) -> Self {
+ Device { ifname, wg_command }
+ }
+
+ pub fn apply_diff(&mut self, old: &Config, new: &Config) -> io::Result<()> {
+ use ::std::process::{Command, Stdio};
+
+ let mut proc = Command::new(&self.wg_command);
+ proc.stdin(Stdio::piped());
+ proc.stdout(Stdio::null());
+ proc.arg("set");
+ proc.arg(&self.ifname);
+
+ let mut psks = Vec::<&str>::new();
+
+ for (pubkey, conf) in new.peers.iter() {
+ if let Some(old_peer) = old.peers.get(pubkey) {
+ if *old_peer == *conf {
+ continue;
+ }
+ }
+ proc.arg("peer");
+ proc.arg(pubkey);
+
+ // TODO: maybe skip endpoint?
+ proc.arg("endpoint");
+ proc.arg(format!("{}", conf.endpoint));
+
+ if let Some(psk) = &conf.psk {
+ proc.arg("preshared-key");
+ proc.arg("/dev/stdin");
+ psks.push(psk);
+ }
+
+ let mut ips = String::new();
+ {
+ use std::fmt::Write;
+ for ip in conf.ipv4.iter() {
+ if !ips.is_empty() { ips.push(','); }
+ write!(ips, "{}", ip).unwrap();
+ }
+ for ip in conf.ipv6.iter() {
+ if !ips.is_empty() { ips.push(','); }
+ write!(ips, "{}", ip).unwrap();
+ }
+ }
+
+ proc.arg("allowed-ips");
+ proc.arg(ips);
+ }
+
+ for pubkey in old.peers.keys() {
+ if new.peers.contains_key(pubkey) {
+ continue;
+ }
+ proc.arg("peer");
+ proc.arg(pubkey);
+ proc.arg("remove");
+ }
+
+ let mut proc = proc.spawn()?;
+ {
+ use std::io::Write;
+ let stdin = proc.stdin.as_mut().unwrap();
+ for psk in psks {
+ write!(stdin, "{}\n", psk)?;
+ }
+ }
+
+ let r = proc.wait()?;
+ if !r.success() {
+ return Err(io::Error::new(io::ErrorKind::Other, "Child process failed"));
+ }
+ Ok(())
+ }
+}
+
+fn valid_key(s: &str) -> bool {
+ let s = s.as_bytes();
+ if s.len() != 44 {
+ return false;
+ }
+ if s[43] != b'=' {
+ return false;
+ }
+ for c in s[0..42].iter().cloned() {
+ if c >= b'0' && c <= b'9' { continue; }
+ if c >= b'A' && c <= b'Z' { continue; }
+ if c >= b'a' && c <= b'z' { continue; }
+ if c == b'+' || c <= b'/' { continue; }
+ return false;
+ }
+ b"048AEIMQUYcgkosw".contains(&s[42])
+}