blacklist for mobiles

This commit is contained in:
2026-03-18 19:59:27 +07:00
parent 530568d27a
commit c4c45564d3
12 changed files with 336 additions and 249 deletions
+106 -98
View File
@@ -3,146 +3,154 @@ use hickory_proto::op::{Message, MessageType, ResponseCode};
use hickory_proto::rr::{RData, Record, RecordType};
use netrunner_logger::{debug, error, info, warn};
use std::collections::HashSet;
use std::fs::{self, File};
use std::io::{BufRead, BufReader, Write};
use std::path::Path;
use std::time::{Duration, SystemTime};
use tokio::fs::{self, File};
use tokio::io::{AsyncBufReadExt, BufReader};
pub struct DnsHandler {
block_list: HashSet<String>,
forbidden_suffixes: Vec<String>,
cache_path: String,
}
impl DnsHandler {
pub fn new() -> Self {
pub fn new(cache_dir: &str) -> Self {
Self {
block_list: HashSet::new(),
forbidden_suffixes: vec![
".lan".to_string(),
".local".to_string(),
".home".to_string(),
".arpa".to_string(),
],
cache_path: "hosts_cache.txt".to_string(),
forbidden_suffixes: vec![".lan", ".local", ".home", ".arpa"]
.into_iter()
.map(String::from)
.collect(),
cache_path: format!("{}/hosts_cache.txt", cache_dir),
}
}
/// Основной метод инициализации: решает, качать или брать из кэша
pub fn init(&mut self) -> anyhow::Result<()> {
let url = "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts";
let path = Path::new(&self.cache_path);
pub async fn init(&mut self) -> anyhow::Result<()> {
let path = std::path::PathBuf::from(&self.cache_path);
let should_download = if path.exists() {
let metadata = fs::metadata(path)?;
let last_modified = metadata.modified()?;
let elapsed = SystemTime::now().duration_since(last_modified)?.as_secs();
if path.exists() {
let _ = self.load_from_file().await;
}
elapsed > 604800
let needs_update = if let Ok(meta) = fs::metadata(&path).await {
SystemTime::now()
.duration_since(meta.modified()?)
.unwrap_or_default()
> Duration::from_secs(604800)
} else {
true
};
if should_download {
info!("Blocklist is outdated or missing. Trying to download...");
if let Err(e) = self.download_and_cache(url) {
warn!(
"Failed to download blocklist ({}), trying to use cache if available",
e
);
if needs_update {
let p_clone = self.cache_path.clone();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(10)).await;
Self::download_blocklist(p_clone);
});
}
Ok(())
}
fn download_blocklist(cache_path: String) {
let runtime = crate::RUNTIME.get_or_init(|| {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("Failed to create Tokio runtime")
});
runtime.block_on(async move {
info!("DNS: Starting background download to {}", cache_path);
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.build();
let client = match client {
Ok(c) => c,
Err(e) => {
netrunner_logger::error!("DNS: Failed to create client: {}", e);
return;
}
};
match client
.get("https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts")
.send()
.await
{
Ok(resp) if resp.status().is_success() => {
if let Ok(bytes) = resp.bytes().await {
if let Err(e) = tokio::fs::write(&cache_path, bytes).await {
netrunner_logger::error!("DNS: File write error: {}", e);
} else {
info!("DNS: Blocklist downloaded successfully to {}", cache_path);
}
}
}
Ok(resp) => {
netrunner_logger::error!("DNS: Download failed with status {}", resp.status())
}
Err(e) => {
netrunner_logger::error!("DNS: Request error (check internet/TLS): {}", e)
}
}
}
if path.exists() {
self.load_from_file()?;
} else {
error!("No blocklist available (neither online nor cache)!");
}
Ok(())
});
}
fn download_and_cache(&self, url: &str) -> anyhow::Result<()> {
let mut response = reqwest::blocking::get(url)?;
if !response.status().is_success() {
return Err(anyhow::anyhow!(
"Server returned status {}",
response.status()
));
}
let mut file = File::create(&self.cache_path)?;
response.copy_to(&mut file)?;
info!("Blocklist downloaded and cached locally.");
Ok(())
}
fn load_from_file(&mut self) -> anyhow::Result<()> {
let file = File::open(&self.cache_path)?;
let reader = BufReader::new(file);
async fn load_from_file(&mut self) -> anyhow::Result<()> {
let file = File::open(&self.cache_path).await?;
let mut lines = BufReader::new(file).lines();
let mut count = 0;
for line in reader.lines() {
let line = line?;
if line.starts_with('#') || line.trim().is_empty() {
while let Some(line) = lines.next_line().await? {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
// Берем домен (второй столбец в StevenBlack hosts)
self.block_list.insert(parts[1].to_lowercase());
if let Some(domain) = line.split_whitespace().nth(1) {
self.block_list.insert(domain.to_lowercase());
count += 1;
}
}
info!("Loaded {} domains from cache.", count);
info!("DNS: Loaded {} domains from cache.", count);
Ok(())
}
pub fn handle_query(&self, data: &[u8], store: &mut FakeIpStore) -> Option<Vec<u8>> {
let request = Message::from_vec(data).ok()?;
let query = request.queries().first()?;
let mut response = Message::new();
response
.set_id(request.id())
.set_message_type(MessageType::Response)
.set_recursion_available(true)
.add_query(query.clone());
let req = Message::from_vec(data).ok()?;
let query = req.queries().first()?;
let name = query
.name()
.to_string()
.trim_end_matches('.')
.to_lowercase();
// 1. Спец. суффиксы
if self.forbidden_suffixes.iter().any(|s| name.ends_with(s)) {
info!(domain = %name, "DNS: Blocked (Suffix)");
response.set_response_code(ResponseCode::NXDomain);
return response.to_vec().ok();
let mut res = Message::new();
res.set_id(req.id())
.set_message_type(MessageType::Response)
.set_recursion_available(true)
.add_query(query.clone());
// Проверка блокировок
if self.forbidden_suffixes.iter().any(|s| name.ends_with(s))
|| self.block_list.contains(&name)
{
info!(domain = %name, "DNS: Blocked");
res.set_response_code(ResponseCode::NXDomain);
return res.to_vec().ok();
}
// 2. Бан-лист (AdBlock)
if self.block_list.contains(&name) {
info!(domain = %name, "DNS: Blocked (AdList)");
response.set_response_code(ResponseCode::NXDomain);
return response.to_vec().ok();
if query.query_type() == RecordType::A {
let fake_ip = store.get_or_assign(&name);
res.add_answer(Record::from_rdata(
query.name().clone(),
60,
RData::A(fake_ip.into()),
));
res.set_response_code(ResponseCode::NoError);
}
// 3. Fake IP Logic
match query.query_type() {
RecordType::A => {
let fake_ip = store.get_or_assign(&name);
let record = Record::from_rdata(query.name().clone(), 60, RData::A(fake_ip.into()));
response.set_response_code(ResponseCode::NoError);
response.add_answer(record);
}
_ => {
response.set_response_code(ResponseCode::NoError);
}
}
response.to_vec().ok()
res.to_vec().ok()
}
}
+2 -2
View File
@@ -2,8 +2,8 @@ uniffi::setup_scaffolding!();
pub mod connections;
pub mod tun;
use netrunner_logger::info;
use std::sync::OnceLock;
use tokio::runtime::Runtime;
use std::{sync::OnceLock, time::Duration};
use tokio::{fs::File, io::AsyncWriteExt, runtime::Runtime};
use tokio_util::sync::CancellationToken;
use crate::tun::routing::reset_platform_routing;
+67 -71
View File
@@ -1,5 +1,3 @@
use std::net::Ipv4Addr;
use netrunner_client::{
connections::dns::DnsHandler,
tun::{
@@ -11,94 +9,92 @@ use netrunner_client::{
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 tokio_util::sync::CancellationToken;
// Убираем макрос #[tokio::main]
fn main() -> anyhow::Result<()> {
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// 1. Инициализация логгера (теперь он внутри асинхронного контекста)
netrunner_logger::Logger::init();
info!("Initializing NetRunner Stack...");
// 1. СИНХРОННАЯ ЧАСТЬ
// Здесь reqwest::blocking может спокойно создавать и удалять свои ресурсы
let mut dns_handler = DnsHandler::new();
if let Err(e) = dns_handler.init() {
// 2. Инициализация DNS асинхронно
// Передаем пустую строку или путь к кэшу (на десктопе можно ".")
let mut dns_handler = DnsHandler::new(".");
if let Err(e) = dns_handler.init().await {
error!("Failed to initialize DNS blocklist: {}", e);
}
// 2. АСИНХРОННАЯ ЧАСТЬ
// Создаем рантайм вручную
let rt = tokio::runtime::Runtime::new()?;
// 3. Настройка TUN устройства
let tun_device = Tun::create(|config| {
config
.tun_name("netr0")
.address((10, 0, 0, 1))
.netmask((255, 255, 255, 0))
.destination((10, 0, 0, 2))
.up();
})
.expect("Failed to initialize TUN device");
// Передаем управление в асинхронный блок
rt.block_on(async move {
let tun_device = Tun::create(|config| {
config
.tun_name("netr0")
.address((10, 0, 0, 1))
.netmask((255, 255, 255, 0))
.destination((10, 0, 0, 2))
.up();
})
.expect("Failed to initialize TUN device");
let remote_address: String = "62.60.244.156:443".into();
setup_platform_routing(&remote_address);
let remote_address: String = "62.60.244.156:443".into();
setup_platform_routing(&remote_address);
info!("TUN interface is UP: 10.0.0.1/24");
info!("TUN interface is UP: 10.0.0.1/24");
// 4. Конфигурация стека протоколов (MTU 1280 для стабильности)
let config = Config::new(smoltcp::wire::HardwareAddress::Ip);
let mut caps = DeviceCapabilities::default();
caps.max_transmission_unit = 1280;
caps.medium = smoltcp::phy::Medium::Ip;
let config = Config::new(smoltcp::wire::HardwareAddress::Ip);
let mut caps = DeviceCapabilities::default();
caps.max_transmission_unit = 1280;
caps.medium = smoltcp::phy::Medium::Ip;
let network = Network::new(
"0.0.0.0".into(),
8080,
ConnectionRole::Client,
Some(remote_address.clone()),
);
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 proxy_ip = network.get_self_local_address();
// 5. Запуск сетевого прокси в отдельной задаче
let network_token = CancellationToken::new();
let net_token_for_spawn = network_token.clone();
tokio::spawn(async move {
info!("Network thread started");
network.run(net_token_for_spawn).await;
});
// Запускаем сетевой поток
let network_token = CancellationToken::new();
tokio::spawn(async move {
info!("Network thread started");
network.run(network_token).await;
});
// 6. Инициализация Engine
let mut engine = Engine::new(config, caps, proxy_ip, dns_handler);
engine.set_any_ip(true);
engine.set_transparent_mode();
engine.set_default_gateway(Ipv4Addr::new(10, 0, 0, 2));
engine.activate();
// Создаем Engine и передаем уже готовый dns_handler
let mut engine = Engine::new(config, caps, proxy_ip, dns_handler);
engine.set_any_ip(true);
engine.set_transparent_mode();
engine.set_default_gateway(Ipv4Addr::new(10, 0, 0, 2));
engine.activate();
info!("Stack IP initialized: 10.0.0.2");
info!("Engine starting process loop...");
info!("Stack IP initialized: 10.0.0.2");
info!("Engine starting process loop...");
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...");
}
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...");
}
}
// Очистка при выходе
info!("Restoring system routing...");
let addr: std::net::SocketAddr = remote_address.parse().expect("Invalid address format");
let p_ip = addr.ip().to_string();
if let Err(e) = reset_platform_routing(Some(&p_ip)) {
error!("Failed to reset routing: {}", e);
} else {
info!("System routing restored successfully.");
}
// 8. Очистка ресурсов
network_token.cancel();
info!("Restoring system routing...");
let addr: std::net::SocketAddr = remote_address.parse().expect("Invalid address format");
let p_ip = addr.ip().to_string();
Ok::<(), anyhow::Error>(())
})?;
if let Err(e) = reset_platform_routing(Some(&p_ip)) {
error!("Failed to restore routing: {}", e);
} else {
info!("System routing restored successfully.");
}
Ok(())
}
+1 -2
View File
@@ -2,8 +2,7 @@ namespace netrunner_client {};
interface SessionManager {
constructor();
Session start_mobile(string remote_address, optional i32 tun_fd);
Session start_desktop(string remote_address);
Session start_mobile(string remote_address, optional i32 tun_fd, string cache_path);
};
interface Session {
+1 -2
View File
@@ -2,9 +2,8 @@ use super::{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)
self.spawn_session(remote_address)
}
}
+11 -2
View File
@@ -4,7 +4,16 @@ 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))
pub fn start_mobile(
&self,
remote_address: String,
tun_fd: i32,
cache_dir: String,
) -> Arc<Session> {
self.spawn_session(remote_address, Some(tun_fd), cache_dir)
}
pub fn start_desktop(&self, remote_address: String, cache_dir: String) -> Arc<Session> {
self.spawn_session(remote_address, None, cache_dir)
}
}
+38 -18
View File
@@ -47,21 +47,37 @@ impl SessionManager {
pub(crate) fn spawn_session(
&self,
remote_address: String,
#[cfg(any(target_os = "android", target_os = "ios"))] // Путь обязателен для мобилок
tun_fd: Option<i32>,
#[cfg(any(target_os = "android", target_os = "ios"))] cache_dir: String,
) -> Arc<Session> {
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 cache_path = {
#[cfg(any(target_os = "android", target_os = "ios"))]
{
cache_dir
} // Используем путь из мобильного приложения
#[cfg(any(target_os = "linux", target_os = "windows"))]
{
".".to_string()
} // На ПК пишем в локальную папку
};
let mut dns_handler = DnsHandler::new(&cache_path);
if let Err(e) = dns_handler.init().await {
error!("Failed to initialize DNS blocklist: {}", e);
}
let tun_device = {
#[cfg(any(target_os = "android", target_os = "ios"))]
{
@@ -93,7 +109,7 @@ impl SessionManager {
let config = Config::new(smoltcp::wire::HardwareAddress::Ip);
let mut caps = DeviceCapabilities::default();
caps.max_transmission_unit = 1500;
caps.max_transmission_unit = 1280;
caps.medium = smoltcp::phy::Medium::Ip;
let network = Network::new(
@@ -104,11 +120,6 @@ impl SessionManager {
);
let proxy_ip = network.get_self_local_address();
let mut dns_handler = DnsHandler::new();
if let Err(e) = dns_handler.init() {
error!("Failed to initialize DNS blocklist: {}", e);
}
tokio::spawn(async move {
network.run(net_token).await;
});
@@ -119,17 +130,26 @@ impl SessionManager {
engine.set_default_gateway(Ipv4Addr::new(10, 0, 0, 2));
engine.activate();
tokio::select! {
res = engine.run(tun_device) => error!("Engine loop error: {:?}", res),
_ = cancel_token.cancelled() => {
info!("Shutdown signal received");
let cancel_token_for_engine = cancel_token.clone();
std::thread::spawn(move || {
info!("Dedicated OS thread started for Engine");
let _ = reset_platform_routing(Some(&proxy_ip_for_thread));
},
_ = shutdown_signal => {
cancel_token.cancel();
}
}
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
rt.block_on(async {
tokio::select! {
res = engine.run(tun_device) => {
error!("Engine loop error: {:?}", res);
},
_ = cancel_token_for_engine.cancelled() => {
info!("Engine thread shutting down via token");
}
}
});
});
});
Arc::new(Session {
+21 -8
View File
@@ -175,26 +175,39 @@ impl ConnectionManager {
}
fn create_tcp_socket<'a>() -> tcp::Socket<'a> {
const BUF_SIZE: usize = 65536;
tcp::Socket::new(
const BUF_SIZE: usize = 128 * 1024; // Увеличил до 128KB для стабильного потока
let mut socket = tcp::Socket::new(
tcp::SocketBuffer::new(vec![0; BUF_SIZE]),
tcp::SocketBuffer::new(vec![0; BUF_SIZE]),
)
);
socket.set_nagle_enabled(false);
socket.set_ack_delay(None);
socket.set_keep_alive(Some(smoltcp::time::Duration::from_secs(30)));
socket.set_hop_limit(Some(64));
socket
}
fn create_udp_socket<'a>() -> udp::Socket<'a> {
const BUF_SIZE: usize = 32768;
const BUF_SIZE: usize = 65536;
const PACKET_COUNT: usize = 128;
udp::Socket::new(
udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY; 16], vec![0; BUF_SIZE]),
udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY; 16], vec![0; BUF_SIZE]),
udp::PacketBuffer::new(
vec![udp::PacketMetadata::EMPTY; PACKET_COUNT],
vec![0; BUF_SIZE],
),
udp::PacketBuffer::new(
vec![udp::PacketMetadata::EMPTY; PACKET_COUNT],
vec![0; BUF_SIZE],
),
)
}
fn create_icmp_socket<'a>() -> icmp::Socket<'a> {
let icmp_rx_buffer =
icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY; 4], vec![0; 1024]);
icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY; 8], vec![0; 2048]);
let icmp_tx_buffer =
icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY; 4], vec![0; 1024]);
icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY; 8], vec![0; 2048]);
icmp::Socket::new(icmp_rx_buffer, icmp_tx_buffer)
}