Skip to content

Commit ddd3e76

Browse files
committed
AzireVPN Wireguard working
1 parent 3163ecd commit ddd3e76

File tree

9 files changed

+480
-23
lines changed

9 files changed

+480
-23
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/target
22
Cargo.lock
3+
dialoguer

Cargo.toml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pretty_env_logger = "0.4"
1919
clap = "2"
2020
which = "4"
2121
users = "0.10"
22-
nix = "0.17"
22+
nix = "0.18"
2323
serde = {version = "1", features = ["derive", "std"]}
2424
csv = "1"
2525
dialoguer = "0.6"
@@ -32,11 +32,11 @@ chrono = "0.4"
3232
compound_duration = "1"
3333
ipnet = {version = "2", features = ["serde"]}
3434
reqwest = {default-features = false, version = "0.10", features = ["blocking", "json", "rustls-tls"]}
35-
sysinfo = "0.14"
36-
base64 = "0.12"
37-
x25519-dalek = "0.6"
38-
strum = "0.18"
39-
strum_macros = "0.18"
35+
sysinfo = "0.15"
36+
base64 = "0.13"
37+
x25519-dalek = "1"
38+
strum = "0.19"
39+
strum_macros = "0.19"
4040
zip = "0.5"
4141
maplit = "1"
4242
webbrowser = "0.5"

src/providers/azirevpn/mod.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// TODO mod openvpn;
2+
mod wireguard;
3+
4+
use super::{ConfigurationChoice, OpenVpnProvider, Provider, WireguardProvider};
5+
use crate::vpn::Protocol;
6+
use crate::wireguard::{de_socketaddr, de_vec_ipaddr, de_vec_ipnet};
7+
use dialoguer::{Input, Password};
8+
use ipnet::IpNet;
9+
use serde::Deserialize;
10+
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
11+
12+
pub struct AzireVPN {}
13+
14+
impl Provider for AzireVPN {
15+
fn alias(&self) -> String {
16+
"azire".to_string()
17+
}
18+
fn default_protocol(&self) -> Protocol {
19+
Protocol::Wireguard
20+
}
21+
}
22+
23+
#[derive(Deserialize, Debug, Clone)]
24+
struct ConnectResponse {
25+
status: String,
26+
data: WgResponse,
27+
}
28+
29+
#[derive(Deserialize, Debug, Clone)]
30+
struct WgResponse {
31+
#[serde(alias = "DNS", deserialize_with = "de_vec_ipaddr")]
32+
dns: Vec<IpAddr>,
33+
#[serde(alias = "Address", deserialize_with = "de_vec_ipnet")]
34+
address: Vec<IpNet>,
35+
#[serde(alias = "PublicKey")]
36+
public_key: String,
37+
#[serde(alias = "Endpoint", deserialize_with = "de_socketaddr")]
38+
endpoint: std::net::SocketAddr,
39+
}
40+
41+
impl AzireVPN {
42+
fn request_userpass(&self) -> anyhow::Result<(String, String)> {
43+
let username = Input::<String>::new()
44+
.with_prompt("AzireVPN username")
45+
.interact()?;
46+
let username = username.trim();
47+
let password = Password::new()
48+
.with_prompt("AzireVPN password")
49+
.interact()?;
50+
let password = password.trim();
51+
Ok((username.to_string(), password.to_string()))
52+
}
53+
}

src/providers/azirevpn/openvpn.rs

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
use super::Mullvad;
2+
use super::{ConfigurationChoice, OpenVpnProvider};
3+
use crate::util::delete_all_files_in_dir;
4+
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;
12+
use std::fs::create_dir_all;
13+
use std::fs::File;
14+
use std::io::Write;
15+
use std::net::{IpAddr, Ipv4Addr};
16+
use std::path::PathBuf;
17+
use strum::IntoEnumIterator;
18+
use strum_macros::EnumIter;
19+
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 {
46+
fn provider_dns(&self) -> Option<Vec<IpAddr>> {
47+
Some(vec![IpAddr::V4(Ipv4Addr::new(193, 138, 218, 74))])
48+
}
49+
50+
fn prompt_for_auth(&self) -> anyhow::Result<(String, String)> {
51+
let username = self.request_mullvad_username()?;
52+
Ok((username, "m".to_string()))
53+
}
54+
55+
fn auth_file_path(&self) -> anyhow::Result<PathBuf> {
56+
Ok(self.openvpn_dir()?.join("mullvad_userpass.txt"))
57+
}
58+
59+
fn create_openvpn_config(&self) -> anyhow::Result<()> {
60+
let openvpn_dir = self.openvpn_dir()?;
61+
create_dir_all(&openvpn_dir)?;
62+
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)?;
178+
}
179+
180+
// Write OpenVPN credentials file
181+
let (user, pass) = self.prompt_for_auth()?;
182+
let mut outfile = File::create(self.auth_file_path()?)?;
183+
write!(outfile, "{}\n{}", user, pass)?;
184+
Ok(())
185+
}
186+
}
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+
}

0 commit comments

Comments
 (0)