Migrate to API v2

This commit is contained in:
Andrey Golovizin 2023-04-09 14:15:48 +02:00
parent 61bb0dd397
commit 8b79f11b39
3 changed files with 48 additions and 78 deletions

2
Cargo.lock generated
View file

@ -25,7 +25,7 @@ checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
[[package]] [[package]]
name = "azirevpn" name = "azirevpn"
version = "0.1.0" version = "0.2.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "azirevpn" name = "azirevpn"
version = "0.1.0" version = "0.2.0"
authors = ["Andrey Golovizin <ag@sologoc.com>"] authors = ["Andrey Golovizin <ag@sologoc.com>"]
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"

View file

@ -1,5 +1,5 @@
use std::io::Write; use std::io::Write;
use std::net::{AddrParseError, IpAddr, ToSocketAddrs}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::process; use std::process;
use log::debug; use log::debug;
@ -7,7 +7,7 @@ use log::debug;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
const BASE_URL: &str = "https://api.azirevpn.com/v1"; const BASE_URL: &str = "https://api.azirevpn.com/v2";
/// AzireVPN client /// AzireVPN client
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
@ -37,18 +37,16 @@ struct ConfigOpts {
#[derive(Subcommand, Debug)] #[derive(Subcommand, Debug)]
enum Command { enum Command {
/// Prints the list of VPN endpoints /// Prints the list of VPN endpoints
Endpoints, Locations,
/// Prints WireGuard config /// Prints WireGuard config
Config(ConfigOpts), Config(ConfigOpts),
/// Checks connection status
Check,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
struct Endpoints { struct Locations {
wireguard: String, status: String,
locations: Vec<Location>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -57,36 +55,27 @@ struct Location {
city: String, city: String,
country: String, country: String,
iso: String, iso: String,
endpoints: Endpoints, pool: String,
} pubkey: String,
#[derive(Serialize, Deserialize, Debug)]
struct Locations {
locations: Vec<Location>,
}
#[derive(Serialize, Deserialize, Debug)]
struct CheckResult {
connected: bool,
ip: String, // XXX
location: Option<String>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
struct WireguardConfig { struct WireguardConfig {
status: String, status: String,
data: WireguardConfigData, ipv4: WireguardConfigIpv4,
ipv6: WireguardConfigIpv6,
dns: Vec<IpAddr>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "PascalCase")] struct WireguardConfigIpv4 {
struct WireguardConfigData { address: Ipv4Addr,
public_key: String, netmask: u8,
address: String, }
endpoint: String, #[derive(Serialize, Deserialize, Debug)]
struct WireguardConfigIpv6 {
#[serde(rename = "DNS")] address: Ipv6Addr,
dns: String, netmask: u8,
} }
#[derive(Debug)] #[derive(Debug)]
@ -95,34 +84,17 @@ struct WireguardKeyPair {
private_key: String, private_key: String,
} }
impl WireguardConfigData {
fn addresses(&self) -> Result<Vec<ipnet::IpNet>, ipnet::AddrParseError> {
self.address
.split(',')
.map(|s: &str| -> Result<ipnet::IpNet, ipnet::AddrParseError> { s.trim().parse() })
.collect()
}
fn dns(&self) -> Result<Vec<IpAddr>, AddrParseError> {
self.dns
.split(',')
.map(|s: &str| -> Result<IpAddr, AddrParseError> { s.trim().parse() })
.collect()
}
}
fn main() -> Result<(), anyhow::Error> { fn main() -> Result<(), anyhow::Error> {
env_logger::init(); env_logger::init();
let opts = Opts::parse(); let opts = Opts::parse();
match &opts.command { match &opts.command {
Command::Endpoints => list_endpoints(&opts)?, Command::Locations => list_locations(&opts)?,
Command::Config(get_config_opts) => get_config(&opts, get_config_opts)?, Command::Config(get_config_opts) => get_config(&opts, get_config_opts)?,
Command::Check => check(&opts)?,
} }
Ok(()) Ok(())
} }
fn list_endpoints(opts: &Opts) -> Result<(), anyhow::Error> { fn list_locations(opts: &Opts) -> Result<(), anyhow::Error> {
let locations: Locations = get_locations()?; let locations: Locations = get_locations()?;
if opts.json { if opts.json {
println!("{}", serde_json::to_string(&locations)?); println!("{}", serde_json::to_string(&locations)?);
@ -135,7 +107,8 @@ fn list_endpoints(opts: &Opts) -> Result<(), anyhow::Error> {
println!("City: {}", location.city); println!("City: {}", location.city);
println!("Country: {}", location.country); println!("Country: {}", location.country);
println!("Country code: {}", location.iso); println!("Country code: {}", location.iso);
println!("WireGuard endpoint: {}", location.endpoints.wireguard); println!("Pool: {}", location.pool);
println!("Pubkey: {}", location.pubkey);
} }
} }
Ok(()) Ok(())
@ -151,33 +124,46 @@ fn get_config(_opts: &Opts, config_opts: &ConfigOpts) -> Result<(), anyhow::Erro
debug!("location = {:?}", &location); debug!("location = {:?}", &location);
let keys = generage_keys()?; let keys = generage_keys()?;
debug!("keys = {:?}", &keys); debug!("keys = {:?}", &keys);
let config: WireguardConfig = ureq::post(&location.endpoints.wireguard) let url = format!("{}/ip/add", BASE_URL);
let config: WireguardConfig = ureq::post(&url)
.send_form(&[ .send_form(&[
("username", &config_opts.username), ("username", &config_opts.username),
("token", &config_opts.token), ("token", &config_opts.token),
("pubkey", &keys.public_key), ("key", &keys.public_key),
])? ])?
.into_json()?; .into_json()?;
debug!("response = {:?}", &config);
debug!("config = {:?}", &config); debug!("config = {:?}", &config);
write_config(&mut std::io::stdout().lock(), config_opts, &config, &keys) write_config(
&mut std::io::stdout().lock(),
config_opts,
location,
&config,
&keys,
)
} }
fn write_config( fn write_config(
output: &mut dyn Write, output: &mut dyn Write,
config_opts: &ConfigOpts, config_opts: &ConfigOpts,
location: &Location,
config: &WireguardConfig, config: &WireguardConfig,
keys: &WireguardKeyPair, keys: &WireguardKeyPair,
) -> Result<(), anyhow::Error> { ) -> Result<(), anyhow::Error> {
writeln!(output, "[Interface]")?; writeln!(output, "[Interface]")?;
writeln!(output, "PrivateKey = {}", &keys.private_key)?; writeln!(output, "PrivateKey = {}", &keys.private_key)?;
let addresses = config.data.addresses()?; let allowed_addresses = if config_opts.no_ipv6 {
let allowed_addresses = addresses vec![IpAddr::V4(config.ipv4.address)]
.iter() } else {
.filter(|addr| addr.addr().is_ipv4() || !config_opts.no_ipv6); vec![
IpAddr::V4(config.ipv4.address),
IpAddr::V6(config.ipv6.address),
]
};
write_list(output, "Address = ", allowed_addresses)?; write_list(output, "Address = ", allowed_addresses)?;
if !config_opts.no_dns { if !config_opts.no_dns {
let dns_addrs = config.data.dns()?; let dns_addrs = &config.dns;
let allowed_dns_addrs = dns_addrs let allowed_dns_addrs = dns_addrs
.iter() .iter()
.filter(|addr| addr.is_ipv4() || !config_opts.no_ipv6); .filter(|addr| addr.is_ipv4() || !config_opts.no_ipv6);
@ -186,10 +172,9 @@ fn write_config(
writeln!(output)?; writeln!(output)?;
writeln!(output, "[Peer]")?; writeln!(output, "[Peer]")?;
writeln!(output, "PublicKey = {}", &config.data.public_key)?; writeln!(output, "PublicKey = {}", &location.pubkey)?;
let endpoint_addrs = config.data.endpoint.to_socket_addrs()?; writeln!(output, "Endpoint = {}:51820", &location.pool)?;
write_list(output, "Endpoint = ", endpoint_addrs)?;
let allowed_ips: &[&str] = if config_opts.no_ipv6 { let allowed_ips: &[&str] = if config_opts.no_ipv6 {
&["0.0.0.0/0"] &["0.0.0.0/0"]
@ -217,21 +202,6 @@ where
Ok(()) Ok(())
} }
fn check(opts: &Opts) -> Result<(), anyhow::Error> {
let url = format!("{}/check", BASE_URL);
let result: CheckResult = ureq::get(&url).call()?.into_json()?;
if opts.json {
println!("{}", serde_json::to_string(&result)?);
} else {
println!("Connected: {:?}", result.connected);
println!("IP: {}", result.ip);
if let Some(location) = result.location {
println!("Location: {}", location);
}
}
Ok(())
}
fn get_locations() -> Result<Locations, anyhow::Error> { fn get_locations() -> Result<Locations, anyhow::Error> {
let url = format!("{}/locations", BASE_URL); let url = format!("{}/locations", BASE_URL);
let locations: Locations = ureq::get(&url).call()?.into_json()?; let locations: Locations = ureq::get(&url).call()?.into_json()?;