Skip to content

Commit

Permalink
Merge pull request #27 from jamesmcm/azirevpn
Browse files Browse the repository at this point in the history
Add support for AzireVPN
  • Loading branch information
jamesmcm authored Oct 3, 2020
2 parents 3163ecd + 75c79a0 commit c84734c
Show file tree
Hide file tree
Showing 11 changed files with 306 additions and 27 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target
Cargo.lock
dialoguer
14 changes: 7 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "vopono"
description = "Launch applications via VPN tunnels using temporary network namespaces"
version = "0.4.0"
version = "0.4.1"
authors = ["James McMurray <[email protected]>"]
edition = "2018"
license = "GPL-3.0-or-later"
Expand All @@ -19,7 +19,7 @@ pretty_env_logger = "0.4"
clap = "2"
which = "4"
users = "0.10"
nix = "0.17"
nix = "0.18"
serde = {version = "1", features = ["derive", "std"]}
csv = "1"
dialoguer = "0.6"
Expand All @@ -32,11 +32,11 @@ chrono = "0.4"
compound_duration = "1"
ipnet = {version = "2", features = ["serde"]}
reqwest = {default-features = false, version = "0.10", features = ["blocking", "json", "rustls-tls"]}
sysinfo = "0.14"
base64 = "0.12"
x25519-dalek = "0.6"
strum = "0.18"
strum_macros = "0.18"
sysinfo = "0.15"
base64 = "0.13"
x25519-dalek = "1"
strum = "0.19"
strum_macros = "0.19"
zip = "0.5"
maplit = "1"
webbrowser = "0.5"
Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ as normal.

vopono includes built-in killswitches for both Wireguard and OpenVPN.

Currently Mullvad, MozillaVPN, TigerVPN, ProtonVPN and
Currently Mullvad, AzireVPN, MozillaVPN, TigerVPN, ProtonVPN and
PrivateInternetAccess are supported directly, with custom configuration files
also supported with the `--custom` argument.

Expand All @@ -16,6 +16,11 @@ check the security of their browser's connection. This was used with the
Mullvad configuration to verify that there is no DNS leaking or
BitTorrent leaking for both the OpenVPN and Wireguard configurations.

AzireVPN users can use [their security check page](https://www.azirevpn.com/check)
for the same (note the instructions on disabling WebRTC). I noticed that
when using IPv6 with OpenVPN it incorrectly states you are not connected
via AzireVPN though (Wireguard works correctly).

Mullvad port forwarding works for both Wireguard and OpenVPN. You will
need to enable the ports in your [Mullvad account](https://mullvad.net/en/account/#/ports).

Expand All @@ -36,6 +41,7 @@ lynx all running through different VPN connections:
| Provider | OpenVPN support | Wireguard support |
|-----------------------|-----------------|-------------------|
| Mullvad |||
| AzireVPN |||
| PrivateInternetAccess |||
| TigerVPN |||
| ProtonVPN |||
Expand Down Expand Up @@ -118,7 +124,7 @@ The sync process will save your credentials to a file in the
config directory of the provider, so it can be passed to OpenVPN.
If it is missing you will be prompted for your credentials.

For PrivateInternetAccess these should be the same as your account
For PrivateInternetAccess and AzireVPN these should be the same as your account
credentials.

For TigerVPN you can view your OpenVPN credentials [online on the "geeks" dashboard](https://www.tigervpn.com/dashboard/geeks).
Expand Down
63 changes: 63 additions & 0 deletions src/providers/azirevpn/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
mod openvpn;
mod wireguard;

use super::{ConfigurationChoice, OpenVpnProvider, Provider, WireguardProvider};
use crate::vpn::Protocol;
use crate::wireguard::{de_socketaddr, de_vec_ipaddr, de_vec_ipnet};
use dialoguer::{Input, Password};
use ipnet::IpNet;
use serde::Deserialize;
use std::net::IpAddr;

// AzireVPN details: https://www.azirevpn.com/docs/servers

pub struct AzireVPN {}

impl AzireVPN {
fn server_aliases(&self) -> &[&str] {
&[
"ca1", "dk1", "fr1", "de1", "it1", "es1", "nl1", "no1", "ro1", "se1", "se2", "ch1",
"th1", "us1", "us2", "uk1",
]
}
}
impl Provider for AzireVPN {
fn alias(&self) -> String {
"azire".to_string()
}
fn default_protocol(&self) -> Protocol {
Protocol::Wireguard
}
}

#[derive(Deserialize, Debug, Clone)]
struct ConnectResponse {
status: String,
data: WgResponse,
}

#[derive(Deserialize, Debug, Clone)]
struct WgResponse {
#[serde(alias = "DNS", deserialize_with = "de_vec_ipaddr")]
dns: Vec<IpAddr>,
#[serde(alias = "Address", deserialize_with = "de_vec_ipnet")]
address: Vec<IpNet>,
#[serde(alias = "PublicKey")]
public_key: String,
#[serde(alias = "Endpoint", deserialize_with = "de_socketaddr")]
endpoint: std::net::SocketAddr,
}

impl AzireVPN {
fn request_userpass(&self) -> anyhow::Result<(String, String)> {
let username = Input::<String>::new()
.with_prompt("AzireVPN username")
.interact()?;
let username = username.trim();
let password = Password::new()
.with_prompt("AzireVPN password")
.interact()?;
let password = password.trim();
Ok((username.to_string(), password.to_string()))
}
}
68 changes: 68 additions & 0 deletions src/providers/azirevpn/openvpn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use super::AzireVPN;
use super::{ConfigurationChoice, OpenVpnProvider};
use crate::util::delete_all_files_in_dir;
use crate::vpn::OpenVpnProtocol;
use log::{debug, info};
use std::fs::create_dir_all;
use std::fs::File;
use std::io::Write;
use std::net::{IpAddr, Ipv4Addr};
use std::path::PathBuf;

impl OpenVpnProvider for AzireVPN {
// AzireVPN details: https://www.azirevpn.com/docs/servers
// TODO: Add IPv6 DNS
fn provider_dns(&self) -> Option<Vec<IpAddr>> {
Some(vec![
IpAddr::V4(Ipv4Addr::new(91, 231, 153, 2)),
IpAddr::V4(Ipv4Addr::new(192, 211, 0, 2)),
])
}

fn prompt_for_auth(&self) -> anyhow::Result<(String, String)> {
self.request_userpass()
}

fn auth_file_path(&self) -> anyhow::Result<PathBuf> {
Ok(self.openvpn_dir()?.join("auth.txt"))
}

fn create_openvpn_config(&self) -> anyhow::Result<()> {
let protocol = OpenVpnProtocol::choose_one()?;
// TODO: Allow port selection, TLS version selection
let openvpn_dir = self.openvpn_dir()?;
let country_map = crate::util::country_map::code_to_country_map();
create_dir_all(&openvpn_dir)?;
delete_all_files_in_dir(&openvpn_dir)?;
for alias in self.server_aliases() {
let url = format!("https://www.azirevpn.com/cfg/openvpn/generate?country={}&os=linux-cli&nat=1&port=random&protocol={}&tls=gcm&keys=0", alias, protocol);
let file = reqwest::blocking::get(&url)?.bytes()?;

let file_contents = std::str::from_utf8(&file)?;
let file_contents = file_contents
.split('\n')
.filter(|&x| !(x.starts_with("up ") || x.starts_with("down ")))
.collect::<Vec<&str>>()
.join("\n");

let country = country_map
.get(&alias[0..2])
.expect("Could not map country to name");
let filename = format!("{}-{}.ovpn", country, alias);
debug!("Writing file: {}", filename);
let mut outfile =
File::create(openvpn_dir.join(filename.to_lowercase().replace(' ', "_")))?;
write!(outfile, "{}", file_contents)?;
}

// Write OpenVPN credentials file
let (user, pass) = self.prompt_for_auth()?;
let mut outfile = File::create(self.auth_file_path()?)?;
write!(outfile, "{}\n{}", user, pass)?;
info!(
"AzireVPN OpenVPN config written to {}",
openvpn_dir.display()
);
Ok(())
}
}
98 changes: 98 additions & 0 deletions src/providers/azirevpn/wireguard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use super::AzireVPN;
use super::{ConnectResponse, WgResponse, WireguardProvider};
use crate::util::country_map::code_to_country_map;
use crate::util::delete_all_files_in_dir;
use crate::util::wireguard::{generate_keypair, WgKey};
use crate::wireguard::{WireguardConfig, WireguardInterface, WireguardPeer};
use ipnet::IpNet;
use log::{debug, info};
use regex::Regex;
use reqwest::blocking::Client;
use std::fs::create_dir_all;
use std::io::Write;
use std::str::FromStr;

impl WireguardProvider for AzireVPN {
fn create_wireguard_config(&self) -> anyhow::Result<()> {
let wireguard_dir = self.wireguard_dir()?;
create_dir_all(&wireguard_dir)?;
delete_all_files_in_dir(&wireguard_dir)?;

let client = Client::new();

// TODO: Hardcoded list, can this be retrieved from the API?
let aliases = self.server_aliases();
let country_map = code_to_country_map();
let (username, password) = self.request_userpass()?;
let keypair: WgKey = generate_keypair()?;
debug!("Chosen keypair: {:?}", keypair);

let mut peers: Vec<(String, WgResponse)> = vec![];
for alias in aliases {
let response = client
.post(reqwest::Url::parse(&format!(
"https://api.azirevpn.com/v1/wireguard/connect/{}",
alias
))?)
.form(&[
("username", &username),
("password", &password),
("pubkey", &keypair.public),
])
.send()?;
debug!("Response: {:?}", response);
let response: ConnectResponse = response.json()?;

peers.push((alias.to_string(), response.data));
}

// TODO: Allow custom port - need to check AzireVPN's restrictions
// let port = 51820;

let allowed_ips = vec![IpNet::from_str("0.0.0.0/0")?, IpNet::from_str("::0/0")?];

// TODO: avoid hacky regex for TOML -> wireguard config conversion
let re = Regex::new(r"=\s\[(?P<value>[^\]]+)\]")?;
for (alias, wg_peer) in peers {
let interface = WireguardInterface {
private_key: keypair.private.clone(),
address: wg_peer.address,
dns: wg_peer.dns,
};

let wireguard_peer = WireguardPeer {
public_key: wg_peer.public_key.clone(),
allowed_ips: allowed_ips.clone(),
endpoint: wg_peer.endpoint,
};

let wireguard_conf = WireguardConfig {
interface: interface.clone(),
peer: wireguard_peer,
};

let country = country_map
.get(&alias[0..2])
.expect("Could not map country code");

let path = wireguard_dir.join(format!("{}-{}.conf", country, alias));

let mut toml = toml::to_string(&wireguard_conf)?;
toml.retain(|c| c != '"');
let toml = toml.replace(", ", ",");
let toml = re.replace_all(&toml, "= $value").to_string();
// Create file, write TOML
{
let mut f = std::fs::File::create(path)?;
write!(f, "{}", toml)?;
}
}

info!(
"AzireVPN Wireguard config written to {}",
wireguard_dir.display()
);

Ok(())
}
}
25 changes: 15 additions & 10 deletions src/providers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod azirevpn;
mod mozilla;
mod mullvad;
mod pia;
Expand Down Expand Up @@ -30,9 +31,10 @@ arg_enum! {
pub enum VpnProvider {
PrivateInternetAccess,
Mullvad,
TigerVpn,
ProtonVpn,
MozillaVpn,
TigerVPN,
ProtonVPN,
MozillaVPN,
AzireVPN,
Custom,
}
}
Expand All @@ -43,9 +45,10 @@ impl VpnProvider {
match self {
Self::PrivateInternetAccess => Box::new(pia::PrivateInternetAccess {}),
Self::Mullvad => Box::new(mullvad::Mullvad {}),
Self::TigerVpn => Box::new(tigervpn::TigerVPN {}),
Self::ProtonVpn => Box::new(protonvpn::ProtonVPN {}),
Self::MozillaVpn => Box::new(mozilla::MozillaVPN {}),
Self::TigerVPN => Box::new(tigervpn::TigerVPN {}),
Self::ProtonVPN => Box::new(protonvpn::ProtonVPN {}),
Self::MozillaVPN => Box::new(mozilla::MozillaVPN {}),
Self::AzireVPN => Box::new(azirevpn::AzireVPN {}),
Self::Custom => unimplemented!("Custom provider uses separate logic"),
}
}
Expand All @@ -54,17 +57,19 @@ impl VpnProvider {
match self {
Self::PrivateInternetAccess => Ok(Box::new(pia::PrivateInternetAccess {})),
Self::Mullvad => Ok(Box::new(mullvad::Mullvad {})),
Self::TigerVpn => Ok(Box::new(tigervpn::TigerVPN {})),
Self::ProtonVpn => Ok(Box::new(protonvpn::ProtonVPN {})),
Self::MozillaVpn => Err(anyhow!("MozillaVPN only supports Wireguard!")),
Self::TigerVPN => Ok(Box::new(tigervpn::TigerVPN {})),
Self::ProtonVPN => Ok(Box::new(protonvpn::ProtonVPN {})),
Self::AzireVPN => Ok(Box::new(azirevpn::AzireVPN {})),
Self::MozillaVPN => Err(anyhow!("MozillaVPN only supports Wireguard!")),
Self::Custom => Err(anyhow!("Custom provider uses separate logic")),
}
}

pub fn get_dyn_wireguard_provider(&self) -> anyhow::Result<Box<dyn WireguardProvider>> {
match self {
Self::Mullvad => Ok(Box::new(mullvad::Mullvad {})),
Self::MozillaVpn => Ok(Box::new(mozilla::MozillaVPN {})),
Self::MozillaVPN => Ok(Box::new(mozilla::MozillaVPN {})),
Self::AzireVPN => Ok(Box::new(azirevpn::AzireVPN {})),
Self::Custom => Err(anyhow!("Custom provider uses separate logic")),
_ => Err(anyhow!("Wireguard not implemented")),
}
Expand Down
2 changes: 1 addition & 1 deletion src/providers/mozilla/wireguard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ impl WireguardProvider for MozillaVPN {
IpNet::from(wg_peer.ipv4_address),
IpNet::from(wg_peer.ipv6_address),
],
dns: IpAddr::from(dns),
dns: vec![IpAddr::from(dns)],
};

let port = request_port()?;
Expand Down
2 changes: 1 addition & 1 deletion src/providers/mullvad/wireguard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ impl WireguardProvider for Mullvad {
IpNet::from(wg_peer.ipv4_address),
IpNet::from(wg_peer.ipv6_address),
],
dns: IpAddr::from(dns),
dns: vec![IpAddr::from(dns)],
};

let port = request_port()?;
Expand Down
Loading

0 comments on commit c84734c

Please sign in to comment.