killswitch/killswitch/
mod.rs1mod network;
2mod pf;
3mod rules;
4
5use crate::cli::verbosity::Verbosity;
6use anyhow::{Context, Result, bail};
7
8#[must_use]
10pub fn is_private_ip(ip: &std::net::Ipv4Addr) -> bool {
11 let o = ip.octets();
12 o[0] == 10
14 || (o[0] == 172 && (16..=31).contains(&o[1]))
16 || (o[0] == 192 && o[1] == 168)
18 || o[0] == 127
20 || (o[0] == 169 && o[1] == 254)
22}
23
24fn check_root() -> Result<()> {
25 let euid = unsafe { libc::geteuid() };
26 if euid != 0 {
27 bail!("This operation requires root privileges. Try: sudo killswitch");
28 }
29 Ok(())
30}
31
32fn validate_ipv4(ip: &str) -> Result<()> {
33 use std::net::IpAddr;
34 let addr: IpAddr = ip.parse().context("Invalid IP address")?;
35 let IpAddr::V4(v4) = addr else {
36 bail!("IPv6 addresses are not supported: {ip}");
37 };
38 if is_private_ip(&v4) {
39 bail!("{ip} is a private/reserved IP address. VPN peer must be a public IP");
40 }
41 Ok(())
42}
43
44fn resolve_vpn_ip(ipv4: Option<&str>, verbose: Verbosity) -> Result<String> {
46 if let Some(ip) = ipv4 {
47 validate_ipv4(ip)?;
48 if verbose.is_debug() {
49 eprintln!(" Using provided VPN gateway: {ip}");
50 }
51 Ok(ip.to_string())
52 } else {
53 if verbose.is_verbose() {
54 eprintln!(" Auto-detecting VPN gateway address...");
55 }
56 network::detect_vpn_gateway(verbose)
57 }
58}
59
60pub fn enable(leak: bool, local: bool, ipv4: Option<&str>, verbose: Verbosity) -> Result<()> {
68 check_root()?;
69
70 let vpn_ip = resolve_vpn_ip(ipv4, verbose)?;
71
72 if verbose.is_debug() {
73 eprintln!(" VPN gateway: {vpn_ip}");
74 eprintln!(" Generating firewall rules...");
75 }
76
77 let rules_content = rules::generate(&vpn_ip, leak, local, verbose)?;
78
79 if verbose.is_debug() {
80 eprintln!(" Applying rules to pf...");
81 }
82
83 pf::apply_rules(&rules_content, verbose)?;
84
85 Ok(())
86}
87
88pub fn disable(verbose: Verbosity) -> Result<()> {
95 check_root()?;
96 pf::disable(verbose)?;
97 Ok(())
98}
99
100#[must_use = "status returns the current state which should be displayed or checked"]
105pub fn status() -> Result<String> {
106 pf::status()
107}
108
109pub fn generate_rules(
116 leak: bool,
117 local: bool,
118 ipv4: Option<&str>,
119 verbose: Verbosity,
120) -> Result<String> {
121 let vpn_ip = resolve_vpn_ip(ipv4, verbose)?;
122
123 rules::generate(&vpn_ip, leak, local, verbose)
124}
125
126pub fn show_interfaces(verbose: Verbosity) -> Result<String> {
132 use std::fmt::Write;
133
134 let interfaces = network::get_interfaces()?;
135
136 if interfaces.is_empty() {
137 bail!("No active interfaces found, verify you are connected to the network");
138 }
139
140 let mut out = String::new();
141 let _ = writeln!(out, "Interface MAC address IP");
142
143 let has_vpn = interfaces.iter().any(network::InterfaceInfo::is_p2p);
144
145 for iface in &interfaces {
146 let _ = writeln!(
147 out,
148 "{:<10} {:<19} {}",
149 iface.name(),
150 iface.mac(),
151 iface.ip()
152 );
153 }
154
155 if let Ok(public_ip) = network::get_public_ip() {
157 let _ = writeln!(out, "\nPublic IP address: \x1b[0;31m{public_ip}\x1b[0m");
158 }
159
160 match network::detect_vpn_gateway(verbose) {
162 Ok(peer) => {
163 let _ = writeln!(out, "PEER IP address: \x1b[0;33m{peer}\x1b[0m");
164 }
165 Err(_) if !has_vpn => {
166 let _ = writeln!(out, "\nNo VPN interface found, verify VPN is connected");
167 }
168 Err(_) => {}
169 }
170
171 let _ = writeln!(out, "\nTo enable the kill switch run: sudo killswitch -e");
172 let _ = writeln!(out, "To disable: sudo killswitch -d");
173
174 Ok(out)
175}