1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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"
);
}
}
}
|