29 Commits

Author SHA1 Message Date
shimunn
5f997ca191 supply more info
Some checks are pending
continuous-integration/drone/push Build was killed
2019-01-19 16:44:05 +01:00
shimunn
32a86b45ab more reliable events 2019-01-19 15:43:55 +01:00
Drone CI
215cb7ec8f proper hash
Some checks are pending
continuous-integration/drone/push Build is failing
2019-01-18 19:03:27 +01:00
Drone CI
5526084882 use debug build
Some checks are pending
continuous-integration/drone/push Build is failing
2019-01-18 18:54:00 +01:00
Drone CI
a96c557b45 target alpine
Some checks are pending
continuous-integration/drone/push Build is failing
2019-01-18 10:37:16 +01:00
Drone CI
fa757372cb verify working binary
Some checks are pending
continuous-integration/drone/push Build is failing
2019-01-18 10:27:42 +01:00
Drone CI
fa79ceae9e rust 1.32.0
Some checks are pending
continuous-integration/drone/push Build is failing
2019-01-18 10:18:19 +01:00
Drone CI
22f51f85f1 optimizing breaks binary
Some checks are pending
continuous-integration/drone/push Build is failing
2019-01-18 08:38:14 +01:00
shimunn
7acd090b21 delay 3 sec
Some checks are pending
continuous-integration/drone/push Build is failing
2019-01-18 01:21:20 +01:00
shimunn
1dbc9596c2 pass args correctly
Some checks are pending
continuous-integration/drone/push Build is failing
2019-01-18 00:53:02 +01:00
shimunn
edf635ca9b some events fire correctly
Some checks are pending
continuous-integration/drone/push Build was killed
2019-01-18 00:48:46 +01:00
shimunn
34e04ae726 parse nanos as nanos 2019-01-18 00:47:46 +01:00
shimunn
e2dbf0112e smaller binary
Some checks are pending
continuous-integration/drone/push Build is failing
2019-01-17 23:14:25 +01:00
Drone CI
198703c01b working test setup
Some checks are pending
continuous-integration/drone/push Build is failing
2019-01-17 14:59:54 +01:00
shimunn
5879bcc4e6 add missing path
Some checks are pending
continuous-integration/drone/push Build is failing
2019-01-13 17:25:29 +01:00
shimunn
a06edf48dd enable ScriptListener
Some checks are pending
continuous-integration/drone/push Build was killed
2019-01-13 17:15:03 +01:00
shim_
1ba7eb556e cache build steps
Some checks are pending
continuous-integration/drone/push Build is failing
2019-01-13 17:04:07 +01:00
shim_
7a17b0d22c drone
Some checks are pending
continuous-integration/drone/push Build is failing
2019-01-13 16:48:26 +01:00
shim_
c1e6d09246 strip binary 2019-01-13 16:43:21 +01:00
shim_
f036aeca41 make 2019-01-13 16:38:35 +01:00
shim_
b7641871fb untilise event-gen as wait loop 2019-01-13 16:34:59 +01:00
shim_
e432793aeb use base64 ids 2019-01-13 15:31:18 +01:00
shim_
da029637a1 test 2019-01-13 14:33:10 +01:00
shim_
03fb5cef29 restructured 2019-01-13 14:32:52 +01:00
shim_
c58c970cad track events 2019-01-09 22:22:49 +01:00
shim_
f6c1c2771e prevent config loss 2019-01-04 22:33:48 +01:00
shim_
43a0f495f6 improve shutdown characteristics 2019-01-04 22:23:57 +01:00
shim_
459888cafa redirect dns 2019-01-04 22:07:16 +01:00
shim_
7c93c5739a add nload 2019-01-04 20:38:53 +01:00
12 changed files with 912 additions and 9 deletions

1
.dockerignore Normal file
View File

@@ -0,0 +1 @@
*/target

37
.drone.yml Normal file
View File

@@ -0,0 +1,37 @@
kind: pipeline
name: default
steps:
- name: event-gen
image: plugins/docker
repo: repo.shimun.net/shimun/wireguard-user::build-event-gen
registry: repo.shimun.net
cache_from: ["repo.shimun.net/shimun/wireguard-user:build-event-gen"]
target: eventbuild
username:
from_secret: docker_username
password:
from_secret: docker_password
- name: git-modules
image: alpine/git
commands:
- git submodule update --recursive --remote --init
- name: wireguard-go
image: plugins/docker
repo: repo.shimun.net/shimun/wireguard-user:build-wireguard-go
registry: repo.shimun.net
cache-from: ["repo.shimun.net/shimun/wireguard-user:build-wireguard-go", "repo.shimun.net/shimun/wireguard-user:build-event-gen"]
target: build
username:
from_secret: docker_username
password:
from_secret: docker_password
- name: package
image: plugins/docker
repo: repo.shimun.net/shimun/wireguard-user
registry: repo.shimun.net
cache-from: ["repo.shimun.net/shimun/wireguard-user:build-wireguard-go", "repo.shimun.net/shimun/wireguard-user:build-event-gen", "repo.shimun.net/shimun/wireguard-user"]
username:
from_secret: docker_username
password:
from_secret: docker_password

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*/target

View File

@@ -1,3 +1,23 @@
FROM rust:1.32-slim AS eventbuild
WORKDIR /build
COPY wg-event-gen/Cargo.* /build/
RUN rustup target add x86_64-unknown-linux-musl
RUN mkdir -p src && echo "fn main() {}" > src/main.rs && cargo build --release --target x86_64-unknown-linux-musl
COPY wg-event-gen/ /build
RUN cargo build --target x86_64-unknown-linux-musl
FROM frolvlad/alpine-glibc AS test
COPY --from=eventbuild /build/target/x86_64-unknown-linux-musl/debug/wg-event-gen /usr/bin/
RUN echo "2df798799c5049324174c4df0189695d -" > test.md5 && wg-event-gen | md5sum -c test.md5
FROM golang AS build
COPY wireguard-go /go/src/wireguard
@@ -10,12 +30,14 @@ RUN go build
FROM frolvlad/alpine-glibc
RUN echo http://nl.alpinelinux.org/alpine/edge/testing >> /etc/apk/repositories && apk --no-cache add wireguard-tools bash
RUN echo http://nl.alpinelinux.org/alpine/edge/testing >> /etc/apk/repositories && apk --no-cache add wireguard-tools bash nload
ENV WG_I_PREFER_BUGGY_USERSPACE_TO_POLISHED_KMOD=1
COPY --from=build /go/bin/wireguard /usr/bin/wireguard-go
COPY --from=eventbuild /build/target/x86_64-unknown-linux-musl/debug/wg-event-gen /usr/bin/
COPY init.sh /init.sh
RUN chmod +x /init.sh

7
Makefile Normal file
View File

@@ -0,0 +1,7 @@
REPO := repo.shimun.net/shimun/wireguard-user
build:
docker build . -t ${REPO}
push: build
docker push ${REPO}

1
README.md Normal file
View File

@@ -0,0 +1 @@
[![Build Status](https://ci.shimun.net/api/badges/shimun/wireguard-user/status.svg)](https://ci.shimun.net/shimun/wireguard-user)

37
init.sh
View File

@@ -8,11 +8,23 @@ PHY_IF=${WG_HOST_INTERFACE:-$(ip route | awk '/default/ { print $5 }')}
ADDRESS=${WG_ADDRESS:-10.200.200.1/24}
function shutdown() {
CONF=$(wg showconf $WG_IF)
if [ ! -z "$CONF" ]; then
echo "$CONF" > /etc/wireguard/$WG_IF.conf
fi
ip link del dev $WG_IF
iptables -D FORWARD -i $WG_IF -j ACCEPT; iptables -D FORWARD -i $WG_IF -o $PHY_IF -m state --state RELATED,ESTABLISHED -j ACCEPT; iptables -D FORWARD -i $PHY_IF -o $WG_IF -m state --state RELATED,ESTABLISHED -j ACCEPT; iptables -t nat -D POSTROUTING -s $ADDRESS -o $PHY_IF -j MASQUERADE;
#iptables -D FORWARD -i $WG_IF -j ACCEPT; iptables -t nat -D POSTROUTING -o $PHY_IF -j MASQUERADE
wg showconf $WG_IF > /etc/wireguard/$WG_IF.conf
killall sleep
setup_iptables "D"
}
function setup_iptables() {
if [ ! -z "$WG_REDIRECT_DNS" ]; then
iptables -t nat -$1 OUTPUT -p udp --dport 53 -j DNAT --to $WG_REDIRECT_DNS
iptables -t nat -$1 OUTPUT -p tcp --dport 53 -j DNAT --to $WG_REDIRECT_DNS
fi
iptables -$1 FORWARD -i $WG_IF -j ACCEPT
iptables -$1 FORWARD -i $WG_IF -o $PHY_IF -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -$1 FORWARD -i $PHY_IF -o $WG_IF -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -t nat -$1 POSTROUTING -s $ADDRESS -o $PHY_IF -j MASQUERADE;
}
/usr/bin/wireguard-go $WG_IF
@@ -26,11 +38,20 @@ else
wg setconf $WG_IF /etc/wireguard/$WG_IF.conf
fi
trap shutdown EXIT
trap shutdown EXIT SIGTERM SIGTERM
ip link set up dev $WG_IF
ip address add $ADDRESS dev $WG_IF
iptables -A FORWARD -i $WG_IF -j ACCEPT; iptables -A FORWARD -i $WG_IF -o $PHY_IF -m state --state RELATED,ESTABLISHED -j ACCEPT; iptables -A FORWARD -i $PHY_IF -o $WG_IF -m state --state RELATED,ESTABLISHED -j ACCEPT; iptables -t nat -A POSTROUTING -s $ADDRESS -o $PHY_IF -j MASQUERADE;
#iptables -A FORWARD -i $WG_IF -j ACCEPT; iptables -t nat -A POSTROUTING -o $PHY_IF -j MASQUERADE
setup_iptables "A"
sleep 100000000
sleep 3
if [ -e "/usr/bin/wg-event-gen" ]; then
sleep 3 && /usr/bin/wg-event-gen /var/run/wireguard/$WG_IF.sock 3000
else
while [ -e "/sys/class/net/$WG_IF/operstate" ]; do
sleep 10
done
fi
shutdown

76
wg-event-gen/Cargo.lock generated Normal file
View File

@@ -0,0 +1,76 @@
[[package]]
name = "base64"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "byteorder"
version = "1.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "hex"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "redox_syscall"
version = "0.1.50"
source = "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.47 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wg-event-gen"
version = "0.1.0"
dependencies = [
"base64 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (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-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 base64 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "621fc7ecb8008f86d7fb9b95356cd692ce9514b80a86d85b397f32a22da7b9e2"
"checksum byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94f88df23a25417badc922ab0f5716cc1330e87f71ddd9203b3a3ccd9cedf75d"
"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
"checksum libc 0.2.47 (registry+https://github.com/rust-lang/crates.io-index)" = "48450664a984b25d5b479554c29cc04e3150c97aa4c01da5604a2d4ed9151476"
"checksum redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)" = "52ee9a534dc1301776eff45b4fa92d2c39b1d8c3d3357e6eb593e0d795506fc2"
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
"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"

13
wg-event-gen/Cargo.toml Normal file
View File

@@ -0,0 +1,13 @@
[package]
name = "wg-event-gen"
version = "0.1.0"
authors = ["shimun <wg.shimun@shimun.net>"]
edition = "2018"
[dependencies]
hex = "0.3.2"
base64 = "0.10.0"
time = "0.1.42"
[profile.release]
lto = false

269
wg-event-gen/src/gen.rs Normal file
View File

@@ -0,0 +1,269 @@
use crate::listener::*;
use crate::*;
use std::collections::{HashMap, HashSet};
use std::env;
use std::fmt;
use std::io::prelude::*;
use std::io::{BufRead, BufReader, Error, ErrorKind, Result};
use std::net::SocketAddr;
use std::os::unix::net::UnixStream;
use std::path::PathBuf;
use std::rc::Rc;
use std::{thread, time};
pub(crate) fn gen_events(
state: &HashMap<String, Peer>,
prev: &HashMap<String, Peer>,
listeners: &Vec<Box<EventListener>>,
timeout: time::Duration,
poll_interval: time::Duration,
) {
let side_by_side = {
state
.keys()
.map(String::as_ref)
.chain(prev.keys().map(String::as_ref))
.collect::<HashSet<&str>>()
.iter()
.map(|p| (p.to_owned(), (prev.get(*p), state.get(*p))))
.collect::<HashMap<&str, (Option<&Peer>, Option<&Peer>)>>()
};
for (_id, (prev, cur)) in side_by_side {
match (prev, cur) {
(Some(prev), Some(cur)) => {
let timedout = |peer: &Peer| match peer.last_handshake_rel() {
Some(shake) if shake > timeout && shake + poll_interval < timeout => true,
Some(_) => false,
_ => true,
};
if !timedout(&prev) && timedout(&cur) {
listeners.disconnected(&cur);
continue;
}
if timedout(&prev) && !timedout(&cur) {
listeners.connected(&cur);
}
if prev.endpoint != cur.endpoint {
if let (Some(prev_addr), Some(_)) = (prev.endpoint, cur.endpoint) {
listeners.roaming(&cur, prev_addr);
}
}
}
(None, Some(cur)) => listeners.added(&cur),
(Some(prev), None) => listeners.removed(&prev),
(None, Some(_cur)) => (),
(Some(_prev), None) => (),
fail => {
println!("{:?}", fail);
unreachable!()
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::listener::*;
use crate::*;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::env;
use std::fmt;
use std::io::prelude::*;
use std::io::{BufRead, BufReader, Error, ErrorKind, Result};
use std::net::SocketAddr;
use std::os::unix::net::UnixStream;
use std::path::PathBuf;
use std::{thread, time};
struct TestListener {
calls: Rc<RefCell<Vec<String>>>,
}
impl TestListener {
fn new() -> TestListener {
Self::from(Rc::new(RefCell::new(vec![])))
}
fn from(calls: Rc<RefCell<Vec<String>>>) -> TestListener {
TestListener { calls: calls }
}
}
impl EventListener for TestListener {
fn added<'a>(&self, peer: &'a Peer) {
self.calls
.borrow_mut()
.push(format!("add {}", peer.public_key));
}
fn connected<'a>(&self, peer: &'a Peer) {
self.calls
.borrow_mut()
.push(format!("con {}", peer.public_key));
}
fn disconnected<'a>(&self, peer: &'a Peer) {
self.calls
.borrow_mut()
.push(format!("dis {}", peer.public_key));
}
fn removed<'a>(&self, peer: &'a Peer) {
self.calls
.borrow_mut()
.push(format!("rem {}", peer.public_key));
}
fn roaming<'a>(&self, peer: &'a Peer, previous_addr: SocketAddr) {
self.calls
.borrow_mut()
.push(format!("rom {}", peer.public_key));
}
}
fn listeners() -> (Vec<Box<EventListener>>, Rc<RefCell<Vec<String>>>) {
let l = TestListener::new();
let calls = l.calls.clone();
(vec![Box::new(l)], calls)
}
#[test]
fn test_setup() {
let (listeners, calls) = listeners();
let peer = peer();
listeners.connected(&peer);
assert_eq!(
vec![["con", &peer.public_key].join(" ")],
calls.borrow().clone()
);
}
fn b2h(b: &str) -> String {
hex::encode(base64::decode(b).unwrap())
}
fn peer() -> Peer {
let bkey = "HhRgEL2xsnEIqThSTUKLGaTXusorM1MFdjSSYvzBynY=";
let key = b2h(bkey);
Peer::from_kv(&vec![
("public_key".to_string(), key.clone()),
/*(
"last_handshake_time_nsec".to_string(),
(1000 * 1000 * 1).to_string(),
),*/
("endpoint".to_string(), "1.1.1.1:22222".to_string()),
])
.unwrap()
}
#[test]
fn connected() {
let peer = peer();
let mut peer_cur = peer.clone();
let mut prev: HashMap<String, Peer> = HashMap::new();
let mut cur: HashMap<String, Peer> = HashMap::new();
cur.insert(peer_cur.public_key.clone(), peer_cur.clone());
let (listener, calls) = listeners();
let interval = time::Duration::from_secs(3);
gen_events(
&cur,
&prev,
&listener,
time::Duration::from_secs(3),
interval,
);
assert_eq!(
vec![["add", &peer_cur.public_key].join(" ")],
calls.borrow().clone()
);
gen_events(
&cur,
&cur,
&listener,
time::Duration::from_secs(3),
interval,
);
//Shouldn't gen any new events
assert!(calls.borrow().len() == 1);
let (listener, calls) = listeners();
gen_events(
&prev,
&cur,
&listener,
time::Duration::from_secs(10),
interval,
);
assert_eq!(
vec![["rem", &peer.public_key].join(" ")],
calls.borrow().clone()
);
calls.borrow_mut().clear();
let mut peer_prev = peer.clone();
peer_prev.endpoint = Some("2.2.2.2:33333".parse::<SocketAddr>().unwrap());
peer_prev.last_handshake = Some(time::Duration::from_secs(1000));
prev.insert(peer_prev.public_key.clone(), peer_prev.clone());
gen_events(
&prev,
&cur,
&listener,
time::Duration::from_secs(10),
interval,
);
assert!(calls
.borrow()
.clone()
.contains(&["rom", &peer.public_key].join(" ")));
calls.borrow_mut().clear();
let mut peer_prev = peer.clone();
peer_cur.last_handshake = Some(time::Duration::from_secs(5));
cur.insert(peer_cur.public_key.clone(), peer_cur.clone());
prev.insert(peer_prev.public_key.clone(), peer_prev.clone());
gen_events(
&cur,
&prev,
&listener,
time::Duration::from_secs(10),
interval,
);
assert_eq!(
vec![["con", &peer.public_key].join(" ")],
calls.borrow().clone()
);
calls.borrow_mut().clear();
//Other way around should be a disconnect
gen_events(
&prev,
&cur,
&listener,
time::Duration::from_secs(3),
interval,
);
assert_eq!(
vec![["dis", &peer.public_key].join(" ")],
calls.borrow().clone()
);
}
}

View File

@@ -0,0 +1,159 @@
use crate::Peer;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::process::Command;
use std::thread;
pub trait EventListener {
fn added<'a>(&self, peer: &'a Peer) {
self.connected(peer);
}
fn connected<'a>(&self, peer: &'a Peer);
fn disconnected<'a>(&self, peer: &'a Peer);
fn removed<'a>(&self, peer: &'a Peer) {
self.disconnected(peer)
}
fn roaming<'a>(&self, peer: &'a Peer, previous_addr: SocketAddr);
}
impl EventListener for Vec<Box<EventListener>> {
fn added<'a>(&self, peer: &'a Peer) {
if cfg!(feature = "addrem") || cfg!(test) {
self.iter().for_each(|l| l.added(&peer));
}
}
fn connected<'a>(&self, peer: &'a Peer) {
self.iter().for_each(|l| l.connected(&peer));
}
fn disconnected<'a>(&self, peer: &'a Peer) {
self.iter().for_each(|l| l.disconnected(&peer));
}
fn removed<'a>(&self, peer: &'a Peer) {
if cfg!(feature = "addrem") || cfg!(test) {
self.iter().for_each(|l| l.removed(&peer));
}
}
fn roaming<'a>(&self, peer: &'a Peer, previous_addr: SocketAddr) {
self.iter().for_each(|l| l.roaming(&peer, previous_addr));
}
}
pub struct LogListener;
impl EventListener for LogListener {
fn connected<'a>(&self, peer: &'a Peer) {
println!("{} connected!", peer.public_key);
}
fn disconnected<'a>(&self, peer: &'a Peer) {
println!("{} disconnected!", peer.public_key);
}
fn added<'a>(&self, peer: &'a Peer) {
println!("{} added!", peer.public_key);
}
fn removed<'a>(&self, peer: &'a Peer) {
println!("{} removed!", peer.public_key);
}
fn roaming<'a>(&self, peer: &'a Peer, previous_addr: SocketAddr) {
println!(
"{} roamed {} -> {}!",
peer.public_key,
previous_addr,
peer.endpoint.unwrap()
);
}
}
pub struct ScriptListener {
pub script: PathBuf,
}
impl ScriptListener {
pub fn new(script: PathBuf) -> ScriptListener {
ScriptListener { script }
}
fn peer_props<'a>(&self,peer: &'a Peer) -> String {
format!(
"{id} {allowed_ips} {endpoint} {last_handshake} {persistent_keepalive} {traffic}",
id = peer.public_key,
allowed_ips = peer
.allowed_ips
.iter()
.map(|(addr, mask)| [addr.to_string(), mask.to_string()].join("/"))
.collect::<Vec<String>>()
.join(","),
endpoint = peer
.endpoint
.map(|e| e.to_string())
.unwrap_or("0".to_owned()),
last_handshake = peer
.last_handshake
.map(|s| s.as_secs() as i64)
.unwrap_or(-1),
persistent_keepalive = peer
.persistent_keepalive
.map(|k| k.as_secs() as i64)
.unwrap_or(-1),
traffic = {
let (rx, tx) = peer.traffic;
[rx.to_string(), tx.to_string()].join(",")
}
)
}
fn mkcmd<'a>(&self, args: Vec<&'a str>) -> Command {
let mut cmd = Command::new("/bin/sh");
cmd.arg("-c");
cmd.arg(format!(
"{} {}",
(&self.script).to_str().unwrap(),
args.join(" ")
));
cmd
}
fn call_sub<'a>(&self, args: Vec<&'a str>) {
let mut cmd = self.mkcmd(args);
thread::spawn(move || {
cmd.spawn().expect("Failed to call Script hooḱ!");
});
}
}
impl EventListener for ScriptListener {
fn connected<'a>(&self, peer: &'a Peer) {
self.call_sub(vec!["connected", &self.peer_props(peer)]);
}
fn disconnected<'a>(&self, peer: &'a Peer) {
self.call_sub(vec!["disconnected", &self.peer_props(peer)]);
}
fn added<'a>(&self, peer: &'a Peer) {
self.call_sub(vec!["added", &self.peer_props(peer)]);
}
fn removed<'a>(&self, peer: &'a Peer) {
self.call_sub(vec!["removed", &self.peer_props(peer)]);
}
fn roaming<'a>(&self, peer: &'a Peer, previous_addr: SocketAddr) {
self.call_sub(vec![
"roaming",
&self.peer_props(peer),
&previous_addr.to_string(),
]);
}
}

296
wg-event-gen/src/main.rs Normal file
View File

@@ -0,0 +1,296 @@
mod gen;
mod listener;
use crate::gen::*;
use crate::listener::*;
use base64;
use hex;
use std::collections::HashMap;
use std::env;
use std::fmt;
use std::io::prelude::*;
use std::io::{BufRead, BufReader, Error, ErrorKind, Result};
use std::net::{IpAddr, SocketAddr};
use std::os::unix::net::UnixStream;
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
use time;
pub type KV = (String, String);
#[derive(Debug, PartialEq, Eq, Hash)]
enum State {
Interface(Vec<KV>),
Peer(Vec<KV>),
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Peer {
public_key: String,
endpoint: Option<SocketAddr>,
allowed_ips: Vec<(IpAddr, u8)>,
last_handshake: Option<Duration>,
persistent_keepalive: Option<Duration>,
traffic: (u64, u64),
parsed: time::Timespec,
}
impl Peer {
fn from_kv(entries: &Vec<KV>) -> Result<Peer> {
let key = match entries
.iter()
.filter(|(key, _)| key == &"public_key")
.map(|(_, value)| value)
.next()
{
Some(key) => key,
None => return Err(Error::new(ErrorKind::Other, "Peer is missing key")),
};
Ok(Peer {
public_key: base64::encode(&hex::decode(key).unwrap()),
endpoint: entries
.iter()
.filter(|(key, _)| key == &"endpoint")
.map(|(_, value)| value.parse::<SocketAddr>().unwrap())
.next(),
allowed_ips: entries
.iter()
.filter(|(key, _)| key == &"allowed_ip")
.map(|(_, value)| {
let mut parts = value.split("/").into_iter();
match (
parts.next().and_then(|addr| addr.parse::<IpAddr>().ok()),
parts.next().and_then(|mask| mask.parse::<u8>().ok()),
) {
(Some(addr), Some(mask)) => Some((addr, mask)),
(Some(addr), None) if addr.is_ipv6() => Some((addr, 128)),
(Some(addr), None) => Some((addr, 32)),
_ => None,
}
})
.filter_map(|net| net)
.collect::<Vec<(IpAddr, u8)>>(),
last_handshake: entries
.iter()
.filter_map(|(key, value)| {
let value = || value.parse::<u64>().unwrap();
match key.as_ref() {
"last_handshake_time_sec" if value() != 0 => {
Some(Duration::new(value(), 0))
}
"last_handshake_time_nsec" if value() != 0 => {
Some(Duration::from_nanos(value()))
}
_ => None,
}
})
.fold(None, |acc, add| {
if let Some(dur) = acc {
Some(dur + add)
} else {
Some(add)
}
}),
persistent_keepalive: entries
.iter()
.filter(|(key, _)| key == &"persistent_keepalive")
.map(|(_, value)| Duration::from_secs(value.parse::<u64>().unwrap()))
.next(),
traffic: (0, 0),
parsed: time::get_time(),
})
}
pub fn last_handshake_rel(&self) -> Option<Duration> {
let time = self.parsed;
Some(Duration::new(time.sec as u64, time.nsec as u32) - self.last_handshake?)
}
}
impl State {
pub fn kv(&self) -> &Vec<KV> {
match self {
State::Interface(kv) => kv,
State::Peer(kv) => kv,
}
}
fn kv_mut(&mut self) -> &mut Vec<KV> {
match self {
State::Interface(kv) => kv,
State::Peer(kv) => kv,
}
}
pub fn id<'a>(&'a self) -> Option<String> {
self.kv()
.iter()
.filter(|(key, _)| key == &"private_key" || key == &"public_key")
.map(|(_, value)| base64::encode(&hex::decode(&value).unwrap()))
.next()
}
pub fn addr(&self) -> Option<SocketAddr> {
self.kv()
.iter()
.filter(|(key, _)| key == &"endpoint")
.map(|(_, value)| value.parse::<SocketAddr>().unwrap())
.next()
}
pub fn last_handshake(&self) -> Option<u64> {
self.kv()
.iter()
.filter(|(key, _)| key == &"last_handshake_time_nsec")
.map(|(_, value)| value.parse::<u64>().unwrap())
.next()
}
pub fn push(&mut self, key: String, value: String) {
self.kv_mut().push((key, value));
}
pub fn delta(&self, other: Self) -> Vec<KV> {
let kv = self.kv();
other
.kv()
.iter()
.filter(|pair| !kv.contains(pair))
.map(|p| p.clone())
.collect::<Vec<KV>>()
}
}
impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (k, v) in self.kv() {
write!(f, "({:10}= {})", k, v)?;
}
Ok(())
}
}
impl fmt::Display for Peer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
// write!(f, "peer {}\nshake {} ago\naddr {}\nkeepalive {}\n", self.public_key, self.last_handshake.map(|d|d.to_string()).unwrap_or("-"), self.endpoint.map(|d|d.to_string()).unwrap_or("-"), self.persistent_keepalive.map(|d|d.to_string()).unwrap_or("-"))
}
}
struct Socket {
pub path: PathBuf,
}
impl Socket {
pub fn get(&self) -> Result<Vec<State>> {
let mut stream = UnixStream::connect(&self.path)?;
stream.write_all(b"get=1\n")?;
let mut state: Vec<State> = vec![];
let mut cur = State::Interface(Vec::with_capacity(0));
for line in BufReader::new(stream).lines() {
let line = line?;
let mut iter = line.chars();
let key = iter.by_ref().take_while(|c| c != &'=').collect::<String>();
let value = iter.collect::<String>();
match key.as_ref() {
"errno" if value != "0" => Err(Error::new(
ErrorKind::Other,
format!("Socket said error: {}", value),
))?,
"public_key" | "private_key" => {
state.push(cur);
cur = if key == "private_key" {
State::Interface(Vec::with_capacity(3))
} else {
State::Peer(Vec::with_capacity(5))
};
cur.push(key, value);
}
_ => cur.push(key, value),
}
}
Ok(state)
}
pub fn get_by_id(&self) -> Result<HashMap<String, State>> {
let state = self.get()?;
let mut ided = HashMap::new();
for s in state {
if let Some(id) = s.id() {
ided.insert(id.clone(), s);
}
}
Ok(ided)
}
pub fn get_peers(&self) -> Result<HashMap<String, Peer>> {
let by_id = self.get_by_id()?;
Ok(by_id
.iter()
.filter_map(|(id, state)| {
Peer::from_kv(state.kv())
.ok()
.map(|peer| (id.to_owned(), peer))
})
.collect())
}
}
fn main() {
let mut args = env::args();
args.next(); //Ignore program name
let path = args
.next()
.map(PathBuf::from)
.filter(|p| p.as_path().exists());
let interval = Duration::from_millis(
args.next()
.map(|i| {
i.parse::<u64>()
.expect("[interval] has to be a positive int")
})
.unwrap_or(1000),
);
let mut listeners: Vec<Box<EventListener>> = vec![Box::new(LogListener)];
let events: PathBuf = "/etc/wireguard/events.sh".into();
if events.exists() {
listeners.push(Box::new(ScriptListener::new(events)))
}
let timeout = env::vars()
.collect::<HashMap<String, String>>()
.get("WG_EVENT_GEN_TIMEOUT")
.map(|timeout| {
Duration::from_secs(
timeout
.parse::<u64>()
.expect(&format!("Can't parse {} as timeout", timeout)),
)
})
.unwrap_or(Duration::from_secs(30));
if let Some(path) = path {
let sock = Socket { path };
let mut prev_state: Option<HashMap<String, Peer>> = None;
loop {
let state = match sock.get_peers() {
Ok(state) => state,
Err(err) => {
eprintln!("Failed to read from socket: {}", err);
continue;
}
};
if let Some(prev_state) = prev_state {
gen::gen_events(&state, &prev_state, &listeners, timeout, interval);
}
prev_state = Some(state);
thread::sleep(interval);
}
} else {
println!("<path> does not exist");
}
}