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 "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#[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}