routing, cfg compile and routing reset

This commit is contained in:
2026-03-15 12:26:45 +07:00
parent 0cc44d0037
commit 102099e1cd
14 changed files with 344 additions and 213 deletions
+13 -93
View File
@@ -1,24 +1,13 @@
uniffi::setup_scaffolding!();
pub mod connections;
mod connections;
pub mod tun;
use std::sync::{Arc, Mutex};
use netrunner_core::{
logger_init,
proxy::{connection::connection::ConnectionRole, network::Network},
};
use smoltcp::{iface::Config, phy::DeviceCapabilities};
use std::net::Ipv4Addr;
use std::sync::Mutex;
use std::sync::OnceLock;
use tokio::runtime::Runtime;
use tokio::sync::oneshot;
use tracing::{error, info};
pub mod platform;
use tracing::info;
mod session;
use crate::{
platform::setup_platform_routing,
tun::{engine::Engine, tun::Tun},
};
static RUNTIME: OnceLock<Runtime> = OnceLock::new();
fn get_runtime() -> &'static Runtime {
@@ -29,10 +18,9 @@ fn get_runtime() -> &'static Runtime {
.expect("Failed to create tokio runtime")
})
}
#[derive(uniffi::Object)]
pub struct Session {
shutdown_tx: Mutex<Option<oneshot::Sender<()>>>,
pub(crate) shutdown_tx: Mutex<Option<oneshot::Sender<()>>>,
}
#[uniffi::export]
@@ -45,82 +33,14 @@ impl Session {
}
}
#[derive(uniffi::Object)]
pub struct SessionManager;
#[uniffi::export]
impl SessionManager {
#[uniffi::constructor]
pub fn new() -> Self {
logger_init();
info!("SessionManager initialized");
Self
}
pub fn start(&self, remote_address: String, tun_fd: Option<i32>) -> Arc<Session> {
let (tx, rx) = oneshot::channel();
let runtime = get_runtime();
runtime.spawn(async move {
info!("Starting VPN session thread...");
let tun_device = if let Some(fd) = tun_fd {
info!("Using provided FD for TUN: {}", fd);
Tun::from_android_fd(fd).expect("Failed to init TUN from FD")
} else {
info!("Creating TUN device manually");
Tun::create(|config| {
config.tun_name("tun0").address((10, 0, 0, 1)).up();
})
.expect("Failed to init TUN")
};
setup_platform_routing(&tun_device, &remote_address);
let config = Config::new(smoltcp::wire::HardwareAddress::Ip);
let mut caps = DeviceCapabilities::default();
caps.max_transmission_unit = 1500;
caps.medium = smoltcp::phy::Medium::Ip;
info!("Initializing Network with remote: {}", remote_address);
let network = Network::new(
"0.0.0.0".into(),
8080,
ConnectionRole::Client,
Some(remote_address),
);
let proxy_ip = network.get_self_local_address();
info!("Proxy self address: {:?}", proxy_ip);
let net_handle = tokio::spawn(async move {
network.run().await;
});
info!("Configuring Engine...");
let mut engine = Engine::new(config, caps, proxy_ip);
engine.set_any_ip(true);
engine.set_transparent_mode();
engine.set_default_gateway(Ipv4Addr::new(10, 0, 0, 2));
engine.activate();
info!("Engine activated");
tokio::select! {
res = engine.run(tun_device) => {
error!("Engine loop terminated unexpectedly: {:?}", res);
}
_ = rx => {
info!("Shutdown signal received. Cleaning up...");
}
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(());
}
net_handle.abort();
info!("VPN session fully stopped.");
});
Arc::new(Session {
shutdown_tx: Mutex::new(Some(tx)),
})
}
let _ = crate::tun::routing::reset_platform_routing();
}
}
+26 -6
View File
@@ -1,8 +1,9 @@
use std::net::Ipv4Addr;
use netrunner_client::{
platform::setup_platform_routing,
tun::{engine::Engine, tun::Tun},
use netrunner_client::tun::{
engine::Engine,
routing::{reset_platform_routing, setup_platform_routing},
tun::Tun,
};
use netrunner_core::{
logger_init,
@@ -17,7 +18,7 @@ async fn main() {
info!("Initializing NetRunner Stack...");
let tun_device = Tun::create(|config| {
config
.tun_name("tun0")
.tun_name("netr0")
.address((10, 0, 0, 1))
.netmask((255, 255, 255, 0))
.destination((10, 0, 0, 2))
@@ -25,7 +26,7 @@ async fn main() {
})
.expect("Failed to initialize TUN device");
let remote_address: String = "62.60.244.156:443".into();
setup_platform_routing(&tun_device, &remote_address);
setup_platform_routing(&remote_address).expect("Failed to setup routing");
info!("TUN interface is UP: 10.0.0.1/24");
@@ -58,5 +59,24 @@ async fn main() {
info!("Stack IP initialized: 10.0.0.2");
info!("Engine starting process loop...");
engine.run(tun_device).await;
// 3. Используем select для перехвата сигнала завершения
let ctrl_c = tokio::signal::ctrl_c();
tokio::select! {
res = engine.run(tun_device) => {
error!("Engine loop error: {:?}", res);
},
_ = ctrl_c => {
info!("Ctrl+C received, shutting down...");
}
}
// 4. ГАРАНТИРОВАННЫЙ СБРОС
info!("Restoring system routing...");
if let Err(e) = reset_platform_routing() {
error!("Failed to reset routing: {}", e);
} else {
info!("System routing restored successfully.");
}
}
+2 -1
View File
@@ -2,7 +2,8 @@ namespace netrunner_client {};
interface SessionManager {
constructor();
Session start(string remote_address, optional i32 tun_fd);
Session start_mobile(string remote_address, optional i32 tun_fd);
Session start_desktop(string remote_address);
};
interface Session {
-9
View File
@@ -1,9 +0,0 @@
use crate::tun::tun::Tun;
use std::io;
pub fn setup_platform_routing(tun_device: &Tun, remote_address: &str) -> io::Result<()> {
let proxy_ip = remote_address.split(':').next().unwrap_or(remote_address);
tun_device.setup_routing(proxy_ip)?;
tun_device.setup_dns_redirection()?;
Ok(())
}
-7
View File
@@ -1,7 +0,0 @@
use crate::tun::tun::Tun;
use std::io;
pub fn setup_platform_routing(_tun_device: &Tun, _remote_address: &str) -> io::Result<()> {
eprintln!("Android routing on Kotlin side");
Ok(())
}
-15
View File
@@ -1,15 +0,0 @@
#[cfg(feature = "desktop")]
pub mod desktop;
#[cfg(feature = "mobile")]
pub mod mobile;
pub fn setup_platform_routing(tun: &crate::tun::tun::Tun, addr: &str) -> std::io::Result<()> {
#[cfg(feature = "desktop")]
{
desktop::setup_platform_routing(tun, addr)
}
#[cfg(not(feature = "desktop"))]
{
mobile::setup_platform_routing(tun, addr)
}
}
+10
View File
@@ -0,0 +1,10 @@
use crate::session::{Session, SessionManager};
use std::sync::Arc;
use uniffi;
#[uniffi::export]
impl SessionManager {
pub fn start_desktop(&self, remote_address: String) -> Arc<Session> {
self.spawn_session(remote_address, None)
}
}
+10
View File
@@ -0,0 +1,10 @@
use crate::session::{Session, SessionManager};
use std::sync::Arc;
use uniffi;
#[uniffi::export]
impl SessionManager {
pub fn start_mobile(&self, remote_address: String, tun_fd: i32) -> Arc<Session> {
self.spawn_session(remote_address, Some(tun_fd))
}
}
+116
View File
@@ -0,0 +1,116 @@
#[cfg(feature = "desktop")]
pub mod desktop;
#[cfg(feature = "mobile")]
pub mod mobile;
use crate::{
Session, get_runtime,
tun::{
engine::Engine,
routing::{reset_platform_routing, setup_platform_routing},
tun::Tun,
},
};
use netrunner_core::{
logger_init,
proxy::{connection::connection::ConnectionRole, network::Network},
};
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};
#[derive(uniffi::Object)]
pub struct SessionManager;
#[uniffi::export]
impl SessionManager {
#[uniffi::constructor]
pub fn new() -> Self {
logger_init();
info!("SessionManager initialized");
Self
}
}
impl SessionManager {
pub(crate) fn spawn_session(
&self,
remote_address: String,
tun_fd: Option<i32>,
) -> Arc<Session> {
let (tx, rx) = oneshot::channel();
let runtime = get_runtime();
let shutdown_signal = signal::ctrl_c();
runtime.spawn(async move {
info!("Starting VPN session thread...");
let tun_device = {
#[cfg(feature = "mobile")]
{
Tun::from_fd(tun_fd.expect("TUN FD required on mobile"))
.expect("Failed to init TUN from FD")
}
#[cfg(feature = "desktop")]
{
Tun::create(|config| {
config
.tun_name("netr0")
.address((10, 0, 0, 1))
.netmask((255, 255, 255, 0))
.up();
})
.expect("Failed to init TUN")
}
};
setup_platform_routing(&remote_address).expect("Failed to setup routing");
let config = Config::new(smoltcp::wire::HardwareAddress::Ip);
let mut caps = DeviceCapabilities::default();
caps.max_transmission_unit = 1500;
caps.medium = smoltcp::phy::Medium::Ip;
let network = Network::new(
"0.0.0.0".into(),
8080,
ConnectionRole::Client,
Some(remote_address.clone()),
);
let proxy_ip = network.get_self_local_address();
let net_handle = tokio::spawn(async move {
network.run().await;
});
let mut engine = Engine::new(config, caps, proxy_ip);
engine.set_any_ip(true);
engine.set_transparent_mode();
engine.set_default_gateway(Ipv4Addr::new(10, 0, 0, 2));
engine.activate();
tokio::select! {
res = engine.run(tun_device) => error!("Engine loop error: {:?}", res),
_ = rx => {
info!("Shutdown signal received");
let _ = reset_platform_routing(); // Очистка при сигнале
},
_ = shutdown_signal => {
info!("Ctrl+C detected, shutting down gracefully...");
info!("Restoring routing...");
let _ = reset_platform_routing();
net_handle.abort();
}
}
});
Arc::new(Session {
shutdown_tx: Mutex::new(Some(tx)),
})
}
}
+1
View File
@@ -1,4 +1,5 @@
pub mod connection_manager;
pub mod device;
pub mod engine;
pub mod routing;
pub mod tun;
+160
View File
@@ -0,0 +1,160 @@
use crate::tun::tun::Tun;
use std::io;
use tracing::{error, info, warn};
#[cfg(any(feature = "linux", feature = "windows"))]
use std::process::Command;
pub fn setup_platform_routing(remote_address: &str) -> io::Result<()> {
let proxy_ip = remote_address.split(':').next().unwrap_or(remote_address);
#[cfg(feature = "linux")]
{
// 1. Бэкап и получение текущего маршрута по умолчанию
let _ = Command::new("sudo")
.args(&["cp", "/etc/resolv.conf", "/etc/resolv.conf.bak"])
.status();
let output = Command::new("ip")
.args(&["route", "show", "default"])
.output()?;
let default_route = String::from_utf8_lossy(&output.stdout).trim().to_string();
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)?;
// 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.");
}
// 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()?;
}
#[cfg(feature = "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()?;
// DNS
Command::new("netsh")
.args(&[
"interface",
"ipv4",
"set",
"dnsservers",
"name=netr0",
"static",
"10.0.0.2",
"primary",
])
.status()?;
}
#[cfg(feature = "mobile")]
{
eprintln!("Android/Mobile routing on native side");
}
Ok(())
}
pub fn reset_platform_routing() -> io::Result<()> {
#[cfg(feature = "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();
}
// 2. Удаляем наш специфичный маршрут к прокси
// (Опционально, если он был добавлен)
// 3. Восстанавливаем DNS (если есть бэкап)
let _ = Command::new("sudo")
.args(&["cp", "/etc/resolv.conf.bak", "/etc/resolv.conf"])
.status();
info!("Linux routing restored.");
}
#[cfg(feature = "windows")]
{
// В Windows можно просто удалить маршрут и интерфейс
let _ = Command::new("route").args(&["delete", "0.0.0.0"]).status();
// При переподключении сети (или ipconfig /renew) Windows сам подтянет настройки
info!("Windows routing reset requested.");
}
Ok(())
}
+3 -79
View File
@@ -21,6 +21,7 @@ impl Tun {
}
}
#[cfg(feature = "desktop")]
pub fn create<F>(f: F) -> io::Result<Self>
where
F: FnOnce(&mut Configuration),
@@ -30,7 +31,8 @@ impl Tun {
Self::new(&config)
}
pub fn from_android_fd(fd: i32) -> io::Result<Self> {
#[cfg(feature = "mobile")]
pub fn from_fd(fd: i32) -> io::Result<Self> {
let mut config = Configuration::default();
config.raw_fd(fd);
config.up();
@@ -42,82 +44,4 @@ impl Tun {
let (writer, reader) = self.device.split()?;
Ok((writer, reader))
}
fn get_route_part<'a>(parts: &'a [&'a str], key: &str) -> Option<&'a str> {
parts
.iter()
.position(|&x| x == key)
.and_then(|i| parts.get(i + 1))
.copied()
}
#[cfg(feature = "desktop")]
pub fn setup_routing(&self, remote_address: &str) -> io::Result<()> {
use std::process::Command;
info!(
"Начинаем настройку маршрутизации для прокси: {}",
remote_address
);
let output = Command::new("ip")
.args(&["route", "get", remote_address])
.output()?;
let route_info = String::from_utf8_lossy(&output.stdout);
let parts: Vec<&str> = route_info.split_whitespace().collect();
let dev = Self::get_route_part(&parts, "dev").unwrap_or("eth0");
let via = Self::get_route_part(&parts, "via");
info!("Обнаружен физический маршрут: dev={}, via={:?}", dev, via);
info!("Добавляем статический маршрут для прокси...");
let mut proxy_route = vec!["ip", "route", "add", remote_address, "dev", dev];
if let Some(gw) = via {
proxy_route.extend_from_slice(&["via", gw]);
}
let status = Command::new("sudo").args(proxy_route).status()?;
if !status.success() {
warn!("Маршрут к прокси уже существует или возникла ошибка (это нормально).");
}
info!("Переключаем default маршрут на tun0...");
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", "tun0", "metric", "1",
])
.status()?;
if status.success() {
info!("TUN успешно установлен как основной default.");
} else {
error!("Не удалось установить tun0 как default!");
}
if let Some(gw) = via {
info!("Добавляем резервный маршрут через {} с метрикой 100", dev);
let _ = Command::new("sudo")
.args(&[
"ip", "route", "add", "default", "via", gw, "dev", dev, "metric", "100",
])
.status();
}
info!("Маршрутизация полностью настроена.");
Ok(())
}
#[cfg(feature = "desktop")]
pub fn setup_dns_redirection(&self) -> io::Result<()> {
let _ = std::fs::write("/tmp/resolv.conf.netrunner", "nameserver 10.0.0.2\n");
let _ = std::process::Command::new("sudo")
.args(&["cp", "/tmp/resolv.conf.netrunner", "/etc/resolv.conf"])
.status();
info!("DNS перенаправлен на 10.0.0.1");
Ok(())
}
}