From 1939479aa67e68917564924d4ca426a11c163cfe Mon Sep 17 00:00:00 2001 From: Ishan Jain Date: Sat, 20 Aug 2022 10:38:51 +0530 Subject: [PATCH] read config from file --- .gitignore | 2 +- Cargo.lock | 69 ++++++++++++++++++++++ Cargo.toml | 4 +- src/main.rs | 160 ++++++++++++++++++++++++++++++++++++++-------------- 4 files changed, 190 insertions(+), 45 deletions(-) diff --git a/.gitignore b/.gitignore index 093f24e..bed047d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /target env - +config.toml diff --git a/Cargo.lock b/Cargo.lock index 7d0ed15..66e072c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -148,6 +148,17 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "h2" version = "0.3.13" @@ -516,10 +527,12 @@ version = "0.1.0" dependencies = [ "ipnetwork", "mikrotik", + "ping", "reqwest", "serde", "thiserror", "tokio", + "toml", "urlencoding", ] @@ -535,12 +548,29 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "ping" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69044d1c00894fc1f43d9485aadb6ab6e68df90608fa52cf1074cda6420c6b76" +dependencies = [ + "rand", + "socket2", + "thiserror", +] + [[package]] name = "pkg-config" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + [[package]] name = "proc-macro2" version = "1.0.43" @@ -559,6 +589,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -920,6 +980,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + [[package]] name = "tower-service" version = "0.3.2" diff --git a/Cargo.toml b/Cargo.toml index 725dbcf..5d549ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,13 +3,13 @@ name = "pia-mikrotik" version = "0.1.0" 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" +ping = "0.4.0" reqwest = { version= "0.11.11", features=["json", "rustls-tls"]} serde = { version = "1.0.143", features = ["derive"]} thiserror = "1.0.32" tokio = { version = "1.20.1", features = ["full"] } +toml = "0.5.9" urlencoding = "2.1.0" diff --git a/src/main.rs b/src/main.rs index b4a947b..b6b6c21 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,23 +1,27 @@ use ipnetwork::{IpNetwork, Ipv4Network}; use mikrotik::{ - interface::wireguard::{AddWireguardPeerInput, AllowedAddresses, WireguardPeer}, + interface::wireguard::{AddWireguardPeerInput, AllowedAddresses}, ip::address, }; +use ping::ping; use reqwest::{Certificate, Client, ClientBuilder, Url}; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, + fmt::Display, io, net::{IpAddr, Ipv4Addr}, str::FromStr, + time::Duration, }; 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)] + PiaError(#[from] PiaError), + #[error(transparent)] ReqwestError(#[from] reqwest::Error), @@ -26,45 +30,105 @@ pub enum PError { #[error(transparent)] MikrotikError(#[from] mikrotik::ClientError), + + #[error(transparent)] + TomlError(#[from] toml::de::Error), +} + +#[derive(Debug, Error, Deserialize, Serialize)] +pub struct PiaError { + status: String, + message: String, +} + +impl Display for PiaError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!( + "status = {} message = {}", + self.status, self.message, + )) + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Config { + router_url: String, + router_password: String, + pia_user: String, + pia_pass: String, + pia_exit_node_url: String, + router_pubkey: String, + interface: String, } #[tokio::main] async fn main() -> Result<(), PError> { - let token = get_token().await?; + let config: Config = { + let mut f = File::open("./config.toml").await?; + + let mut contents = String::new(); + f.read_to_string(&mut contents).await?; + + toml::from_str(&contents)? + }; + + loop { + // 3. Check if the tunnel dies on it's own after some time and we if need to do something to + // keep it active. + let wg_details = match activate_tunnel(&config).await { + Ok(v) => v, + Err(e) => { + println!("error in activating tunnel: {:?}", e); + + tokio::time::sleep(Duration::from_secs(60)).await; + continue; + } + }; + + println!("added address and peer"); + + while ping( + wg_details.server_vip.parse().unwrap(), + Some(Duration::from_secs(30)), + Some(0), + None, + Some(255), + None, + ) + .is_ok() + { + println!("vpn server ip is reachabable"); + tokio::time::sleep(Duration::from_secs(10)).await; + } + } +} + +async fn activate_tunnel(config: &Config) -> Result { + let token = get_token(config).await?; // 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?; + let response = register_wireguard_pub_key(config, &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. + add_address(config, &response).await?; - println!("{:?}", response); + add_peer(config, &response).await?; - add_address(&response).await?; - - add_peer(&response).await?; - - Ok(()) + Ok(response) } -async fn add_peer(ip: &RegisterWireguardPubKeyResponse) -> Result<(), PError> { +async fn add_peer(config: &Config, 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(), + Url::from_str(&config.router_url).unwrap(), + config.interface.to_string(), + config.router_password.to_string(), true, )?; let peers = mikrotik::interface::wireguard::list_peers(&mut mclient) .await? .into_iter() - .filter(|peer| peer.interface == "pia"); + .filter(|peer| peer.interface == config.interface); mikrotik::interface::wireguard::add_peer( &mut mclient, @@ -76,35 +140,43 @@ async fn add_peer(ip: &RegisterWireguardPubKeyResponse) -> Result<(), PError> { disabled: false, endpoint_address: Some(IpAddr::from_str(&ip.server_ip).unwrap()), endpoint_port: ip.server_port, - interface: "pia".to_string(), + interface: config.interface.to_string(), persistent_keepalive: Some(25), preshared_key: None, public_key: ip.server_key.clone(), }, ) - .await?; + .await + .expect("error in adding peer"); for peer in peers { - mikrotik::interface::wireguard::remove_peer(&mut mclient, &peer.id).await?; + mikrotik::interface::wireguard::remove_peer(&mut mclient, &peer.id) + .await + .expect("error in removing peer"); } Ok(()) } -async fn add_address(ip: &RegisterWireguardPubKeyResponse) -> Result<(), PError> { +async fn add_address(config: &Config, ip: &RegisterWireguardPubKeyResponse) -> Result<(), PError> { + // 1. List all addresses in `config.interface` interface, remove everything except for the address we just + // received in response. + // 2. List all peers attached to `config.interface` interface, remove all peers except for the peer details + // we just received in response. + let mut mclient = mikrotik::Client::new( - Url::from_str("https://10.0.99.1").unwrap(), - "pia".to_string(), - "qwertyuiop".to_string(), + Url::from_str(&config.router_url).unwrap(), + config.interface.to_string(), + config.router_password.to_string(), true, )?; - // This represents all the addresses on `pia` interface. + // This represents all the addresses on `config.interface` 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") + .filter(|addr| addr.interface == config.interface) .map(|addr| (addr.address, addr.id)) .collect(); @@ -117,7 +189,7 @@ async fn add_address(ip: &RegisterWireguardPubKeyResponse) -> Result<(), PError> address: ip.peer_ip.clone(), comment: Some("Managed by pia-mikrotik".to_string()), disabled: false, - interface: "pia".to_string(), + interface: config.interface.to_string(), network: None, }, ) @@ -152,9 +224,9 @@ pub struct RegisterWireguardPubKeyResponse { } async fn register_wireguard_pub_key( + config: &Config, token: &str, ) -> Result { - let pubkey = std::env::var("PUBKEY").unwrap(); let certificate = read_certificate().await?; let client = ClientBuilder::new() @@ -165,9 +237,10 @@ async fn register_wireguard_pub_key( let response = client .get(format!( - "https://sg.privacy.network:1337/addKey?pt={}&pubkey={}", + "{}/addKey?pt={}&pubkey={}", + config.pia_exit_node_url, urlencoding::encode(token), - urlencoding::encode(&pubkey) + urlencoding::encode(&config.router_pubkey) )) .header("Content-Type", "application/x-www-form-urlencoded") .send() @@ -183,16 +256,19 @@ pub struct GetTokenOutput { token: String, } -async fn get_token() -> Result { - let username = std::env::var("PIA_USER").unwrap(); - let password = Some(std::env::var("PIA_PASS").unwrap()); - +async fn get_token(config: &Config) -> Result { let client = Client::new(); let response = client .get("https://www.privateinternetaccess.com/gtoken/generateToken") - .basic_auth(username, password) + .basic_auth(&config.pia_user, Some(&config.pia_pass)) .send(); - Ok(response.await?.json::().await?) + let response = response.await?; + + if response.status().is_success() { + Ok(response.json::().await?) + } else { + Err(PError::PiaError(response.json::().await?)) + } }