Skip to content

Commit

Permalink
Merge pull request #237 from jamesmcm/cloudflare_warp
Browse files Browse the repository at this point in the history
Add Cloudflare Warp support
  • Loading branch information
jamesmcm authored Sep 9, 2023
2 parents 7132200 + 2b5d310 commit f1b2e50
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 2 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ vopono includes built-in killswitches for both Wireguard and OpenVPN.
Currently Mullvad, AzireVPN, MozillaVPN, ProtonVPN, iVPN,
NordVPN, AirVPN, HMA (HideMyAss) and PrivateInternetAccess are supported directly, with custom
configuration files also supported with the `--custom` argument.
Cloudflare Warp is also supported.

For custom connections the OpenConnect and OpenFortiVPN protocols are
also supported (e.g. for enterprise VPNs). See the [vopono User Guide](USERGUIDE.md) for more details.
Expand All @@ -34,13 +35,20 @@ lynx all running through different VPN connections:
| NordVPN |||
| HMA (HideMyAss) |||
| AirVPN |||
| Cloudflare Warp\*\*\* |||

\* For ProtonVPN you can generate and download specific Wireguard config
files, and use them as a custom provider config. See the [User Guide](USERGUIDE.md)
for details - note that port forwarding is currently not supported for ProtonVPN.

\*\* Port forwarding is not currently supported for PrivateInternetAccess.

\*\*\* Cloudflare Warp uses its own protocol. Set both the provider and
protocol to `warp`. Note you must first register with `sudo warp-cli
register` and then run it once with `sudo warp-svc` and `sudo warp-cli
connect` outside of vopono. Please verify this works first before trying
it with vopono.

## Usage

Set up VPN provider configuration files:
Expand Down
15 changes: 15 additions & 0 deletions USERGUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,21 @@ the `vopono sync` process in order to copy the `AUTH-*` cookie to
access the OpenVPN configuration files, and the OpenVPN specific
credentials to use them.

Cloudflare Warp users must first register with Warp via the CLI client:
```
$ sudo warp-cli register
```
And then run Warp once to enable automatic connection on service
availability:
```
$ sudo warp-svc
$ sudo warp-cli connect
```
You can then kill `warp-svc` and run it via vopono:
```
$ vopono -v exec --no-killswitch --provider warp --protocol warp firefox-developer-edition
```

### VPN Provider limitations

#### ProtonVPN
Expand Down
19 changes: 17 additions & 2 deletions src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,11 +259,18 @@ pub fn exec(command: ExecCommand, uiclient: &dyn UiClient) -> anyhow::Result<()>
.unwrap_or_else(|| provider.get_dyn_provider().default_protocol());
}

if provider != VpnProvider::Custom {
if (provider == VpnProvider::Warp && protocol != Protocol::Warp)
|| (provider != VpnProvider::Warp && protocol == Protocol::Warp)
{
bail!("Cloudflare Warp protocol must use Warp provider");
}

if provider != VpnProvider::Custom && protocol != Protocol::Warp {
// Check config files exist for provider
let cdir = match protocol {
Protocol::OpenVpn => provider.get_dyn_openvpn_provider()?.openvpn_dir(),
Protocol::Wireguard => provider.get_dyn_wireguard_provider()?.wireguard_dir(),
Protocol::Warp => unreachable!("Unreachable, Warp must use Warp provider"),
Protocol::OpenConnect => bail!("OpenConnect must use Custom provider"),
Protocol::OpenFortiVpn => bail!("OpenFortiVpn must use Custom provider"),
}?;
Expand Down Expand Up @@ -332,12 +339,15 @@ pub fn exec(command: ExecCommand, uiclient: &dyn UiClient) -> anyhow::Result<()>
}?;
debug!("Interface: {}", &interface.name);

let config_file = if provider != VpnProvider::Custom {
let config_file = if protocol == Protocol::Warp {
None
} else if provider != VpnProvider::Custom {
let cdir = match protocol {
Protocol::OpenVpn => provider.get_dyn_openvpn_provider()?.openvpn_dir(),
Protocol::Wireguard => provider.get_dyn_wireguard_provider()?.wireguard_dir(),
Protocol::OpenConnect => bail!("OpenConnect must use Custom provider"),
Protocol::OpenFortiVpn => bail!("OpenFortiVpn must use Custom provider"),
Protocol::Warp => unreachable!(),
}?;
Some(get_config_from_alias(&cdir, &server_name)?)
} else {
Expand Down Expand Up @@ -389,6 +399,11 @@ pub fn exec(command: ExecCommand, uiclient: &dyn UiClient) -> anyhow::Result<()>
)?;
_sysctl = SysCtl::enable_ipv4_forwarding();
match protocol {
Protocol::Warp => ns.run_warp(
command.open_ports.as_ref(),
command.forward_ports.as_ref(),
firewall,
)?,
Protocol::OpenVpn => {
// Handle authentication check
let auth_file = if provider != VpnProvider::Custom {
Expand Down
1 change: 1 addition & 0 deletions src/list_configs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub fn print_configs(cmd: ServersCommand) -> anyhow::Result<()> {
let cdir = match protocol {
Protocol::OpenVpn => provider.get_dyn_openvpn_provider()?.openvpn_dir(),
Protocol::Wireguard => provider.get_dyn_wireguard_provider()?.wireguard_dir(),
Protocol::Warp => bail!("Config listing not implemented for Cloudflare Warp"),
Protocol::OpenConnect => bail!("Config listing not implemented for OpenConnect"),
Protocol::OpenFortiVpn => bail!("Config listing not implemented for OpenFortiVPN"),
}?;
Expand Down
4 changes: 4 additions & 0 deletions src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub fn synch(
protocol: Option<Protocol>,
uiclient: &dyn UiClient,
) -> anyhow::Result<()> {
// TODO: Separate availability from functionality, so we can filter disabled protocols from the UI
match protocol {
Some(Protocol::OpenVpn) => {
info!("Starting OpenVPN configuration...");
Expand All @@ -57,6 +58,9 @@ pub fn synch(
Some(Protocol::OpenFortiVpn) => {
error!("vopono sync not supported for OpenFortiVpn protocol");
}
Some(Protocol::Warp) => {
error!("vopono sync not supported for Cloudflare Warp protocol");
}
// TODO: Fix this asking for same credentials twice
None => {
if let Ok(p) = provider.get_dyn_wireguard_provider() {
Expand Down
6 changes: 6 additions & 0 deletions vopono_core/src/config/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod nordvpn;
mod pia;
mod protonvpn;
mod ui;
mod warp;

use crate::config::vpn::Protocol;
use crate::util::vopono_dir;
Expand Down Expand Up @@ -40,6 +41,7 @@ pub enum VpnProvider {
IVPN,
NordVPN,
HMA,
Warp,
Custom,
}

Expand All @@ -56,6 +58,7 @@ impl VpnProvider {
Self::IVPN => Box::new(ivpn::IVPN {}),
Self::NordVPN => Box::new(nordvpn::NordVPN {}),
Self::HMA => Box::new(hma::HMA {}),
Self::Warp => Box::new(warp::Warp {}),
Self::Custom => unimplemented!("Custom provider uses separate logic"),
}
}
Expand All @@ -70,6 +73,7 @@ impl VpnProvider {
Self::IVPN => Ok(Box::new(ivpn::IVPN {})),
Self::NordVPN => Ok(Box::new(nordvpn::NordVPN {})),
Self::HMA => Ok(Box::new(hma::HMA {})),
Self::Warp => Err(anyhow!("Cloudflare Warp supports only the Warp protocol")),
Self::MozillaVPN => Err(anyhow!("MozillaVPN only supports Wireguard!")),
Self::Custom => Err(anyhow!("Custom provider uses separate logic")),
}
Expand All @@ -83,6 +87,7 @@ impl VpnProvider {
Self::AzireVPN => Ok(Box::new(azirevpn::AzireVPN {})),
Self::IVPN => Ok(Box::new(ivpn::IVPN {})),
Self::Custom => Err(anyhow!("Custom provider uses separate logic")),
Self::Warp => Err(anyhow!("Cloudflare Warp supports only the Warp protocol")),
_ => Err(anyhow!("Wireguard not implemented")),
}
}
Expand All @@ -91,6 +96,7 @@ impl VpnProvider {
match self {
Self::Mullvad => Ok(Box::new(mullvad::Mullvad {})),
Self::Custom => Err(anyhow!("Start Shadowsocks manually for custom provider")),
Self::Warp => Err(anyhow!("Cloudflare Warp supports only the Warp protocol")),
_ => Err(anyhow!("Shadowsocks not supported")),
}
}
Expand Down
18 changes: 18 additions & 0 deletions vopono_core/src/config/providers/warp/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use super::Provider;
use crate::config::vpn::Protocol;

pub struct Warp {}

impl Provider for Warp {
fn alias(&self) -> String {
"warp".to_string()
}

fn alias_2char(&self) -> String {
"wp".to_string()
}

fn default_protocol(&self) -> Protocol {
Protocol::Warp
}
}
1 change: 1 addition & 0 deletions vopono_core/src/config/vpn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ pub enum Protocol {
Wireguard,
OpenConnect,
OpenFortiVpn,
Warp,
}

#[derive(Serialize, Deserialize)]
Expand Down
1 change: 1 addition & 0 deletions vopono_core/src/network/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ pub mod openvpn;
pub mod shadowsocks;
pub mod sysctl;
pub mod veth_pair;
pub mod warp;
pub mod wireguard;
13 changes: 13 additions & 0 deletions vopono_core/src/network/netns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use super::openfortivpn::OpenFortiVpn;
use super::openvpn::OpenVpn;
use super::shadowsocks::Shadowsocks;
use super::veth_pair::VethPair;
use super::warp::Warp;
use super::wireguard::Wireguard;
use crate::config::providers::{UiClient, VpnProvider};
use crate::config::vpn::Protocol;
Expand Down Expand Up @@ -36,6 +37,7 @@ pub struct NetworkNamespace {
pub veth_pair_ips: Option<VethPairIPs>,
pub openconnect: Option<OpenConnect>,
pub openfortivpn: Option<OpenFortiVpn>,
pub warp: Option<Warp>,
pub provider: VpnProvider,
pub protocol: Protocol,
pub firewall: Firewall,
Expand Down Expand Up @@ -93,6 +95,7 @@ impl NetworkNamespace {
veth_pair_ips: None,
openconnect: None,
openfortivpn: None,
warp: None,
provider,
protocol,
firewall,
Expand Down Expand Up @@ -353,6 +356,16 @@ impl NetworkNamespace {
Ok(())
}

pub fn run_warp(
&mut self,
open_ports: Option<&Vec<u16>>,
forward_ports: Option<&Vec<u16>>,
firewall: Firewall,
) -> anyhow::Result<()> {
self.warp = Some(Warp::run(self, open_ports, forward_ports, firewall)?);
Ok(())
}

pub fn run_shadowsocks(
&mut self,
config_file: &Path,
Expand Down
64 changes: 64 additions & 0 deletions vopono_core/src/network/warp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use super::firewall::Firewall;
use super::netns::NetworkNamespace;
use anyhow::{anyhow, Context};
use log::{debug, error, info};
use serde::{Deserialize, Serialize};

// Cloudflare Warp

#[derive(Serialize, Deserialize, Debug)]
pub struct Warp {
pid: u32,
}

impl Warp {
#[allow(clippy::too_many_arguments)]
pub fn run(
netns: &NetworkNamespace,
open_ports: Option<&Vec<u16>>,
forward_ports: Option<&Vec<u16>>,
firewall: Firewall,
) -> anyhow::Result<Self> {
// TODO: Add Killswitch using - https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/deployment/firewall/

if let Err(x) = which::which("warp-svc") {
error!("Cloudflare Warp warp-svc not found. Is warp-svc installed and on PATH?");
return Err(anyhow!(
"warp-svc not found. Is warp-svc installed and on PATH?: {:?}",
x
));
}

info!("Launching Warp...");

let handle = netns
.exec_no_block(&["warp-svc"], None, None, false, false, false, None)
.context("Failed to launch warp-svc - is waro-svc installed?")?;

let id = handle.id();

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

// Allow input to and output from forwarded ports
if let Some(forwards) = forward_ports {
crate::util::open_ports(netns, forwards.as_slice(), firewall)?;
}

Ok(Self { pid: id })
}
}

impl Drop for Warp {
fn drop(&mut self) {
match nix::sys::signal::kill(
nix::unistd::Pid::from_raw(self.pid as i32),
nix::sys::signal::Signal::SIGKILL,
) {
Ok(_) => debug!("Killed warp-svc (pid: {})", self.pid),
Err(e) => error!("Failed to kill warp-svc (pid: {}): {:?}", self.pid, e),
}
}
}

0 comments on commit f1b2e50

Please sign in to comment.