Skip to main content

killswitch/cli/actions/
run.rs

1use super::Action;
2use crate::killswitch;
3use anyhow::Result;
4
5/// Execute the given action
6///
7/// # Errors
8/// Returns an error if the killswitch operation fails
9pub 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        // Print action should not require root privileges
80        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        // Should succeed without root
88        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        // Should fail without root (unless running as root)
131        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}