From 13ef0ba5d19781209d3d01c36ad3915ff7ed6a34 Mon Sep 17 00:00:00 2001 From: Ishan Jain Date: Fri, 12 Aug 2022 00:30:11 +0530 Subject: [PATCH] completed a basic version of the project which can fetch wireguard connection details, add address and wireguard peer details to the router. Also added License --- Cargo.lock | 26 +++++++++++ Cargo.toml | 2 + LICENSE | 22 ++++++++++ src/main.rs | 122 ++++++++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 158 insertions(+), 14 deletions(-) create mode 100644 LICENSE diff --git a/Cargo.lock b/Cargo.lock index 61ac7b5..7d0ed15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -302,6 +302,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +[[package]] +name = "ipnetwork" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" +dependencies = [ + "serde", +] + [[package]] name = "itoa" version = "1.0.3" @@ -360,6 +369,21 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "mikrotik" +version = "0.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0da58feff66690e8458a59dfe54dbb3ec9e710435443a07a4022788d3df3ab" +dependencies = [ + "base64", + "ipnetwork", + "reqwest", + "serde", + "serde_json", + "thiserror", + "url", +] + [[package]] name = "mime" version = "0.3.16" @@ -490,6 +514,8 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" name = "pia-mikrotik" version = "0.1.0" dependencies = [ + "ipnetwork", + "mikrotik", "reqwest", "serde", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 1bd6130..725dbcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ipnetwork = "0.20.0" +mikrotik = "0.0.15" reqwest = { version= "0.11.11", features=["json", "rustls-tls"]} serde = { version = "1.0.143", features = ["derive"]} thiserror = "1.0.32" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fc6ba40 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2022 Ishan Jain + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/src/main.rs b/src/main.rs index 4e67c1d..b4a947b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,21 @@ -use reqwest::{Certificate, Client, ClientBuilder}; +use ipnetwork::{IpNetwork, Ipv4Network}; +use mikrotik::{ + interface::wireguard::{AddWireguardPeerInput, AllowedAddresses, WireguardPeer}, + ip::address, +}; +use reqwest::{Certificate, Client, ClientBuilder, Url}; use serde::{Deserialize, Serialize}; -use std::io; +use std::{ + collections::HashMap, + io, + net::{IpAddr, Ipv4Addr}, + str::FromStr, +}; use thiserror::Error; use tokio::{fs::File, io::AsyncReadExt}; +// TODO(ishan): Interface name should not hardcoded to be `pia` and should be user configurable + #[derive(Debug, Error)] pub enum PError { #[error(transparent)] @@ -11,20 +23,110 @@ pub enum PError { #[error(transparent)] IoError(#[from] io::Error), + + #[error(transparent)] + MikrotikError(#[from] mikrotik::ClientError), } #[tokio::main] async fn main() -> Result<(), PError> { let token = get_token().await?; - println!("{:?}", token); - // TODO(ishan): In future, give an option to specify exit node. // for now, we assume de-frankfurt, would prefer SG but some issues in the path to SG right now let response = register_wireguard_pub_key(&token.token).await?; + // TODO(ishan): + // 1. List all addresses in `pia` interface, remove everything except for the address we just + // received in response. DONE + // 2. List all peers attached to `pia` interface, remove all peers except for the peer details + // we just received in response. DONE + // 3. Check if the tunnel dies on it's own after some time and we if need to do something to + // keep it active. + println!("{:?}", response); + add_address(&response).await?; + + add_peer(&response).await?; + + Ok(()) +} + +async fn add_peer(ip: &RegisterWireguardPubKeyResponse) -> Result<(), PError> { + let mut mclient = mikrotik::Client::new( + Url::from_str("https://10.0.99.1").unwrap(), + "pia".to_string(), + "qwertyuiop".to_string(), + true, + )?; + + let peers = mikrotik::interface::wireguard::list_peers(&mut mclient) + .await? + .into_iter() + .filter(|peer| peer.interface == "pia"); + + mikrotik::interface::wireguard::add_peer( + &mut mclient, + AddWireguardPeerInput { + allowed_address: AllowedAddresses(vec![IpNetwork::V4( + Ipv4Network::new(Ipv4Addr::new(0, 0, 0, 0), 0).unwrap(), + )]), + comment: Some("Managed by pia-mikrotik".to_string()), + disabled: false, + endpoint_address: Some(IpAddr::from_str(&ip.server_ip).unwrap()), + endpoint_port: ip.server_port, + interface: "pia".to_string(), + persistent_keepalive: Some(25), + preshared_key: None, + public_key: ip.server_key.clone(), + }, + ) + .await?; + + for peer in peers { + mikrotik::interface::wireguard::remove_peer(&mut mclient, &peer.id).await?; + } + + Ok(()) +} + +async fn add_address(ip: &RegisterWireguardPubKeyResponse) -> Result<(), PError> { + let mut mclient = mikrotik::Client::new( + Url::from_str("https://10.0.99.1").unwrap(), + "pia".to_string(), + "qwertyuiop".to_string(), + true, + )?; + + // This represents all the addresses on `pia` interface. + // All but `ip.peer_ip` address have to be removed + let mut addresses: HashMap = address::list(&mut mclient) + .await? + .into_iter() + .filter(|addr| addr.interface == "pia") + .map(|addr| (addr.address, addr.id)) + .collect(); + + if addresses.contains_key(&ip.peer_ip) { + addresses.remove(&ip.peer_ip); + } else { + mikrotik::ip::address::add( + &mut mclient, + address::AddAddressInput { + address: ip.peer_ip.clone(), + comment: Some("Managed by pia-mikrotik".to_string()), + disabled: false, + interface: "pia".to_string(), + network: None, + }, + ) + .await?; + } + for (_, id) in addresses.into_iter() { + mikrotik::ip::address::remove(&mut mclient, &id).await?; + } + Ok(()) } @@ -38,22 +140,14 @@ async fn read_certificate() -> Result { } #[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] pub struct RegisterWireguardPubKeyResponse { pub status: String, - #[serde(rename = "server_key")] pub server_key: String, - #[serde(rename = "server_port")] - pub server_port: i64, - #[serde(rename = "server_ip")] + pub server_port: u16, pub server_ip: String, - #[serde(rename = "server_vip")] pub server_vip: String, - #[serde(rename = "peer_ip")] pub peer_ip: String, - #[serde(rename = "peer_pubkey")] pub peer_pubkey: String, - #[serde(rename = "dns_servers")] pub dns_servers: Vec, } @@ -71,7 +165,7 @@ async fn register_wireguard_pub_key( let response = client .get(format!( - "https://de-frankfurt.privacy.network:1337/addKey?pt={}&pubkey={}", + "https://sg.privacy.network:1337/addKey?pt={}&pubkey={}", urlencoding::encode(token), urlencoding::encode(&pubkey) ))