Compare commits
24 Commits
test
...
event_gen_
Author | SHA1 | Date | |
---|---|---|---|
![]() |
57ade5590c | ||
![]() |
fa79ceae9e | ||
![]() |
22f51f85f1 | ||
![]() |
7acd090b21 | ||
![]() |
1dbc9596c2 | ||
![]() |
edf635ca9b | ||
![]() |
34e04ae726 | ||
![]() |
e2dbf0112e | ||
![]() |
198703c01b | ||
![]() |
5879bcc4e6 | ||
![]() |
a06edf48dd | ||
![]() |
1ba7eb556e | ||
![]() |
7a17b0d22c | ||
![]() |
c1e6d09246 | ||
![]() |
f036aeca41 | ||
![]() |
b7641871fb | ||
![]() |
e432793aeb | ||
![]() |
da029637a1 | ||
![]() |
03fb5cef29 | ||
![]() |
c58c970cad | ||
![]() |
f6c1c2771e | ||
![]() |
43a0f495f6 | ||
![]() |
459888cafa | ||
![]() |
7c93c5739a |
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
||||
*/target
|
37
.drone.yml
Normal file
37
.drone.yml
Normal 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
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*/target
|
16
Dockerfile
16
Dockerfile
@@ -1,3 +1,15 @@
|
||||
FROM rust:1.32-slim AS eventbuild
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
COPY wg-event-gen/Cargo.* /build/
|
||||
|
||||
RUN mkdir -p src && echo "fn main() {}" > src/main.rs && cargo build --release
|
||||
|
||||
COPY wg-event-gen/ /build
|
||||
|
||||
RUN cargo build --release
|
||||
|
||||
FROM golang AS build
|
||||
|
||||
COPY wireguard-go /go/src/wireguard
|
||||
@@ -10,12 +22,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/release/wg-event-gen /usr/bin/
|
||||
|
||||
COPY init.sh /init.sh
|
||||
|
||||
RUN chmod +x /init.sh
|
||||
|
7
Makefile
Normal file
7
Makefile
Normal 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
1
README.md
Normal file
@@ -0,0 +1 @@
|
||||
[](https://ci.shimun.net/shimun/wireguard-user)
|
33
init.sh
33
init.sh
@@ -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,16 @@ 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
|
||||
/usr/bin/wg-event-gen /var/run/wireguard/$WG_IF.sock 3000 ||
|
||||
|
||||
while [ -e "/sys/class/net/$WG_IF/operstate" ]; do
|
||||
sleep 10
|
||||
done
|
||||
|
||||
shutdown
|
||||
|
76
wg-event-gen/Cargo.lock
generated
Normal file
76
wg-event-gen/Cargo.lock
generated
Normal 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
13
wg-event-gen/Cargo.toml
Normal 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
|
230
wg-event-gen/src/gen.rs
Normal file
230
wg-event-gen/src/gen.rs
Normal file
@@ -0,0 +1,230 @@
|
||||
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,
|
||||
) {
|
||||
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)) if prev.endpoint != cur.endpoint => {
|
||||
if let (Some(prev_addr), Some(_)) = (prev.endpoint, cur.endpoint) {
|
||||
listeners.roaming(&cur, prev_addr);
|
||||
}
|
||||
}
|
||||
(Some(prev), Some(cur)) => {
|
||||
let timedout = |peer: &Peer| match peer.last_handshake_rel() {
|
||||
Some(shake) if shake < timeout => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
//if _id == "HhRgEL2xsnEIqThSTUKLGaTXusorM1MFdjSSYvzBynY=" { dbg!((cur.last_handshake_rel(),timedout(&prev) , timedout(&cur))); }
|
||||
|
||||
if !timedout(&prev) && timedout(&cur) {
|
||||
listeners.disconnected(&cur);
|
||||
}
|
||||
|
||||
if timedout(&prev) && !timedout(&cur) {
|
||||
listeners.connected(&cur);
|
||||
}
|
||||
}
|
||||
(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();
|
||||
gen_events(&cur, &prev, &listener, time::Duration::from_secs(3));
|
||||
assert_eq!(
|
||||
vec![["add", &peer_cur.public_key].join(" ")],
|
||||
calls.borrow().clone()
|
||||
);
|
||||
|
||||
gen_events(&cur, &cur, &listener, time::Duration::from_secs(3));
|
||||
|
||||
//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));
|
||||
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));
|
||||
|
||||
assert_eq!(
|
||||
vec![["rom", &peer.public_key].join(" ")],
|
||||
calls.borrow().clone()
|
||||
);
|
||||
|
||||
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));
|
||||
|
||||
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));
|
||||
|
||||
assert_eq!(
|
||||
vec![["dis", &peer.public_key].join(" ")],
|
||||
calls.borrow().clone()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
123
wg-event-gen/src/listener.rs
Normal file
123
wg-event-gen/src/listener.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
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) {
|
||||
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) {
|
||||
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 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", &peer.public_key]);
|
||||
}
|
||||
|
||||
fn disconnected<'a>(&self, peer: &'a Peer) {
|
||||
self.call_sub(vec!["disconnected", &peer.public_key]);
|
||||
}
|
||||
|
||||
fn added<'a>(&self, peer: &'a Peer) {
|
||||
self.call_sub(vec!["added", &peer.public_key]);
|
||||
}
|
||||
|
||||
fn removed<'a>(&self, peer: &'a Peer) {
|
||||
self.call_sub(vec!["removed", &peer.public_key]);
|
||||
}
|
||||
|
||||
fn roaming<'a>(&self, peer: &'a Peer, previous_addr: SocketAddr) {
|
||||
self.call_sub(vec![
|
||||
"roaming",
|
||||
&peer.public_key,
|
||||
&previous_addr.to_string(),
|
||||
&peer.endpoint.unwrap().to_string(),
|
||||
]);
|
||||
}
|
||||
}
|
263
wg-event-gen/src/main.rs
Normal file
263
wg-event-gen/src/main.rs
Normal file
@@ -0,0 +1,263 @@
|
||||
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::SocketAddr;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::path::PathBuf;
|
||||
use time;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
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>,
|
||||
last_handshake: Option<Duration>,
|
||||
persistent_keepalive: Option<Duration>,
|
||||
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(),
|
||||
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(),
|
||||
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 = 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);
|
||||
}
|
||||
prev_state = Some(state);
|
||||
thread::sleep(Duration::from_millis(interval));
|
||||
}
|
||||
} else {
|
||||
println!("<path> does not exist");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user