Skip to content

Commit 75c79a0

Browse files
committed
Finished AzireVPN support
1 parent ddd3e76 commit 75c79a0

File tree

7 files changed

+65
-243
lines changed

7 files changed

+65
-243
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "vopono"
33
description = "Launch applications via VPN tunnels using temporary network namespaces"
4-
version = "0.4.0"
4+
version = "0.4.1"
55
authors = ["James McMurray <[email protected]>"]
66
edition = "2018"
77
license = "GPL-3.0-or-later"

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ as normal.
77

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

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

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

19+
AzireVPN users can use [their security check page](https://www.azirevpn.com/check)
20+
for the same (note the instructions on disabling WebRTC). I noticed that
21+
when using IPv6 with OpenVPN it incorrectly states you are not connected
22+
via AzireVPN though (Wireguard works correctly).
23+
1924
Mullvad port forwarding works for both Wireguard and OpenVPN. You will
2025
need to enable the ports in your [Mullvad account](https://mullvad.net/en/account/#/ports).
2126

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

121-
For PrivateInternetAccess these should be the same as your account
127+
For PrivateInternetAccess and AzireVPN these should be the same as your account
122128
credentials.
123129

124130
For TigerVPN you can view your OpenVPN credentials [online on the "geeks" dashboard](https://www.tigervpn.com/dashboard/geeks).

src/providers/azirevpn/mod.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// TODO mod openvpn;
1+
mod openvpn;
22
mod wireguard;
33

44
use super::{ConfigurationChoice, OpenVpnProvider, Provider, WireguardProvider};
@@ -7,10 +7,20 @@ use crate::wireguard::{de_socketaddr, de_vec_ipaddr, de_vec_ipnet};
77
use dialoguer::{Input, Password};
88
use ipnet::IpNet;
99
use serde::Deserialize;
10-
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
10+
use std::net::IpAddr;
11+
12+
// AzireVPN details: https://www.azirevpn.com/docs/servers
1113

1214
pub struct AzireVPN {}
1315

16+
impl AzireVPN {
17+
fn server_aliases(&self) -> &[&str] {
18+
&[
19+
"ca1", "dk1", "fr1", "de1", "it1", "es1", "nl1", "no1", "ro1", "se1", "se2", "ch1",
20+
"th1", "us1", "us2", "uk1",
21+
]
22+
}
23+
}
1424
impl Provider for AzireVPN {
1525
fn alias(&self) -> String {
1626
"azire".to_string()

src/providers/azirevpn/openvpn.rs

Lines changed: 37 additions & 231 deletions
Original file line numberDiff line numberDiff line change
@@ -1,262 +1,68 @@
1-
use super::Mullvad;
1+
use super::AzireVPN;
22
use super::{ConfigurationChoice, OpenVpnProvider};
33
use crate::util::delete_all_files_in_dir;
44
use crate::vpn::OpenVpnProtocol;
5-
use anyhow::Context;
6-
use log::warn;
7-
use rand::seq::SliceRandom;
8-
use reqwest::blocking::Client;
9-
use serde::Deserialize;
10-
use std::collections::HashMap;
11-
use std::fmt::Display;
5+
use log::{debug, info};
126
use std::fs::create_dir_all;
137
use std::fs::File;
148
use std::io::Write;
159
use std::net::{IpAddr, Ipv4Addr};
1610
use std::path::PathBuf;
17-
use strum::IntoEnumIterator;
18-
use strum_macros::EnumIter;
1911

20-
impl Mullvad {
21-
fn get_default_openvpn_settings(&self) -> Vec<&'static str> {
22-
vec![
23-
"client",
24-
"dev tun",
25-
"resolv-retry infinite",
26-
"nobind",
27-
"persist-key",
28-
"persist-tun",
29-
"verb 3",
30-
"remote-cert-tls server",
31-
"ping 10",
32-
"ping-restart 60",
33-
"sndbuf 524288",
34-
"rcvbuf 524288",
35-
"cipher AES-256-CBC",
36-
"tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA",
37-
"auth-user-pass mullvad_userpass.txt",
38-
"ca mullvad_ca.crt",
39-
"tun-ipv6",
40-
"script-security 2",
41-
]
42-
}
43-
}
44-
45-
impl OpenVpnProvider for Mullvad {
12+
impl OpenVpnProvider for AzireVPN {
13+
// AzireVPN details: https://www.azirevpn.com/docs/servers
14+
// TODO: Add IPv6 DNS
4615
fn provider_dns(&self) -> Option<Vec<IpAddr>> {
47-
Some(vec![IpAddr::V4(Ipv4Addr::new(193, 138, 218, 74))])
16+
Some(vec![
17+
IpAddr::V4(Ipv4Addr::new(91, 231, 153, 2)),
18+
IpAddr::V4(Ipv4Addr::new(192, 211, 0, 2)),
19+
])
4820
}
4921

5022
fn prompt_for_auth(&self) -> anyhow::Result<(String, String)> {
51-
let username = self.request_mullvad_username()?;
52-
Ok((username, "m".to_string()))
23+
self.request_userpass()
5324
}
5425

5526
fn auth_file_path(&self) -> anyhow::Result<PathBuf> {
56-
Ok(self.openvpn_dir()?.join("mullvad_userpass.txt"))
27+
Ok(self.openvpn_dir()?.join("auth.txt"))
5728
}
5829

5930
fn create_openvpn_config(&self) -> anyhow::Result<()> {
31+
let protocol = OpenVpnProtocol::choose_one()?;
32+
// TODO: Allow port selection, TLS version selection
6033
let openvpn_dir = self.openvpn_dir()?;
34+
let country_map = crate::util::country_map::code_to_country_map();
6135
create_dir_all(&openvpn_dir)?;
6236
delete_all_files_in_dir(&openvpn_dir)?;
63-
64-
let client = Client::new();
65-
let relays: Vec<OpenVpnRelay> = client
66-
.get("https://api.mullvad.net/www/relays/openvpn/")
67-
.send()?
68-
.json()?;
69-
70-
let mut config_choice = ConfigType::choose_one()?;
71-
let port = config_choice.generate_port();
72-
73-
let use_ips = dialoguer::Confirm::new()
74-
.with_prompt(
75-
"Use IP addresses instead of hostnames? (may be resistant to DNS blocking, but need to be synced more frequently)"
76-
)
77-
.default(false)
78-
.interact()?;
79-
80-
let use_bridges = dialoguer::Confirm::new()
81-
.with_prompt(
82-
"Connect via a bridge? (route over two separate servers, requires connecting on TCP port 443)"
83-
)
84-
.default(false)
85-
.interact()?;
86-
87-
let mut settings = self.get_default_openvpn_settings();
88-
89-
if use_bridges {
90-
if config_choice != ConfigType::Tcp443 {
91-
warn!("Overriding chosen protocol and port to TCP 443 due to use of bridge");
92-
config_choice = ConfigType::Tcp443;
93-
}
94-
settings.push("socks-proxy 127.0.0.1 1080");
95-
}
96-
97-
match config_choice.get_protocol() {
98-
OpenVpnProtocol::UDP => {
99-
settings.push("proto udp");
100-
settings.push("fast-io");
101-
}
102-
OpenVpnProtocol::TCP => {
103-
settings.push("proto tcp");
104-
}
105-
}
106-
107-
// Group relays by country
108-
// Generate config file per relay given options
109-
// Naming: country_name-hostalias.ovpn
110-
let mut file_set: HashMap<String, Vec<String>> = HashMap::with_capacity(128);
111-
for relay in relays.into_iter().filter(|x| x.active) {
112-
let file_name = format!(
113-
"{}-{}.ovpn",
114-
relay.country_name.to_lowercase().replace(' ', "_"),
115-
relay.country_code
116-
);
117-
118-
let remote_string = if use_ips {
119-
format!(
120-
"remote {} {} # {}",
121-
relay.ipv4_addr_in, port, relay.hostname
122-
)
123-
} else {
124-
format!("remote {}.mullvad.net {}", relay.hostname, port)
125-
};
126-
127-
file_set
128-
.entry(file_name)
129-
.or_insert_with(Vec::new)
130-
.push(remote_string);
131-
}
132-
133-
let bridge_vec = if use_bridges {
134-
let bridges: Vec<OpenVpnRelay> = client
135-
.get("https://api.mullvad.net/www/relays/bridge/")
136-
.send()?
137-
.json()?;
138-
bridges
139-
.into_iter()
140-
.filter(|x| x.active)
141-
.map(|x| {
142-
format!(
143-
"route {} 255.255.255.255 net_gateway # {}",
144-
x.ipv4_addr_in, x.hostname
145-
)
146-
})
147-
.collect::<Vec<String>>()
148-
} else {
149-
Vec::new()
150-
};
151-
152-
for (file_name, mut remote_vec) in file_set.into_iter() {
153-
let mut file = File::create(&openvpn_dir.join(file_name))?;
154-
writeln!(file, "{}", settings.join("\n"))?;
155-
156-
remote_vec.shuffle(&mut rand::thread_rng());
157-
writeln!(
158-
file,
159-
"{}",
160-
remote_vec[0..remote_vec.len().min(64)].join("\n")
161-
)?;
162-
if remote_vec.len() > 1 {
163-
writeln!(file, "remote-random")?;
164-
}
165-
166-
if !bridge_vec.is_empty() {
167-
writeln!(file, "{}", bridge_vec.join("\n"))?;
168-
}
169-
}
170-
171-
// Write CA cert
172-
let ca = include_str!("mullvad_ca.crt");
173-
{
174-
let file = File::create(openvpn_dir.join("mullvad_ca.crt"))
175-
.context("Could not create mullvad CA file")?;
176-
let mut write_buf = std::io::BufWriter::new(file);
177-
write!(write_buf, "{}", ca)?;
37+
for alias in self.server_aliases() {
38+
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);
39+
let file = reqwest::blocking::get(&url)?.bytes()?;
40+
41+
let file_contents = std::str::from_utf8(&file)?;
42+
let file_contents = file_contents
43+
.split('\n')
44+
.filter(|&x| !(x.starts_with("up ") || x.starts_with("down ")))
45+
.collect::<Vec<&str>>()
46+
.join("\n");
47+
48+
let country = country_map
49+
.get(&alias[0..2])
50+
.expect("Could not map country to name");
51+
let filename = format!("{}-{}.ovpn", country, alias);
52+
debug!("Writing file: {}", filename);
53+
let mut outfile =
54+
File::create(openvpn_dir.join(filename.to_lowercase().replace(' ', "_")))?;
55+
write!(outfile, "{}", file_contents)?;
17856
}
17957

18058
// Write OpenVPN credentials file
18159
let (user, pass) = self.prompt_for_auth()?;
18260
let mut outfile = File::create(self.auth_file_path()?)?;
18361
write!(outfile, "{}\n{}", user, pass)?;
62+
info!(
63+
"AzireVPN OpenVPN config written to {}",
64+
openvpn_dir.display()
65+
);
18466
Ok(())
18567
}
18668
}
187-
188-
#[derive(EnumIter, PartialEq)]
189-
enum ConfigType {
190-
DefaultUdp,
191-
Udp53,
192-
Tcp80,
193-
Tcp443,
194-
}
195-
196-
impl ConfigType {
197-
fn get_protocol(&self) -> OpenVpnProtocol {
198-
match self {
199-
Self::DefaultUdp => OpenVpnProtocol::UDP,
200-
Self::Udp53 => OpenVpnProtocol::UDP,
201-
Self::Tcp80 => OpenVpnProtocol::TCP,
202-
Self::Tcp443 => OpenVpnProtocol::TCP,
203-
}
204-
}
205-
206-
fn generate_port(&self) -> u16 {
207-
match self {
208-
Self::DefaultUdp => *[1300, 1301, 1302, 1194, 1195, 1196, 1197]
209-
.choose(&mut rand::thread_rng())
210-
.expect("Could not choose default port"),
211-
Self::Udp53 => 53,
212-
Self::Tcp80 => 80,
213-
Self::Tcp443 => 443,
214-
}
215-
}
216-
}
217-
218-
impl Display for ConfigType {
219-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220-
let s = match self {
221-
Self::DefaultUdp => "Default (UDP)",
222-
Self::Udp53 => "UDP (Port 53)",
223-
Self::Tcp80 => "TCP (Port 80)",
224-
Self::Tcp443 => "TCP (Port 443)",
225-
};
226-
write!(f, "{}", s)
227-
}
228-
}
229-
230-
impl Default for ConfigType {
231-
fn default() -> Self {
232-
Self::DefaultUdp
233-
}
234-
}
235-
236-
impl ConfigurationChoice for ConfigType {
237-
fn prompt() -> String {
238-
"Please choose your OpenVPN connection protocol and port".to_string()
239-
}
240-
241-
fn variants() -> Vec<Self> {
242-
ConfigType::iter().collect()
243-
}
244-
245-
fn description(&self) -> Option<String> {
246-
None
247-
}
248-
}
249-
250-
// Note we ignore ipv6 addr here
251-
#[derive(Deserialize, Debug)]
252-
struct OpenVpnRelay {
253-
hostname: String,
254-
country_code: String,
255-
country_name: String,
256-
city_code: String,
257-
city_name: String,
258-
active: bool,
259-
owned: bool,
260-
provider: String,
261-
ipv4_addr_in: std::net::Ipv4Addr,
262-
}

src/providers/azirevpn/wireguard.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,7 @@ impl WireguardProvider for AzireVPN {
2121
let client = Client::new();
2222

2323
// TODO: Hardcoded list, can this be retrieved from the API?
24-
let aliases = vec![
25-
"ca1", "dk1", "fr1", "de1", "it1", "es1", "nl1", "no1", "ro1", "se1", "se2", "ch1",
26-
"th1", "us1", "us2", "uk1",
27-
];
28-
24+
let aliases = self.server_aliases();
2925
let country_map = code_to_country_map();
3026
let (username, password) = self.request_userpass()?;
3127
let keypair: WgKey = generate_keypair()?;

src/providers/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ impl VpnProvider {
5959
Self::Mullvad => Ok(Box::new(mullvad::Mullvad {})),
6060
Self::TigerVPN => Ok(Box::new(tigervpn::TigerVPN {})),
6161
Self::ProtonVPN => Ok(Box::new(protonvpn::ProtonVPN {})),
62-
Self::AzireVPN => Err(anyhow!("AzireVPN not yet implemented")),
62+
Self::AzireVPN => Ok(Box::new(azirevpn::AzireVPN {})),
6363
Self::MozillaVPN => Err(anyhow!("MozillaVPN only supports Wireguard!")),
6464
Self::Custom => Err(anyhow!("Custom provider uses separate logic")),
6565
}

0 commit comments

Comments
 (0)