killswitch/cli/actions/
run.rs1use super::Action;
2use crate::killswitch;
3use anyhow::Result;
4
5pub fn execute(action: &Action) -> Result<()> {
10 match action {
11 Action::Enable {
12 ipv4,
13 leak,
14 local,
15 verbose,
16 } => {
17 if verbose.is_verbose() {
18 eprintln!("Enabling VPN kill switch...");
19 if let Some(ip) = ipv4 {
20 eprintln!(" VPN gateway: {ip}");
21 }
22 if *leak {
23 eprintln!(" Allowing ICMP and DNS");
24 }
25 if *local {
26 eprintln!(" Allowing local network");
27 }
28 }
29 killswitch::enable(*leak, *local, ipv4.as_deref(), *verbose)?;
30 println!("✓ VPN kill switch enabled");
31 }
32
33 Action::Disable { verbose } => {
34 if verbose.is_verbose() {
35 eprintln!("Disabling VPN kill switch...");
36 }
37 killswitch::disable(*verbose)?;
38 println!("✓ VPN kill switch disabled");
39 }
40
41 Action::Status { verbose } => {
42 if verbose.is_verbose() {
43 eprintln!("Checking kill switch status...");
44 }
45 let status = killswitch::status()?;
46 println!("{status}");
47 }
48
49 Action::Print {
50 ipv4,
51 leak,
52 local,
53 verbose,
54 } => {
55 if verbose.is_verbose() {
56 eprintln!("Generating pf rules...");
57 }
58 let rules = killswitch::generate_rules(*leak, *local, ipv4.as_deref(), *verbose)?;
59 println!("{rules}");
60 }
61
62 Action::ShowInterfaces { verbose } => {
63 let output = killswitch::show_interfaces(*verbose)?;
64 print!("{output}");
65 }
66 }
67
68 Ok(())
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use crate::cli::verbosity::Verbosity;
75
76 #[allow(clippy::unwrap_used)]
77 #[test]
78 fn test_action_print_execution() {
79 let action = Action::Print {
81 ipv4: Some("203.0.113.1".to_string()),
82 leak: false,
83 local: false,
84 verbose: Verbosity::Normal,
85 };
86
87 let result = execute(&action);
89 assert!(result.is_ok());
90 }
91
92 #[allow(clippy::unwrap_used)]
93 #[test]
94 fn test_action_print_with_options() {
95 let action = Action::Print {
96 ipv4: Some("198.51.100.1".to_string()),
97 leak: true,
98 local: true,
99 verbose: Verbosity::Normal,
100 };
101
102 let result = execute(&action);
103 assert!(result.is_ok());
104 }
105
106 #[allow(clippy::unwrap_used)]
107 #[test]
108 fn test_action_print_rejects_private_ip() {
109 let action = Action::Print {
110 ipv4: Some("10.8.0.1".to_string()),
111 leak: false,
112 local: false,
113 verbose: Verbosity::Normal,
114 };
115
116 let result = execute(&action);
117 assert!(result.is_err());
118 }
119
120 #[allow(clippy::unwrap_used)]
121 #[test]
122 fn test_action_enable_requires_root() {
123 let action = Action::Enable {
124 ipv4: Some("10.8.0.1".to_string()),
125 leak: false,
126 local: false,
127 verbose: Verbosity::Normal,
128 };
129
130 let result = execute(&action);
132 let euid = unsafe { libc::geteuid() };
133 if euid != 0 {
134 assert!(result.is_err());
135 assert!(result.unwrap_err().to_string().contains("root privileges"));
136 }
137 }
138
139 #[allow(clippy::unwrap_used)]
140 #[test]
141 fn test_action_disable_requires_root() {
142 let action = Action::Disable {
143 verbose: Verbosity::Normal,
144 };
145
146 let result = execute(&action);
147 let euid = unsafe { libc::geteuid() };
148 if euid != 0 {
149 assert!(result.is_err());
150 assert!(result.unwrap_err().to_string().contains("root privileges"));
151 }
152 }
153}