summaryrefslogtreecommitdiff
path: root/src/linux/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/linux/mod.rs')
-rw-r--r--src/linux/mod.rs119
1 files changed, 119 insertions, 0 deletions
diff --git a/src/linux/mod.rs b/src/linux/mod.rs
new file mode 100644
index 0000000..68bb3a8
--- /dev/null
+++ b/src/linux/mod.rs
@@ -0,0 +1,119 @@
+pub mod firewall;
+pub mod routes;
+
+use crate::{model::Trojan, runtime::Runtime, xray};
+use anyhow::Result;
+use async_trait::async_trait;
+use std::{path::Path, time::Duration};
+
+pub struct LinuxRuntime {
+ routing: String,
+ applied: Option<Trojan>,
+}
+impl Default for LinuxRuntime {
+ fn default() -> Self {
+ Self {
+ routing: "unknown".into(),
+ applied: None,
+ }
+ }
+}
+
+#[async_trait]
+impl Runtime for LinuxRuntime {
+ async fn arm(&mut self) -> Result<()> {
+ xray::preflight().await?;
+ firewall::preflight()?;
+ routes::preflight().await?;
+ let (uid, _) = xray::identity()?;
+ firewall::apply(true, uid)?;
+ // Persist the boot guard input before recording enabled intent in SQLite.
+ write_guard(true)?;
+ self.routing = "blocked".into();
+ Ok(())
+ }
+ async fn apply(&mut self, connection: Option<&Trojan>, _: u64) -> Result<()> {
+ if self.applied.as_ref() == connection {
+ if let Some(index) = routes::tun_index().await? {
+ routes::capture(index).await?;
+ self.routing = "tun".into();
+ return Ok(());
+ }
+ }
+ self.routing = "blocked".into();
+ self.applied = None;
+ xray::systemctl(&["stop", "roentgen-xray.service"]).await?;
+ routes::remove().await?;
+ if let Some(connection) = connection {
+ xray::write_config(
+ Path::new("/run/roentgen/xray.json"),
+ &xray::configuration(connection, true, None),
+ )?;
+ xray::systemctl(&["start", "roentgen-xray.service"]).await?;
+ for _ in 0..100 {
+ if let Some(index) = routes::tun_index().await? {
+ routes::capture(index).await?;
+ self.routing = "tun".into();
+ self.applied = Some(connection.clone());
+ return Ok(());
+ }
+ tokio::time::sleep(Duration::from_millis(50)).await;
+ }
+ anyhow::bail!("Xray did not create roentgen0; traffic remains blocked");
+ }
+ Ok(())
+ }
+ async fn maintain(&mut self, connection: Option<&Trojan>, generation: u64) -> Result<()> {
+ match routes::tun_index().await? {
+ Some(index) if connection.is_some() => {
+ routes::capture(index).await?;
+ self.routing = "tun".into();
+ Ok(())
+ }
+ _ => self.apply(connection, generation).await,
+ }
+ }
+ async fn disable(&mut self) -> Result<()> {
+ xray::systemctl(&["stop", "roentgen-xray.service"]).await?;
+ routes::remove().await?;
+ self.applied = None;
+ write_guard(false)?;
+ firewall::apply(false, xray::identity()?.0)?;
+ self.routing = "direct".into();
+ Ok(())
+ }
+ fn routing(&self) -> &str {
+ &self.routing
+ }
+}
+
+pub fn write_guard(enabled: bool) -> Result<()> {
+ crate::storage::atomic_private_write(
+ Path::new("/etc/roentgen/guard.toml"),
+ format!("version = 1\nenabled = {enabled}\n").as_bytes(),
+ )
+}
+
+pub fn restore_guard() -> Result<()> {
+ #[derive(serde::Deserialize)]
+ #[serde(deny_unknown_fields)]
+ struct Guard {
+ version: u32,
+ enabled: bool,
+ }
+ let uid = xray::identity()?.0;
+ let input = std::fs::read_to_string("/etc/roentgen/guard.toml");
+ let value = input
+ .ok()
+ .and_then(|input| toml::from_str::<Guard>(&input).ok())
+ .filter(|value| value.version == 1);
+ match value {
+ Some(value) => firewall::apply(value.enabled, uid),
+ None => {
+ firewall::apply(true, uid)?;
+ anyhow::bail!(
+ "Corrupt or missing guard state; protection was armed. Use explicit recover --disable"
+ );
+ }
+ }
+}