Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Cloudflare Warp support #237

Merged
merged 1 commit into from
Sep 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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),
}
}
}
Loading