15 march works with app

This commit is contained in:
2026-03-15 23:47:15 +07:00
parent 102099e1cd
commit 7dbfaec60d
38 changed files with 659 additions and 542 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
use lru::LruCache;
use netrunner_logger::{debug, info};
use std::net::Ipv4Addr;
use std::num::NonZeroUsize;
use tracing::{debug, info};
pub struct FakeIpStore {
cache: LruCache<String, Ipv4Addr>,
+6 -7
View File
@@ -5,7 +5,6 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::sync::{mpsc, oneshot};
use tokio_util::sync::CancellationToken;
use tracing::{debug, info, trace, warn};
pub enum ConnectionState {
Established,
@@ -38,24 +37,24 @@ impl TcpConnection {
tokio::spawn(async move {
let mut stream = match TcpStream::connect(&proxy_addr).await {
Ok(s) => {
debug!(%handle, "Connected to proxy successfully");
netrunner_logger::debug!(%handle, "Connected to proxy successfully");
s
}
Err(e) => {
debug!(%handle, error = %e, "Failed to connect to proxy");
netrunner_logger::debug!(%handle, error = %e, "Failed to connect to proxy");
return;
}
};
if let Err(e) = SocksRequest::perform_client_handshake(&mut stream, &target_addr).await
{
debug!(%handle, error = %e, "SOCKS handshake failed");
netrunner_logger::debug!(%handle, error = %e, "SOCKS handshake failed");
return;
}
let _ = handshake_tx.send(());
debug!(%handle, "SOCKS handshake successful, starting data bridge");
netrunner_logger::debug!(%handle, "SOCKS handshake successful, starting data bridge");
let (mut reader, mut writer) = stream.into_split();
@@ -84,7 +83,7 @@ impl TcpConnection {
tokio::select! {
_ = to_proxy => {}
_ = from_proxy => {}
_ = task_token.cancelled() => { debug!(%handle, "Task cancelled by Manager"); }
_ = task_token.cancelled() => { netrunner_logger::debug!(%handle, "Task cancelled by Manager"); }
}
});
@@ -170,7 +169,7 @@ impl TcpConnection {
if !self.pending_data.is_empty() {
if self.pending_data.len() > MAX_PENDING {
warn!(%self.handle, "Buffer overflow! Aborting connection.");
netrunner_logger::warn!(%self.handle, "Buffer overflow! Aborting connection.");
socket.abort();
self.token.cancel();
return;
+1 -2
View File
@@ -1,7 +1,6 @@
use crate::connections::dns::handle_dns_query;
use crate::connections::ip_store::FakeIpStore;
use smoltcp::socket::udp;
use tracing::{debug, trace};
pub struct UdpConnection;
@@ -16,7 +15,7 @@ impl UdpConnection {
let endpoint = metadata.endpoint;
if let Some(response) = handle_dns_query(&data, store) {
debug!(to = %endpoint, "Sending DNS response");
netrunner_logger::debug!(to = %endpoint, "Sending DNS response");
let _ = socket.send_slice(&response, metadata);
}
}
+13 -24
View File
@@ -1,46 +1,35 @@
uniffi::setup_scaffolding!();
mod connections;
pub mod tun;
use std::sync::Mutex;
use netrunner_logger::info;
use std::sync::OnceLock;
use tokio::runtime::Runtime;
use tokio::sync::oneshot;
use tracing::info;
mod session;
use tokio_util::sync::CancellationToken;
use crate::tun::routing::reset_platform_routing;
pub mod session;
static RUNTIME: OnceLock<Runtime> = OnceLock::new();
fn get_runtime() -> &'static Runtime {
RUNTIME.get_or_init(|| {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("Failed to create tokio runtime")
})
}
#[derive(uniffi::Object)]
pub struct Session {
pub(crate) shutdown_tx: Mutex<Option<oneshot::Sender<()>>>,
pub(crate) cancel_token: CancellationToken,
pub(crate) proxy_ip: String,
}
#[uniffi::export]
impl Session {
pub fn stop(&self) {
let mut guard = self.shutdown_tx.lock().unwrap();
if let Some(tx) = guard.take() {
let _ = tx.send(());
}
info!("Stopping session...");
self.cancel_token.cancel();
let _ = reset_platform_routing(Some(&self.proxy_ip));
}
}
impl Drop for Session {
fn drop(&mut self) {
info!("Session dropped, resetting platform routing...");
if let Ok(mut tx) = self.shutdown_tx.lock() {
if let Some(tx) = tx.take() {
let _ = tx.send(());
}
}
let _ = crate::tun::routing::reset_platform_routing();
info!("Session dropped, stopping all tasks...");
self.cancel_token.cancel();
let _ = reset_platform_routing(Some(&self.proxy_ip));
}
}
+10 -12
View File
@@ -5,16 +5,14 @@ use netrunner_client::tun::{
routing::{reset_platform_routing, setup_platform_routing},
tun::Tun,
};
use netrunner_core::{
logger_init,
proxy::{connection::connection::ConnectionRole, network::Network},
};
use netrunner_core::proxy::{connection::connection::ConnectionRole, network::Network};
use netrunner_logger::{error, info};
use smoltcp::{iface::Config, phy::DeviceCapabilities};
use tracing::{error, info};
use tokio_util::sync::CancellationToken;
#[tokio::main]
async fn main() {
logger_init();
netrunner_logger::Logger::init();
info!("Initializing NetRunner Stack...");
let tun_device = Tun::create(|config| {
config
@@ -26,7 +24,7 @@ async fn main() {
})
.expect("Failed to initialize TUN device");
let remote_address: String = "62.60.244.156:443".into();
setup_platform_routing(&remote_address).expect("Failed to setup routing");
setup_platform_routing(&remote_address);
info!("TUN interface is UP: 10.0.0.1/24");
@@ -41,14 +39,14 @@ async fn main() {
"0.0.0.0".into(),
8080,
ConnectionRole::Client,
Some(remote_address),
Some(remote_address.clone()),
);
let proxy_ip = network.get_self_local_address();
tokio::spawn(async move {
info!("Network thread started");
network.run().await;
network.run(CancellationToken::new()).await;
});
let mut engine = Engine::new(config, caps, proxy_ip);
@@ -60,7 +58,6 @@ async fn main() {
info!("Engine starting process loop...");
// 3. Используем select для перехвата сигнала завершения
let ctrl_c = tokio::signal::ctrl_c();
tokio::select! {
@@ -72,9 +69,10 @@ async fn main() {
}
}
// 4. ГАРАНТИРОВАННЫЙ СБРОС
info!("Restoring system routing...");
if let Err(e) = reset_platform_routing() {
let addr: std::net::SocketAddr = remote_address.parse().expect("Invalid address format");
let proxy_ip = addr.ip().to_string();
if let Err(e) = reset_platform_routing(Some(&proxy_ip)) {
error!("Failed to reset routing: {}", e);
} else {
info!("System routing restored successfully.");
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::session::{Session, SessionManager};
use super::{Session, SessionManager};
use std::sync::Arc;
use uniffi;
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::session::{Session, SessionManager};
use super::{Session, SessionManager};
use std::sync::Arc;
use uniffi;
+44 -26
View File
@@ -1,27 +1,33 @@
#[cfg(feature = "desktop")]
#[cfg(any(target_os = "linux", target_os = "windows"))]
pub mod desktop;
#[cfg(feature = "mobile")]
#[cfg(any(target_os = "android", target_os = "ios"))]
pub mod mobile;
use crate::{
Session, get_runtime,
RUNTIME, Session,
tun::{
engine::Engine,
routing::{reset_platform_routing, setup_platform_routing},
tun::Tun,
},
};
use netrunner_core::{
logger_init,
proxy::{connection::connection::ConnectionRole, network::Network},
};
use netrunner_core::proxy::{connection::connection::ConnectionRole, network::Network};
use netrunner_logger::{error, info};
use smoltcp::{iface::Config, phy::DeviceCapabilities};
use std::net::Ipv4Addr;
use std::sync::{Arc, Mutex};
use tokio::signal;
use tokio::sync::oneshot;
use tracing::{error, info};
use std::sync::Arc;
use tokio::{runtime::Runtime, signal};
use tokio_util::sync::CancellationToken;
fn get_runtime() -> &'static Runtime {
RUNTIME.get_or_init(|| {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("Failed to create tokio runtime")
})
}
#[derive(uniffi::Object)]
pub struct SessionManager;
@@ -30,7 +36,7 @@ pub struct SessionManager;
impl SessionManager {
#[uniffi::constructor]
pub fn new() -> Self {
logger_init();
netrunner_logger::Logger::init();
info!("SessionManager initialized");
Self
}
@@ -42,34 +48,47 @@ impl SessionManager {
remote_address: String,
tun_fd: Option<i32>,
) -> Arc<Session> {
let (tx, rx) = oneshot::channel();
let runtime = get_runtime();
let cancel_token = CancellationToken::new();
let sesison_token = cancel_token.clone();
let net_token = cancel_token.clone();
let shutdown_signal = signal::ctrl_c();
let addr: std::net::SocketAddr = remote_address.parse().expect("Invalid address format");
let remote_proxy_ip = addr.ip().to_string();
let proxy_ip_for_thread = remote_proxy_ip.clone();
runtime.spawn(async move {
info!("Starting VPN session thread...");
let tun_device = {
#[cfg(feature = "mobile")]
#[cfg(any(target_os = "android", target_os = "ios"))]
{
Tun::from_fd(tun_fd.expect("TUN FD required on mobile"))
.expect("Failed to init TUN from FD")
}
#[cfg(feature = "desktop")]
#[cfg(target_os = "linux")]
{
Tun::create(|config| {
config
.tun_name("netr0")
.address((10, 0, 0, 1))
.netmask((255, 255, 255, 0))
.mtu(1200)
.up();
})
.expect("Failed to init TUN")
}
#[cfg(target_os = "windows")]
{
Tun::create(|config| {
config.tun_name("netr0");
})
.expect("Failed to init TUN")
}
};
setup_platform_routing(&remote_address).expect("Failed to setup routing");
setup_platform_routing(&remote_address);
let config = Config::new(smoltcp::wire::HardwareAddress::Ip);
let mut caps = DeviceCapabilities::default();
@@ -84,8 +103,8 @@ impl SessionManager {
);
let proxy_ip = network.get_self_local_address();
let net_handle = tokio::spawn(async move {
network.run().await;
tokio::spawn(async move {
network.run(net_token).await;
});
let mut engine = Engine::new(config, caps, proxy_ip);
@@ -96,21 +115,20 @@ impl SessionManager {
tokio::select! {
res = engine.run(tun_device) => error!("Engine loop error: {:?}", res),
_ = rx => {
_ = cancel_token.cancelled() => {
info!("Shutdown signal received");
let _ = reset_platform_routing(); // Очистка при сигнале
let _ = reset_platform_routing(Some(&proxy_ip_for_thread));
},
_ = shutdown_signal => {
info!("Ctrl+C detected, shutting down gracefully...");
info!("Restoring routing...");
let _ = reset_platform_routing();
net_handle.abort();
cancel_token.cancel();
}
}
});
Arc::new(Session {
shutdown_tx: Mutex::new(Some(tx)),
cancel_token: sesison_token,
proxy_ip: remote_proxy_ip,
})
}
}
+1 -1
View File
@@ -1,4 +1,5 @@
use netrunner_core::protocol::codec::socks::TargetAddress;
use netrunner_logger::{debug, info, warn};
use smoltcp::{
iface::{SocketHandle, SocketSet},
socket::{AnySocket, icmp, tcp, udp},
@@ -8,7 +9,6 @@ use std::{
collections::HashMap,
time::{Duration, Instant as StdInstant},
};
use tracing::{debug, info, warn};
use crate::{
connections::{
-1
View File
@@ -14,7 +14,6 @@ use smoltcp::{
time::Instant,
};
use tokio::sync::mpsc;
use tracing::trace;
pub struct VirtTunDevice {
capabilities: DeviceCapabilities,
+1 -1
View File
@@ -16,7 +16,7 @@ use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
use tokio::time::{Duration, sleep};
use tun::{DeviceReader, DeviceWriter};
use tracing::{debug, info, warn};
use netrunner_logger::{debug, info, warn};
use crate::tun::connection_manager::ConnectionManager;
use crate::tun::device::{TokenBuffer, VirtTunDevice};
+138 -129
View File
@@ -1,160 +1,169 @@
use crate::tun::tun::Tun;
use netrunner_logger::{error, info, warn};
use std::io;
use tracing::{error, info, warn};
#[cfg(any(feature = "linux", feature = "windows"))]
use std::process::Command;
fn run_cmd_ext(full_cmd: &str, ignore_errors: bool) -> io::Result<()> {
let parts = shlex::split(full_cmd)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Invalid syntax"))?;
if parts.is_empty() {
return Ok(());
}
let status = Command::new(&parts[0]).args(&parts[1..]).status()?;
if !status.success() && !ignore_errors {
let err = format!("Command failed: {} with status {}", full_cmd, status);
error!("{}", err);
return Err(io::Error::new(io::ErrorKind::Other, err));
}
Ok(())
}
#[cfg(target_os = "windows")]
fn get_active_interface_index() -> Option<u32> {
// Получаем список интерфейсов через netsh
let output = Command::new("netsh")
.args(["interface", "ipv4", "show", "interfaces"])
.output()
.ok()?;
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
if line.contains("Connected") && !line.contains("netr0") {
if let Some(idx_str) = line.split_whitespace().next() {
if let Ok(idx) = idx_str.parse::<u32>() {
return Some(idx);
}
}
}
}
None
}
pub fn setup_platform_routing(remote_address: &str) -> io::Result<()> {
let proxy_ip = remote_address.split(':').next().unwrap_or(remote_address);
#[cfg(feature = "linux")]
#[cfg(target_os = "linux")]
{
// 1. Бэкап и получение текущего маршрута по умолчанию
let _ = Command::new("sudo")
.args(&["cp", "/etc/resolv.conf", "/etc/resolv.conf.bak"])
.status();
// 1. Предварительная настройка ядра (rp_filter и пересылка)
let _ = run_cmd_ext("sysctl -w net.ipv4.conf.all.rp_filter=0", true);
let _ = run_cmd_ext("sysctl -w net.ipv4.conf.netr0.rp_filter=0", true);
let _ = run_cmd_ext("sysctl -w net.ipv4.ip_forward=1", true);
let output = Command::new("ip")
.args(&["route", "show", "default"])
.output()?;
let default_route = String::from_utf8_lossy(&output.stdout).trim().to_string();
// 3. Маршрутизация (игнорируем ошибки, если правила уже есть)
let _ = run_cmd_ext("ip rule add fwmark 0x1 table 100", true);
let _ = run_cmd_ext("ip route add default dev netr0 table 100", true);
if default_route.is_empty() {
error!("Default route не найден!");
return Err(io::Error::new(
io::ErrorKind::NotFound,
"No default route found",
));
}
std::fs::write("/tmp/netrunner_default_route", &default_route)?;
// 4. NFTables
let _ = run_cmd_ext("nft delete table ip netrunner", true);
run_cmd_ext("nft add table ip netrunner", false)?;
run_cmd_ext(
"nft add chain ip netrunner output { type route hook output priority 0; }",
false,
)?;
// 2. Парсинг параметров из маршрута по умолчанию
let parts: Vec<&str> = default_route.split_whitespace().collect();
let dev = parts
.iter()
.position(|&x| x == "dev")
.and_then(|i| parts.get(i + 1))
.copied()
.unwrap_or("eth0");
let via = parts
.iter()
.position(|&x| x == "via")
.and_then(|i| parts.get(i + 1))
.copied();
// 3. Добавляем маршрут до прокси-сервера
let mut proxy_route = vec!["ip", "route", "add", proxy_ip, "dev", dev];
if let Some(gw) = via {
proxy_route.extend_from_slice(&["via", gw]);
}
let _ = Command::new("sudo").args(proxy_route).status();
// 4. Заменяем default маршрут на наш TUN
let _ = Command::new("sudo")
.args(&["ip", "route", "del", "default"])
.status();
let status = Command::new("sudo")
.args(&[
"ip", "route", "add", "default", "via", "10.0.0.2", "dev", "netr0", "metric", "1",
])
.status()?;
if status.success() {
info!("Linux TUN default set.");
}
let mark_rule = format!(
"nft add rule ip netrunner output ip daddr != {} oifname != \"netr0\" mark set 0x1",
proxy_ip
);
run_cmd_ext(&mark_rule, false)?;
// 5. DNS
std::fs::write("/tmp/resolv.conf.netrunner", "nameserver 10.0.0.2\n")?;
Command::new("sudo")
.args(&["cp", "/tmp/resolv.conf.netrunner", "/etc/resolv.conf"])
.status()?;
}
let _ = Command::new("resolvectl")
.args(["dns", "netr0", "10.0.0.2"])
.status();
let _ = Command::new("resolvectl")
.args(["domain", "netr0", "~."])
.status();
#[cfg(feature = "windows")]
info!("Linux network auto-configured: RPF=0, MTU=1280, Rules active.");
}
#[cfg(target_os = "windows")]
{
// Логика Windows
Command::new("route")
.args(&[
"add",
proxy_ip,
"mask",
"255.255.255.255",
"0.0.0.0",
"metric",
"1",
])
.status()?;
Command::new("netsh")
.args(&[
"interface",
"ipv4",
"set",
"address",
"name=netr0",
"static",
"10.0.0.1",
"255.255.255.0",
"10.0.0.2",
])
.status()?;
use std::{process::Command, thread, time::Duration};
// DNS
Command::new("netsh")
.args(&[
"interface",
"ipv4",
"set",
"dnsservers",
"name=netr0",
"static",
"10.0.0.2",
"primary",
])
.status()?;
// 1. Инициализация Wintun
let wintun = unsafe { wintun::load_from_path("wintun.dll") }.map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("Wintun load error: {}", e))
})?;
let adapter = match wintun::Adapter::open(&wintun, "netr0") {
Ok(a) => a,
Err(_) => {
wintun::Adapter::create(&wintun, "netr0", "Wintun Tunnel", None).map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("Wintun create error: {}", e))
})?
}
};
// 2. Получаем индекс активного интерфейса для маршрутизации прокси
let if_idx = get_active_interface_index().unwrap_or(1);
info!(
"Wintun adapter active. Routing proxy traffic via interface index: {}",
if_idx
);
// 3. Добавляем маршрут к IP прокси через ИНДЕКС (самый надежный способ)
let route_cmd = format!(
"netsh interface ipv4 add route {}/32 interface={} metric=1",
proxy_ip, if_idx
);
// Игнорируем ошибку, если маршрут уже существует
let _ = run_cmd_ext(&route_cmd, true);
// 4. Настраиваем адрес (БЕЗ шлюза 10.0.0.2, чтобы не перехватить весь трафик)
let addr_cmd =
"netsh interface ipv4 set address name=\"netr0\" static 10.0.0.1 255.255.255.0";
// 5. Задаем DNS и применяем настройки
let dns_cmd = "netsh interface ipv4 set dnsservers name=\"netr0\" static 10.0.0.2 primary validate=no";
let mut attempt = 0;
while attempt < 5 {
if run_cmd_ext(addr_cmd, false).is_ok() {
let _ = run_cmd_ext(dns_cmd, false);
break;
}
attempt += 1;
thread::sleep(Duration::from_millis(1000));
}
}
#[cfg(feature = "mobile")]
#[cfg(any(target_os = "android", target_os = "ios"))]
{
eprintln!("Android/Mobile routing on native side");
}
Ok(())
}
pub fn reset_platform_routing() -> io::Result<()> {
#[cfg(feature = "linux")]
pub fn reset_platform_routing(proxy_ip: Option<&str>) -> io::Result<()> {
#[cfg(target_os = "linux")]
{
// 1. Возвращаем дефолтный маршрут из файла
if let Ok(saved_route) = std::fs::read_to_string("/tmp/netrunner_default_route") {
let _ = Command::new("sudo")
.args(&["ip", "route", "del", "default"])
.status();
let args: Vec<&str> = saved_route.split_whitespace().collect();
let _ = Command::new("sudo")
.args(&["ip", "route", "add"])
.args(args)
.status();
let _ = run_cmd_ext("ip rule del fwmark 0x1 table 100", true);
let _ = run_cmd_ext("ip route flush table 100", true);
let _ = run_cmd_ext("nft delete table ip netrunner", true);
info!("Linux routing reset.");
}
#[cfg(target_os = "windows")]
{
if let Some(ip) = proxy_ip {
// Удаляем только маршрут к конкретному IP прокси-сервера.
// Это гораздо безопаснее, чем удалять маршрут по умолчанию (0.0.0.0).
let cmd = format!("route delete {}", ip);
let _ = run_cmd_ext(&cmd, true);
let _ = run_cmd_ext("netsh interface delete interface name=\"netr0\"", true);
info!("Windows routing for proxy {} removed.", ip);
} else {
error!("Cannot reset Windows routing: proxy_ip is missing.");
}
// 2. Удаляем наш специфичный маршрут к прокси
// (Опционально, если он был добавлен)
// 3. Восстанавливаем DNS (если есть бэкап)
let _ = Command::new("sudo")
.args(&["cp", "/etc/resolv.conf.bak", "/etc/resolv.conf"])
.status();
info!("Linux routing restored.");
}
#[cfg(feature = "windows")]
#[cfg(any(target_os = "android", target_os = "ios"))]
{
// В Windows можно просто удалить маршрут и интерфейс
let _ = Command::new("route").args(&["delete", "0.0.0.0"]).status();
// При переподключении сети (или ipconfig /renew) Windows сам подтянет настройки
info!("Windows routing reset requested.");
eprintln!("Android/Mobile routing on native side");
}
Ok(())
}
+1 -3
View File
@@ -1,5 +1,5 @@
use netrunner_logger::{error, info, warn};
use std::io;
use tracing::{error, info, warn};
use tun::{AsyncDevice, Configuration, DeviceReader, DeviceWriter, create_as_async};
pub struct Tun {
@@ -21,7 +21,6 @@ impl Tun {
}
}
#[cfg(feature = "desktop")]
pub fn create<F>(f: F) -> io::Result<Self>
where
F: FnOnce(&mut Configuration),
@@ -31,7 +30,6 @@ impl Tun {
Self::new(&config)
}
#[cfg(feature = "mobile")]
pub fn from_fd(fd: i32) -> io::Result<Self> {
let mut config = Configuration::default();
config.raw_fd(fd);