Skip to content

Commit

Permalink
Merge pull request #43 from jamesmcm/ivpn
Browse files Browse the repository at this point in the history
Add iVPN provider support and update-resolv-conf OpenVPN DNS behaviour
  • Loading branch information
jamesmcm authored Nov 7, 2020
2 parents ef9653b + 9859737 commit 1c52e58
Show file tree
Hide file tree
Showing 18 changed files with 685 additions and 19 deletions.
2 changes: 1 addition & 1 deletion 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.5.2"
version = "0.5.3"
authors = ["James McMurray <[email protected]>"]
edition = "2018"
license = "GPL-3.0-or-later"
Expand Down
11 changes: 8 additions & 3 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, AzireVPN, MozillaVPN, TigerVPN, ProtonVPN and
Currently Mullvad, AzireVPN, MozillaVPN, TigerVPN, ProtonVPN, iVPN and
PrivateInternetAccess are supported directly, with custom configuration files
also supported with the `--custom` argument.

Expand All @@ -24,6 +24,7 @@ lynx all running through different VPN connections:
|-----------------------|-----------------|-------------------|
| Mullvad |||
| AzireVPN |||
| iVPN |||
| PrivateInternetAccess |||
| TigerVPN |||
| ProtonVPN |||
Expand Down Expand Up @@ -133,11 +134,15 @@ $ rustc --version

## Known issues

* When launching a new application in an existing vopono namespace, any
modifications to the firewall rules (i.e. forwarding and opening
ports) will not be applied.
* Connections to the host's PulseAudio and D-bus servers will likely
fail since the connection from the network namespace will not appear as a localhost
connection. See [issue #38](https://github.com/jamesmcm/vopono/issues/38) for work on solving this.
* OpenVPN credentials are always stored in plaintext in configuration - may add
option to not store credentials, but it seems OpenVPN needs them
provided in plaintext.
* ProtonVPN DNS servers do not reliably connect, so Google's DNS is used
for now (you can override this with the `--dns` argument.
* There is no easy way to delete MozillaVPN devices (Wireguard
keypairs) - unlike Mullvad this _cannot_ be done on the webpage. I recommend using [MozWire](https://github.com/NilsIrl/MozWire) to manage this.

Expand Down
36 changes: 34 additions & 2 deletions USERGUIDE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# vopono User Guide

## asciinema example

[![asciicast](https://asciinema.org/a/369367.png)](https://asciinema.org/a/369367)

## Usage

Applications will be run as the current user by default (you can use
Expand Down Expand Up @@ -259,8 +263,36 @@ 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).

### VPN Provider limitations

#### MozillaVPN

There is no easy way to delete MozillaVPN devices (Wireguard keypairs),
unlike Mullvad this _cannot_ be done on the webpage.
I recommend using [MozWire](https://github.com/NilsIrl/MozWire) to manage this.

#### iVPN

iVPN Wireguard keypairs must be uploaded manually, as the Client Area is
behind a captcha login.

### Tunnel Port Forwarding

Some providers allow port forwarding inside the tunnel, so you can open
some ports inside the network namespace which can be accessed via the
Wireguard/OpenVPN tunnel (this can be important for BitTorrent
connectivity, etc.).

Mullvad tunnel 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).
Remember to open the port with the `-o PORTNUMBER` argument to
`vopono exec` if you have the killswitch enabled!

For iVPN port forwarding also works the same way, however it is __only
supported for OpenVPN__ on iVPN's side. So remember to pass
`--protocol openvpn -o PORTNUMBER` when trying it! Enable port
forwarding in the [Port Forwarding page in the iVPN client area](https://www.ivpn.net/clientarea/vpn/273887).

## Dependencies

Expand Down
9 changes: 7 additions & 2 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,12 @@ pub struct ExecCommand {
#[structopt(long = "keep-alive", short = "k")]
pub keep_alive: bool,

/// List of ports to forward from network namespace - usefuel for running servers and daemons
/// List of ports to open on network namespace (to allow port forwarding through the tunnel,
/// e.g. for BitTorrent, etc.)
#[structopt(long = "open-ports", short = "o")]
pub open_ports: Option<Vec<u16>>,

/// List of ports to forward from network namespace to host - useful for running servers and daemons
#[structopt(long = "forward", short = "f")]
pub forward_ports: Option<Vec<u16>>,

Expand All @@ -115,7 +120,7 @@ pub struct ExecCommand {

#[derive(StructOpt)]
pub struct ListCommand {
/// VPN Provider (if not given will use default)
/// VPN Provider
#[structopt(possible_values = &["namespaces", "applications"])]
pub list_type: Option<String>,
}
Expand Down
15 changes: 14 additions & 1 deletion src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> {

let dns = command
.dns
.clone()
.or_else(|| {
provider
.get_dyn_openvpn_provider()
Expand All @@ -150,8 +151,8 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> {
})
.unwrap_or_else(|| vec![IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8))]);

// TODO: Don't rely on Google DNS here - could copy local one?
ns.dns_config(&dns)?;

// Check if using Shadowsocks
if let Some((ss_host, ss_lport)) =
uses_shadowsocks(config_file.as_ref().expect("No config file provided"))?
Expand All @@ -177,6 +178,7 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> {
auth_file,
&dns,
!command.no_killswitch,
command.open_ports.as_ref(),
command.forward_ports.as_ref(),
firewall,
command.disable_ipv6,
Expand All @@ -194,11 +196,21 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> {
"OpenVPN not running in network namespace, probable dead lock file authentication error"
));
}

// Set DNS with OpenVPN server response if present
if command.dns.is_none() {
if let Some(newdns) = ns.openvpn.as_ref().unwrap().openvpn_dns {
let old_dns = ns.dns_config.take();
std::mem::forget(old_dns);
ns.dns_config(&[newdns])?;
}
}
}
Protocol::Wireguard => {
ns.run_wireguard(
config_file.expect("No config file provided"),
!command.no_killswitch,
command.open_ports.as_ref(),
command.forward_ports.as_ref(),
firewall,
command.disable_ipv6,
Expand All @@ -211,6 +223,7 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> {
ns.dns_config(&dns)?;
ns.run_openconnect(
config_file,
command.open_ports.as_ref(),
command.forward_ports.as_ref(),
firewall,
&server_name,
Expand Down
2 changes: 0 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ use util::clean_dead_namespaces;
use util::elevate_privileges;

// TODO:
// - Support update_resolv_conf with OpenVPN (i.e. get DNS server from OpenVPN headers)
// - Disable ipv6 traffic when not routed?
// - Allow for not saving OpenVPN creds to config

fn main() -> anyhow::Result<()> {
Expand Down
8 changes: 7 additions & 1 deletion src/netns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
pub struct NetworkNamespace {
pub name: String,
pub veth_pair: Option<VethPair>,
dns_config: Option<DnsConfig>,
pub dns_config: Option<DnsConfig>,
pub openvpn: Option<OpenVpn>,
pub wireguard: Option<Wireguard>,
pub host_masquerade: Option<HostMasquerade>,
Expand Down Expand Up @@ -207,6 +207,7 @@ impl NetworkNamespace {
auth_file: Option<PathBuf>,
dns: &[IpAddr],
use_killswitch: bool,
open_ports: Option<&Vec<u16>>,
forward_ports: Option<&Vec<u16>>,
firewall: Firewall,
disable_ipv6: bool,
Expand All @@ -217,6 +218,7 @@ impl NetworkNamespace {
auth_file,
dns,
use_killswitch,
open_ports,
forward_ports,
firewall,
disable_ipv6,
Expand All @@ -227,13 +229,15 @@ impl NetworkNamespace {
pub fn run_openconnect(
&mut self,
config_file: Option<PathBuf>,
open_ports: Option<&Vec<u16>>,
forward_ports: Option<&Vec<u16>>,
firewall: Firewall,
server: &str,
) -> anyhow::Result<()> {
self.openconnect = Some(OpenConnect::run(
&self,
config_file,
open_ports,
forward_ports,
firewall,
server,
Expand Down Expand Up @@ -264,6 +268,7 @@ impl NetworkNamespace {
&mut self,
config_file: PathBuf,
use_killswitch: bool,
open_ports: Option<&Vec<u16>>,
forward_ports: Option<&Vec<u16>>,
firewall: Firewall,
disable_ipv6: bool,
Expand All @@ -272,6 +277,7 @@ impl NetworkNamespace {
self,
config_file,
use_killswitch,
open_ports,
forward_ports,
firewall,
disable_ipv6,
Expand Down
6 changes: 6 additions & 0 deletions src/openconnect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ impl OpenConnect {
pub fn run(
netns: &NetworkNamespace,
config_file: Option<PathBuf>,
open_ports: Option<&Vec<u16>>,
forward_ports: Option<&Vec<u16>>,
firewall: Firewall,
server: &str,
Expand Down Expand Up @@ -49,6 +50,11 @@ impl OpenConnect {
.context("Failed to launch OpenConnect - is openconnect installed?")?;
let id = handle.id();

// Allow input to and output from open ports (for port forwarding in tunnel)
if let Some(opens) = open_ports {
super::util::open_ports(&netns, opens.as_slice(), firewall)?;
}

// Allow input to and output from forwarded ports
if let Some(forwards) = forward_ports {
super::util::open_ports(&netns, forwards.as_slice(), firewall)?;
Expand Down
30 changes: 28 additions & 2 deletions src/openvpn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use std::str::FromStr;
#[derive(Serialize, Deserialize)]
pub struct OpenVpn {
pid: u32,
pub openvpn_dns: Option<IpAddr>,
}

impl OpenVpn {
Expand All @@ -25,6 +26,7 @@ impl OpenVpn {
auth_file: Option<PathBuf>,
dns: &[IpAddr],
use_killswitch: bool,
open_ports: Option<&Vec<u16>>,
forward_ports: Option<&Vec<u16>>,
firewall: Firewall,
disable_ipv6: bool,
Expand Down Expand Up @@ -77,6 +79,9 @@ impl OpenVpn {
let mut logfile = BufReader::with_capacity(64, File::open(log_file_str)?);
let mut pos: usize = 0;

// Parse DNS header from OpenVPN response
let dns_regex = Regex::new(r"dhcp-option DNS ([0-9.]+)").unwrap();
let mut openvpn_dns: Option<IpAddr> = None;
// Tail OpenVPN log file
loop {
let x = logfile.read_line(&mut buffer)?;
Expand All @@ -86,6 +91,19 @@ impl OpenVpn {
debug!("{:?}", buffer);
}

if openvpn_dns.is_none() {
if let Some(cap) = dns_regex.captures(&buffer) {
if let Some(ipstr) = cap.get(1) {
debug!("Found OpenVPN DNS response: {}", ipstr.as_str());
let ipaddr = IpAddr::from_str(ipstr.as_str());
if let Ok(ip) = ipaddr {
openvpn_dns = Some(ip);
debug!("Set OpenVPN DNS to: {:?}", ip);
}
}
}
}

if buffer.contains("Initialization Sequence Completed")
|| buffer.contains("AUTH_FAILED")
|| buffer.contains("Options error")
Expand Down Expand Up @@ -114,7 +132,12 @@ impl OpenVpn {
return Err(anyhow!("OpenVPN options error, use -v for full log output"));
}

// Allow input to and output from forwarded ports
// Allow input to and output from open ports (for port forwarding in tunnel)
if let Some(opens) = open_ports {
super::util::open_ports(&netns, opens.as_slice(), firewall)?;
}

// Allow input to and output from forwarded ports (will be proxied to host)
if let Some(forwards) = forward_ports {
super::util::open_ports(&netns, forwards.as_slice(), firewall)?;
}
Expand All @@ -123,7 +146,10 @@ impl OpenVpn {
killswitch(netns, dns, remotes.as_slice(), firewall, disable_ipv6)?;
}

Ok(Self { pid: id })
Ok(Self {
pid: id,
openvpn_dns,
})
}

pub fn check_if_running(&self) -> bool {
Expand Down
Loading

0 comments on commit 1c52e58

Please sign in to comment.