mirror of https://github.com/ospab/ostp.git
Release v0.3.18
This commit is contained in:
parent
ad3a8cbe8c
commit
78c7a9e886
|
|
@ -1447,7 +1447,7 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ostp"
|
name = "ostp"
|
||||||
version = "0.3.16"
|
version = "0.3.18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
|
@ -1467,11 +1467,13 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"url",
|
"url",
|
||||||
|
"windows",
|
||||||
|
"winres",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ostp-client"
|
name = "ostp-client"
|
||||||
version = "0.3.16"
|
version = "0.3.18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
|
@ -1506,7 +1508,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ostp-core"
|
name = "ostp-core"
|
||||||
version = "0.3.16"
|
version = "0.3.18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
|
@ -1543,7 +1545,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ostp-server"
|
name = "ostp-server"
|
||||||
version = "0.3.16"
|
version = "0.3.18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
|
|
@ -1576,7 +1578,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ostp-tun"
|
name = "ostp-tun"
|
||||||
version = "0.3.16"
|
version = "0.3.18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
@ -1584,11 +1586,12 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tun",
|
"tun",
|
||||||
"winapi",
|
"winapi",
|
||||||
|
"windows",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ostp-tun-helper"
|
name = "ostp-tun-helper"
|
||||||
version = "0.3.16"
|
version = "0.3.18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"hex",
|
"hex",
|
||||||
|
|
@ -2912,6 +2915,27 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.62.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
|
||||||
|
dependencies = [
|
||||||
|
"windows-collections",
|
||||||
|
"windows-core",
|
||||||
|
"windows-future",
|
||||||
|
"windows-numerics",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-collections"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.62.2"
|
version = "0.62.2"
|
||||||
|
|
@ -2925,6 +2949,17 @@ dependencies = [
|
||||||
"windows-strings",
|
"windows-strings",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-future"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core",
|
||||||
|
"windows-link",
|
||||||
|
"windows-threading",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-implement"
|
name = "windows-implement"
|
||||||
version = "0.60.2"
|
version = "0.60.2"
|
||||||
|
|
@ -2953,6 +2988,16 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-numerics"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-result"
|
name = "windows-result"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
|
@ -3029,6 +3074,15 @@ dependencies = [
|
||||||
"windows_x86_64_msvc 0.52.6",
|
"windows_x86_64_msvc 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-threading"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ resolver = "2"
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "BSL 1.1"
|
license = "BSL 1.1"
|
||||||
version = "0.3.16"
|
version = "0.3.18"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
|
|
||||||
|
|
@ -104,8 +104,14 @@ pub struct TransportConfig {
|
||||||
pub resolver: Option<String>,
|
pub resolver: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub pubkey: Option<String>,
|
pub pubkey: Option<String>,
|
||||||
|
|
||||||
|
// Obfuscation
|
||||||
|
#[serde(default = "default_false")]
|
||||||
|
pub tcp_fragmentation: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_false() -> bool { false }
|
||||||
|
|
||||||
fn default_transport_mode() -> String { "udp".to_string() }
|
fn default_transport_mode() -> String { "udp".to_string() }
|
||||||
|
|
||||||
impl Default for TransportConfig {
|
impl Default for TransportConfig {
|
||||||
|
|
@ -115,6 +121,7 @@ impl Default for TransportConfig {
|
||||||
domain: None,
|
domain: None,
|
||||||
resolver: None,
|
resolver: None,
|
||||||
pubkey: None,
|
pubkey: None,
|
||||||
|
tcp_fragmentation: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ pub async fn run_client_core(
|
||||||
|
|
||||||
// TODO: Detect physical interface index for bypassing
|
// TODO: Detect physical interface index for bypassing
|
||||||
let phys_if_for_bypass = None;
|
let phys_if_for_bypass = None;
|
||||||
let outbound_manager = Arc::new(OutboundManager::new(balancer.clone(), phys_if_for_bypass, None));
|
let outbound_manager = Arc::new(OutboundManager::new(balancer.clone(), phys_if_for_bypass, None, Some(metrics.clone())));
|
||||||
|
|
||||||
// When a TUN inbound is present it is the primary one and owns the connected
|
// When a TUN inbound is present it is the primary one and owns the connected
|
||||||
// state; the SOCKS proxy is then secondary and must not report "connected".
|
// state; the SOCKS proxy is then secondary and must not report "connected".
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,7 @@ pub async fn run_socks_inbound(
|
||||||
// the TUN inbound owns the connected state — it is set after the device and
|
// the TUN inbound owns the connected state — it is set after the device and
|
||||||
// server bypass route are in place — so we must not flip it prematurely.
|
// server bypass route are in place — so we must not flip it prematurely.
|
||||||
if is_primary {
|
if is_primary {
|
||||||
metrics.connection_state.store(2, Ordering::Relaxed);
|
tracing::info!("SOCKS proxy is primary: marking connected");
|
||||||
tracing::info!("{} proxy inbound ready on {}, connection state = connected", protocol, bind_addr);
|
|
||||||
} else {
|
} else {
|
||||||
tracing::info!("{} proxy inbound ready on {}", protocol, bind_addr);
|
tracing::info!("{} proxy inbound ready on {}", protocol, bind_addr);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ pub async fn run_tun_inbound(
|
||||||
|
|
||||||
let (mut stack_sink, mut stack_stream) = stack.split();
|
let (mut stack_sink, mut stack_stream) = stack.split();
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
let mut _route_guard = None;
|
let mut _route_guard = None;
|
||||||
|
|
||||||
|
|
@ -187,8 +188,7 @@ pub async fn run_tun_inbound(
|
||||||
|
|
||||||
// TUN device is up and the default route has been installed inside
|
// TUN device is up and the default route has been installed inside
|
||||||
// OstpTunInterface::create — the tunnel is now carrying traffic.
|
// OstpTunInterface::create — the tunnel is now carrying traffic.
|
||||||
metrics.connection_state.store(2, Ordering::Relaxed);
|
tracing::info!("TUN inbound is primary: marking connected (waiting for bridge)");
|
||||||
tracing::info!("TUN inbound ready, connection state = connected");
|
|
||||||
|
|
||||||
// ── TCP Handler ──
|
// ── TCP Handler ──
|
||||||
let outbound_manager_tcp = outbound_manager.clone();
|
let outbound_manager_tcp = outbound_manager.clone();
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ pub struct OutboundManager {
|
||||||
balancer: Arc<Balancer>,
|
balancer: Arc<Balancer>,
|
||||||
phys_if_index: Option<u32>,
|
phys_if_index: Option<u32>,
|
||||||
_phys_if_name: Option<String>,
|
_phys_if_name: Option<String>,
|
||||||
|
metrics: Option<Arc<crate::bridge::BridgeMetrics>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutboundManager {
|
impl OutboundManager {
|
||||||
|
|
@ -19,11 +20,13 @@ impl OutboundManager {
|
||||||
balancer: Arc<Balancer>,
|
balancer: Arc<Balancer>,
|
||||||
phys_if_index: Option<u32>,
|
phys_if_index: Option<u32>,
|
||||||
phys_if_name: Option<String>,
|
phys_if_name: Option<String>,
|
||||||
|
metrics: Option<Arc<crate::bridge::BridgeMetrics>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
balancer,
|
balancer,
|
||||||
phys_if_index,
|
phys_if_index,
|
||||||
_phys_if_name: phys_if_name,
|
_phys_if_name: phys_if_name,
|
||||||
|
metrics,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,7 +42,7 @@ impl OutboundManager {
|
||||||
block::dial_tcp(target_host, target_port).await
|
block::dial_tcp(target_host, target_port).await
|
||||||
}
|
}
|
||||||
OutboundConfig::Ostp { server, port, access_key, transport, multiplex, .. } => {
|
OutboundConfig::Ostp { server, port, access_key, transport, multiplex, .. } => {
|
||||||
ostp::dial_tcp(target_host, target_port, server, *port, access_key, transport, multiplex).await
|
ostp::dial_tcp(target_host, target_port, server, *port, access_key, transport, multiplex, self.metrics.clone()).await
|
||||||
}
|
}
|
||||||
OutboundConfig::Socks { server, port, .. } => {
|
OutboundConfig::Socks { server, port, .. } => {
|
||||||
socks::dial_tcp(target_host, target_port, server, *port).await
|
socks::dial_tcp(target_host, target_port, server, *port).await
|
||||||
|
|
@ -66,7 +69,7 @@ impl OutboundManager {
|
||||||
block::handle_udp(client_src, target_dst, payload).await
|
block::handle_udp(client_src, target_dst, payload).await
|
||||||
}
|
}
|
||||||
OutboundConfig::Ostp { server, port, access_key, transport, multiplex, .. } => {
|
OutboundConfig::Ostp { server, port, access_key, transport, multiplex, .. } => {
|
||||||
ostp::handle_udp(client_src, target_dst, payload, server, *port, access_key, transport, multiplex).await
|
ostp::handle_udp(client_src, target_dst, payload, server, *port, access_key, transport, multiplex, self.metrics.clone()).await
|
||||||
}
|
}
|
||||||
OutboundConfig::Socks { server, port, .. } => {
|
OutboundConfig::Socks { server, port, .. } => {
|
||||||
socks::handle_udp(client_src, target_dst, payload, server, *port).await
|
socks::handle_udp(client_src, target_dst, payload, server, *port).await
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ fn make_initiator_config(
|
||||||
psk: secrets.psk,
|
psk: secrets.psk,
|
||||||
session_id,
|
session_id,
|
||||||
handshake_payload: payload,
|
handshake_payload: payload,
|
||||||
max_padding: 256,
|
max_padding: 1024,
|
||||||
padding_strategy: ostp_core::framing::PaddingStrategy::Adaptive,
|
padding_strategy: ostp_core::framing::PaddingStrategy::Adaptive,
|
||||||
obfuscation_key: secrets.obfuscation_key,
|
obfuscation_key: secrets.obfuscation_key,
|
||||||
max_reorder: 16384,
|
max_reorder: 16384,
|
||||||
|
|
@ -77,6 +77,7 @@ pub async fn dial_tcp(
|
||||||
access_key: &str,
|
access_key: &str,
|
||||||
transport_cfg: &TransportConfig,
|
transport_cfg: &TransportConfig,
|
||||||
_multiplex: &MultiplexConfig,
|
_multiplex: &MultiplexConfig,
|
||||||
|
metrics: Option<std::sync::Arc<crate::bridge::BridgeMetrics>>,
|
||||||
) -> Result<TcpStream> {
|
) -> Result<TcpStream> {
|
||||||
tracing::info!("Dialing OSTP server {}:{} for target {}:{}", server, port, target_host, target_port);
|
tracing::info!("Dialing OSTP server {}:{} for target {}:{}", server, port, target_host, target_port);
|
||||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
|
||||||
|
|
@ -122,9 +123,16 @@ pub async fn dial_tcp(
|
||||||
|
|
||||||
if !handshake_success {
|
if !handshake_success {
|
||||||
tracing::warn!("TCP handshake failed or protocol machine error");
|
tracing::warn!("TCP handshake failed or protocol machine error");
|
||||||
|
if let Some(m) = &metrics {
|
||||||
|
m.connection_state.store(0, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(m) = &metrics {
|
||||||
|
m.connection_state.store(2, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
// Send connection request
|
// Send connection request
|
||||||
let connect_msg = ostp_core::relay::RelayMessage::Connect(format!("{}:{}", target_host_str, target_port));
|
let connect_msg = ostp_core::relay::RelayMessage::Connect(format!("{}:{}", target_host_str, target_port));
|
||||||
let connect_encoded = connect_msg.encode();
|
let connect_encoded = connect_msg.encode();
|
||||||
|
|
@ -240,6 +248,7 @@ pub async fn handle_udp(
|
||||||
access_key: &str,
|
access_key: &str,
|
||||||
transport_cfg: &TransportConfig,
|
transport_cfg: &TransportConfig,
|
||||||
_multiplex: &MultiplexConfig,
|
_multiplex: &MultiplexConfig,
|
||||||
|
metrics: Option<std::sync::Arc<crate::bridge::BridgeMetrics>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let transport = make_transport(transport_cfg, server, port).await?;
|
let transport = make_transport(transport_cfg, server, port).await?;
|
||||||
|
|
||||||
|
|
@ -259,6 +268,18 @@ pub async fn handle_udp(
|
||||||
let config = make_initiator_config(session_id, access_key, transport_cfg);
|
let config = make_initiator_config(session_id, access_key, transport_cfg);
|
||||||
let mut machine = ProtocolMachine::new(config)?;
|
let mut machine = ProtocolMachine::new(config)?;
|
||||||
|
|
||||||
|
// Send UDP Junk Packets (Amnezia style) to break DPI heuristics
|
||||||
|
use rand::Rng;
|
||||||
|
let num_junk = rand::thread_rng().gen_range(2..=5);
|
||||||
|
for _ in 0..num_junk {
|
||||||
|
let junk_len = rand::thread_rng().gen_range(100..=1000);
|
||||||
|
let mut junk = vec![0u8; junk_len];
|
||||||
|
rand::thread_rng().fill(&mut junk[..]);
|
||||||
|
let junk_bytes = bytes::Bytes::from(junk);
|
||||||
|
let _ = transport.send(&junk_bytes).await;
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(5)).await;
|
||||||
|
}
|
||||||
|
|
||||||
// Send handshake first
|
// Send handshake first
|
||||||
if let Ok(action) = machine.on_event(OstpEvent::Start) {
|
if let Ok(action) = machine.on_event(OstpEvent::Start) {
|
||||||
handle_udp_action(action, &transport).await;
|
handle_udp_action(action, &transport).await;
|
||||||
|
|
@ -272,9 +293,15 @@ pub async fn handle_udp(
|
||||||
).await {
|
).await {
|
||||||
Ok(Ok(n)) => {
|
Ok(Ok(n)) => {
|
||||||
let _ = machine.on_event(OstpEvent::Inbound(bytes::Bytes::copy_from_slice(&buf[..n])));
|
let _ = machine.on_event(OstpEvent::Inbound(bytes::Bytes::copy_from_slice(&buf[..n])));
|
||||||
|
if let Some(m) = &metrics {
|
||||||
|
m.connection_state.store(2, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
tracing::warn!("OSTP handshake timeout for {}:{}", server, port);
|
tracing::warn!("OSTP handshake timeout for {}:{}", server, port);
|
||||||
|
if let Some(m) = &metrics {
|
||||||
|
m.connection_state.store(0, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -376,6 +403,62 @@ async fn make_transport(
|
||||||
_guard: std::sync::Arc::new(tokio::sync::Mutex::new(process)),
|
_guard: std::sync::Arc::new(tokio::sync::Mutex::new(process)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
"uot" | "tcp" => {
|
||||||
|
let stream = tokio::net::TcpStream::connect((server, port)).await?;
|
||||||
|
let _ = stream.set_nodelay(true);
|
||||||
|
let (mut rh, mut wh) = stream.into_split();
|
||||||
|
|
||||||
|
let (tx_send, mut tx_recv) = tokio::sync::mpsc::channel::<bytes::Bytes>(1024);
|
||||||
|
let (rx_send, rx_recv) = tokio::sync::mpsc::channel::<bytes::Bytes>(1024);
|
||||||
|
|
||||||
|
let tcp_fragmentation = transport_cfg.tcp_fragmentation;
|
||||||
|
|
||||||
|
// Writer task
|
||||||
|
tokio::spawn(async move {
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
let mut first_packet = true;
|
||||||
|
while let Some(data) = tx_recv.recv().await {
|
||||||
|
let mut len_buf = [0u8; 2];
|
||||||
|
len_buf.copy_from_slice(&(data.len() as u16).to_be_bytes());
|
||||||
|
|
||||||
|
if first_packet && tcp_fragmentation {
|
||||||
|
first_packet = false;
|
||||||
|
// Split the length header and first byte of payload
|
||||||
|
if wh.write_all(&len_buf[0..1]).await.is_err() { break; }
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(5)).await;
|
||||||
|
if wh.write_all(&len_buf[1..2]).await.is_err() { break; }
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(5)).await;
|
||||||
|
|
||||||
|
// Send data in 1-2 byte chunks for the first packet (handshake)
|
||||||
|
for chunk in data.chunks(2) {
|
||||||
|
if wh.write_all(chunk).await.is_err() { break; }
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(2)).await;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if wh.write_all(&len_buf).await.is_err() { break; }
|
||||||
|
if wh.write_all(&data).await.is_err() { break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reader task
|
||||||
|
tokio::spawn(async move {
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
loop {
|
||||||
|
let mut len_buf = [0u8; 2];
|
||||||
|
if rh.read_exact(&mut len_buf).await.is_err() { break; }
|
||||||
|
let len = u16::from_be_bytes(len_buf) as usize;
|
||||||
|
let mut buf = vec![0u8; len];
|
||||||
|
if rh.read_exact(&mut buf).await.is_err() { break; }
|
||||||
|
if rx_send.send(bytes::Bytes::from(buf)).await.is_err() { break; }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(crate::transport::Transport::Uot {
|
||||||
|
tx: tx_send,
|
||||||
|
rx: std::sync::Arc::new(tokio::sync::Mutex::new(rx_recv)),
|
||||||
|
})
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let udp = tokio::net::UdpSocket::bind("0.0.0.0:0").await?;
|
let udp = tokio::net::UdpSocket::bind("0.0.0.0:0").await?;
|
||||||
udp.connect((server, port)).await?;
|
udp.connect((server, port)).await?;
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,7 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||||
"mode": transportMode,
|
"mode": transportMode,
|
||||||
"stealth_sni": stealthSni,
|
"stealth_sni": stealthSni,
|
||||||
"wss": wss,
|
"wss": wss,
|
||||||
|
"tcp_fragmentation": widget.prefs.getBool('tcp_fragmentation') ?? false,
|
||||||
},
|
},
|
||||||
"multiplex": {
|
"multiplex": {
|
||||||
"enabled": muxEnabled,
|
"enabled": muxEnabled,
|
||||||
|
|
@ -203,6 +204,7 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||||
"mode": transportMode,
|
"mode": transportMode,
|
||||||
"stealth_sni": stealthSni,
|
"stealth_sni": stealthSni,
|
||||||
"wss": wss,
|
"wss": wss,
|
||||||
|
"tcp_fragmentation": widget.prefs.getBool('tcp_fragmentation') ?? false,
|
||||||
},
|
},
|
||||||
"multiplex": {
|
"multiplex": {
|
||||||
"enabled": muxEnabled,
|
"enabled": muxEnabled,
|
||||||
|
|
@ -521,14 +523,28 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||||
Center(
|
Center(
|
||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: theme.brightness == Brightness.dark ? 0.05 : 0.06,
|
opacity: theme.brightness == Brightness.dark ? 0.05 : 0.06,
|
||||||
child: SvgPicture.asset(
|
child: Platform.isIOS
|
||||||
'assets/logo.svg',
|
? ClipRRect(
|
||||||
width: MediaQuery.of(context).size.width * 0.8,
|
borderRadius: BorderRadius.circular(32),
|
||||||
fit: BoxFit.contain,
|
child: SvgPicture.asset(
|
||||||
colorFilter: theme.brightness == Brightness.light
|
'assets/logo.svg',
|
||||||
? const ColorFilter.mode(Colors.black, BlendMode.srcIn)
|
width: MediaQuery.of(context).size.width * 0.8,
|
||||||
: null,
|
fit: BoxFit.contain,
|
||||||
),
|
colorFilter: theme.brightness == Brightness.light
|
||||||
|
? const ColorFilter.mode(Colors.black, BlendMode.srcIn)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: ClipOval(
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/logo.svg',
|
||||||
|
width: MediaQuery.of(context).size.width * 0.8,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
colorFilter: theme.brightness == Brightness.light
|
||||||
|
? const ColorFilter.mode(Colors.black, BlendMode.srcIn)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,15 +45,23 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
late TextEditingController _muxSessionsCtrl;
|
late TextEditingController _muxSessionsCtrl;
|
||||||
bool _isCheckingUpdates = false;
|
bool _isCheckingUpdates = false;
|
||||||
|
|
||||||
|
bool _tcpFragmentation = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_importCtrl = TextEditingController();
|
_importCtrl = TextEditingController();
|
||||||
_serverCtrl = TextEditingController(text: widget.prefs.getString('server_addr') ?? '127.0.0.1:443');
|
_loadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _loadSettings() {
|
||||||
|
_serverCtrl = TextEditingController(text: widget.prefs.getString('server_addr') ?? '');
|
||||||
_localBindCtrl = TextEditingController(text: widget.prefs.getString('local_bind') ?? '127.0.0.1:1088');
|
_localBindCtrl = TextEditingController(text: widget.prefs.getString('local_bind') ?? '127.0.0.1:1088');
|
||||||
_keyCtrl = TextEditingController(text: widget.prefs.getString('access_key') ?? '');
|
_keyCtrl = TextEditingController(text: widget.prefs.getString('access_key') ?? '');
|
||||||
_dnsCtrl = TextEditingController(text: widget.prefs.getString('dns_server') ?? '1.1.1.1');
|
_dnsCtrl = TextEditingController(text: widget.prefs.getString('dns_server') ?? '');
|
||||||
_mtuCtrl = TextEditingController(text: widget.prefs.getString('mtu') ?? '1140');
|
_mtuCtrl = TextEditingController(text: widget.prefs.getString('mtu') ?? '1140');
|
||||||
|
_transportMode = widget.prefs.getString('transport_mode') ?? 'udp';
|
||||||
|
_tcpFragmentation = widget.prefs.getBool('tcp_fragmentation') ?? false;
|
||||||
_domainsCtrl = TextEditingController(text: widget.prefs.getString('ex_domains') ?? '');
|
_domainsCtrl = TextEditingController(text: widget.prefs.getString('ex_domains') ?? '');
|
||||||
_ipsCtrl = TextEditingController(text: widget.prefs.getString('ex_ips') ?? '');
|
_ipsCtrl = TextEditingController(text: widget.prefs.getString('ex_ips') ?? '');
|
||||||
_processesCtrl = TextEditingController(text: widget.prefs.getString('ex_processes') ?? '');
|
_processesCtrl = TextEditingController(text: widget.prefs.getString('ex_processes') ?? '');
|
||||||
|
|
@ -61,7 +69,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
_dnsRegionCtrl = TextEditingController(text: widget.prefs.getString('dns_region') ?? '1.1.1.1');
|
_dnsRegionCtrl = TextEditingController(text: widget.prefs.getString('dns_region') ?? '1.1.1.1');
|
||||||
_pbkCtrl = TextEditingController(text: widget.prefs.getString('tun_pbk') ?? '');
|
_pbkCtrl = TextEditingController(text: widget.prefs.getString('tun_pbk') ?? '');
|
||||||
_sidCtrl = TextEditingController(text: widget.prefs.getString('sid') ?? '');
|
_sidCtrl = TextEditingController(text: widget.prefs.getString('sid') ?? '');
|
||||||
_transportMode = widget.prefs.getString('transport_mode') ?? 'udp';
|
|
||||||
_tunStack = widget.prefs.getString('tun_stack') ?? 'ostp';
|
_tunStack = widget.prefs.getString('tun_stack') ?? 'ostp';
|
||||||
_debugMode = widget.prefs.getBool('debug_mode') ?? false;
|
_debugMode = widget.prefs.getBool('debug_mode') ?? false;
|
||||||
_muxEnabled = widget.prefs.getBool('mux_enabled') ?? false;
|
_muxEnabled = widget.prefs.getBool('mux_enabled') ?? false;
|
||||||
|
|
@ -94,11 +101,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
widget.prefs.setString('access_key', _keyCtrl.text.trim());
|
widget.prefs.setString('access_key', _keyCtrl.text.trim());
|
||||||
widget.prefs.setString('dns_server', _dnsCtrl.text.trim());
|
widget.prefs.setString('dns_server', _dnsCtrl.text.trim());
|
||||||
widget.prefs.setString('mtu', _mtuCtrl.text.trim());
|
widget.prefs.setString('mtu', _mtuCtrl.text.trim());
|
||||||
|
widget.prefs.setString('transport_mode', _transportMode);
|
||||||
|
widget.prefs.setBool('tcp_fragmentation', _tcpFragmentation);
|
||||||
widget.prefs.setString('ex_domains', _domainsCtrl.text.trim());
|
widget.prefs.setString('ex_domains', _domainsCtrl.text.trim());
|
||||||
widget.prefs.setString('ex_ips', _ipsCtrl.text.trim());
|
widget.prefs.setString('ex_ips', _ipsCtrl.text.trim());
|
||||||
widget.prefs.setString('ex_processes', _processesCtrl.text.trim());
|
widget.prefs.setString('ex_processes', _processesCtrl.text.trim());
|
||||||
widget.prefs.setBool('debug_mode', _debugMode);
|
widget.prefs.setBool('debug_mode', _debugMode);
|
||||||
widget.prefs.setString('transport_mode', _transportMode);
|
|
||||||
widget.prefs.setString('tun_stack', _tunStack);
|
widget.prefs.setString('tun_stack', _tunStack);
|
||||||
widget.prefs.setString('dns_domain', _dnsDomainCtrl.text.trim());
|
widget.prefs.setString('dns_domain', _dnsDomainCtrl.text.trim());
|
||||||
widget.prefs.setString('dns_region', _dnsRegionCtrl.text.trim());
|
widget.prefs.setString('dns_region', _dnsRegionCtrl.text.trim());
|
||||||
|
|
@ -254,6 +262,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: Colors.black,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||||
),
|
),
|
||||||
child: const Text('Import', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.black)),
|
child: const Text('Import', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.black)),
|
||||||
|
|
@ -302,10 +311,21 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
value: 'uot',
|
value: 'uot',
|
||||||
groupValue: _transportMode,
|
groupValue: _transportMode,
|
||||||
title: const Text('UoT (UDP-over-TCP)', style: TextStyle(fontWeight: FontWeight.w600)),
|
title: const Text('UoT (UDP-over-TCP)', style: TextStyle(fontWeight: FontWeight.w600)),
|
||||||
subtitle: const Text('Works reliably on mobile networks and strict firewalls', style: TextStyle(color: Colors.white54, fontSize: 12)),
|
subtitle: const Text('Reliable on strict networks. Enables TCP DPI bypass.', style: TextStyle(color: Colors.white54, fontSize: 12)),
|
||||||
activeColor: Theme.of(context).colorScheme.primary,
|
activeColor: Theme.of(context).colorScheme.primary,
|
||||||
onChanged: (v) => setState(() { _transportMode = v!; _saveSettings(); }),
|
onChanged: (v) => setState(() { _transportMode = v!; _saveSettings(); }),
|
||||||
),
|
),
|
||||||
|
if (_transportMode == 'uot')
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16.0, right: 8.0, bottom: 8.0),
|
||||||
|
child: SwitchListTile(
|
||||||
|
title: const Text('TCP Fragmentation', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
|
||||||
|
subtitle: const Text('Bypass DPI by chunking handshake (Zapret style)', style: TextStyle(fontSize: 12, color: Colors.white54)),
|
||||||
|
value: _tcpFragmentation,
|
||||||
|
activeColor: Theme.of(context).colorScheme.primary,
|
||||||
|
onChanged: (v) => setState(() { _tcpFragmentation = v; _saveSettings(); }),
|
||||||
|
),
|
||||||
|
),
|
||||||
Divider(color: Colors.white.withOpacity(0.05), height: 1),
|
Divider(color: Colors.white.withOpacity(0.05), height: 1),
|
||||||
RadioListTile<String>(
|
RadioListTile<String>(
|
||||||
value: 'dns',
|
value: 'dns',
|
||||||
|
|
@ -339,7 +359,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.dns, size: 16, color: Colors.orangeAccent),
|
const Icon(Icons.dns, size: 16, color: Colors.orangeAccent),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
const Text('DNS Proxy Settings', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.orangeAccent, fontSize: 14)),
|
const Text('DNS Tunnel Settings', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.orangeAccent, fontSize: 14)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
|
|
@ -585,6 +605,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
label: const Text('Copy Link', style: TextStyle(color: Colors.black, fontWeight: FontWeight.bold)),
|
label: const Text('Copy Link', style: TextStyle(color: Colors.black, fontWeight: FontWeight.bold)),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: Colors.black,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 0.3.16+29
|
version: 0.3.18+30
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.11.4
|
sdk: ^3.11.4
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,4 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^2"
|
"@tauri-apps/cli": "^2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2665,7 +2665,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ostp-client"
|
name = "ostp-client"
|
||||||
version = "0.3.12"
|
version = "0.3.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
|
|
@ -2700,7 +2700,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ostp-core"
|
name = "ostp-core"
|
||||||
version = "0.3.12"
|
version = "0.3.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
|
@ -2742,7 +2742,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ostp-tun"
|
name = "ostp-tun"
|
||||||
version = "0.3.12"
|
version = "0.3.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
@ -2750,6 +2750,7 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tun",
|
"tun",
|
||||||
"winapi",
|
"winapi",
|
||||||
|
"windows 0.62.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3867,7 +3868,7 @@ dependencies = [
|
||||||
"tao-macros",
|
"tao-macros",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"url",
|
"url",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
"windows-version",
|
"windows-version",
|
||||||
"x11-dl",
|
"x11-dl",
|
||||||
|
|
@ -3938,7 +3939,7 @@ dependencies = [
|
||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
"webview2-com",
|
"webview2-com",
|
||||||
"window-vibrancy",
|
"window-vibrancy",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4037,7 +4038,7 @@ dependencies = [
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"url",
|
"url",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
"zbus",
|
"zbus",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -4063,7 +4064,7 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
"webview2-com",
|
"webview2-com",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4088,7 +4089,7 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
"webview2-com",
|
"webview2-com",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
"wry",
|
"wry",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -5015,7 +5016,7 @@ checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"webview2-com-macros",
|
"webview2-com-macros",
|
||||||
"webview2-com-sys",
|
"webview2-com-sys",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
"windows-implement",
|
"windows-implement",
|
||||||
"windows-interface",
|
"windows-interface",
|
||||||
|
|
@ -5039,7 +5040,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c"
|
checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -5095,11 +5096,23 @@ version = "0.61.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
|
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-collections",
|
"windows-collections 0.2.0",
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
"windows-future",
|
"windows-future 0.2.1",
|
||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
"windows-numerics",
|
"windows-numerics 0.2.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.62.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
|
||||||
|
dependencies = [
|
||||||
|
"windows-collections 0.3.2",
|
||||||
|
"windows-core 0.62.2",
|
||||||
|
"windows-future 0.3.2",
|
||||||
|
"windows-numerics 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -5111,6 +5124,15 @@ dependencies = [
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-collections"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core 0.62.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
|
|
@ -5145,7 +5167,18 @@ checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
"windows-threading",
|
"windows-threading 0.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-future"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core 0.62.2",
|
||||||
|
"windows-link 0.2.1",
|
||||||
|
"windows-threading 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -5192,6 +5225,16 @@ dependencies = [
|
||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-numerics"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core 0.62.2",
|
||||||
|
"windows-link 0.2.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-result"
|
name = "windows-result"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
|
|
@ -5295,6 +5338,15 @@ dependencies = [
|
||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-threading"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link 0.2.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-version"
|
name = "windows-version"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
|
|
@ -5580,7 +5632,7 @@ dependencies = [
|
||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
"webkit2gtk-sys",
|
"webkit2gtk-sys",
|
||||||
"webview2-com",
|
"webview2-com",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
"windows-version",
|
"windows-version",
|
||||||
"x11-dl",
|
"x11-dl",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "ostp-gui",
|
"productName": "ostp-gui",
|
||||||
"version": "0.3.16",
|
"version": "0.3.18",
|
||||||
"identifier": "com.ospab.ostp",
|
"identifier": "com.ospab.ostp",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../src"
|
"frontendDist": "../src"
|
||||||
|
|
@ -32,4 +32,4 @@
|
||||||
"icons/icon.ico"
|
"icons/icon.ico"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,29 @@
|
||||||
// build.rs for ostp-tun-helper
|
|
||||||
// Embeds a Windows manifest that requests Administrator privileges.
|
|
||||||
// This makes Windows show a UAC prompt when the binary is double-clicked
|
|
||||||
// or launched via ShellExecuteW("runas").
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
#[cfg(windows)]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
let mut res = winres::WindowsResource::new();
|
let mut res = winres::WindowsResource::new();
|
||||||
|
res.set_icon("..\\ostp-gui\\src-tauri\\icons\\icon.ico");
|
||||||
|
res.set("ProductName", "OSTP Core");
|
||||||
|
res.set("FileDescription", "OSTP Tunnel Helper");
|
||||||
|
res.set("CompanyName", "Ospab Foundation");
|
||||||
|
res.set("LegalCopyright", "Copyright (c) 2026 Ospab Foundation");
|
||||||
|
|
||||||
|
// This manifest explicitly requests administrator privileges, which triggers
|
||||||
|
// UAC when the helper is run directly. The GUI launches it as admin anyway,
|
||||||
|
// but this ensures it always runs elevated.
|
||||||
res.set_manifest(r#"
|
res.set_manifest(r#"
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
||||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
<security>
|
<security>
|
||||||
<requestedPrivileges>
|
<requestedPrivileges>
|
||||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||||
</requestedPrivileges>
|
</requestedPrivileges>
|
||||||
</security>
|
</security>
|
||||||
</trustInfo>
|
</trustInfo>
|
||||||
<dependency>
|
|
||||||
<dependentAssembly>
|
|
||||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls"
|
|
||||||
version="6.0.0.0" processorArchitecture="*"
|
|
||||||
publicKeyToken="6595b64144ccf1df" language="*"/>
|
|
||||||
</dependentAssembly>
|
|
||||||
</dependency>
|
|
||||||
</assembly>
|
</assembly>
|
||||||
"#);
|
"#);
|
||||||
res.compile().expect("failed to compile Windows resources");
|
if let Err(e) = res.compile() {
|
||||||
|
println!("cargo:warning=Failed to compile Windows resources: {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ tun = { version = "0.8.9", features = ["async"] }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winapi = { version = "0.3.9", features = ["iphlpapi", "tcpmib", "processthreadsapi", "psapi", "handleapi", "winerror", "minwindef", "winnt", "iptypes", "ws2def"] }
|
winapi = { version = "0.3.9", features = ["iphlpapi", "tcpmib", "processthreadsapi", "psapi", "handleapi", "winerror", "minwindef", "winnt", "iptypes", "ws2def"] }
|
||||||
|
windows = { version = "0.62.2", features = ["Win32_NetworkManagement_WindowsFirewall", "Win32_NetworkManagement_IpHelper", "Win32_System_Com", "Win32_Foundation", "Win32_Security", "Win32_Networking_WinSock", "Win32_NetworkManagement_Ndis"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use windows::core::{BSTR, GUID};
|
||||||
|
use windows::Win32::Foundation::{ERROR_SUCCESS, WIN32_ERROR};
|
||||||
|
use windows::Win32::NetworkManagement::WindowsFirewall::{
|
||||||
|
INetFwPolicy2, INetFwRule, NetFwPolicy2, NetFwRule, NET_FW_ACTION_ALLOW,
|
||||||
|
NET_FW_PROFILE2_ALL, NET_FW_RULE_DIR_IN, NET_FW_RULE_DIR_OUT,
|
||||||
|
};
|
||||||
|
use windows::Win32::System::Com::{
|
||||||
|
CoCreateInstance, CoInitializeEx, CLSCTX_INPROC_SERVER, COINIT_MULTITHREADED,
|
||||||
|
};
|
||||||
|
use windows::Win32::NetworkManagement::IpHelper::{
|
||||||
|
CreateIpForwardEntry, DeleteIpForwardEntry, MIB_IPFORWARDROW,
|
||||||
|
};
|
||||||
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
|
fn init_com() -> Result<()> {
|
||||||
|
unsafe {
|
||||||
|
let hr = CoInitializeEx(None, COINIT_MULTITHREADED);
|
||||||
|
if hr.is_err() && hr.0 != windows::Win32::Foundation::RPC_E_CHANGED_MODE.0 {
|
||||||
|
return Err(anyhow::anyhow!("CoInitializeEx failed: {}", hr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_firewall_rules(exe_path: &str) -> Result<()> {
|
||||||
|
init_com()?;
|
||||||
|
unsafe {
|
||||||
|
let policy: INetFwPolicy2 = CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER)?;
|
||||||
|
|
||||||
|
let rules = policy.Rules()?;
|
||||||
|
|
||||||
|
// Rule IN
|
||||||
|
let rule_in: INetFwRule = CoCreateInstance(&NetFwRule, None, CLSCTX_INPROC_SERVER)?;
|
||||||
|
rule_in.SetName(&BSTR::from("OSTP Tunnel In"))?;
|
||||||
|
rule_in.SetApplicationName(&BSTR::from(exe_path))?;
|
||||||
|
rule_in.SetAction(NET_FW_ACTION_ALLOW)?;
|
||||||
|
rule_in.SetDirection(NET_FW_RULE_DIR_IN)?;
|
||||||
|
rule_in.SetProfiles(NET_FW_PROFILE2_ALL.0)?;
|
||||||
|
rules.Add(&rule_in)?;
|
||||||
|
|
||||||
|
// Rule OUT
|
||||||
|
let rule_out: INetFwRule = CoCreateInstance(&NetFwRule, None, CLSCTX_INPROC_SERVER)?;
|
||||||
|
rule_out.SetName(&BSTR::from("OSTP Tunnel Out"))?;
|
||||||
|
rule_out.SetApplicationName(&BSTR::from(exe_path))?;
|
||||||
|
rule_out.SetAction(NET_FW_ACTION_ALLOW)?;
|
||||||
|
rule_out.SetDirection(NET_FW_RULE_DIR_OUT)?;
|
||||||
|
rule_out.SetProfiles(NET_FW_PROFILE2_ALL.0)?;
|
||||||
|
rules.Add(&rule_out)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_firewall_rules() -> Result<()> {
|
||||||
|
init_com()?;
|
||||||
|
unsafe {
|
||||||
|
let policy: INetFwPolicy2 = CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER)?;
|
||||||
|
let rules = policy.Rules()?;
|
||||||
|
let _ = rules.Remove(&BSTR::from("OSTP Tunnel In"));
|
||||||
|
let _ = rules.Remove(&BSTR::from("OSTP Tunnel Out"));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimal implementation of Kill Switch (blackhole route to 127.0.0.1) using WinAPI
|
||||||
|
pub fn set_kill_switch_route(enable: bool) -> Result<()> {
|
||||||
|
// 0.0.0.0/0 -> 127.0.0.1 with metric 10 and if_index 1 (Loopback)
|
||||||
|
let mut row: MIB_IPFORWARDROW = unsafe { std::mem::zeroed() };
|
||||||
|
row.dwForwardDest = 0;
|
||||||
|
row.dwForwardMask = 0;
|
||||||
|
row.dwForwardPolicy = 0;
|
||||||
|
row.dwForwardNextHop = u32::from_ne_bytes(Ipv4Addr::new(127, 0, 0, 1).octets());
|
||||||
|
row.dwForwardIfIndex = 1; // Loopback interface
|
||||||
|
row.Anonymous1.dwForwardType = 3; // MIB_IPROUTE_TYPE_INDIRECT
|
||||||
|
row.Anonymous2.dwForwardProto = 3; // MIB_IPPROTO_NETMGMT
|
||||||
|
row.dwForwardAge = 0;
|
||||||
|
row.dwForwardNextHopAS = 0;
|
||||||
|
row.dwForwardMetric1 = 10;
|
||||||
|
row.dwForwardMetric2 = !0;
|
||||||
|
row.dwForwardMetric3 = !0;
|
||||||
|
row.dwForwardMetric4 = !0;
|
||||||
|
row.dwForwardMetric5 = !0;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
if enable {
|
||||||
|
let res = WIN32_ERROR(CreateIpForwardEntry(&row));
|
||||||
|
if res != ERROR_SUCCESS && res != windows::Win32::Foundation::ERROR_OBJECT_ALREADY_EXISTS {
|
||||||
|
return Err(anyhow::anyhow!("CreateIpForwardEntry failed: {}", res.0));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let res = WIN32_ERROR(DeleteIpForwardEntry(&row));
|
||||||
|
if res != ERROR_SUCCESS && res != windows::Win32::Foundation::ERROR_NOT_FOUND {
|
||||||
|
return Err(anyhow::anyhow!("DeleteIpForwardEntry failed: {}", res.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNS setting using WMI (requires wmi crate) or IP Helper API.
|
||||||
|
// SetInterfaceDnsSettings was added in 1607, let's use it.
|
||||||
|
pub fn set_dns_servers(adapter_luid: u64, dns: &str) -> Result<()> {
|
||||||
|
use windows::Win32::NetworkManagement::IpHelper::{
|
||||||
|
SetInterfaceDnsSettings, DNS_INTERFACE_SETTINGS,
|
||||||
|
};
|
||||||
|
use std::os::windows::ffi::OsStrExt;
|
||||||
|
|
||||||
|
let dns_wstr: Vec<u16> = std::ffi::OsStr::new(dns)
|
||||||
|
.encode_wide()
|
||||||
|
.chain(Some(0))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut settings = DNS_INTERFACE_SETTINGS {
|
||||||
|
Version: 1, // DNS_INTERFACE_SETTINGS_VERSION1
|
||||||
|
Flags: 1, // DNS_SETTING_IPV4
|
||||||
|
Domain: windows::core::PWSTR::null(),
|
||||||
|
NameServer: windows::core::PWSTR::from_raw(dns_wstr.as_ptr() as *mut _),
|
||||||
|
SearchList: windows::core::PWSTR::null(),
|
||||||
|
RegistrationEnabled: 0,
|
||||||
|
RegisterAdapterName: 0,
|
||||||
|
EnableLLMNR: 0,
|
||||||
|
QueryAdapterName: 0,
|
||||||
|
ProfileNameServer: windows::core::PWSTR::null(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let luid = windows::Win32::NetworkManagement::Ndis::NET_LUID_LH { Value: adapter_luid };
|
||||||
|
let guid = GUID::zeroed(); // We can pass zeroed GUID and just use LUID? Wait, SetInterfaceDnsSettings requires GUID.
|
||||||
|
|
||||||
|
// Actually, setting DNS via SetInterfaceDnsSettings requires the interface GUID, which we can get from ConvertInterfaceLuidToGuid.
|
||||||
|
unsafe {
|
||||||
|
let mut if_guid = GUID::zeroed();
|
||||||
|
let err = windows::Win32::NetworkManagement::IpHelper::ConvertInterfaceLuidToGuid(&luid, &mut if_guid);
|
||||||
|
if err != ERROR_SUCCESS {
|
||||||
|
return Err(anyhow::anyhow!("ConvertInterfaceLuidToGuid failed: {}", err.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
let err = SetInterfaceDnsSettings(if_guid, &settings);
|
||||||
|
if err != ERROR_SUCCESS {
|
||||||
|
return Err(anyhow::anyhow!("SetInterfaceDnsSettings failed: {}", err.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
use crate::{OstpTunInterface, OstpTunOptions};
|
use crate::{OstpTunInterface, OstpTunOptions};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use std::process::Command;
|
use tun::AbstractDeviceExt;
|
||||||
use std::os::windows::process::CommandExt;
|
|
||||||
|
|
||||||
pub mod windows_route {
|
pub mod windows_route {
|
||||||
include!("windows_route.rs");
|
include!("windows_route.rs");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[path = "sys_win_api.rs"]
|
||||||
|
mod sys_win_api;
|
||||||
|
|
||||||
struct WindowsRouteGuard {
|
struct WindowsRouteGuard {
|
||||||
bypass_routes: Vec<(std::net::Ipv4Addr, std::net::Ipv4Addr, u32)>,
|
bypass_routes: Vec<(std::net::Ipv4Addr, std::net::Ipv4Addr, u32)>,
|
||||||
kill_switch: bool,
|
kill_switch: bool,
|
||||||
|
|
@ -14,33 +16,14 @@ struct WindowsRouteGuard {
|
||||||
|
|
||||||
impl Drop for WindowsRouteGuard {
|
impl Drop for WindowsRouteGuard {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
|
||||||
|
|
||||||
windows_route::sys::remove_bypass_routes(&self.bypass_routes);
|
windows_route::sys::remove_bypass_routes(&self.bypass_routes);
|
||||||
tracing::info!("Removed {} bypass routes.", self.bypass_routes.len());
|
tracing::info!("Removed {} bypass routes.", self.bypass_routes.len());
|
||||||
|
|
||||||
let is_kill_switch = self.kill_switch;
|
let _ = sys_win_api::remove_firewall_rules();
|
||||||
let _ = std::thread::spawn(move || {
|
|
||||||
let _ = Command::new("netsh")
|
if self.kill_switch {
|
||||||
.creation_flags(CREATE_NO_WINDOW)
|
let _ = sys_win_api::set_kill_switch_route(false);
|
||||||
.args(["advfirewall", "firewall", "delete", "rule", "name=OSTP Tunnel In"])
|
}
|
||||||
.output();
|
|
||||||
let _ = Command::new("netsh")
|
|
||||||
.creation_flags(CREATE_NO_WINDOW)
|
|
||||||
.args(["advfirewall", "firewall", "delete", "rule", "name=OSTP Tunnel Out"])
|
|
||||||
.output();
|
|
||||||
let _ = Command::new("netsh")
|
|
||||||
.creation_flags(CREATE_NO_WINDOW)
|
|
||||||
.args(["interface", "ipv4", "set", "dnsservers",
|
|
||||||
"name=ostp_tun", "source=dhcp"])
|
|
||||||
.output();
|
|
||||||
if is_kill_switch {
|
|
||||||
let _ = Command::new("route")
|
|
||||||
.creation_flags(CREATE_NO_WINDOW)
|
|
||||||
.args(["delete", "0.0.0.0", "mask", "0.0.0.0", "127.0.0.1"])
|
|
||||||
.output();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,18 +66,8 @@ pub async fn create(opts: OstpTunOptions) -> Result<OstpTunInterface> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up any stale Wintun adapters matching our name prefix. This prevents
|
// No need to call powershell to clean up adapters, WinTun handles it well
|
||||||
// Wintun from creating "ostp_tun 2" (which violates the strict naming requirement
|
// if we don't try to use friendly-name matching.
|
||||||
// and causes the 15-second interface index lookup timeout below).
|
|
||||||
tracing::info!("Cleaning up any stale 'ostp_tun*' adapters...");
|
|
||||||
let _ = std::process::Command::new("powershell")
|
|
||||||
.creation_flags(0x08000000)
|
|
||||||
.args([
|
|
||||||
"-NoProfile",
|
|
||||||
"-Command",
|
|
||||||
"try { Get-NetAdapter -Name 'ostp_tun*' -ErrorAction Stop | Remove-NetAdapter -Confirm:$false -ErrorAction SilentlyContinue } catch {}"
|
|
||||||
])
|
|
||||||
.output();
|
|
||||||
|
|
||||||
let mut tun_cfg = tun::Configuration::default();
|
let mut tun_cfg = tun::Configuration::default();
|
||||||
tun_cfg
|
tun_cfg
|
||||||
|
|
@ -136,88 +109,34 @@ pub async fn create(opts: OstpTunOptions) -> Result<OstpTunInterface> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let luid = dev.tun_luid();
|
||||||
let dev = tun::AsyncDevice::new(dev).map_err(|e| anyhow!("TUN device async failed: {}", e))?;
|
let dev = tun::AsyncDevice::new(dev).map_err(|e| anyhow!("TUN device async failed: {}", e))?;
|
||||||
tracing::info!("TUN device 'ostp_tun' created.");
|
tracing::info!("TUN device 'ostp_tun' created.");
|
||||||
let name_owned = "ostp_tun".to_string();
|
// We rely entirely on the LUID-based route established by the `tun` crate's `.destination()`.
|
||||||
|
// It is instant and reliable. The fallback polling loop has been removed for instant startup.
|
||||||
|
|
||||||
let current_exe = std::env::current_exe()?.to_string_lossy().into_owned();
|
let current_exe = std::env::current_exe()?.to_string_lossy().into_owned();
|
||||||
|
|
||||||
// A freshly created WinTun adapter can take several seconds to appear in
|
|
||||||
// GetAdaptersAddresses (it only shows up once it has an operational IPv4
|
|
||||||
// binding). The default route via the TUN is what actually captures
|
|
||||||
// traffic. The `tun` crate already added an LUID-based route which works
|
|
||||||
// instantly, but we add a secondary index-based route for robustness.
|
|
||||||
// We run this in the background so it doesn't block tunnel startup.
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let mut tun_index = None;
|
|
||||||
for _ in 0..75 {
|
|
||||||
if let Some(idx) = windows_route::sys::get_interface_index(&name_owned) {
|
|
||||||
tun_index = Some(idx);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(idx) = tun_index {
|
|
||||||
match windows_route::sys::add_ipv4_route(
|
|
||||||
std::net::Ipv4Addr::new(0, 0, 0, 0),
|
|
||||||
std::net::Ipv4Addr::new(0, 0, 0, 0),
|
|
||||||
std::net::Ipv4Addr::new(10, 1, 0, 1),
|
|
||||||
idx,
|
|
||||||
5,
|
|
||||||
) {
|
|
||||||
Ok(()) => tracing::info!("Default route via TUN (if_index={idx}, metric=5) added."),
|
|
||||||
Err(e) => tracing::error!("Failed to add default route via TUN (if_index={idx}): {e} — traffic will NOT be captured."),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tracing::warn!("Could not find '{}' index in routing table after 15s — fallback route not installed.", name_owned);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let exe1 = current_exe.clone();
|
let exe1 = current_exe.clone();
|
||||||
let exe2 = current_exe.clone();
|
|
||||||
let _ = tokio::task::spawn_blocking(move || {
|
let _ = tokio::task::spawn_blocking(move || {
|
||||||
let _ = Command::new("netsh")
|
if let Err(e) = sys_win_api::add_firewall_rules(&exe1) {
|
||||||
.creation_flags(CREATE_NO_WINDOW)
|
tracing::warn!("Failed to add firewall rules via WinAPI: {}", e);
|
||||||
.args(["advfirewall", "firewall", "add", "rule",
|
}
|
||||||
"name=OSTP Tunnel In", "dir=in", "action=allow",
|
|
||||||
&format!("program={}", exe1)])
|
|
||||||
.output();
|
|
||||||
let _ = Command::new("netsh")
|
|
||||||
.creation_flags(CREATE_NO_WINDOW)
|
|
||||||
.args(["advfirewall", "firewall", "add", "rule",
|
|
||||||
"name=OSTP Tunnel Out", "dir=out", "action=allow",
|
|
||||||
&format!("program={}", exe2)])
|
|
||||||
.output();
|
|
||||||
let _ = Command::new("netsh")
|
|
||||||
.creation_flags(CREATE_NO_WINDOW)
|
|
||||||
.args(["interface", "ipv4", "set", "interface", "name=ostp_tun",
|
|
||||||
"routerdiscovery=disabled", "dadtransmits=0",
|
|
||||||
"managedaddress=disabled", "otherstateful=disabled"])
|
|
||||||
.output();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(ref dns) = opts.dns_server {
|
if let Some(ref dns) = opts.dns_server {
|
||||||
if !dns.is_empty() {
|
if !dns.is_empty() {
|
||||||
let dns_clone = dns.clone();
|
let dns_clone = dns.clone();
|
||||||
let _ = tokio::task::spawn_blocking(move || {
|
let _ = tokio::task::spawn_blocking(move || {
|
||||||
let _ = Command::new("netsh")
|
if let Err(e) = sys_win_api::set_dns_servers(luid, &dns_clone) {
|
||||||
.creation_flags(CREATE_NO_WINDOW)
|
tracing::warn!("Failed to set DNS via WinAPI: {}", e);
|
||||||
.args(["interface", "ipv4", "set", "dnsservers",
|
}
|
||||||
"name=ostp_tun", "static", &dns_clone, "primary"])
|
|
||||||
.output();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.kill_switch {
|
if opts.kill_switch {
|
||||||
tracing::info!("Kill Switch enabled: Adding metric 10 blackhole route to prevent leakage");
|
tracing::info!("Kill Switch enabled: Adding metric 10 blackhole route to prevent leakage");
|
||||||
let _ = tokio::task::spawn_blocking(move || {
|
let _ = sys_win_api::set_kill_switch_route(true);
|
||||||
let _ = Command::new("route")
|
|
||||||
.creation_flags(CREATE_NO_WINDOW)
|
|
||||||
.args(["add", "0.0.0.0", "mask", "0.0.0.0", "127.0.0.1", "metric", "10", "if", "1"])
|
|
||||||
.output();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(OstpTunInterface {
|
Ok(OstpTunInterface {
|
||||||
|
|
|
||||||
|
|
@ -24,3 +24,9 @@ reqwest = { version = "0.12", default-features = false, features = ["blocking",
|
||||||
pico-args = "0.5.0"
|
pico-args = "0.5.0"
|
||||||
clipboard-win = "3.1.1"
|
clipboard-win = "3.1.1"
|
||||||
|
|
||||||
|
[target."cfg(windows)".build-dependencies]
|
||||||
|
winres = "0.1.12"
|
||||||
|
|
||||||
|
[target."cfg(windows)".dependencies]
|
||||||
|
windows = { version = "0.62.2", features = ["Win32_Security", "Win32_UI_Shell", "Win32_Foundation"] }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
fn main() {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
let mut res = winres::WindowsResource::new();
|
||||||
|
res.set_icon("..\\ostp-gui\\src-tauri\\icons\\icon.ico");
|
||||||
|
res.set("ProductName", "OSTP Core");
|
||||||
|
res.set("FileDescription", "OSTP CLI");
|
||||||
|
res.set("CompanyName", "Ospab Foundation");
|
||||||
|
res.set("LegalCopyright", "Copyright (c) 2026 Ospab Foundation");
|
||||||
|
if let Err(e) = res.compile() {
|
||||||
|
println!("cargo:warning=Failed to compile Windows resources: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2030,6 +2030,11 @@ async fn run_client_directly(client_cfg: serde_json::Value) -> Result<()> {
|
||||||
let mode_str = if is_tun_enabled { "tun" } else { "proxy" };
|
let mode_str = if is_tun_enabled { "tun" } else { "proxy" };
|
||||||
println!("{} Starting client (mode={})", "[ostp]".cyan().bold(), mode_str.yellow());
|
println!("{} Starting client (mode={})", "[ostp]".cyan().bold(), mode_str.yellow());
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
if is_tun_enabled {
|
||||||
|
ensure_elevated_for_tun()?;
|
||||||
|
}
|
||||||
|
|
||||||
// Run the client implementation
|
// Run the client implementation
|
||||||
let (_shutdown_tx, rx) = tokio::sync::watch::channel(false);
|
let (_shutdown_tx, rx) = tokio::sync::watch::channel(false);
|
||||||
let metrics = std::sync::Arc::new(ostp_client::bridge::BridgeMetrics::default());
|
let metrics = std::sync::Arc::new(ostp_client::bridge::BridgeMetrics::default());
|
||||||
|
|
@ -2039,6 +2044,53 @@ async fn run_client_directly(client_cfg: serde_json::Value) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn ensure_elevated_for_tun() -> Result<()> {
|
||||||
|
#[link(name = "shell32")]
|
||||||
|
extern "system" {
|
||||||
|
fn IsUserAnAdmin() -> i32;
|
||||||
|
fn ShellExecuteW(h: *mut std::ffi::c_void, op: *const u16, f: *const u16, p: *const u16, d: *const u16, s: i32) -> isize;
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_admin = unsafe { IsUserAnAdmin() != 0 };
|
||||||
|
if is_admin {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::os::windows::ffi::OsStrExt;
|
||||||
|
|
||||||
|
let exe = std::env::current_exe()?;
|
||||||
|
let exe_wstr: Vec<u16> = exe.as_os_str().encode_wide().chain(Some(0)).collect();
|
||||||
|
let verb_wstr: Vec<u16> = OsStr::new("runas").encode_wide().chain(Some(0)).collect();
|
||||||
|
|
||||||
|
// Reconstruct arguments
|
||||||
|
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||||
|
let params_str = args.iter().map(|s| format!("\"{}\"", s)).collect::<Vec<_>>().join(" ");
|
||||||
|
let params_wstr: Vec<u16> = OsStr::new(¶ms_str).encode_wide().chain(Some(0)).collect();
|
||||||
|
|
||||||
|
let cwd = std::env::current_dir()?;
|
||||||
|
let cwd_wstr: Vec<u16> = cwd.as_os_str().encode_wide().chain(Some(0)).collect();
|
||||||
|
|
||||||
|
println!("{}", "[ostp] TUN mode requires administrator privileges. Requesting elevation...".yellow());
|
||||||
|
|
||||||
|
let ret = unsafe {
|
||||||
|
ShellExecuteW(
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
verb_wstr.as_ptr(),
|
||||||
|
exe_wstr.as_ptr(),
|
||||||
|
params_wstr.as_ptr(),
|
||||||
|
cwd_wstr.as_ptr(),
|
||||||
|
1, // SW_SHOWNORMAL
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if ret <= 32 {
|
||||||
|
anyhow::bail!("UAC elevation was denied or failed. Please run as Administrator.");
|
||||||
|
}
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
fn cmd_migrate(config_path: &std::path::Path) -> Result<()> {
|
fn cmd_migrate(config_path: &std::path::Path) -> Result<()> {
|
||||||
if !config_path.exists() {
|
if !config_path.exists() {
|
||||||
anyhow::bail!("Configuration file not found at {:?}", config_path);
|
anyhow::bail!("Configuration file not found at {:?}", config_path);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue