these changes don't work correctly

This commit is contained in:
2026-03-02 22:13:17 +07:00
parent b6381b8b7b
commit 1f247073ca
13 changed files with 994 additions and 11 deletions
+2
View File
@@ -0,0 +1,2 @@
pub mod stack;
pub mod tun;
+67 -1
View File
@@ -1 +1,67 @@
fn main() {}
mod stack;
mod tun;
use netrunner_common::{
logger_init, proxy::connection::connection::ConnectionRole, proxy::network::Network,
};
use stack::interface::NetStack;
use tracing::{error, info};
use tun::{desktop::create_linux_tun, device::TunDevice};
#[tokio::main]
async fn main() {
// 1. Инициализация логов
logger_init();
info!("Starting NetRunner VPN Bridge...");
// 2. Настройка физического уровня (TUN)
let tun_dev = create_linux_tun();
let my_phy = TunDevice::new(tun_dev, 1500);
// 3. Создаем объект Network
let net = Network::new(8080, ConnectionRole::Client, Some("0.0.0.0:4443".into()));
// 4. Инициализируем ЕДИНЫЙ туннель для всего приложения.
// Этот метод внутри создает TLS-подключение, Muxer и запускает TunnelEngine.
info!("Initializing global TLS tunnel to proxy...");
let muxer = match net.initialize_client_tunnel().await {
Ok(m) => m,
Err(e) => {
error!("Failed to initialize global tunnel: {}", e);
return;
}
};
// 5. Создаем стек, передавая ему РАБОЧИЙ муксер.
// Теперь данные из TUN будут уходить в реальный TLS-туннель.
let mut stack = NetStack::new(my_phy, muxer.clone());
// 6. Запускаем SOCKS-сервер (Network), чтобы он слушал порт 8080
// и использовал тот же самый муксер для обычных прокси-запросов.
let net_handle = {
let muxer_for_net = muxer.clone();
tokio::spawn(async move {
info!("SOCKS5 server starting on 127.0.0.1:8080");
net.run_with_muxer(muxer_for_net).await;
})
};
info!("VPN BRIDGE IS RUNNING");
// 7. Запускаем цикл обработки стека (блокирующий поток)
let stack_loop = tokio::task::spawn_blocking(move || loop {
stack.poll();
});
// Ждем завершения (по сути бесконечно)
tokio::select! {
res = stack_loop => {
if let Err(e) = res {
error!("Stack loop panicked: {:?}", e);
}
}
_ = net_handle => {
error!("Network server stopped unexpectedly");
}
}
}
+353
View File
@@ -0,0 +1,353 @@
use bytes::{Bytes, BytesMut};
use netrunner_common::protocol::codec::frame::FrameType;
use netrunner_common::protocol::codec::socks::{SocksReply, SocksRequest};
use netrunner_common::protocol::parser::parser::Parser;
use netrunner_common::proxy::connection::muxer::{MuxMessage, Muxer};
use smoltcp::iface::{Config, Interface, SocketHandle, SocketSet, SocketStorage};
use smoltcp::socket::tcp::{Socket as SmolTcpSocket, SocketBuffer};
use smoltcp::socket::AnySocket;
use smoltcp::time::Instant;
use smoltcp::wire::IpListenEndpoint;
use std::collections::HashMap;
use std::os::unix::io::AsRawFd;
use tracing::{debug, info, trace};
pub enum BridgeState {
WaitingHandshake,
WaitingConnect,
DataTransferring {
tx_to_muxer: tokio::sync::mpsc::Sender<MuxMessage>,
stream_id: u32,
},
}
pub struct NetStack<D: smoltcp::phy::Device + AsRawFd + 'static> {
interface: Interface,
device: &'static mut D,
sockets: SocketSet<'static>,
socket_buffers: HashMap<SocketHandle, BytesMut>,
bridges: HashMap<SocketHandle, BridgeState>,
muxer: Muxer,
outbound_rx: tokio::sync::mpsc::UnboundedReceiver<(SocketHandle, Bytes)>,
outbound_tx: tokio::sync::mpsc::UnboundedSender<(SocketHandle, Bytes)>,
}
lazy_static::lazy_static! {
static ref START_TIME: std::time::Instant = std::time::Instant::now();
}
fn _current_smoltcp_time() -> Instant {
let nanos = START_TIME.elapsed().as_micros() as i64;
smoltcp::time::Instant::from_micros(nanos)
}
impl<D: smoltcp::phy::Device + AsRawFd> NetStack<D> {
pub fn new(device_obj: D, muxer: Muxer) -> Self {
let now = _current_smoltcp_time();
let device: &'static mut D = Box::leak(Box::new(device_obj));
let config = Config::new(smoltcp::wire::HardwareAddress::Ip);
let mut interface = Interface::new(config, device, now);
interface.set_any_ip(true);
interface.update_ip_addrs(|addrs| {
addrs
.push(smoltcp::wire::IpCidr::new(
smoltcp::wire::IpAddress::v4(10, 0, 0, 2),
24,
))
.unwrap();
});
interface
.routes_mut()
.add_default_ipv4_route(smoltcp::wire::Ipv4Address::new(10, 0, 0, 1))
.unwrap();
let rx_data = Box::leak(vec![0u8; 4096].into_boxed_slice());
let tx_data = Box::leak(vec![0u8; 4096].into_boxed_slice());
let mut socket = SmolTcpSocket::new(SocketBuffer::new(rx_data), SocketBuffer::new(tx_data));
// 4. Теперь listen() сработает, так как у интерфейса есть адрес!
let endpoint = IpListenEndpoint {
addr: None, // ВАЖНО: Принимаем пакеты для ЛЮБОГО IP назначения
port: 443, // Для HTTPS
};
socket.listen(endpoint).unwrap();
let storage: Vec<SocketStorage> = (0..16).map(|_| SocketStorage::EMPTY).collect();
let mut sockets = SocketSet::new(Box::leak(storage.into_boxed_slice()));
sockets.add(socket);
let (outbound_tx, outbound_rx) = tokio::sync::mpsc::unbounded_channel();
Self {
interface,
device,
sockets,
socket_buffers: HashMap::new(),
bridges: HashMap::new(),
muxer,
outbound_rx,
outbound_tx,
}
}
fn drive_interface(&mut self, timestamp: Instant) {
match self
.interface
.poll(timestamp, self.device, &mut self.sockets)
{
smoltcp::iface::PollResult::None => {}
res => debug!("Interface activity: {:?}", res),
}
let delay = self
.interface
.poll_delay(timestamp, &self.sockets)
.map(|d| d.total_millis() as i32)
.unwrap_or(10);
let raw_fd = self.device.as_raw_fd();
let mut fds = [libc::pollfd {
fd: raw_fd,
events: libc::POLLIN, // Ждем данные на вход
revents: 0,
}];
unsafe {
libc::poll(fds.as_mut_ptr(), 1, delay);
}
}
fn manage_socket_lifecycle(&mut self) {
for (_, any_socket) in self.sockets.iter_mut() {
let socket = SmolTcpSocket::downcast_mut(any_socket).unwrap();
// Если сокет закрылся или "отвисел" в TimeWait, возвращаем его в Listen
if !socket.is_active() && !socket.is_listening() {
let endpoint = IpListenEndpoint {
addr: None,
port: 443,
};
socket.listen(endpoint).unwrap();
println!("DEBUG: Сокет готов к новому соединению");
}
}
}
fn process_socks_logic(&mut self, handle: SocketHandle) {
let buf = self.socket_buffers.get_mut(&handle).unwrap();
let state = self
.bridges
.entry(handle)
.or_insert(BridgeState::WaitingHandshake);
trace!(handle = ?handle, buf_len = buf.len(), "Processing SOCKS logic");
match state {
BridgeState::WaitingHandshake => {
if let Ok(Some(SocksRequest::Handshake { .. })) = SocksRequest::parse(buf) {
let mut reply = BytesMut::with_capacity(2);
SocksReply::HandshakeSelect { method: 0x00 }.write_to(&mut reply);
let socket = self.sockets.get_mut::<SmolTcpSocket>(handle);
socket.send_slice(&reply).unwrap();
*state = BridgeState::WaitingConnect;
return; // <--- ВАЖНО: Выходим, чтобы smoltcp отправил этот пакет отдельно
}
}
BridgeState::WaitingConnect => {
// 1. Парсим запрос (парсер должен сам отрезать байты через advance/split_to)
if let Ok(Some(SocksRequest::Connect { target, .. })) = SocksRequest::parse(buf) {
let stream_id = self.muxer.next_id();
let target_str = target.to_string();
info!(handle = ?handle, target = %target_str, stream_id, "SOCKS5 Connect request");
let (v_tx, mut v_rx) = tokio::sync::mpsc::channel::<Bytes>(1024);
// 2. Пытаемся зарегистрировать стрим СИНХРОННО
// Это гарантирует, что Muxer узнает об ID до того, как придет ответ из сети
if self.muxer.try_register_stream(stream_id, v_tx.clone()) {
debug!(stream_id, "Stream registered synchronously via try_write");
// Сразу шлем Connect в сеть
let _ = self.muxer.to_network.try_send(MuxMessage {
stream_id,
frame_type: FrameType::Connect,
data: Bytes::from(target_str),
});
} else {
// ПЛАН Б: Если лок занят, спавним асинхронную задачу.
// ВАЖНО: Мы НЕ шлем Connect здесь, его пришлет сама задача ПОСЛЕ регистрации.
let muxer_clone = self.muxer.clone();
let target_bytes = Bytes::from(target_str);
let v_tx_clone = v_tx.clone();
tokio::spawn(async move {
muxer_clone.register_stream(stream_id, v_tx_clone).await;
let _ = muxer_clone
.to_network
.send(MuxMessage {
stream_id,
frame_type: FrameType::Connect,
data: target_bytes,
})
.await;
debug!(
stream_id,
"Stream registered asynchronously (lock was busy)"
);
});
}
// 3. Запускаем задачу проброса данных из сети в smoltcp
let outbound_tx = self.outbound_tx.clone();
let handle_clone = handle;
tokio::spawn(async move {
while let Some(data) = v_rx.recv().await {
if outbound_tx.send((handle_clone, data)).is_err() {
break;
}
}
});
buf.clear();
// 4. Отвечаем клиенту (curl)
let mut reply = BytesMut::new();
SocksReply::ConnectResult {
reply_code: 0x00,
atyp: 0x01,
addr: [0, 0, 0, 0],
port: 0,
}
.write_to(&mut reply);
let socket = self.sockets.get_mut::<SmolTcpSocket>(handle);
socket
.send_slice(&reply)
.expect("Failed to send SOCKS reply");
// ПЕРЕХОД
*state = BridgeState::DataTransferring {
tx_to_muxer: self.muxer.to_network.clone(),
stream_id,
};
return;
}
}
BridgeState::DataTransferring {
tx_to_muxer,
stream_id,
} => {
if !buf.is_empty() {
// Забираем все накопленные данные из буфера
let data = buf.split().freeze();
trace!(handle = ?handle, stream_id = *stream_id, bytes = data.len(), "Forwarding data smoltcp -> muxer");
// Отправляем в Muxer
let _ = tx_to_muxer.try_send(MuxMessage {
stream_id: *stream_id,
frame_type: FrameType::Data,
data,
});
}
}
}
}
fn process_payloads(&mut self) {
let handles: Vec<_> = self.sockets.iter().map(|(h, _)| h).collect();
for handle in handles {
let socket = self.sockets.get_mut::<SmolTcpSocket>(handle);
// 1. СОСТОЯНИЕ: Соединение только что установилось
if socket.is_active() && !self.bridges.contains_key(&handle) {
if let Some(target) = socket.local_endpoint() {
let stream_id = self.muxer.next_id();
info!("TUN Intercept: Auto-initiating SOCKS for target {}", target);
// ВАЖНО: Мы САМИ инициируем процесс для локального прокси
// Регистрируем стрим в муксере
let (v_tx, mut v_rx) = tokio::sync::mpsc::channel::<Bytes>(1024);
self.muxer.try_register_stream(stream_id, v_tx);
// Шлем CONNECT (твой муксер/прокси поймет это как SOCKS-запрос)
let _ = self.muxer.to_network.try_send(MuxMessage {
stream_id,
frame_type: FrameType::Connect,
data: Bytes::from(target.to_string()),
});
// Запускаем стандартную задачу проброса данных ИЗ сети в smoltcp
let outbound_tx = self.outbound_tx.clone();
tokio::spawn(async move {
while let Some(data) = v_rx.recv().await {
let _ = outbound_tx.send((handle, data));
}
});
// Переходим сразу в режим передачи данных
// Теперь всё, что пришлет Firefox, мы будем просто гнать в Muxer как FrameType::Data
self.bridges.insert(
handle,
BridgeState::DataTransferring {
stream_id,
tx_to_muxer: self.muxer.to_network.clone(),
},
);
continue;
}
}
// 2. СОСТОЯНИЕ: Передача данных (DataTransferring)
if let Some(BridgeState::DataTransferring {
stream_id,
tx_to_muxer,
}) = self.bridges.get(&handle)
{
if socket.can_recv() {
let mut temp_buf = vec![0u8; 2048];
if let Ok(size) = socket.recv_slice(&mut temp_buf) {
// Это РЕАЛЬНЫЕ данные от Firefox (например, TLS Client Hello)
// Мы их просто оборачиваем в твой протокол и шлем в муксер
let _ = tx_to_muxer.try_send(MuxMessage {
stream_id: *stream_id,
frame_type: FrameType::Data,
data: Bytes::copy_from_slice(&temp_buf[..size]),
});
}
}
}
}
}
pub fn poll(&mut self) {
let timestamp = _current_smoltcp_time();
self.drive_interface(timestamp);
self.process_payloads();
while let Ok((handle, data)) = self.outbound_rx.try_recv() {
if let Some(socket_state) = self.bridges.get(&handle) {
// ПИШЕМ ТОЛЬКО ЕСЛИ МЫ УЖЕ В DATA TRANSFER
if let BridgeState::DataTransferring { .. } = socket_state {
if self.sockets.iter().any(|(h, _)| h == handle) {
let socket = self.sockets.get_mut::<SmolTcpSocket>(handle);
if socket.can_send() {
debug!(handle = ?handle, len = data.len(), head = ?&data[..std::cmp::min(data.len(), 10)], "Writing to curl");
let _ = socket.send_slice(&data);
}
}
} else {
// Если мы еще в WaitingConnect, возвращаем данные в очередь
// или просто игнорируем (они придут следующим тиком)
debug!(handle = ?handle, "Data arrived before SOCKS handshake finished, delaying...");
// Можно использовать self.outbound_tx.send(...) чтобы вернуть в хвост
}
}
}
self.manage_socket_lifecycle();
}
}
+1
View File
@@ -0,0 +1 @@
pub mod interface;
+14
View File
@@ -0,0 +1,14 @@
use tun::Device;
pub fn create_linux_tun() -> Device {
let mut config = tun::Configuration::default();
config
.tun_name("netr0")
.address((10, 0, 0, 1)) // IP нашего "моста"
.netmask((255, 255, 255, 0)) // Маска подсети
.up(); // Сразу включаем интерфейс
// Это создаст в системе интерфейс tun0
let dev = tun::create(&config).expect("Нужны права root или CAP_NET_ADMIN!");
dev
}
+105
View File
@@ -0,0 +1,105 @@
use bytes::{Bytes, BytesMut};
use smoltcp::phy::{ChecksumCapabilities, Device, DeviceCapabilities, Medium, RxToken, TxToken};
use std::io::{Read, Write};
use std::os::fd::RawFd;
use std::os::unix::io::AsRawFd;
pub struct TunDevice<T: Read + Write + AsRawFd> {
pub io: T, // Сюда мы запихнем или File (Android) или Tun-либу
mtu: usize,
read_buffer: BytesMut,
}
impl<T: Read + Write + AsRawFd> TunDevice<T> {
pub fn new(io: T, mtu: usize) -> Self {
let fd = io.as_raw_fd();
unsafe {
let flags = libc::fcntl(fd, libc::F_GETFL);
if flags == -1 {
panic!("Не удалось получить флаги файла");
}
libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK);
}
Self {
io,
mtu,
read_buffer: BytesMut::with_capacity(mtu),
}
}
}
impl<T: Read + Write + AsRawFd> Device for TunDevice<T> {
type RxToken<'a>
= TunRxToken
where
Self: 'a;
type TxToken<'a>
= TunTxToken<'a, T>
where
Self: 'a;
fn capabilities(&self) -> DeviceCapabilities {
let mut caps = DeviceCapabilities::default();
caps.medium = Medium::Ip;
caps.max_transmission_unit = self.mtu;
caps.checksum = ChecksumCapabilities::ignored();
caps
}
fn receive(
&mut self,
_timestamp: smoltcp::time::Instant,
) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
let mut tmp_buf = [0u8; 2048];
match self.io.read(&mut tmp_buf) {
Ok(n) if n > 0 => {
let rx_data = Bytes::copy_from_slice(&tmp_buf[..n]);
let rx = TunRxToken { buffer: rx_data };
let tx = TunTxToken { io: &mut self.io };
Some((rx, tx))
}
_ => None,
}
}
fn transmit(&mut self, _timestamp: smoltcp::time::Instant) -> Option<Self::TxToken<'_>> {
Some(TunTxToken { io: &mut self.io })
}
}
pub struct TunRxToken {
buffer: Bytes,
}
impl RxToken for TunRxToken {
fn consume<R, F>(mut self, f: F) -> R
where
F: FnOnce(&[u8]) -> R,
{
f(&mut self.buffer)
}
}
pub struct TunTxToken<'a, T: Write> {
io: &'a mut T,
}
impl<'a, T: Write> TxToken for TunTxToken<'a, T> {
fn consume<R, F>(self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
// ПРЯМАЯ ЗАПИСЬ БЕЗ ДОБАВЛЕНИЯ PI-ЗАГОЛОВКА
let mut buffer = vec![0u8; len];
let result = f(&mut buffer);
let _ = self.io.write_all(&buffer);
result
}
}
impl<T: Read + Write + AsRawFd> AsRawFd for TunDevice<T> {
fn as_raw_fd(&self) -> RawFd {
self.io.as_raw_fd()
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod desktop;
pub mod device;