tproxy_config/
lib.rs

1#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
2mod common;
3mod fn_holder;
4mod linux;
5mod macos;
6mod private_ip;
7mod tproxy_args;
8mod windows;
9
10use std::net::{IpAddr, Ipv4Addr, SocketAddr};
11pub use {private_ip::is_private_ip, tproxy_args::TproxyArgs};
12
13pub use cidr::IpCidr;
14
15#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
16pub use common::{tproxy_remove, tproxy_setup};
17
18#[cfg(target_os = "linux")]
19use rtnetlink::packet_route::route::RouteMessage;
20
21pub const TUN_NAME: &str = if cfg!(target_os = "linux") {
22    "tun0"
23} else if cfg!(target_os = "windows") {
24    "wintun"
25} else if cfg!(target_os = "macos") {
26    "utun5"
27} else {
28    // panic!("Unsupported OS")
29    "unknown-tun"
30};
31pub const TUN_MTU: u16 = 1500;
32pub const PROXY_ADDR: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 1080);
33pub const TUN_IPV4: IpAddr = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 33));
34pub const TUN_NETMASK: IpAddr = IpAddr::V4(Ipv4Addr::new(255, 255, 255, 0));
35pub const TUN_GATEWAY: IpAddr = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
36pub const TUN_DNS: IpAddr = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8));
37pub const SOCKET_FWMARK_TABLE: &str = "100";
38
39#[allow(dead_code)]
40#[cfg(unix)]
41pub(crate) const ETC_RESOLV_CONF_FILE: &str = "/etc/resolv.conf";
42
43#[allow(dead_code)]
44pub(crate) fn run_command(command: &str, args: &[&str]) -> std::io::Result<Vec<u8>> {
45    let full_cmd = format!("{} {}", command, args.join(" "));
46    log::trace!("Running command: \"{full_cmd}\"...");
47    let out = match std::process::Command::new(command).args(args).output() {
48        Ok(out) => out,
49        Err(e) => {
50            log::trace!("Run command: \"{full_cmd}\" failed with: {e}");
51            return Err(e);
52        }
53    };
54    if !out.status.success() {
55        let err = String::from_utf8_lossy(if out.stderr.is_empty() { &out.stdout } else { &out.stderr });
56        let info = format!("Run command: \"{full_cmd}\" failed with {err}");
57        log::trace!("{info}");
58        return Err(std::io::Error::other(info));
59    }
60    Ok(out.stdout)
61}
62
63#[allow(dead_code)]
64#[cfg(all(feature = "unsafe-state-file", any(target_os = "macos", target_os = "windows")))]
65pub(crate) fn get_state_file_path() -> std::path::PathBuf {
66    let temp_dir = std::env::temp_dir();
67    temp_dir.join("tproxy_config_restore_state.json")
68}
69
70#[cfg(target_os = "linux")]
71#[derive(Debug, Clone)]
72pub(crate) struct FwmarkRestore {
73    pub(crate) ip_version: rtnetlink::IpVersion,
74    pub(crate) fwmark: u32,
75    pub(crate) table: u32,
76}
77
78#[allow(dead_code)]
79#[derive(Debug, Default, Clone)]
80#[cfg_attr(
81    all(all(feature = "unsafe-state-file", any(target_os = "macos", target_os = "windows"))),
82    derive(serde::Serialize, serde::Deserialize)
83)]
84pub(crate) struct TproxyStateInner {
85    pub(crate) tproxy_args: Option<TproxyArgs>,
86    pub(crate) original_dns_servers: Option<Vec<IpAddr>>,
87    pub(crate) gateway: Option<IpAddr>,
88    pub(crate) gw_scope: Option<String>,
89    pub(crate) umount_resolvconf: bool,
90    pub(crate) restore_resolvconf_content: Option<Vec<u8>>,
91    pub(crate) tproxy_removed_done: bool,
92    #[cfg(target_os = "linux")]
93    pub(crate) restore_routes: Vec<RouteMessage>,
94    #[cfg(target_os = "linux")]
95    pub(crate) remove_routes: Vec<RouteMessage>,
96    #[cfg(target_os = "linux")]
97    pub(crate) restore_gateway_mode: Option<Vec<String>>,
98    #[cfg(target_os = "linux")]
99    pub(crate) restore_ip_forwarding: bool,
100    #[cfg(target_os = "linux")]
101    pub(crate) restore_socket_fwmark: Vec<FwmarkRestore>,
102    #[cfg(target_os = "macos")]
103    pub(crate) default_service_id: Option<String>,
104    #[cfg(target_os = "macos")]
105    pub(crate) default_service_dns: Option<Vec<IpAddr>>,
106    #[cfg(target_os = "macos")]
107    pub(crate) orig_iface_name: Option<String>,
108}
109
110#[allow(dead_code)]
111#[derive(Debug, Default, Clone)]
112pub struct TproxyState {
113    inner: std::sync::Arc<futures::lock::Mutex<TproxyStateInner>>,
114}
115
116#[allow(dead_code)]
117impl TproxyState {
118    fn new(state: TproxyStateInner) -> Self {
119        Self {
120            inner: std::sync::Arc::new(futures::lock::Mutex::new(state)),
121        }
122    }
123}
124
125#[allow(dead_code)]
126#[cfg(all(feature = "unsafe-state-file", any(target_os = "macos", target_os = "windows")))]
127pub(crate) fn store_intermediate_state(state: &TproxyStateInner) -> std::io::Result<()> {
128    let contents = serde_json::to_string(&state)?;
129    std::fs::write(crate::get_state_file_path(), contents)?;
130    Ok(())
131}
132
133#[allow(dead_code)]
134#[cfg(all(feature = "unsafe-state-file", any(target_os = "macos", target_os = "windows")))]
135pub(crate) fn retrieve_intermediate_state() -> std::io::Result<TproxyStateInner> {
136    let path = crate::get_state_file_path();
137    if !path.exists() {
138        return Err(std::io::Error::new(std::io::ErrorKind::NotFound, "No state file found"));
139    }
140    let s = std::fs::read_to_string(path)?;
141    Ok(serde_json::from_str::<TproxyStateInner>(&s)?)
142}
143
144/// Compare two version strings
145/// Returns 1 if v1 > v2, -1 if v1 < v2, 0 if v1 == v2
146#[allow(dead_code)]
147pub(crate) fn compare_version(v1: &str, v2: &str) -> i32 {
148    let n = v1.len().abs_diff(v2.len());
149    let split_parse = |ver: &str| -> Vec<i32> {
150        ver.split('.')
151            .filter_map(|s| s.parse::<i32>().ok())
152            .chain(std::iter::repeat_n(0, n))
153            .collect()
154    };
155
156    std::iter::zip(split_parse(v1), split_parse(v2))
157        .skip_while(|(a, b)| a == b)
158        .map(|(a, b)| if a > b { 1 } else { -1 })
159        .next()
160        .unwrap_or(0)
161}