use std::path::Path;
use crate::util::hostname_to_ip;
use crate::util::unix::run_program_in_netns_with_path_redirect;
use super::firewall::{Firewall, apply_tunnel_only_killswitch, wait_for_tunnel_interface};
use super::netns::NetworkNamespace;
use anyhow::{Context, anyhow};
use log::{debug, error, info};
use serde::{Deserialize, Serialize};
pub const WARP_ENDPOINT_HOST: &str = "engage.cloudflareclient.com";
const WARP_TUNNEL_INTERFACE_PREFIXES: &[&str] = &["CloudflareWARP", "warp"];
#[derive(Serialize, Deserialize, Debug)]
pub struct Warp {
pub(crate) pid: u32,
#[serde(skip)]
cleanup_enabled: bool,
}
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> {
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
));
}
let resolv_conf_path = format!("/etc/netns/{}/resolv.conf", netns.name);
let dir_path = format!("/etc/netns/{}", netns.name);
if !std::path::Path::new(&resolv_conf_path).exists() {
std::fs::create_dir_all(Path::new(&dir_path))?;
std::fs::File::create(&resolv_conf_path)
.with_context(|| format!("Failed to create resolv.conf: {}", resolv_conf_path))?;
}
info!("Launching Warp...");
let id = run_program_in_netns_with_path_redirect(
"warp-svc",
&[],
&netns.name,
"/etc/resolv.conf",
&resolv_conf_path,
)
.context("Failed to launch warp-svc - is warp-svc installed?")?;
info!("Warp launched with PID: {id}");
if let Some(opens) = open_ports {
crate::util::open_ports(netns, opens.as_slice(), firewall)?;
}
if let Some(forwards) = forward_ports {
crate::util::open_ports(netns, forwards.as_slice(), firewall)?;
}
Ok(Self {
pid: id,
cleanup_enabled: true,
})
}
pub fn apply_killswitch(
netns: &NetworkNamespace,
firewall: Firewall,
disable_ipv6: bool,
) -> anyhow::Result<()> {
let endpoints = hostname_to_ip(WARP_ENDPOINT_HOST).map_err(|e| {
anyhow!("Cannot resolve WARP control endpoint {WARP_ENDPOINT_HOST} for killswitch: {e}")
})?;
if endpoints.is_empty() {
anyhow::bail!(
"WARP control endpoint {WARP_ENDPOINT_HOST} resolved to no addresses for killswitch"
);
}
let tunnel_iface =
wait_for_tunnel_interface(&netns.name, WARP_TUNNEL_INTERFACE_PREFIXES, 20)?;
apply_tunnel_only_killswitch(
netns,
tunnel_iface.as_deref(),
&endpoints,
firewall,
disable_ipv6,
)
}
}
impl Drop for Warp {
fn drop(&mut self) {
if !self.cleanup_enabled {
return;
}
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),
}
}
}
impl Warp {
pub(crate) fn set_cleanup_enabled(&mut self, enabled: bool) {
self.cleanup_enabled = enabled;
}
}