summaryrefslogtreecommitdiff
path: root/src/linux
diff options
context:
space:
mode:
authorAndrew Guschin <guschin@altlinux.org>2026-10-30 03:32:00 +0400
committerAndrew Guschin <guschin@altlinux.org>2026-10-30 03:32:00 +0400
commitfc3c1d51b1be9db6ba6ad286584fbc34bb305f38 (patch)
treed23628e0534f0a4646c111c2ef6dd1501f18ae17 /src/linux
parentd608ffeeda1a479c9dc0e5f6ad269ba07a510d7f (diff)
Fix protected activation ordering and diagnostic review findings
Diffstat (limited to 'src/linux')
-rw-r--r--src/linux/firewall.rs18
-rw-r--r--src/linux/mod.rs53
-rw-r--r--src/linux/routes.rs3
3 files changed, 60 insertions, 14 deletions
diff --git a/src/linux/firewall.rs b/src/linux/firewall.rs
index 79731b7..8a0dd43 100644
--- a/src/linux/firewall.rs
+++ b/src/linux/firewall.rs
@@ -84,6 +84,12 @@ pub fn preflight() -> Result<()> {
/// Replacing the table is one kernel transaction. Connection marks survive table replacement.
pub fn apply(enabled: bool, xray_uid: u32) -> Result<()> {
+ apply_policy(enabled, xray_uid, false)
+}
+pub fn forward(xray_uid: u32) -> Result<()> {
+ apply_policy(true, xray_uid, true)
+}
+fn apply_policy(enabled: bool, xray_uid: u32, allow_tun: bool) -> Result<()> {
let existing = tables()?;
let table = Table::new(TABLE, ProtoFamily::Inet);
let mut batch = Batch::new();
@@ -124,11 +130,13 @@ pub fn apply(enabled: bool, xray_uid: u32) -> Result<()> {
guard.set_policy(Policy::Drop);
batch.add(&guard, MsgType::Add);
exceptions(&mut batch, &guard, xray_uid);
- let mut tun = Rule::new(&guard);
- tun.add_expr(&nft_expr!(meta oifname));
- tun.add_expr(&nft_expr!(cmp == &b"roentgen0\0"[..]));
- tun.add_expr(&nft_expr!(verdict accept));
- batch.add(&tun, MsgType::Add);
+ if allow_tun {
+ let mut tun = Rule::new(&guard);
+ tun.add_expr(&nft_expr!(meta oifname));
+ tun.add_expr(&nft_expr!(cmp == &b"roentgen0\0"[..]));
+ tun.add_expr(&nft_expr!(verdict accept));
+ batch.add(&tun, MsgType::Add);
+ }
}
send(batch.finalize()).context("Could not apply Roentgen firewall transaction")
}
diff --git a/src/linux/mod.rs b/src/linux/mod.rs
index 68bb3a8..fd9c471 100644
--- a/src/linux/mod.rs
+++ b/src/linux/mod.rs
@@ -9,12 +9,14 @@ use std::{path::Path, time::Duration};
pub struct LinuxRuntime {
routing: String,
applied: Option<Trojan>,
+ armed: bool,
}
impl Default for LinuxRuntime {
fn default() -> Self {
Self {
routing: "unknown".into(),
applied: None,
+ armed: false,
}
}
}
@@ -25,17 +27,39 @@ impl Runtime for LinuxRuntime {
xray::preflight().await?;
firewall::preflight()?;
routes::preflight().await?;
+ anyhow::ensure!(
+ self.applied.is_some() || routes::tun_index().await?.is_none(),
+ "roentgen0 already exists outside the active owned configuration; resolve the interface conflict before enabling"
+ );
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();
+ self.armed = true;
+ Ok(())
+ }
+ async fn block(&mut self) -> Result<()> {
+ firewall::apply(true, xray::identity()?.0)?;
+ write_guard(true)?;
+ self.routing = "blocked".into();
+ self.applied = None;
+ xray::systemctl(&["stop", "roentgen-xray.service"]).await?;
+ routes::remove().await?;
Ok(())
}
async fn apply(&mut self, connection: Option<&Trojan>, _: u64) -> Result<()> {
- if self.applied.as_ref() == connection {
+ if connection.is_none() {
+ return self.block().await;
+ }
+ anyhow::ensure!(
+ self.armed,
+ "Activation requires successful compatibility checks and an armed guard"
+ );
+ if connection.is_some() && self.applied.as_ref() == connection {
if let Some(index) = routes::tun_index().await? {
routes::capture(index).await?;
+ firewall::forward(xray::identity()?.0)?;
self.routing = "tun".into();
return Ok(());
}
@@ -45,14 +69,22 @@ impl Runtime for LinuxRuntime {
xray::systemctl(&["stop", "roentgen-xray.service"]).await?;
routes::remove().await?;
if let Some(connection) = connection {
+ anyhow::ensure!(
+ routes::tun_index().await?.is_none(),
+ "roentgen0 remained after stopping the owned Xray unit; refusing to take over another interface"
+ );
+ xray::preflight().await?;
+ firewall::preflight()?;
+ routes::preflight().await?;
xray::write_config(
Path::new("/run/roentgen/xray.json"),
- &xray::configuration(connection, true, None),
+ &xray::configuration(connection, true),
)?;
xray::systemctl(&["start", "roentgen-xray.service"]).await?;
for _ in 0..100 {
if let Some(index) = routes::tun_index().await? {
routes::capture(index).await?;
+ firewall::forward(xray::identity()?.0)?;
self.routing = "tun".into();
self.applied = Some(connection.clone());
return Ok(());
@@ -64,14 +96,16 @@ impl Runtime for LinuxRuntime {
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,
+ if connection.is_none() {
+ return self.block().await;
+ }
+ if !self.armed {
+ self.arm().await?;
}
+ self.apply(connection, generation).await
+ }
+ async fn probe(&mut self, connection: &Trojan, duration: u64) -> Result<xray::Probe> {
+ xray::Probe::start(connection, duration).await
}
async fn disable(&mut self) -> Result<()> {
xray::systemctl(&["stop", "roentgen-xray.service"]).await?;
@@ -80,6 +114,7 @@ impl Runtime for LinuxRuntime {
write_guard(false)?;
firewall::apply(false, xray::identity()?.0)?;
self.routing = "direct".into();
+ self.armed = false;
Ok(())
}
fn routing(&self) -> &str {
diff --git a/src/linux/routes.rs b/src/linux/routes.rs
index 07083bb..4212183 100644
--- a/src/linux/routes.rs
+++ b/src/linux/routes.rs
@@ -84,6 +84,9 @@ fn owned_rule(rule: &rtnetlink::packet_route::rule::RuleMessage) -> bool {
}
pub async fn capture(interface: u32) -> Result<()> {
+ // Only the product-owned TUN changes this setting. Loose reverse-path
+ // validation accepts proxied replies whose ordinary reverse route is uplink.
+ std::fs::write("/proc/sys/net/ipv4/conf/roentgen0/rp_filter", "2")?;
let handle = connection()?;
for route in [
RouteMessageBuilder::<Ipv4Addr>::new()