clean project from comments
This commit is contained in:
@@ -4,17 +4,13 @@ use hickory_proto::rr::{RData, Record};
|
|||||||
use crate::connections::ip_store::FakeIpStore;
|
use crate::connections::ip_store::FakeIpStore;
|
||||||
|
|
||||||
pub fn handle_dns_query(data: &[u8], store: &mut FakeIpStore) -> Option<Vec<u8>> {
|
pub fn handle_dns_query(data: &[u8], store: &mut FakeIpStore) -> Option<Vec<u8>> {
|
||||||
// 1. Парсим входящий запрос
|
|
||||||
let request = Message::from_vec(data).ok()?;
|
let request = Message::from_vec(data).ok()?;
|
||||||
|
|
||||||
// 2. Берем первый вопрос (обычно запрос только один)
|
|
||||||
let query = request.queries().first()?;
|
let query = request.queries().first()?;
|
||||||
let name = query.name().to_string().trim_end_matches('.').to_string();
|
let name = query.name().to_string().trim_end_matches('.').to_string();
|
||||||
|
|
||||||
// 3. Получаем Fake IP
|
|
||||||
let fake_ip = store.get_or_assign(&name);
|
let fake_ip = store.get_or_assign(&name);
|
||||||
|
|
||||||
// 4. Формируем ответ
|
|
||||||
let mut response = Message::new();
|
let mut response = Message::new();
|
||||||
response
|
response
|
||||||
.set_recursion_available(true)
|
.set_recursion_available(true)
|
||||||
@@ -23,14 +19,8 @@ pub fn handle_dns_query(data: &[u8], store: &mut FakeIpStore) -> Option<Vec<u8>>
|
|||||||
.set_response_code(ResponseCode::NoError)
|
.set_response_code(ResponseCode::NoError)
|
||||||
.add_query(query.clone());
|
.add_query(query.clone());
|
||||||
|
|
||||||
// Создаем запись A (IPv4)
|
let record = Record::from_rdata(query.name().clone(), 1, RData::A(fake_ip.into()));
|
||||||
let record = Record::from_rdata(
|
|
||||||
query.name().clone(),
|
|
||||||
1, // TTL = 1 сек
|
|
||||||
RData::A(fake_ip.into()),
|
|
||||||
);
|
|
||||||
response.add_answer(record);
|
response.add_answer(record);
|
||||||
|
|
||||||
// 5. Сериализуем обратно в байты
|
|
||||||
response.to_vec().ok()
|
response.to_vec().ok()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ pub struct FakeIpStore {
|
|||||||
next_ip: u32,
|
next_ip: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
const START_IP: u32 = 0x64400001; // 100.64.0.1 (IP for Service Networks/Shared Address Space)
|
const START_IP: u32 = 0x64400001;
|
||||||
|
|
||||||
impl FakeIpStore {
|
impl FakeIpStore {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use netrunner_core::protocol::codec::socks::{SocksRequest, TargetAddress};
|
|||||||
use smoltcp::iface::SocketHandle;
|
use smoltcp::iface::SocketHandle;
|
||||||
use smoltcp::socket::tcp;
|
use smoltcp::socket::tcp;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::net::TcpStream; // Твой код парсера
|
use tokio::net::TcpStream;
|
||||||
use tokio::sync::{mpsc, oneshot};
|
use tokio::sync::{mpsc, oneshot};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use tracing::{debug, info, trace, warn};
|
use tracing::{debug, info, trace, warn};
|
||||||
@@ -44,10 +44,9 @@ impl TcpConnection {
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!(%handle, error = %e, "Failed to connect to proxy");
|
debug!(%handle, error = %e, "Failed to connect to proxy");
|
||||||
return;
|
return;
|
||||||
} // Тут можно добавить логирование
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2. SOCKS Handshake
|
|
||||||
if let Err(e) = SocksRequest::perform_client_handshake(&mut stream, &target_addr).await
|
if let Err(e) = SocksRequest::perform_client_handshake(&mut stream, &target_addr).await
|
||||||
{
|
{
|
||||||
debug!(%handle, error = %e, "SOCKS handshake failed");
|
debug!(%handle, error = %e, "SOCKS handshake failed");
|
||||||
@@ -58,10 +57,8 @@ impl TcpConnection {
|
|||||||
|
|
||||||
debug!(%handle, "SOCKS handshake successful, starting data bridge");
|
debug!(%handle, "SOCKS handshake successful, starting data bridge");
|
||||||
|
|
||||||
// 3. Копирование данных (Bridge)
|
|
||||||
let (mut reader, mut writer) = stream.into_split();
|
let (mut reader, mut writer) = stream.into_split();
|
||||||
|
|
||||||
// Читаем из канала -> Пишем в прокси
|
|
||||||
let to_proxy = async {
|
let to_proxy = async {
|
||||||
while let Some(data) = rx_from_smol.recv().await {
|
while let Some(data) = rx_from_smol.recv().await {
|
||||||
if writer.write_all(&data).await.is_err() {
|
if writer.write_all(&data).await.is_err() {
|
||||||
@@ -70,7 +67,6 @@ impl TcpConnection {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Читаем из прокси -> Пишем в канал для smoltcp
|
|
||||||
let from_proxy = async {
|
let from_proxy = async {
|
||||||
let mut buf = [0u8; 65536];
|
let mut buf = [0u8; 65536];
|
||||||
loop {
|
loop {
|
||||||
@@ -94,7 +90,7 @@ impl TcpConnection {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
handle,
|
handle,
|
||||||
state: ConnectionState::Active, // Сразу переходим в Active, т.к. задача пошла
|
state: ConnectionState::Active,
|
||||||
tx: tx_to_proxy,
|
tx: tx_to_proxy,
|
||||||
rx: rx_from_proxy,
|
rx: rx_from_proxy,
|
||||||
pending_data: vec![],
|
pending_data: vec![],
|
||||||
@@ -105,7 +101,6 @@ impl TcpConnection {
|
|||||||
|
|
||||||
pub fn tick(&mut self, socket: &mut tcp::Socket) -> bool {
|
pub fn tick(&mut self, socket: &mut tcp::Socket) -> bool {
|
||||||
let state = socket.state();
|
let state = socket.state();
|
||||||
//trace!(handle=%self.handle, ?state, "Tick");
|
|
||||||
|
|
||||||
match self.state {
|
match self.state {
|
||||||
ConnectionState::Handshaking => {
|
ConnectionState::Handshaking => {
|
||||||
@@ -114,23 +109,22 @@ impl TcpConnection {
|
|||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
self.state = ConnectionState::Active;
|
self.state = ConnectionState::Active;
|
||||||
self.handshake_rx = None;
|
self.handshake_rx = None;
|
||||||
return true; // Успех
|
return true;
|
||||||
}
|
}
|
||||||
Err(oneshot::error::TryRecvError::Empty) => return true, // Ждем
|
Err(oneshot::error::TryRecvError::Empty) => return true,
|
||||||
Err(oneshot::error::TryRecvError::Closed) => {
|
Err(oneshot::error::TryRecvError::Closed) => {
|
||||||
self.state = ConnectionState::Closed; // ФЕЙЛ
|
self.state = ConnectionState::Closed;
|
||||||
return false; // СКАЗАТЬ МЕНЕДЖЕРУ УДАЛИТЬ НАС
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false; // Если rx пропал, но мы не Active — закрываем
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectionState::Active => {
|
ConnectionState::Active => {
|
||||||
self.poll_and_process(socket);
|
self.poll_and_process(socket);
|
||||||
|
|
||||||
// FIN от удалённой стороны
|
|
||||||
if state == tcp::State::CloseWait {
|
if state == tcp::State::CloseWait {
|
||||||
socket.close();
|
socket.close();
|
||||||
self.state = ConnectionState::Closed;
|
self.state = ConnectionState::Closed;
|
||||||
@@ -164,7 +158,6 @@ impl TcpConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn poll_and_process(&mut self, socket: &mut tcp::Socket) {
|
fn poll_and_process(&mut self, socket: &mut tcp::Socket) {
|
||||||
// 1. сначала читаем smoltcp
|
|
||||||
if socket.can_recv() {
|
if socket.can_recv() {
|
||||||
let _ = socket.recv(|data| {
|
let _ = socket.recv(|data| {
|
||||||
let len = data.len();
|
let len = data.len();
|
||||||
@@ -175,12 +168,11 @@ impl TcpConnection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. потом отправляем proxy → smoltcp
|
|
||||||
if !self.pending_data.is_empty() {
|
if !self.pending_data.is_empty() {
|
||||||
if self.pending_data.len() > MAX_PENDING {
|
if self.pending_data.len() > MAX_PENDING {
|
||||||
warn!(%self.handle, "Buffer overflow! Aborting connection.");
|
warn!(%self.handle, "Buffer overflow! Aborting connection.");
|
||||||
socket.abort(); // Убиваем сокет
|
socket.abort();
|
||||||
self.token.cancel(); // Говорим tokio-задаче умереть
|
self.token.cancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +180,7 @@ impl TcpConnection {
|
|||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
self.pending_data.drain(0..n);
|
self.pending_data.drain(0..n);
|
||||||
}
|
}
|
||||||
Err(_) => {} // Оставляем в pending_data на следующий тик
|
Err(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-10
@@ -45,7 +45,6 @@ impl Session {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Фабрика (SessionManager) ---
|
|
||||||
#[derive(uniffi::Object)]
|
#[derive(uniffi::Object)]
|
||||||
pub struct SessionManager;
|
pub struct SessionManager;
|
||||||
|
|
||||||
@@ -70,18 +69,13 @@ impl SessionManager {
|
|||||||
} else {
|
} else {
|
||||||
info!("Creating TUN device manually");
|
info!("Creating TUN device manually");
|
||||||
Tun::create(|config| {
|
Tun::create(|config| {
|
||||||
config
|
config.tun_name("tun0").address((10, 0, 0, 1)).up();
|
||||||
.tun_name("tun0")
|
|
||||||
.address((10, 0, 0, 1))
|
|
||||||
// ... настройки ...
|
|
||||||
.up();
|
|
||||||
})
|
})
|
||||||
.expect("Failed to init TUN")
|
.expect("Failed to init TUN")
|
||||||
};
|
};
|
||||||
|
|
||||||
setup_platform_routing(&tun_device, &remote_address);
|
setup_platform_routing(&tun_device, &remote_address);
|
||||||
|
|
||||||
// 2. Инициализация сети
|
|
||||||
let config = Config::new(smoltcp::wire::HardwareAddress::Ip);
|
let config = Config::new(smoltcp::wire::HardwareAddress::Ip);
|
||||||
let mut caps = DeviceCapabilities::default();
|
let mut caps = DeviceCapabilities::default();
|
||||||
caps.max_transmission_unit = 1500;
|
caps.max_transmission_unit = 1500;
|
||||||
@@ -97,13 +91,13 @@ impl SessionManager {
|
|||||||
|
|
||||||
let proxy_ip = network.get_self_local_address();
|
let proxy_ip = network.get_self_local_address();
|
||||||
info!("Proxy self address: {:?}", proxy_ip);
|
info!("Proxy self address: {:?}", proxy_ip);
|
||||||
// Запускаем сетевой поток
|
|
||||||
let net_handle = tokio::spawn(async move {
|
let net_handle = tokio::spawn(async move {
|
||||||
network.run().await;
|
network.run().await;
|
||||||
});
|
});
|
||||||
|
|
||||||
info!("Configuring Engine...");
|
info!("Configuring Engine...");
|
||||||
// 3. Инициализация Engine
|
|
||||||
let mut engine = Engine::new(config, caps, proxy_ip);
|
let mut engine = Engine::new(config, caps, proxy_ip);
|
||||||
engine.set_any_ip(true);
|
engine.set_any_ip(true);
|
||||||
engine.set_transparent_mode();
|
engine.set_transparent_mode();
|
||||||
@@ -112,7 +106,6 @@ impl SessionManager {
|
|||||||
|
|
||||||
info!("Engine activated");
|
info!("Engine activated");
|
||||||
|
|
||||||
// 4. Главный цикл с поддержкой остановки
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
res = engine.run(tun_device) => {
|
res = engine.run(tun_device) => {
|
||||||
error!("Engine loop terminated unexpectedly: {:?}", res);
|
error!("Engine loop terminated unexpectedly: {:?}", res);
|
||||||
|
|||||||
+1
-5
@@ -13,7 +13,6 @@ use tracing::{error, info};
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// 1. Инициализируем систему логирования
|
|
||||||
logger_init();
|
logger_init();
|
||||||
info!("Initializing NetRunner Stack...");
|
info!("Initializing NetRunner Stack...");
|
||||||
let tun_device = Tun::create(|config| {
|
let tun_device = Tun::create(|config| {
|
||||||
@@ -46,8 +45,6 @@ async fn main() {
|
|||||||
|
|
||||||
let proxy_ip = network.get_self_local_address();
|
let proxy_ip = network.get_self_local_address();
|
||||||
|
|
||||||
// 2. ВЫНОСИМ СЕТЬ В ОТДЕЛЬНЫЙ ЦИКЛ
|
|
||||||
// Это запустит SOCKS5 сервер и TLS туннель параллельно движку
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
info!("Network thread started");
|
info!("Network thread started");
|
||||||
network.run().await;
|
network.run().await;
|
||||||
@@ -56,11 +53,10 @@ async fn main() {
|
|||||||
let mut engine = Engine::new(config, caps, proxy_ip);
|
let mut engine = Engine::new(config, caps, proxy_ip);
|
||||||
engine.set_any_ip(true);
|
engine.set_any_ip(true);
|
||||||
engine.set_transparent_mode();
|
engine.set_transparent_mode();
|
||||||
engine.set_default_gateway(Ipv4Addr::new(10, 0, 0, 2)); // to smoltcp
|
engine.set_default_gateway(Ipv4Addr::new(10, 0, 0, 2));
|
||||||
engine.activate();
|
engine.activate();
|
||||||
info!("Stack IP initialized: 10.0.0.2");
|
info!("Stack IP initialized: 10.0.0.2");
|
||||||
|
|
||||||
// 5. Запуск
|
|
||||||
info!("Engine starting process loop...");
|
info!("Engine starting process loop...");
|
||||||
engine.run(tun_device).await;
|
engine.run(tun_device).await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::tun::tun::Tun; // Убедись, что путь к Tun верный
|
use crate::tun::tun::Tun;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
pub fn setup_platform_routing(tun_device: &Tun, remote_address: &str) -> io::Result<()> {
|
pub fn setup_platform_routing(tun_device: &Tun, remote_address: &str) -> io::Result<()> {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use crate::{
|
|||||||
pub struct ConnectionManager {
|
pub struct ConnectionManager {
|
||||||
last_activity: HashMap<SocketHandle, StdInstant>,
|
last_activity: HashMap<SocketHandle, StdInstant>,
|
||||||
active_tcp_sessions: HashMap<SocketHandle, TcpConnection>,
|
active_tcp_sessions: HashMap<SocketHandle, TcpConnection>,
|
||||||
//active_udp_sessions: HashMap<SocketHandle, UdpSession>,
|
|
||||||
fake_ip_store: FakeIpStore,
|
fake_ip_store: FakeIpStore,
|
||||||
proxy_ip: String,
|
proxy_ip: String,
|
||||||
failed_until: HashMap<SocketHandle, StdInstant>,
|
failed_until: HashMap<SocketHandle, StdInstant>,
|
||||||
@@ -37,7 +37,6 @@ impl ConnectionManager {
|
|||||||
}
|
}
|
||||||
pub fn start_listening(&mut self, socket_set: &mut SocketSet) {
|
pub fn start_listening(&mut self, socket_set: &mut SocketSet) {
|
||||||
for (_, socket) in socket_set.iter_mut() {
|
for (_, socket) in socket_set.iter_mut() {
|
||||||
// Обработка TCP (как было)
|
|
||||||
if let Some(tcp) = tcp::Socket::downcast_mut(socket) {
|
if let Some(tcp) = tcp::Socket::downcast_mut(socket) {
|
||||||
if !tcp.is_open() {
|
if !tcp.is_open() {
|
||||||
let endpoint = IpListenEndpoint {
|
let endpoint = IpListenEndpoint {
|
||||||
@@ -46,11 +45,8 @@ impl ConnectionManager {
|
|||||||
};
|
};
|
||||||
let _ = tcp.listen(endpoint);
|
let _ = tcp.listen(endpoint);
|
||||||
}
|
}
|
||||||
}
|
} else if let Some(udp) = udp::Socket::downcast_mut(socket) {
|
||||||
// Добавляем обработку UDP
|
|
||||||
else if let Some(udp) = udp::Socket::downcast_mut(socket) {
|
|
||||||
if !udp.is_open() {
|
if !udp.is_open() {
|
||||||
// Биндим на 53 порт, чтобы ловить DNS-запросы
|
|
||||||
let endpoint = IpListenEndpoint {
|
let endpoint = IpListenEndpoint {
|
||||||
addr: None,
|
addr: None,
|
||||||
port: 53,
|
port: 53,
|
||||||
@@ -65,12 +61,11 @@ impl ConnectionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_target(&self, socket: &tcp::Socket) -> TargetAddress {
|
fn resolve_target(&self, socket: &tcp::Socket) -> TargetAddress {
|
||||||
// Безопасно получаем эндпоинт
|
|
||||||
let local_endpoint = match socket.local_endpoint() {
|
let local_endpoint = match socket.local_endpoint() {
|
||||||
Some(ep) => ep,
|
Some(ep) => ep,
|
||||||
None => {
|
None => {
|
||||||
warn!(handle=?socket, "Attempted to resolve target for an unconnected socket");
|
warn!(handle=?socket, "Attempted to resolve target for an unconnected socket");
|
||||||
// Возвращаем дефолт, чтобы не падать
|
|
||||||
return TargetAddress::Domain("disconnected".to_string(), 0);
|
return TargetAddress::Domain("disconnected".to_string(), 0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -116,11 +111,10 @@ impl ConnectionManager {
|
|||||||
fn handle_tcp(&mut self, handle: SocketHandle, socket: &mut tcp::Socket) {
|
fn handle_tcp(&mut self, handle: SocketHandle, socket: &mut tcp::Socket) {
|
||||||
use tcp::State;
|
use tcp::State;
|
||||||
|
|
||||||
// 1. Если сокет закрыт, просто чистим и возвращаем в LISTEN
|
|
||||||
if socket.state() == State::Closed {
|
if socket.state() == State::Closed {
|
||||||
if let Some(until) = self.failed_until.get(&handle) {
|
if let Some(until) = self.failed_until.get(&handle) {
|
||||||
if StdInstant::now() < *until {
|
if StdInstant::now() < *until {
|
||||||
return; // Сокет в штрафе, не открываем его
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,15 +124,11 @@ impl ConnectionManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Если сокет установлен, но в менеджере нет записи
|
|
||||||
if socket.state() == State::Established && !self.active_tcp_sessions.contains_key(&handle) {
|
if socket.state() == State::Established && !self.active_tcp_sessions.contains_key(&handle) {
|
||||||
let target = self.resolve_target(socket);
|
let target = self.resolve_target(socket);
|
||||||
|
|
||||||
// ВАЖНО: Тут можно добавить проверку: если target "плохой" или не резолвится,
|
|
||||||
// сразу убиваем сокет, чтобы не зацикливаться.
|
|
||||||
if let TargetAddress::Domain(d, _) = &target {
|
if let TargetAddress::Domain(d, _) = &target {
|
||||||
if d == "disconnected" {
|
if d == "disconnected" {
|
||||||
// Или другая логика проверки
|
|
||||||
socket.abort();
|
socket.abort();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -148,13 +138,11 @@ impl ConnectionManager {
|
|||||||
self.active_tcp_sessions.insert(handle, conn);
|
self.active_tcp_sessions.insert(handle, conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Обработка активной сессии
|
|
||||||
if let Some(conn) = self.active_tcp_sessions.get_mut(&handle) {
|
if let Some(conn) = self.active_tcp_sessions.get_mut(&handle) {
|
||||||
if !conn.tick(socket) {
|
if !conn.tick(socket) {
|
||||||
// Если tick вернул false, значит сессия завершена или произошла ошибка в tokio-задаче
|
|
||||||
debug!(%handle, "Connection handshake failed or closed, aborting socket.");
|
debug!(%handle, "Connection handshake failed or closed, aborting socket.");
|
||||||
self.active_tcp_sessions.remove(&handle);
|
self.active_tcp_sessions.remove(&handle);
|
||||||
socket.abort(); // Принудительно закрываем "битый" сокет
|
socket.abort();
|
||||||
self.failed_until
|
self.failed_until
|
||||||
.insert(handle, StdInstant::now() + Duration::from_secs(5));
|
.insert(handle, StdInstant::now() + Duration::from_secs(5));
|
||||||
}
|
}
|
||||||
@@ -164,7 +152,6 @@ impl ConnectionManager {
|
|||||||
socket.abort();
|
socket.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Обработка FIN
|
|
||||||
if socket.state() == State::CloseWait {
|
if socket.state() == State::CloseWait {
|
||||||
socket.close();
|
socket.close();
|
||||||
}
|
}
|
||||||
@@ -211,20 +198,16 @@ impl ConnectionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_sockets(n_tcp: usize, n_udp: usize, n_icmp: usize) -> SocketSet<'static> {
|
pub fn setup_sockets(n_tcp: usize, n_udp: usize, n_icmp: usize) -> SocketSet<'static> {
|
||||||
// Создаем хранилище с запасом на все типы сокетов
|
|
||||||
let mut sockets = SocketSet::new(Vec::with_capacity(n_tcp + n_udp + n_icmp));
|
let mut sockets = SocketSet::new(Vec::with_capacity(n_tcp + n_udp + n_icmp));
|
||||||
|
|
||||||
// 1. Добавляем TCP сокеты
|
|
||||||
for _ in 0..n_tcp {
|
for _ in 0..n_tcp {
|
||||||
sockets.add(Self::create_tcp_socket());
|
sockets.add(Self::create_tcp_socket());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Добавляем UDP сокеты
|
|
||||||
for _ in 0..n_udp {
|
for _ in 0..n_udp {
|
||||||
sockets.add(Self::create_udp_socket());
|
sockets.add(Self::create_udp_socket());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Добавляем ICMP сокет
|
|
||||||
for _ in 0..n_icmp {
|
for _ in 0..n_icmp {
|
||||||
sockets.add(Self::create_icmp_socket());
|
sockets.add(Self::create_icmp_socket());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
//! Virtual Device for receiving packets from tun
|
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
mem,
|
mem,
|
||||||
@@ -119,9 +117,6 @@ impl phy::TxToken for VirtTxToken<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maximum number of TokenBuffer cached globally.
|
|
||||||
//
|
|
||||||
// Each of them has capacity 65536 (defined in tun/mod.rs), so 64 * 65536 = 4MB.
|
|
||||||
const TOKEN_BUFFER_LIST_MAX_SIZE: usize = 64;
|
const TOKEN_BUFFER_LIST_MAX_SIZE: usize = 64;
|
||||||
static TOKEN_BUFFER_LIST: LazyLock<Mutex<Vec<BytesMut>>> = LazyLock::new(|| Mutex::new(Vec::new()));
|
static TOKEN_BUFFER_LIST: LazyLock<Mutex<Vec<BytesMut>>> = LazyLock::new(|| Mutex::new(Vec::new()));
|
||||||
|
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ impl Engine {
|
|||||||
pub fn set_transparent_mode(&mut self) {
|
pub fn set_transparent_mode(&mut self) {
|
||||||
self.interface.update_ip_addrs(|addrs| {
|
self.interface.update_ip_addrs(|addrs| {
|
||||||
addrs.clear();
|
addrs.clear();
|
||||||
//accept all internet
|
|
||||||
addrs
|
addrs
|
||||||
.push(IpCidr::new(IpAddress::v4(10, 0, 0, 2), 24))
|
.push(IpCidr::new(IpAddress::v4(10, 0, 0, 2), 24))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
+2
-10
@@ -32,8 +32,8 @@ impl Tun {
|
|||||||
|
|
||||||
pub fn from_android_fd(fd: i32) -> io::Result<Self> {
|
pub fn from_android_fd(fd: i32) -> io::Result<Self> {
|
||||||
let mut config = Configuration::default();
|
let mut config = Configuration::default();
|
||||||
config.raw_fd(fd); // Передаем дескриптор, который нам дал Android VpnService
|
config.raw_fd(fd);
|
||||||
config.up(); // Убеждаемся, что он поднят
|
config.up();
|
||||||
|
|
||||||
Self::new(&config)
|
Self::new(&config)
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,6 @@ impl Tun {
|
|||||||
remote_address
|
remote_address
|
||||||
);
|
);
|
||||||
|
|
||||||
// 1. Находим текущий маршрут до прокси (до изменения default)
|
|
||||||
let output = Command::new("ip")
|
let output = Command::new("ip")
|
||||||
.args(&["route", "get", remote_address])
|
.args(&["route", "get", remote_address])
|
||||||
.output()?;
|
.output()?;
|
||||||
@@ -72,7 +71,6 @@ impl Tun {
|
|||||||
|
|
||||||
info!("Обнаружен физический маршрут: dev={}, via={:?}", dev, via);
|
info!("Обнаружен физический маршрут: dev={}, via={:?}", dev, via);
|
||||||
|
|
||||||
// 2. Добавляем статическое исключение для прокси
|
|
||||||
info!("Добавляем статический маршрут для прокси...");
|
info!("Добавляем статический маршрут для прокси...");
|
||||||
let mut proxy_route = vec!["ip", "route", "add", remote_address, "dev", dev];
|
let mut proxy_route = vec!["ip", "route", "add", remote_address, "dev", dev];
|
||||||
if let Some(gw) = via {
|
if let Some(gw) = via {
|
||||||
@@ -83,13 +81,11 @@ impl Tun {
|
|||||||
warn!("Маршрут к прокси уже существует или возникла ошибка (это нормально).");
|
warn!("Маршрут к прокси уже существует или возникла ошибка (это нормально).");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Удаляем старый default, если он есть
|
|
||||||
info!("Переключаем default маршрут на tun0...");
|
info!("Переключаем default маршрут на tun0...");
|
||||||
let _ = Command::new("sudo")
|
let _ = Command::new("sudo")
|
||||||
.args(&["ip", "route", "del", "default"])
|
.args(&["ip", "route", "del", "default"])
|
||||||
.status();
|
.status();
|
||||||
|
|
||||||
// 4. Устанавливаем tun0 как основной default
|
|
||||||
let status = Command::new("sudo")
|
let status = Command::new("sudo")
|
||||||
.args(&[
|
.args(&[
|
||||||
"ip", "route", "add", "default", "via", "10.0.0.2", "dev", "tun0", "metric", "1",
|
"ip", "route", "add", "default", "via", "10.0.0.2", "dev", "tun0", "metric", "1",
|
||||||
@@ -101,7 +97,6 @@ impl Tun {
|
|||||||
error!("Не удалось установить tun0 как default!");
|
error!("Не удалось установить tun0 как default!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Добавляем резервный маршрут через физический интерфейс
|
|
||||||
if let Some(gw) = via {
|
if let Some(gw) = via {
|
||||||
info!("Добавляем резервный маршрут через {} с метрикой 100", dev);
|
info!("Добавляем резервный маршрут через {} с метрикой 100", dev);
|
||||||
let _ = Command::new("sudo")
|
let _ = Command::new("sudo")
|
||||||
@@ -116,11 +111,8 @@ impl Tun {
|
|||||||
}
|
}
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
pub fn setup_dns_redirection(&self) -> io::Result<()> {
|
pub fn setup_dns_redirection(&self) -> io::Result<()> {
|
||||||
// 1. Создаем временный файл resolv.conf
|
|
||||||
// Мы говорим системе: "Твой DNS-сервер теперь 10.0.0.1" (твой TUN-IP)
|
|
||||||
let _ = std::fs::write("/tmp/resolv.conf.netrunner", "nameserver 10.0.0.2\n");
|
let _ = std::fs::write("/tmp/resolv.conf.netrunner", "nameserver 10.0.0.2\n");
|
||||||
|
|
||||||
// 2. Применяем его (для Linux/systemd)
|
|
||||||
let _ = std::process::Command::new("sudo")
|
let _ = std::process::Command::new("sudo")
|
||||||
.args(&["cp", "/tmp/resolv.conf.netrunner", "/etc/resolv.conf"])
|
.args(&["cp", "/tmp/resolv.conf.netrunner", "/etc/resolv.conf"])
|
||||||
.status();
|
.status();
|
||||||
|
|||||||
@@ -17,12 +17,9 @@ impl NonceState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Возвращает nonce для текущего пакета и ПЕРЕХОДИТ к следующему
|
|
||||||
pub fn next_nonce(&mut self) -> Nonce {
|
pub fn next_nonce(&mut self) -> Nonce {
|
||||||
let mut iv = self.base_iv;
|
let mut iv = self.base_iv;
|
||||||
// В TLS 1.3 используется Little Endian для счетчика при XOR
|
|
||||||
// Но если ты сам пишешь протокол, BE тоже пойдет.
|
|
||||||
// Главное — единообразие.
|
|
||||||
let counter_bytes = self.counter.to_be_bytes();
|
let counter_bytes = self.counter.to_be_bytes();
|
||||||
|
|
||||||
for i in 0..8 {
|
for i in 0..8 {
|
||||||
@@ -54,13 +51,7 @@ impl ChaChaCipher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_keys(
|
pub fn set_keys(&mut self, w_key: [u8; 32], w_iv: [u8; 12], r_key: [u8; 32], r_iv: [u8; 12]) {
|
||||||
&mut self,
|
|
||||||
w_key: [u8; 32],
|
|
||||||
w_iv: [u8; 12], // Write (исходящие)
|
|
||||||
r_key: [u8; 32],
|
|
||||||
r_iv: [u8; 12], // Read (входящие)
|
|
||||||
) {
|
|
||||||
self.encrypt_cipher = ChaCha20Poly1305::new(Key::from_slice(&w_key));
|
self.encrypt_cipher = ChaCha20Poly1305::new(Key::from_slice(&w_key));
|
||||||
self.decrypt_cipher = ChaCha20Poly1305::new(Key::from_slice(&r_key));
|
self.decrypt_cipher = ChaCha20Poly1305::new(Key::from_slice(&r_key));
|
||||||
|
|
||||||
@@ -72,11 +63,10 @@ impl ChaChaCipher {
|
|||||||
}
|
}
|
||||||
impl AeadPacker for ChaChaCipher {
|
impl AeadPacker for ChaChaCipher {
|
||||||
fn encrypt(&mut self, data: &mut BytesMut) -> Result<Bytes, chacha20poly1305::aead::Error> {
|
fn encrypt(&mut self, data: &mut BytesMut) -> Result<Bytes, chacha20poly1305::aead::Error> {
|
||||||
// Сначала получаем текущий counter для лога (до того, как next_nonce его инкрементирует)
|
|
||||||
let current_counter = self.encrypt_state.counter;
|
let current_counter = self.encrypt_state.counter;
|
||||||
let nonce = self.encrypt_state.next_nonce();
|
let nonce = self.encrypt_state.next_nonce();
|
||||||
let data_len = data.len();
|
let data_len = data.len();
|
||||||
// maybe ad should be stream id
|
|
||||||
match self.encrypt_cipher.encrypt_in_place(&nonce, &nonce, data) {
|
match self.encrypt_cipher.encrypt_in_place(&nonce, &nonce, data) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
@@ -104,7 +94,7 @@ impl AeadPacker for ChaChaCipher {
|
|||||||
let current_counter = self.decrypt_state.counter;
|
let current_counter = self.decrypt_state.counter;
|
||||||
let nonce = self.decrypt_state.next_nonce();
|
let nonce = self.decrypt_state.next_nonce();
|
||||||
let data_len = data.len();
|
let data_len = data.len();
|
||||||
// maybe ad should be stream id
|
|
||||||
match self.decrypt_cipher.decrypt_in_place(&nonce, &nonce, data) {
|
match self.decrypt_cipher.decrypt_in_place(&nonce, &nonce, data) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
|
|||||||
@@ -70,9 +70,8 @@ impl SessionKeys {
|
|||||||
&mut self,
|
&mut self,
|
||||||
salt: [u8; 32],
|
salt: [u8; 32],
|
||||||
extensions: &ExtensionStack,
|
extensions: &ExtensionStack,
|
||||||
is_server: bool, // true если мы Сервер (парсим ClientHello), false если Клиент
|
is_server: bool,
|
||||||
) -> Result<([u8; 32], [u8; 12], [u8; 32], [u8; 12]), String> {
|
) -> Result<([u8; 32], [u8; 12], [u8; 32], [u8; 12]), String> {
|
||||||
// Сохраняем соль от удаленной стороны
|
|
||||||
self.salt.set_remote_salt(salt);
|
self.salt.set_remote_salt(salt);
|
||||||
|
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
@@ -88,8 +87,6 @@ impl SessionKeys {
|
|||||||
let mut key_bytes = [0u8; 32];
|
let mut key_bytes = [0u8; 32];
|
||||||
|
|
||||||
if is_server {
|
if is_server {
|
||||||
// МЫ СЕРВЕР: Парсим ClientHello KeyShare (там список)
|
|
||||||
// Минимум: 2 (длина списка) + 2 (группа) + 2 (длина ключа) + 32 (ключ) = 38
|
|
||||||
if dh_data.len() < 38 {
|
if dh_data.len() < 38 {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Client KeyShare too short: {} bytes",
|
"Client KeyShare too short: {} bytes",
|
||||||
@@ -98,7 +95,7 @@ impl SessionKeys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
// Ищем маркер x25519 (00 1d) и длину 32 (00 20)
|
|
||||||
for i in 2..=(dh_data.len() - 34) {
|
for i in 2..=(dh_data.len() - 34) {
|
||||||
if dh_data[i..i + 4] == [0x00, 0x1d, 0x00, 0x20] {
|
if dh_data[i..i + 4] == [0x00, 0x1d, 0x00, 0x20] {
|
||||||
key_bytes.copy_from_slice(&dh_data[i + 4..i + 36]);
|
key_bytes.copy_from_slice(&dh_data[i + 4..i + 36]);
|
||||||
@@ -111,15 +108,12 @@ impl SessionKeys {
|
|||||||
return Err("Could not find x25519 key in ClientHello".into());
|
return Err("Could not find x25519 key in ClientHello".into());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// МЫ КЛИЕНТ: Парсим ServerHello KeyShare (там сразу группа и ключ)
|
|
||||||
// [Group:2] [KeyLen:2] [Key:32] = 36 байт
|
|
||||||
if dh_data.len() < 36 {
|
if dh_data.len() < 36 {
|
||||||
return Err("Server KeyShare too short".into());
|
return Err("Server KeyShare too short".into());
|
||||||
}
|
}
|
||||||
key_bytes.copy_from_slice(&dh_data[4..36]);
|
key_bytes.copy_from_slice(&dh_data[4..36]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверка на "кривой" ключ
|
|
||||||
if key_bytes.iter().all(|&x| x == 0) {
|
if key_bytes.iter().all(|&x| x == 0) {
|
||||||
return Err("Extracted remote public key is all ZEROS!".into());
|
return Err("Extracted remote public key is all ZEROS!".into());
|
||||||
}
|
}
|
||||||
@@ -132,7 +126,6 @@ impl SessionKeys {
|
|||||||
"Key exchange successful, deriving material..."
|
"Key exchange successful, deriving material..."
|
||||||
);
|
);
|
||||||
|
|
||||||
// Вызываем генерацию, которая вернет (w_key, w_iv, r_key, r_iv)
|
|
||||||
self.generate_keys(&public_key, is_server)
|
self.generate_keys(&public_key, is_server)
|
||||||
} else {
|
} else {
|
||||||
Err("No KeyShare extension found in handshake".into())
|
Err("No KeyShare extension found in handshake".into())
|
||||||
@@ -168,11 +161,10 @@ impl SessionKeys {
|
|||||||
"HKDF expansion complete"
|
"HKDF expansion complete"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Распределяем: (write_key, write_iv, read_key, read_iv)
|
|
||||||
if is_server {
|
if is_server {
|
||||||
Ok((s_key, s_iv, c_key, c_iv)) // Сервер пишет своим, читает клиентским
|
Ok((s_key, s_iv, c_key, c_iv))
|
||||||
} else {
|
} else {
|
||||||
Ok((c_key, c_iv, s_key, s_iv)) // Клиент пишет своим, читает серверным
|
Ok((c_key, c_iv, s_key, s_iv))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +183,6 @@ impl SessionKeys {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.as_secs();
|
.as_secs();
|
||||||
|
|
||||||
// Генерируем на основе текущей минуты
|
|
||||||
Self::compute_tag(&self.auth_key, now / 60)
|
Self::compute_tag(&self.auth_key, now / 60)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,11 +194,8 @@ impl SessionKeys {
|
|||||||
|
|
||||||
let current_step = now / 60;
|
let current_step = now / 60;
|
||||||
|
|
||||||
// Вставляем цикл проверки расширенного окна [-2, +2]
|
|
||||||
// Это дает запас по времени в обе стороны
|
|
||||||
for step in (current_step.saturating_sub(2))..=(current_step.saturating_add(2)) {
|
for step in (current_step.saturating_sub(2))..=(current_step.saturating_add(2)) {
|
||||||
if &Self::compute_tag(&self.auth_key, step) == received_tag {
|
if &Self::compute_tag(&self.auth_key, step) == received_tag {
|
||||||
// Если подошел не текущий, а другой шаг — логируем это для диагностики
|
|
||||||
if step != current_step {
|
if step != current_step {
|
||||||
tracing::debug!(expected = %current_step, matched = %step, "Auth tag valid with time offset");
|
tracing::debug!(expected = %current_step, matched = %step, "Auth tag valid with time offset");
|
||||||
}
|
}
|
||||||
@@ -215,7 +203,6 @@ impl SessionKeys {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если ни один не подошел — логируем для отладки
|
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
current_step = %current_step,
|
current_step = %current_step,
|
||||||
"AUTH MISMATCH: All tags rejected for current window"
|
"AUTH MISMATCH: All tags rejected for current window"
|
||||||
|
|||||||
@@ -5,17 +5,13 @@ pub fn logger_init() {
|
|||||||
eprintln!("--- [DEBUG] logger_init start ---");
|
eprintln!("--- [DEBUG] logger_init start ---");
|
||||||
let _ = LogTracer::init();
|
let _ = LogTracer::init();
|
||||||
|
|
||||||
// 4. Фильтр (создаем его один раз для всех)
|
|
||||||
let filter_layer =
|
let filter_layer =
|
||||||
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("trace"));
|
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("trace"));
|
||||||
|
|
||||||
// Инициализируем базовый реестр
|
|
||||||
let registry = tracing_subscriber::registry().with(filter_layer);
|
let registry = tracing_subscriber::registry().with(filter_layer);
|
||||||
|
|
||||||
// 2 & 5. Собираем реестр с учетом платформы
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
{
|
{
|
||||||
// Слой для Android Logcat
|
|
||||||
let android_layer =
|
let android_layer =
|
||||||
tracing_android::layer("NETRUNNER_RUST").expect("Failed to create android layer");
|
tracing_android::layer("NETRUNNER_RUST").expect("Failed to create android layer");
|
||||||
|
|
||||||
@@ -27,7 +23,6 @@ pub fn logger_init() {
|
|||||||
|
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
{
|
{
|
||||||
// 3. Слой для консоли (только для Linux/десктопа)
|
|
||||||
let fmt_layer = fmt::layer()
|
let fmt_layer = fmt::layer()
|
||||||
.with_target(true)
|
.with_target(true)
|
||||||
.with_line_number(true)
|
.with_line_number(true)
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use crate::tlseng::types::{ContentType, HelloType};
|
|||||||
use crate::tlseng::ApplicationData;
|
use crate::tlseng::ApplicationData;
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
|
|
||||||
// --- 1. Общий интерфейс перехвата ---
|
|
||||||
pub trait TlsInterceptor {
|
pub trait TlsInterceptor {
|
||||||
type Output;
|
type Output;
|
||||||
|
|
||||||
@@ -23,7 +22,6 @@ pub trait TlsInterceptor {
|
|||||||
fn handle_record(record: TlsRecord) -> Result<Option<Self::Output>, TlsError>;
|
fn handle_record(record: TlsRecord) -> Result<Option<Self::Output>, TlsError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 2. Обработка Handshake ---
|
|
||||||
pub enum HandshakeMessage {
|
pub enum HandshakeMessage {
|
||||||
Client {
|
Client {
|
||||||
base: ClientHello,
|
base: ClientHello,
|
||||||
@@ -106,7 +104,6 @@ impl TlsInterceptor for HandshakeMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 3. Обработка Application Data ---
|
|
||||||
impl TlsInterceptor for ApplicationData {
|
impl TlsInterceptor for ApplicationData {
|
||||||
type Output = ApplicationData;
|
type Output = ApplicationData;
|
||||||
|
|
||||||
@@ -125,12 +122,9 @@ impl TlsInterceptor for ApplicationData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 4. Высокоуровневый Bridge API ---
|
|
||||||
pub struct TlsBridge;
|
pub struct TlsBridge;
|
||||||
|
|
||||||
impl TlsBridge {
|
impl TlsBridge {
|
||||||
// --- Распаковка (уже была) ---
|
|
||||||
|
|
||||||
pub fn unpack_handshake(buffer: &mut BytesMut) -> Result<Option<HandshakeMessage>, TlsError> {
|
pub fn unpack_handshake(buffer: &mut BytesMut) -> Result<Option<HandshakeMessage>, TlsError> {
|
||||||
HandshakeMessage::start_process(buffer)
|
HandshakeMessage::start_process(buffer)
|
||||||
}
|
}
|
||||||
@@ -139,19 +133,15 @@ impl TlsBridge {
|
|||||||
ApplicationData::start_process(buffer)
|
ApplicationData::start_process(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Запаковка (новое) ---
|
|
||||||
|
|
||||||
/// Создает полный TLS Record с ClientHello внутри
|
|
||||||
pub fn wrap_client_hello(
|
pub fn wrap_client_hello(
|
||||||
profile: &BrowserProfile,
|
profile: &BrowserProfile,
|
||||||
host: &str,
|
host: &str,
|
||||||
public_key: &[u8; 32],
|
public_key: &[u8; 32],
|
||||||
salt: [u8; 32],
|
salt: [u8; 32],
|
||||||
) -> Bytes {
|
) -> Bytes {
|
||||||
ClientHello::make_client_hello(profile, host, public_key, salt) // Передаем ключ дальше
|
ClientHello::make_client_hello(profile, host, public_key, salt)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Создает полный TLS Record с ServerHello, базируясь на данных из HandshakeMessage::Client
|
|
||||||
pub fn wrap_server_hello(
|
pub fn wrap_server_hello(
|
||||||
client_msg: &HandshakeMessage,
|
client_msg: &HandshakeMessage,
|
||||||
server_pub_key: &[u8],
|
server_pub_key: &[u8],
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ impl Codec {
|
|||||||
) -> Result<Bytes, TlsError> {
|
) -> Result<Bytes, TlsError> {
|
||||||
let pub_key = self.session_keys.ecdh.public_key.to_bytes();
|
let pub_key = self.session_keys.ecdh.public_key.to_bytes();
|
||||||
|
|
||||||
// 2. Передаем его в мост
|
|
||||||
Ok(TlsBridge::wrap_client_hello(
|
Ok(TlsBridge::wrap_client_hello(
|
||||||
profile,
|
profile,
|
||||||
host,
|
host,
|
||||||
@@ -69,7 +68,6 @@ impl Codec {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Инициализируем шифратор сервера
|
|
||||||
self.crypto.set_keys(w_key, w_iv, r_key, r_iv);
|
self.crypto.set_keys(w_key, w_iv, r_key, r_iv);
|
||||||
|
|
||||||
Ok(server_hello_record)
|
Ok(server_hello_record)
|
||||||
@@ -85,8 +83,6 @@ impl Codec {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// ОБНОВЛЕНИЕ КЛЮЧЕЙ НА КЛИЕНТЕ
|
|
||||||
// Передаем false, так как клиент ПАРСИТ ServerHello (смещение 4 байта)
|
|
||||||
let (w_key, w_iv, r_key, r_iv) = self
|
let (w_key, w_iv, r_key, r_iv) = self
|
||||||
.session_keys
|
.session_keys
|
||||||
.update_keys(mes.random(), mes.extensions(), false)
|
.update_keys(mes.random(), mes.extensions(), false)
|
||||||
@@ -142,9 +138,6 @@ impl Codec {
|
|||||||
};
|
};
|
||||||
let mut frame_bytes = frame.into_bytes(&tag);
|
let mut frame_bytes = frame.into_bytes(&tag);
|
||||||
|
|
||||||
// ВАЖНО: вызываем шифрование ОДИН РАЗ.
|
|
||||||
// Метод encrypt возвращает Result<Bytes, chacha20poly1305::Error>
|
|
||||||
// Мы вручную превращаем его ошибку в твой TlsError.
|
|
||||||
let encrypted_payload = self.crypto.encrypt(&mut frame_bytes).map_err(|e| {
|
let encrypted_payload = self.crypto.encrypt(&mut frame_bytes).map_err(|e| {
|
||||||
tracing::error!("Encryption failed: {:?}", e);
|
tracing::error!("Encryption failed: {:?}", e);
|
||||||
TlsError::new(
|
TlsError::new(
|
||||||
@@ -154,7 +147,6 @@ impl Codec {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Теперь передаем зашифрованные байты в новый метод упаковки
|
|
||||||
Ok(TlsBridge::pack_app_data(encrypted_payload))
|
Ok(TlsBridge::pack_app_data(encrypted_payload))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,14 +160,12 @@ impl Codec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn inbound(&mut self, buffer: &mut BytesMut) -> Result<Option<Frame>, TlsError> {
|
pub fn inbound(&mut self, buffer: &mut BytesMut) -> Result<Option<Frame>, TlsError> {
|
||||||
// 1. Проверка старых данных
|
|
||||||
if !self.staging.is_empty() {
|
if !self.staging.is_empty() {
|
||||||
if let Some(frame) = self.try_parse_frame()? {
|
if let Some(frame) = self.try_parse_frame()? {
|
||||||
return Ok(Some(frame));
|
return Ok(Some(frame));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Цикл обработки новых рекордов
|
|
||||||
while let Some(app_data) = TlsBridge::unpack_app_data(buffer)? {
|
while let Some(app_data) = TlsBridge::unpack_app_data(buffer)? {
|
||||||
let mut data_to_decrypt = BytesMut::from(app_data.payload);
|
let mut data_to_decrypt = BytesMut::from(app_data.payload);
|
||||||
|
|
||||||
@@ -187,7 +177,6 @@ impl Codec {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// --- КРИТИЧЕСКАЯ ПРОВЕРКА ТЕГА ---
|
|
||||||
if decrypted.len() < 16 {
|
if decrypted.len() < 16 {
|
||||||
return Err(TlsError::new(
|
return Err(TlsError::new(
|
||||||
ErrorStage::Tls("Packet too short for auth"),
|
ErrorStage::Tls("Packet too short for auth"),
|
||||||
@@ -199,7 +188,6 @@ impl Codec {
|
|||||||
let mut received_tag = [0u8; 16];
|
let mut received_tag = [0u8; 16];
|
||||||
received_tag.copy_from_slice(&decrypted[..16]);
|
received_tag.copy_from_slice(&decrypted[..16]);
|
||||||
|
|
||||||
// Используем метод verify_auth_tag, который мы обсуждали ранее
|
|
||||||
if !self.session_keys.verify_auth_tag(&received_tag) {
|
if !self.session_keys.verify_auth_tag(&received_tag) {
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
expected_hash = %hex::encode(&self.session_keys.auth_key[..4]),
|
expected_hash = %hex::encode(&self.session_keys.auth_key[..4]),
|
||||||
@@ -208,11 +196,10 @@ impl Codec {
|
|||||||
);
|
);
|
||||||
return Err(TlsError::new(
|
return Err(TlsError::new(
|
||||||
ErrorStage::Tls("Auth tag mismatch"),
|
ErrorStage::Tls("Auth tag mismatch"),
|
||||||
ErrorAction::Drop, // Убиваем соединение
|
ErrorAction::Drop,
|
||||||
Bytes::new(),
|
Bytes::new(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
// ---------------------------------
|
|
||||||
|
|
||||||
self.staging.extend_from_slice(&decrypted);
|
self.staging.extend_from_slice(&decrypted);
|
||||||
|
|
||||||
@@ -223,7 +210,7 @@ impl Codec {
|
|||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
// Выносим парсинг в отдельный метод, чтобы не дублировать код
|
|
||||||
fn try_parse_frame(&mut self) -> Result<Option<Frame>, TlsError> {
|
fn try_parse_frame(&mut self) -> Result<Option<Frame>, TlsError> {
|
||||||
match Frame::parse(&mut self.staging) {
|
match Frame::parse(&mut self.staging) {
|
||||||
Ok(Some(frame)) => Ok(Some(frame)),
|
Ok(Some(frame)) => Ok(Some(frame)),
|
||||||
|
|||||||
@@ -27,9 +27,7 @@ impl SocksRequest {
|
|||||||
where
|
where
|
||||||
S: tokio::io::AsyncReadExt + tokio::io::AsyncWriteExt + Unpin,
|
S: tokio::io::AsyncReadExt + tokio::io::AsyncWriteExt + Unpin,
|
||||||
{
|
{
|
||||||
// 1. Handshake Phase
|
|
||||||
loop {
|
loop {
|
||||||
// Используем трейт Parser
|
|
||||||
if let Some(req) = Self::parse(buf)? {
|
if let Some(req) = Self::parse(buf)? {
|
||||||
if let SocksRequest::Handshake { .. } = req {
|
if let SocksRequest::Handshake { .. } = req {
|
||||||
let mut reply = BytesMut::with_capacity(2);
|
let mut reply = BytesMut::with_capacity(2);
|
||||||
@@ -44,11 +42,9 @@ impl SocksRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Connect Request Phase
|
|
||||||
loop {
|
loop {
|
||||||
if let Some(req) = Self::parse(buf)? {
|
if let Some(req) = Self::parse(buf)? {
|
||||||
if let SocksRequest::Connect { command, target } = req {
|
if let SocksRequest::Connect { command, target } = req {
|
||||||
// Проверяем, что это именно CONNECT (0x01)
|
|
||||||
if command != 0x01 {
|
if command != 0x01 {
|
||||||
return Err(format!("Unsupported SOCKS command: 0x{:02X}", command));
|
return Err(format!("Unsupported SOCKS command: 0x{:02X}", command));
|
||||||
}
|
}
|
||||||
@@ -68,14 +64,12 @@ impl SocksRequest {
|
|||||||
where
|
where
|
||||||
S: tokio::io::AsyncReadExt + tokio::io::AsyncWriteExt + Unpin,
|
S: tokio::io::AsyncReadExt + tokio::io::AsyncWriteExt + Unpin,
|
||||||
{
|
{
|
||||||
// 1. Отправляем Greeting (SOCKS5, 1 метод: No Auth)
|
|
||||||
let greeting = [SOCKS5_VERSION, 0x01, 0x00];
|
let greeting = [SOCKS5_VERSION, 0x01, 0x00];
|
||||||
stream
|
stream
|
||||||
.write_all(&greeting)
|
.write_all(&greeting)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
// 2. Читаем выбор метода (должно быть 0x05 0x00)
|
|
||||||
let mut method_selection = [0u8; 2];
|
let mut method_selection = [0u8; 2];
|
||||||
stream
|
stream
|
||||||
.read_exact(&mut method_selection)
|
.read_exact(&mut method_selection)
|
||||||
@@ -89,11 +83,10 @@ impl SocksRequest {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Формируем CONNECT запрос
|
|
||||||
let mut connect_req = BytesMut::with_capacity(32);
|
let mut connect_req = BytesMut::with_capacity(32);
|
||||||
connect_req.put_u8(SOCKS5_VERSION);
|
connect_req.put_u8(SOCKS5_VERSION);
|
||||||
connect_req.put_u8(0x01); // CMD: Connect
|
connect_req.put_u8(0x01);
|
||||||
connect_req.put_u8(0x00); // RSV
|
connect_req.put_u8(0x00);
|
||||||
|
|
||||||
match target_addr {
|
match target_addr {
|
||||||
TargetAddress::Ipv4(ip, port) => {
|
TargetAddress::Ipv4(ip, port) => {
|
||||||
@@ -108,7 +101,7 @@ impl SocksRequest {
|
|||||||
}
|
}
|
||||||
TargetAddress::Domain(host, port) => {
|
TargetAddress::Domain(host, port) => {
|
||||||
connect_req.put_u8(ATYP_DOMAIN);
|
connect_req.put_u8(ATYP_DOMAIN);
|
||||||
// SOCKS5 для домена требует: [1 байт длина] + [строка]
|
|
||||||
let host_bytes = host.as_bytes();
|
let host_bytes = host.as_bytes();
|
||||||
connect_req.put_u8(host_bytes.len() as u8);
|
connect_req.put_u8(host_bytes.len() as u8);
|
||||||
connect_req.put_slice(host_bytes);
|
connect_req.put_slice(host_bytes);
|
||||||
@@ -121,8 +114,6 @@ impl SocksRequest {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
// 4. Читаем ответ на Connect (REP)
|
|
||||||
// Нам нужно как минимум 4 байта, чтобы узнать статус (REPLY_SUCCESS)
|
|
||||||
let mut reply_header = [0u8; 4];
|
let mut reply_header = [0u8; 4];
|
||||||
stream
|
stream
|
||||||
.read_exact(&mut reply_header)
|
.read_exact(&mut reply_header)
|
||||||
@@ -136,8 +127,6 @@ impl SocksRequest {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Дочитываем оставшуюся часть адреса в ответе (BND.ADDR + BND.PORT),
|
|
||||||
// чтобы очистить поток перед передачей данных.
|
|
||||||
let atyp = reply_header[3];
|
let atyp = reply_header[3];
|
||||||
let remain_len = match atyp {
|
let remain_len = match atyp {
|
||||||
ATYP_IPV4 => IPV4_SIZE + PORT_SIZE,
|
ATYP_IPV4 => IPV4_SIZE + PORT_SIZE,
|
||||||
@@ -174,9 +163,9 @@ pub enum SocksReply {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum TargetAddress {
|
pub enum TargetAddress {
|
||||||
Ipv4(std::net::Ipv4Addr, u16), // Теперь 2 поля
|
Ipv4(std::net::Ipv4Addr, u16),
|
||||||
Domain(String, u16), // Теперь 2 поля
|
Domain(String, u16),
|
||||||
Ipv6(std::net::Ipv6Addr, u16), // Теперь 2 поля
|
Ipv6(std::net::Ipv6Addr, u16),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -199,7 +188,7 @@ impl SocksReply {
|
|||||||
} => {
|
} => {
|
||||||
buf.put_u8(SOCKS5_VERSION);
|
buf.put_u8(SOCKS5_VERSION);
|
||||||
buf.put_u8(reply_code);
|
buf.put_u8(reply_code);
|
||||||
buf.put_u8(0x00); // Reserved
|
buf.put_u8(0x00);
|
||||||
buf.put_u8(atyp);
|
buf.put_u8(atyp);
|
||||||
buf.put_slice(&addr);
|
buf.put_slice(&addr);
|
||||||
buf.put_u16(port);
|
buf.put_u16(port);
|
||||||
@@ -211,18 +200,15 @@ impl SocksReply {
|
|||||||
impl SocksTarget {
|
impl SocksTarget {
|
||||||
pub fn to_string(&self) -> String {
|
pub fn to_string(&self) -> String {
|
||||||
match &self.addr {
|
match &self.addr {
|
||||||
// Теперь в каждом варианте TargetAddress уже есть порт (port)
|
|
||||||
TargetAddress::Ipv4(ip, port) => {
|
TargetAddress::Ipv4(ip, port) => {
|
||||||
format!("{}:{}", ip, port)
|
format!("{}:{}", ip, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
TargetAddress::Ipv6(ip, port) => {
|
TargetAddress::Ipv6(ip, port) => {
|
||||||
// IPv6 адреса принято заключать в квадратные скобки при наличии порта
|
|
||||||
format!("[{}]:{}", ip, port)
|
format!("[{}]:{}", ip, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
TargetAddress::Domain(domain, port) => {
|
TargetAddress::Domain(domain, port) => {
|
||||||
// Вычищаем нулевые байты, если они случайно попали в строку
|
|
||||||
let clean_domain = domain.replace('\0', "");
|
let clean_domain = domain.replace('\0', "");
|
||||||
format!("{}:{}", clean_domain, port)
|
format!("{}:{}", clean_domain, port)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,10 +32,6 @@ impl TlsError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn log_error(&self) {
|
fn log_error(&self) {
|
||||||
// Определяем уровень логирования в зависимости от действия
|
|
||||||
// Если мы просто ждем данные (Wait) — это не ошибка, а рабочий процесс (debug/trace)
|
|
||||||
// Если дропаем соединение (Drop) — это серьезно (error)
|
|
||||||
|
|
||||||
let stage_name = match &self.stage {
|
let stage_name = match &self.stage {
|
||||||
ErrorStage::Tls(_) => "TLS",
|
ErrorStage::Tls(_) => "TLS",
|
||||||
ErrorStage::Handshake(_) => "Handshake",
|
ErrorStage::Handshake(_) => "Handshake",
|
||||||
@@ -46,7 +42,6 @@ impl TlsError {
|
|||||||
ErrorStage::Tls(m) | ErrorStage::Handshake(m) | ErrorStage::ApplicationData(m) => m,
|
ErrorStage::Tls(m) | ErrorStage::Handshake(m) | ErrorStage::ApplicationData(m) => m,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Подготавливаем превью данных (первые 8 байт в хексе)
|
|
||||||
let data_preview = if !self.data.is_empty() {
|
let data_preview = if !self.data.is_empty() {
|
||||||
let limit = self.data.len().min(8);
|
let limit = self.data.len().min(8);
|
||||||
format!(
|
format!(
|
||||||
@@ -60,7 +55,6 @@ impl TlsError {
|
|||||||
|
|
||||||
match self.action {
|
match self.action {
|
||||||
ErrorAction::Wait => {
|
ErrorAction::Wait => {
|
||||||
// Wait — это нормальное состояние асинхронного чтения
|
|
||||||
trace!(
|
trace!(
|
||||||
stage = stage_name,
|
stage = stage_name,
|
||||||
action = ?self.action,
|
action = ?self.action,
|
||||||
|
|||||||
@@ -50,13 +50,10 @@ impl Parser for Frame {
|
|||||||
type Error = String;
|
type Error = String;
|
||||||
|
|
||||||
fn can_parse(bytes: &BytesMut) -> bool {
|
fn can_parse(bytes: &BytesMut) -> bool {
|
||||||
// 1. Сначала проверяем, есть ли хотя бы заголовок
|
|
||||||
if bytes.len() < FRAME_HEADER_SIZE as usize {
|
if bytes.len() < FRAME_HEADER_SIZE as usize {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Извлекаем длины из заголовка (БЕЗ удаления байтов из буфера)
|
|
||||||
// По твоей структуре: Auth(16) + Stream(4) + Type(1) = 21 байт смещения
|
|
||||||
let p_len = u16::from_be_bytes([bytes[21], bytes[22]]) as usize;
|
let p_len = u16::from_be_bytes([bytes[21], bytes[22]]) as usize;
|
||||||
let pad_len = u16::from_be_bytes([bytes[23], bytes[24]]) as usize;
|
let pad_len = u16::from_be_bytes([bytes[23], bytes[24]]) as usize;
|
||||||
|
|
||||||
@@ -68,7 +65,6 @@ impl Parser for Frame {
|
|||||||
bytes.len()
|
bytes.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
// 3. Проверяем, есть ли в буфере весь фрейм целиком
|
|
||||||
bytes.len() >= (FRAME_HEADER_SIZE as usize + p_len + pad_len)
|
bytes.len() >= (FRAME_HEADER_SIZE as usize + p_len + pad_len)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,13 +73,11 @@ impl Parser for Frame {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Извлекаем заголовок (теперь split_to удалит эти байты из начала bytes)
|
|
||||||
let header = FrameHeader::parse(bytes)?.ok_or("Failed to parse header")?;
|
let header = FrameHeader::parse(bytes)?.ok_or("Failed to parse header")?;
|
||||||
|
|
||||||
let p_len = header.payload_len as usize;
|
let p_len = header.payload_len as usize;
|
||||||
let pad_len = header.padding_len as usize;
|
let pad_len = header.padding_len as usize;
|
||||||
|
|
||||||
// Теперь байты заголовка уже удалены, и в начале 'bytes' лежит Payload
|
|
||||||
if bytes.len() < p_len + pad_len {
|
if bytes.len() < p_len + pad_len {
|
||||||
return Err("Buffer corrupted: length mismatch after header parse".into());
|
return Err("Buffer corrupted: length mismatch after header parse".into());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +6,16 @@ impl Parser for SocksTarget {
|
|||||||
type Error = String;
|
type Error = String;
|
||||||
|
|
||||||
fn can_parse(bytes: &BytesMut) -> bool {
|
fn can_parse(bytes: &BytesMut) -> bool {
|
||||||
// Минимальная длина SOCKS5 CONNECT (VER, CMD, RSV, ATYP) = 4 байта
|
|
||||||
if bytes.len() < 4 {
|
if bytes.len() < 4 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let atyp = bytes[3];
|
let atyp = bytes[3];
|
||||||
match atyp {
|
match atyp {
|
||||||
// IPv4: 4 байта IP + 2 байта порт
|
|
||||||
ATYP_IPV4 => bytes.len() >= 4 + 4 + 2,
|
ATYP_IPV4 => bytes.len() >= 4 + 4 + 2,
|
||||||
// IPv6: 16 байт IP + 2 байта порт
|
|
||||||
ATYP_IPV6 => bytes.len() >= 4 + 16 + 2,
|
ATYP_IPV6 => bytes.len() >= 4 + 16 + 2,
|
||||||
// Domain: 1 байт длины + N байт домена + 2 байта порт
|
|
||||||
ATYP_DOMAIN => {
|
ATYP_DOMAIN => {
|
||||||
if bytes.len() < 5 {
|
if bytes.len() < 5 {
|
||||||
return false;
|
return false;
|
||||||
@@ -35,7 +33,7 @@ impl Parser for SocksTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let atyp = bytes[3];
|
let atyp = bytes[3];
|
||||||
// Вычисляем длину (включая ATYP и порт)
|
|
||||||
let total_len = match atyp {
|
let total_len = match atyp {
|
||||||
ATYP_IPV4 => SOCKS5_MIN_HEADER + IPV4_SIZE + PORT_SIZE,
|
ATYP_IPV4 => SOCKS5_MIN_HEADER + IPV4_SIZE + PORT_SIZE,
|
||||||
ATYP_DOMAIN => SOCKS5_MIN_HEADER + 1 + (bytes[4] as usize) + PORT_SIZE,
|
ATYP_DOMAIN => SOCKS5_MIN_HEADER + 1 + (bytes[4] as usize) + PORT_SIZE,
|
||||||
@@ -44,12 +42,12 @@ impl Parser for SocksTarget {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut packet = bytes.split_to(total_len);
|
let mut packet = bytes.split_to(total_len);
|
||||||
packet.advance(4); // Пропускаем [VER, CMD, RSV, ATYP]
|
packet.advance(4);
|
||||||
|
|
||||||
let addr = match atyp {
|
let addr = match atyp {
|
||||||
ATYP_IPV4 => {
|
ATYP_IPV4 => {
|
||||||
let octets: [u8; 4] = packet.split_to(4)[..].try_into().unwrap();
|
let octets: [u8; 4] = packet.split_to(4)[..].try_into().unwrap();
|
||||||
let port = packet.get_u16(); // Достаем порт сразу
|
let port = packet.get_u16();
|
||||||
TargetAddress::Ipv4(octets.into(), port)
|
TargetAddress::Ipv4(octets.into(), port)
|
||||||
}
|
}
|
||||||
ATYP_DOMAIN => {
|
ATYP_DOMAIN => {
|
||||||
@@ -57,18 +55,17 @@ impl Parser for SocksTarget {
|
|||||||
let domain_bytes = packet.split_to(len);
|
let domain_bytes = packet.split_to(len);
|
||||||
let domain = String::from_utf8(domain_bytes.to_vec())
|
let domain = String::from_utf8(domain_bytes.to_vec())
|
||||||
.map_err(|_| "Invalid UTF-8 domain".to_string())?;
|
.map_err(|_| "Invalid UTF-8 domain".to_string())?;
|
||||||
let port = packet.get_u16(); // Достаем порт сразу
|
let port = packet.get_u16();
|
||||||
TargetAddress::Domain(domain, port)
|
TargetAddress::Domain(domain, port)
|
||||||
}
|
}
|
||||||
ATYP_IPV6 => {
|
ATYP_IPV6 => {
|
||||||
let octets: [u8; 16] = packet.split_to(16)[..].try_into().unwrap();
|
let octets: [u8; 16] = packet.split_to(16)[..].try_into().unwrap();
|
||||||
let port = packet.get_u16(); // Достаем порт сразу
|
let port = packet.get_u16();
|
||||||
TargetAddress::Ipv6(octets.into(), port)
|
TargetAddress::Ipv6(octets.into(), port)
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Теперь SocksTarget — это просто оболочка над новым TargetAddress
|
|
||||||
Ok(Some(SocksTarget { addr }))
|
Ok(Some(SocksTarget { addr }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,12 +80,10 @@ impl Parser for SocksRequest {
|
|||||||
|
|
||||||
let nmethods = bytes[1] as usize;
|
let nmethods = bytes[1] as usize;
|
||||||
if bytes.len() >= 2 + nmethods {
|
if bytes.len() >= 2 + nmethods {
|
||||||
// Это может быть Handshake. Проверяем, не Connect ли это (мин. 6-10 байт)
|
|
||||||
if bytes.len() >= SOCKS5_MIN_HEADER && SocksTarget::can_parse(bytes) {
|
if bytes.len() >= SOCKS5_MIN_HEADER && SocksTarget::can_parse(bytes) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Если для Connect данных мало или структура не совпадает,
|
|
||||||
// но для Handshake достаточно — ок.
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +95,6 @@ impl Parser for SocksRequest {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Пытаемся распарсить как Connect (у него строгая структура)
|
|
||||||
if bytes.len() >= SOCKS5_MIN_HEADER && SocksTarget::can_parse(bytes) {
|
if bytes.len() >= SOCKS5_MIN_HEADER && SocksTarget::can_parse(bytes) {
|
||||||
let command = bytes[1];
|
let command = bytes[1];
|
||||||
if let Some(target) = SocksTarget::parse(bytes)? {
|
if let Some(target) = SocksTarget::parse(bytes)? {
|
||||||
@@ -108,7 +102,6 @@ impl Parser for SocksRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Если не Connect, пробуем Handshake
|
|
||||||
let nmethods = bytes[1] as usize;
|
let nmethods = bytes[1] as usize;
|
||||||
let total_handshake = 2 + nmethods;
|
let total_handshake = 2 + nmethods;
|
||||||
|
|
||||||
|
|||||||
@@ -14,20 +14,14 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use bytes::{Buf, Bytes, BytesMut};
|
use bytes::{Buf, Bytes, BytesMut};
|
||||||
|
|
||||||
// =================================================================
|
|
||||||
// 1. RECORD LAYER
|
|
||||||
// =================================================================
|
|
||||||
|
|
||||||
impl Parser for TlsRecord {
|
impl Parser for TlsRecord {
|
||||||
type Error = TlsError;
|
type Error = TlsError;
|
||||||
|
|
||||||
fn can_parse(bytes: &BytesMut) -> bool {
|
fn can_parse(bytes: &BytesMut) -> bool {
|
||||||
// 1. Минимум 5 байт для заголовка
|
|
||||||
if bytes.len() < 5 {
|
if bytes.len() < 5 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Проверяем ContentType
|
|
||||||
let content_type = bytes[0];
|
let content_type = bytes[0];
|
||||||
let is_valid_type = content_type == ContentType::Handshake as u8
|
let is_valid_type = content_type == ContentType::Handshake as u8
|
||||||
|| content_type == ContentType::ApplicationData as u8
|
|| content_type == ContentType::ApplicationData as u8
|
||||||
@@ -37,21 +31,14 @@ impl Parser for TlsRecord {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Извлекаем заявленную длину тела рекорда
|
|
||||||
let record_len = u16::from_be_bytes([bytes[3], bytes[4]]) as usize;
|
let record_len = u16::from_be_bytes([bytes[3], bytes[4]]) as usize;
|
||||||
|
|
||||||
// 4. Специфика TLS 1.3:
|
|
||||||
// Если это зашифрованные данные (0x17), они ДОЛЖНЫ содержать тег (16 байт)
|
|
||||||
// + как минимум 1 байт зашифрованного типа контента.
|
|
||||||
if content_type == ContentType::ApplicationData as u8 {
|
if content_type == ContentType::ApplicationData as u8 {
|
||||||
if record_len < 17 {
|
if record_len < 17 {
|
||||||
// Если длина < 17, это либо неполный пакет, либо ошибка протокола.
|
|
||||||
// Возвращаем false, чтобы подождать еще данных из сокета.
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Ждем, пока в буфере будет заголовок + всё тело
|
|
||||||
bytes.len() >= 5 + record_len
|
bytes.len() >= 5 + record_len
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +47,6 @@ impl Parser for TlsRecord {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- ТОЛЬКО ТЕПЕРЬ МЫ ИЗМЕНЯЕМ БУФЕР ---
|
|
||||||
let raw_content_type = bytes.get_u8();
|
let raw_content_type = bytes.get_u8();
|
||||||
let raw_version = bytes.get_u16();
|
let raw_version = bytes.get_u16();
|
||||||
let record_len = bytes.get_u16() as usize;
|
let record_len = bytes.get_u16() as usize;
|
||||||
@@ -71,17 +57,12 @@ impl Parser for TlsRecord {
|
|||||||
let version = ProtocolVersion::try_from(raw_version)
|
let version = ProtocolVersion::try_from(raw_version)
|
||||||
.map_err(|e| TlsError::new(ErrorStage::Tls(e), ErrorAction::Drop, Bytes::new()))?;
|
.map_err(|e| TlsError::new(ErrorStage::Tls(e), ErrorAction::Drop, Bytes::new()))?;
|
||||||
|
|
||||||
// Забираем ровно столько, сколько указано в заголовке
|
|
||||||
let payload = bytes.split_to(record_len).freeze();
|
let payload = bytes.split_to(record_len).freeze();
|
||||||
|
|
||||||
Ok(Some(TlsRecord::new(content_type, version, payload)))
|
Ok(Some(TlsRecord::new(content_type, version, payload)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =================================================================
|
|
||||||
// 2. PAYLOAD TYPES
|
|
||||||
// =================================================================
|
|
||||||
|
|
||||||
impl Parser for ApplicationData {
|
impl Parser for ApplicationData {
|
||||||
type Error = TlsError;
|
type Error = TlsError;
|
||||||
|
|
||||||
@@ -128,15 +109,11 @@ impl Parser for HelloHeader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =================================================================
|
|
||||||
// 3. HELLO MESSAGES
|
|
||||||
// =================================================================
|
|
||||||
|
|
||||||
impl Parser for ClientHello {
|
impl Parser for ClientHello {
|
||||||
type Error = TlsError;
|
type Error = TlsError;
|
||||||
|
|
||||||
fn can_parse(bytes: &BytesMut) -> bool {
|
fn can_parse(bytes: &BytesMut) -> bool {
|
||||||
let mut offset = 34; // ProtocolVersion (2) + Random (32)
|
let mut offset = 34;
|
||||||
if bytes.len() < offset + 1 {
|
if bytes.len() < offset + 1 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -165,8 +142,7 @@ impl Parser for ClientHello {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||||
// --- ШАГ 1: Атомарная проверка всего пакета ---
|
let mut offset = 34;
|
||||||
let mut offset = 34; // Version + Random
|
|
||||||
if bytes.len() < offset + 1 {
|
if bytes.len() < offset + 1 {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
@@ -190,13 +166,10 @@ impl Parser for ClientHello {
|
|||||||
offset += 2 + ext_len;
|
offset += 2 + ext_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если нам не хватает данных для полного ClientHello, выходим, не трогая буфер
|
|
||||||
if bytes.len() < offset {
|
if bytes.len() < offset {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- ШАГ 2: Безопасное чтение ---
|
|
||||||
// Изолируем ровно тот кусок, который проверили.
|
|
||||||
let mut msg = bytes.split_to(offset);
|
let mut msg = bytes.split_to(offset);
|
||||||
|
|
||||||
let version = ProtocolVersion::try_from(msg.get_u16())
|
let version = ProtocolVersion::try_from(msg.get_u16())
|
||||||
@@ -216,7 +189,7 @@ impl Parser for ClientHello {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let cmp_len = msg.get_u8() as usize;
|
let cmp_len = msg.get_u8() as usize;
|
||||||
msg.advance(cmp_len); // пропускаем методы сжатия
|
msg.advance(cmp_len);
|
||||||
|
|
||||||
let extensions = if msg.remaining() >= 2 {
|
let extensions = if msg.remaining() >= 2 {
|
||||||
let ext_len = msg.get_u16() as usize;
|
let ext_len = msg.get_u16() as usize;
|
||||||
@@ -239,7 +212,7 @@ impl Parser for ServerHello {
|
|||||||
type Error = TlsError;
|
type Error = TlsError;
|
||||||
|
|
||||||
fn can_parse(bytes: &BytesMut) -> bool {
|
fn can_parse(bytes: &BytesMut) -> bool {
|
||||||
let mut offset = 34; // ProtocolVersion (2) + Random (32)
|
let mut offset = 34;
|
||||||
if bytes.len() < offset + 1 {
|
if bytes.len() < offset + 1 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -247,7 +220,6 @@ impl Parser for ServerHello {
|
|||||||
let session_id_len = bytes[offset] as usize;
|
let session_id_len = bytes[offset] as usize;
|
||||||
offset += 1 + session_id_len;
|
offset += 1 + session_id_len;
|
||||||
|
|
||||||
// Cipher suite (2) + Compression (1)
|
|
||||||
offset += 3;
|
offset += 3;
|
||||||
|
|
||||||
if bytes.len() >= offset + 2 {
|
if bytes.len() >= offset + 2 {
|
||||||
@@ -259,15 +231,14 @@ impl Parser for ServerHello {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse(bytes: &mut bytes::BytesMut) -> Result<Option<Self>, Self::Error> {
|
fn parse(bytes: &mut bytes::BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||||
// --- ШАГ 1: Атомарная проверка всего пакета ---
|
let mut offset = 34;
|
||||||
let mut offset = 34; // Version + Random
|
|
||||||
if bytes.len() < offset + 1 {
|
if bytes.len() < offset + 1 {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
let session_id_len = bytes[offset] as usize;
|
let session_id_len = bytes[offset] as usize;
|
||||||
offset += 1 + session_id_len;
|
offset += 1 + session_id_len;
|
||||||
|
|
||||||
offset += 3; // Cipher Suite (2) + Compression (1)
|
offset += 3;
|
||||||
|
|
||||||
if bytes.len() >= offset + 2 {
|
if bytes.len() >= offset + 2 {
|
||||||
let ext_len = u16::from_be_bytes([bytes[offset], bytes[offset + 1]]) as usize;
|
let ext_len = u16::from_be_bytes([bytes[offset], bytes[offset + 1]]) as usize;
|
||||||
@@ -278,7 +249,6 @@ impl Parser for ServerHello {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- ШАГ 2: Безопасное чтение ---
|
|
||||||
let mut msg = bytes.split_to(offset);
|
let mut msg = bytes.split_to(offset);
|
||||||
|
|
||||||
let version = ProtocolVersion::try_from(msg.get_u16())
|
let version = ProtocolVersion::try_from(msg.get_u16())
|
||||||
@@ -291,7 +261,7 @@ impl Parser for ServerHello {
|
|||||||
let session_id = msg.split_to(sid_len).freeze();
|
let session_id = msg.split_to(sid_len).freeze();
|
||||||
|
|
||||||
let cipher_suite = msg.get_u16();
|
let cipher_suite = msg.get_u16();
|
||||||
msg.advance(1); // compression
|
msg.advance(1);
|
||||||
|
|
||||||
let extensions = if msg.remaining() >= 2 {
|
let extensions = if msg.remaining() >= 2 {
|
||||||
let ext_len = msg.get_u16() as usize;
|
let ext_len = msg.get_u16() as usize;
|
||||||
@@ -310,10 +280,6 @@ impl Parser for ServerHello {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =================================================================
|
|
||||||
// 4. EXTENSIONS
|
|
||||||
// =================================================================
|
|
||||||
|
|
||||||
impl Parser for ExtensionStack {
|
impl Parser for ExtensionStack {
|
||||||
type Error = TlsError;
|
type Error = TlsError;
|
||||||
|
|
||||||
@@ -330,7 +296,6 @@ impl Parser for ExtensionStack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||||
// Проверяем на целостность всех расширений
|
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
let data_len = bytes.len();
|
let data_len = bytes.len();
|
||||||
|
|
||||||
@@ -339,13 +304,10 @@ impl Parser for ExtensionStack {
|
|||||||
offset += 4 + elen;
|
offset += 4 + elen;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если offset > data_len, значит кусок данных расширения отсечен, ждем еще данных
|
|
||||||
if offset > data_len {
|
if offset > data_len {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если offset < data_len, есть лишние байты (Trailing garbage). Но так как мы
|
|
||||||
// обычно передаем сюда точный срез `extensions`, это может быть ошибкой формата.
|
|
||||||
if offset != data_len {
|
if offset != data_len {
|
||||||
return Err(TlsError::new(
|
return Err(TlsError::new(
|
||||||
ErrorStage::Tls("Malformed extension stack: trailing data"),
|
ErrorStage::Tls("Malformed extension stack: trailing data"),
|
||||||
@@ -354,7 +316,6 @@ impl Parser for ExtensionStack {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Теперь гарантированно безопасно парсить всё до конца
|
|
||||||
let mut extensions = Vec::new();
|
let mut extensions = Vec::new();
|
||||||
while bytes.remaining() >= 4 {
|
while bytes.remaining() >= 4 {
|
||||||
let etype = bytes.get_u16();
|
let etype = bytes.get_u16();
|
||||||
|
|||||||
@@ -38,11 +38,11 @@ pub async fn run_proxy_bridge<R, W>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Читаем из туннеля (v_rx) -> шлем в сокет
|
|
||||||
maybe_data = v_rx.recv() => {
|
maybe_data = v_rx.recv() => {
|
||||||
match maybe_data {
|
match maybe_data {
|
||||||
Some(data) => {
|
Some(data) => {
|
||||||
if data.is_empty() { break; } // EOF от другой стороны
|
if data.is_empty() { break; }
|
||||||
if let Err(e) = writer.write_all(&data).await {
|
if let Err(e) = writer.write_all(&data).await {
|
||||||
error!(stream_id, error = %e, "Socket write error");
|
error!(stream_id, error = %e, "Socket write error");
|
||||||
break;
|
break;
|
||||||
@@ -57,7 +57,6 @@ pub async fn run_proxy_bridge<R, W>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Финализация (общая для всех)
|
|
||||||
let _ = muxer
|
let _ = muxer
|
||||||
.to_network
|
.to_network
|
||||||
.send(MuxMessage {
|
.send(MuxMessage {
|
||||||
|
|||||||
@@ -58,18 +58,18 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Читает и парсит запрос SOCKS5 из входящего потока
|
|
||||||
async fn read_socks_request(&mut self) -> Result<SocksRequest, String> {
|
async fn read_socks_request(&mut self) -> Result<SocksRequest, String> {
|
||||||
loop {
|
loop {
|
||||||
// Попытка парсинга из текущего буфера
|
|
||||||
match SocksRequest::parse(&mut self.read_buf) {
|
match SocksRequest::parse(&mut self.read_buf) {
|
||||||
Ok(Some(req)) => {
|
Ok(Some(req)) => {
|
||||||
// Используем Debug-вывод (?req), так как SocksRequest обычно Enum
|
|
||||||
info!(client = %self.addr, request = ?req, "SOCKS request successfully parsed");
|
info!(client = %self.addr, request = ?req, "SOCKS request successfully parsed");
|
||||||
return Ok(req);
|
return Ok(req);
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
// Это не ошибка, просто данных в сокете пока меньше, чем размер структуры SOCKS
|
|
||||||
trace!(client = %self.addr, buffer_len = self.read_buf.len(), "SOCKS parse: need more data");
|
trace!(client = %self.addr, buffer_len = self.read_buf.len(), "SOCKS parse: need more data");
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -78,7 +78,7 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Чтение новых данных из сокета
|
|
||||||
let n = self
|
let n = self
|
||||||
.inbound
|
.inbound
|
||||||
.read_buf(&mut self.read_buf)
|
.read_buf(&mut self.read_buf)
|
||||||
@@ -97,7 +97,7 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Отправляет SOCKS ответ
|
|
||||||
async fn send_socks_reply(&mut self, reply: SocksReply) -> Result<(), String> {
|
async fn send_socks_reply(&mut self, reply: SocksReply) -> Result<(), String> {
|
||||||
let mut buf = BytesMut::with_capacity(24);
|
let mut buf = BytesMut::with_capacity(24);
|
||||||
debug!(client = %self.addr, reply = ?reply, "Sending SOCKS reply to client");
|
debug!(client = %self.addr, reply = ?reply, "Sending SOCKS reply to client");
|
||||||
@@ -124,7 +124,7 @@ impl Connection {
|
|||||||
pub async fn handle_socks_client(mut self, muxer: Muxer) -> Result<(), String> {
|
pub async fn handle_socks_client(mut self, muxer: Muxer) -> Result<(), String> {
|
||||||
info!("Starting SOCKS multiplexed handling");
|
info!("Starting SOCKS multiplexed handling");
|
||||||
|
|
||||||
// 1. SOCKS Handshake
|
|
||||||
debug!("Reading SOCKS handshake request");
|
debug!("Reading SOCKS handshake request");
|
||||||
let _ = self.read_socks_request().await.map_err(|e| {
|
let _ = self.read_socks_request().await.map_err(|e| {
|
||||||
error!("SOCKS handshake failed: {}", e);
|
error!("SOCKS handshake failed: {}", e);
|
||||||
@@ -133,8 +133,8 @@ impl Connection {
|
|||||||
|
|
||||||
self.send_socks_reply(SocksReply::HandshakeSelect { method: 0x00 }).await?;
|
self.send_socks_reply(SocksReply::HandshakeSelect { method: 0x00 }).await?;
|
||||||
|
|
||||||
// 2. SOCKS Connect
|
|
||||||
// 2. SOCKS Connect - читаем, КУДА хочет браузер
|
|
||||||
let req = self.read_socks_request().await?;
|
let req = self.read_socks_request().await?;
|
||||||
let target = if let SocksRequest::Connect { target, .. } = req {
|
let target = if let SocksRequest::Connect { target, .. } = req {
|
||||||
target
|
target
|
||||||
@@ -145,12 +145,12 @@ impl Connection {
|
|||||||
let stream_id = muxer.next_id();
|
let stream_id = muxer.next_id();
|
||||||
let target_str = target.to_string();
|
let target_str = target.to_string();
|
||||||
|
|
||||||
// --- НОВАЯ ЛОГИКА ОЖИДАНИЯ ---
|
|
||||||
// Регистрируем временный канал, чтобы получить Connect-подтверждение от сервера
|
|
||||||
let (v_tx, mut v_rx) = mpsc::channel::<Bytes>(1024);
|
let (v_tx, mut v_rx) = mpsc::channel::<Bytes>(1024);
|
||||||
muxer.register_stream(stream_id, v_tx).await;
|
muxer.register_stream(stream_id, v_tx).await;
|
||||||
|
|
||||||
// Отправляем Connect-кадр на сервер
|
|
||||||
muxer.to_network.send(MuxMessage {
|
muxer.to_network.send(MuxMessage {
|
||||||
stream_id,
|
stream_id,
|
||||||
frame_type: FrameType::Connect,
|
frame_type: FrameType::Connect,
|
||||||
@@ -161,7 +161,7 @@ impl Connection {
|
|||||||
Ok(Some(data)) => data,
|
Ok(Some(data)) => data,
|
||||||
_ => {
|
_ => {
|
||||||
error!(stream_id, "Server timeout or failed to send Connect confirmation");
|
error!(stream_id, "Server timeout or failed to send Connect confirmation");
|
||||||
// Шлем браузеру ошибку, если сервер промолчал
|
|
||||||
self.send_socks_reply(SocksReply::ConnectResult {
|
self.send_socks_reply(SocksReply::ConnectResult {
|
||||||
reply_code: 0x01, atyp: 0x01, addr: [0, 0, 0, 0], port: 0,
|
reply_code: 0x01, atyp: 0x01, addr: [0, 0, 0, 0], port: 0,
|
||||||
}).await.ok();
|
}).await.ok();
|
||||||
@@ -169,20 +169,20 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Проверяем код ответа (второй байт в SOCKS5)
|
|
||||||
if first_payload.len() >= 2 && first_payload[1] == 0x00 {
|
if first_payload.len() >= 2 && first_payload[1] == 0x00 {
|
||||||
debug!(stream_id, "Server confirmed connection, forwarding SOCKS reply to browser");
|
debug!(stream_id, "Server confirmed connection, forwarding SOCKS reply to browser");
|
||||||
|
|
||||||
// ВАЖНО: Отправляем браузеру ТО, что прислал сервер (те самые 10 байт)
|
|
||||||
// Не создаем новый SocksReply вручную, а пробрасываем байты сервера
|
|
||||||
self.outbound.write_all(&first_payload).await.map_err(|e| e.to_string())?;
|
self.outbound.write_all(&first_payload).await.map_err(|e| e.to_string())?;
|
||||||
} else {
|
} else {
|
||||||
// Если сервер прислал ошибку (reply_code != 0), тоже пробрасываем её браузеру и выходим
|
|
||||||
self.outbound.write_all(&first_payload).await.ok();
|
self.outbound.write_all(&first_payload).await.ok();
|
||||||
return Err("Server rejected connection".into());
|
return Err("Server rejected connection".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Разбираем self и запускаем хендлер
|
|
||||||
let Self { inbound: browser_in, outbound: browser_out, .. } = self;
|
let Self { inbound: browser_in, outbound: browser_out, .. } = self;
|
||||||
|
|
||||||
let muxer_clone = muxer.clone();
|
let muxer_clone = muxer.clone();
|
||||||
@@ -201,11 +201,11 @@ impl Connection {
|
|||||||
pub async fn handle_server_tunnel(mut self) -> Result<(), String> {
|
pub async fn handle_server_tunnel(mut self) -> Result<(), String> {
|
||||||
info!("Acting as TLS Server, waiting for ClientHello");
|
info!("Acting as TLS Server, waiting for ClientHello");
|
||||||
|
|
||||||
// Создаем Muxer для сервера
|
|
||||||
let (mux_tx, mux_rx) = mpsc::channel(BUF_SIZE);
|
|
||||||
let muxer = Muxer::new(mux_tx.clone(), false); // false, так как это Сервер
|
|
||||||
|
|
||||||
// 1. TLS Handshake
|
let (mux_tx, mux_rx) = mpsc::channel(BUF_SIZE);
|
||||||
|
let muxer = Muxer::new(mux_tx.clone(), false);
|
||||||
|
|
||||||
|
|
||||||
let server_hello_bytes = loop {
|
let server_hello_bytes = loop {
|
||||||
match self.codec.make_server_handshake(&mut self.read_buf) {
|
match self.codec.make_server_handshake(&mut self.read_buf) {
|
||||||
Ok(bytes) => {
|
Ok(bytes) => {
|
||||||
@@ -227,7 +227,7 @@ impl Connection {
|
|||||||
|
|
||||||
let handler = std::sync::Arc::new(StreamHandler::new(muxer.clone(), ConnectionRole::Server));
|
let handler = std::sync::Arc::new(StreamHandler::new(muxer.clone(), ConnectionRole::Server));
|
||||||
|
|
||||||
// 2. Передача управления в TunnelEngine
|
|
||||||
debug!("Handover to TunnelEngine");
|
debug!("Handover to TunnelEngine");
|
||||||
let engine = TunnelEngine {
|
let engine = TunnelEngine {
|
||||||
inbound: self.inbound,
|
inbound: self.inbound,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ pub struct TunnelEngine {
|
|||||||
pub codec: Codec,
|
pub codec: Codec,
|
||||||
pub read_buf: BytesMut,
|
pub read_buf: BytesMut,
|
||||||
pub mux_rx: Receiver<MuxMessage>,
|
pub mux_rx: Receiver<MuxMessage>,
|
||||||
pub handler: Arc<StreamHandler>, // Добавь это вместо прямого вызова логики
|
pub handler: Arc<StreamHandler>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TunnelEngine {
|
impl TunnelEngine {
|
||||||
@@ -36,7 +36,7 @@ impl TunnelEngine {
|
|||||||
res?
|
res?
|
||||||
}
|
}
|
||||||
|
|
||||||
// НУЖНО ОТПРАВИТЬ В СЕТЬ (В сторону удаленного прокси)
|
|
||||||
Some(msg) = mux_rx.recv() => {
|
Some(msg) = mux_rx.recv() => {
|
||||||
Self::handle_outbound( &mut outbound, &mut codec, msg).await?;
|
Self::handle_outbound( &mut outbound, &mut codec, msg).await?;
|
||||||
}
|
}
|
||||||
@@ -61,20 +61,17 @@ impl TunnelEngine {
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
match codec.inbound(read_buf) {
|
match codec.inbound(read_buf) {
|
||||||
// 1. Успешно достали фрейм
|
|
||||||
Ok(Some(frame)) => {
|
Ok(Some(frame)) => {
|
||||||
handler.handle(frame).await;
|
handler.handle(frame).await;
|
||||||
}
|
}
|
||||||
// 2. Данных в буфере недостаточно (нужно подождать еще)
|
|
||||||
Ok(None) => break,
|
Ok(None) => break,
|
||||||
|
|
||||||
// 3. Ошибка кодека
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Если кодек говорит "подожди", выходим из цикла парсинга
|
|
||||||
if e.action == ErrorAction::Wait {
|
if e.action == ErrorAction::Wait {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Иначе — это реальная проблема (кривой TLS и т.д.)
|
|
||||||
error!(error = ?e, "Codec inbound failed");
|
error!(error = ?e, "Codec inbound failed");
|
||||||
return Err(format!("Codec error: {:?}", e));
|
return Err(format!("Codec error: {:?}", e));
|
||||||
}
|
}
|
||||||
@@ -88,10 +85,8 @@ impl TunnelEngine {
|
|||||||
codec: &mut Codec,
|
codec: &mut Codec,
|
||||||
msg: MuxMessage,
|
msg: MuxMessage,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
// 1. Шифруем данные, используя только кодек
|
|
||||||
match codec.encrypt_data(msg.stream_id, msg.frame_type, msg.data) {
|
match codec.encrypt_data(msg.stream_id, msg.frame_type, msg.data) {
|
||||||
Ok(pkt) => {
|
Ok(pkt) => {
|
||||||
// 2. Пишем в сокет, используя только outbound
|
|
||||||
outbound
|
outbound
|
||||||
.write_all(&pkt)
|
.write_all(&pkt)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use crate::{
|
|||||||
proxy::connection::{bridge::run_proxy_bridge, connection::ConnectionRole, muxer::Muxer},
|
proxy::connection::{bridge::run_proxy_bridge, connection::ConnectionRole, muxer::Muxer},
|
||||||
};
|
};
|
||||||
|
|
||||||
// proxy/connection/stream_handler.rs
|
|
||||||
pub struct StreamHandler {
|
pub struct StreamHandler {
|
||||||
muxer: Muxer,
|
muxer: Muxer,
|
||||||
role: ConnectionRole,
|
role: ConnectionRole,
|
||||||
@@ -44,7 +43,6 @@ impl StreamHandler {
|
|||||||
|
|
||||||
match tokio::net::TcpStream::connect(&target_str).await {
|
match tokio::net::TcpStream::connect(&target_str).await {
|
||||||
Ok(stream) => {
|
Ok(stream) => {
|
||||||
// --- ШАГ 2: ШЛЕМ ПОДТВЕРЖДЕНИЕ ---
|
|
||||||
let mut reply_buf = BytesMut::with_capacity(10);
|
let mut reply_buf = BytesMut::with_capacity(10);
|
||||||
let reply = SocksReply::ConnectResult {
|
let reply = SocksReply::ConnectResult {
|
||||||
reply_code: 0x00,
|
reply_code: 0x00,
|
||||||
@@ -58,13 +56,12 @@ impl StreamHandler {
|
|||||||
.send_control(stream_id, FrameType::Connect, reply_buf.freeze())
|
.send_control(stream_id, FrameType::Connect, reply_buf.freeze())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// --- ШАГ 3: ЗАПУСКАЕМ МОСТ ---
|
|
||||||
let (r, w) = stream.into_split();
|
let (r, w) = stream.into_split();
|
||||||
run_proxy_bridge(stream_id, r, w, muxer, v_rx).await;
|
run_proxy_bridge(stream_id, r, w, muxer, v_rx).await;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(stream_id, error = %e, "Connection failed");
|
error!(stream_id, error = %e, "Connection failed");
|
||||||
// Если не подключились — удаляем стрим, чтобы не висел в мапе
|
|
||||||
muxer.remove_stream(stream_id).await;
|
muxer.remove_stream(stream_id).await;
|
||||||
|
|
||||||
let mut reply_buf = BytesMut::with_capacity(10);
|
let mut reply_buf = BytesMut::with_capacity(10);
|
||||||
@@ -82,7 +79,6 @@ impl StreamHandler {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Логика для клиента (проброс ответа сервера браузеру)
|
|
||||||
self.muxer.dispatch_to_local(stream_id, payload).await;
|
self.muxer.dispatch_to_local(stream_id, payload).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ impl Muxer {
|
|||||||
|
|
||||||
pub async fn remove_stream(&self, stream_id: u32) {
|
pub async fn remove_stream(&self, stream_id: u32) {
|
||||||
let mut lock = self.streams.write().await;
|
let mut lock = self.streams.write().await;
|
||||||
lock.remove(&stream_id); // Просто удаляем, если есть
|
lock.remove(&stream_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_control(
|
pub async fn send_control(
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use tokio::{
|
|||||||
io::{AsyncReadExt, AsyncWriteExt},
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
net::{TcpListener, TcpStream},
|
net::{TcpListener, TcpStream},
|
||||||
};
|
};
|
||||||
use tracing::{error, info, instrument}; // Импортируем макросы
|
use tracing::{error, info, instrument};
|
||||||
|
|
||||||
pub struct Network {
|
pub struct Network {
|
||||||
host: String,
|
host: String,
|
||||||
@@ -36,7 +36,6 @@ impl Network {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем инструмент, чтобы видеть параметры запуска сети в логах
|
|
||||||
#[instrument(skip(self), fields(role = ?self.role, port = self.port))]
|
#[instrument(skip(self), fields(role = ?self.role, port = self.port))]
|
||||||
pub async fn run(&self) {
|
pub async fn run(&self) {
|
||||||
let addr = format!("{}:{}", self.host, self.port);
|
let addr = format!("{}:{}", self.host, self.port);
|
||||||
@@ -60,7 +59,6 @@ impl Network {
|
|||||||
if let Ok((stream, client_addr)) = listener.accept().await {
|
if let Ok((stream, client_addr)) = listener.accept().await {
|
||||||
let current_muxer = muxer.clone();
|
let current_muxer = muxer.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
// Здесь мы просто создаем Connection и сразу в SOCKS
|
|
||||||
let connection = Connection::new(stream, client_addr, false);
|
let connection = Connection::new(stream, client_addr, false);
|
||||||
let _ = connection.handle_socks_client(current_muxer).await;
|
let _ = connection.handle_socks_client(current_muxer).await;
|
||||||
});
|
});
|
||||||
@@ -69,7 +67,6 @@ impl Network {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ConnectionRole::Server => {
|
ConnectionRole::Server => {
|
||||||
// --- ЛОГИКА СЕРВЕРА ---
|
|
||||||
let listener = TcpListener::bind(&addr)
|
let listener = TcpListener::bind(&addr)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to bind Server port");
|
.expect("Failed to bind Server port");
|
||||||
@@ -78,7 +75,6 @@ impl Network {
|
|||||||
loop {
|
loop {
|
||||||
if let Ok((stream, client_addr)) = listener.accept().await {
|
if let Ok((stream, client_addr)) = listener.accept().await {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
// Сервер использует handle_server_tunnel
|
|
||||||
let connection = Connection::new(stream, client_addr, true);
|
let connection = Connection::new(stream, client_addr, true);
|
||||||
if let Err(e) = connection.handle_server_tunnel().await {
|
if let Err(e) = connection.handle_server_tunnel().await {
|
||||||
error!(client = %client_addr, error = %e, "Tunnel error");
|
error!(client = %client_addr, error = %e, "Tunnel error");
|
||||||
@@ -90,21 +86,16 @@ impl Network {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Вспомогательный метод для Клиента: создает TLS туннель и запускает TunnelEngine
|
|
||||||
pub async fn initialize_client_tunnel(&self) -> Result<Muxer, String> {
|
pub async fn initialize_client_tunnel(&self) -> Result<Muxer, String> {
|
||||||
let server_addr = self.remote_proxy_addr.as_ref().ok_or("No proxy addr")?;
|
let server_addr = self.remote_proxy_addr.as_ref().ok_or("No proxy addr")?;
|
||||||
|
|
||||||
// Вместо создания Connection (который нужен для обработки клиентов),
|
|
||||||
// работаем напрямую с TcpStream для первичного TLS-хендшейка.
|
|
||||||
let stream = TcpStream::connect(server_addr)
|
let stream = TcpStream::connect(server_addr)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
let (mut inbound, mut outbound) = stream.into_split();
|
let (mut inbound, mut outbound) = stream.into_split();
|
||||||
|
|
||||||
// Кодек создаем «с чистого листа»
|
|
||||||
let mut codec = crate::protocol::codec::codec::Codec::new(false);
|
let mut codec = crate::protocol::codec::codec::Codec::new(false);
|
||||||
|
|
||||||
// --- TLS Handshake ---
|
|
||||||
let ch = codec
|
let ch = codec
|
||||||
.make_client_handshake(&BrowserProfile::CHROME_131, "google.com")
|
.make_client_handshake(&BrowserProfile::CHROME_131, "google.com")
|
||||||
.map_err(|e| format!("{:?}", e))?;
|
.map_err(|e| format!("{:?}", e))?;
|
||||||
@@ -112,9 +103,8 @@ impl Network {
|
|||||||
|
|
||||||
let mut sh_buf = BytesMut::with_capacity(2048);
|
let mut sh_buf = BytesMut::with_capacity(2048);
|
||||||
loop {
|
loop {
|
||||||
// Пытаемся обработать то, что уже есть в буфере
|
|
||||||
match codec.process_handshake(&mut sh_buf) {
|
match codec.process_handshake(&mut sh_buf) {
|
||||||
Ok(_) => break, // Готово!
|
Ok(_) => break,
|
||||||
Err(e) if e.action == ErrorAction::Wait => {
|
Err(e) if e.action == ErrorAction::Wait => {
|
||||||
let n = inbound
|
let n = inbound
|
||||||
.read_buf(&mut sh_buf)
|
.read_buf(&mut sh_buf)
|
||||||
@@ -128,7 +118,6 @@ impl Network {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Запуск инфраструктуры ---
|
|
||||||
let (mux_tx, mux_rx) = tokio::sync::mpsc::channel(BUF_SIZE);
|
let (mux_tx, mux_rx) = tokio::sync::mpsc::channel(BUF_SIZE);
|
||||||
let muxer = Muxer::new(mux_tx, true);
|
let muxer = Muxer::new(mux_tx, true);
|
||||||
|
|
||||||
@@ -141,7 +130,7 @@ impl Network {
|
|||||||
inbound,
|
inbound,
|
||||||
outbound,
|
outbound,
|
||||||
codec,
|
codec,
|
||||||
read_buf: sh_buf, // Передаем остатки данных из буфера хендшейка в движок!
|
read_buf: sh_buf,
|
||||||
mux_rx,
|
mux_rx,
|
||||||
handler,
|
handler,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,22 +1,14 @@
|
|||||||
/// Handshake message types
|
|
||||||
pub const HANDSHAKE_TYPE_CLIENT_HELLO: u8 = 0x01;
|
pub const HANDSHAKE_TYPE_CLIENT_HELLO: u8 = 0x01;
|
||||||
pub const HANDSHAKE_TYPE_SERVER_HELLO: u8 = 0x02;
|
pub const HANDSHAKE_TYPE_SERVER_HELLO: u8 = 0x02;
|
||||||
|
|
||||||
/// SNI (Server Name Indication) specific
|
|
||||||
pub const TYPE_HOST_NAME: u8 = 0x00;
|
pub const TYPE_HOST_NAME: u8 = 0x00;
|
||||||
|
|
||||||
/// PSK (Pre-Shared Key) modes
|
|
||||||
pub const PSK_DHE_KE_MODE: u8 = 0x01;
|
pub const PSK_DHE_KE_MODE: u8 = 0x01;
|
||||||
|
|
||||||
/// Certificate compression algorithms
|
|
||||||
pub const CERT_COMPRESSION_BROTLI: u16 = 0x0002;
|
pub const CERT_COMPRESSION_BROTLI: u16 = 0x0002;
|
||||||
|
|
||||||
/// Extension internal status types
|
|
||||||
pub const OCSP_STATUS_TYPE: u8 = 0x01;
|
pub const OCSP_STATUS_TYPE: u8 = 0x01;
|
||||||
//pub const EC_POINT_FORMAT_UNCOMPRESSED: u8 = 0x00;
|
|
||||||
|
|
||||||
/// GREASE (Generate Random Extensions And Sustain Extensibility)
|
|
||||||
/// Используется для предотвращения ошибок серверов при встрече с неизвестными ID.
|
|
||||||
pub const GREASE_IDENTIFIERS: [u16; 16] = [
|
pub const GREASE_IDENTIFIERS: [u16; 16] = [
|
||||||
0x0A0A, 0x1A1A, 0x2A2A, 0x3A3A, 0x4A4A, 0x5A5A, 0x6A6A, 0x7A7A, 0x8A8A, 0x9A9A, 0xAAAA, 0xBABA,
|
0x0A0A, 0x1A1A, 0x2A2A, 0x3A3A, 0x4A4A, 0x5A5A, 0x6A6A, 0x7A7A, 0x8A8A, 0x9A9A, 0xAAAA, 0xBABA,
|
||||||
0xCACA, 0xDADA, 0xEAEA, 0xFAFA,
|
0xCACA, 0xDADA, 0xEAEA, 0xFAFA,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use bytes::{BufMut, Bytes, BytesMut};
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
use rand::RngExt;
|
use rand::RngExt;
|
||||||
|
|
||||||
// Using your provided constants and types
|
|
||||||
use crate::tlseng::{
|
use crate::tlseng::{
|
||||||
consts::{
|
consts::{
|
||||||
CERT_COMPRESSION_BROTLI, GREASE_IDENTIFIERS, OCSP_STATUS_TYPE, PSK_DHE_KE_MODE,
|
CERT_COMPRESSION_BROTLI, GREASE_IDENTIFIERS, OCSP_STATUS_TYPE, PSK_DHE_KE_MODE,
|
||||||
@@ -33,16 +32,6 @@ impl ExtensionStack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Extension {
|
impl Extension {
|
||||||
/// Creates a new Extension from the given parameters.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `etype`: The type of extension (e.g., EXT_SUPPORTED_VERSIONS).
|
|
||||||
/// * `data`: The actual data being transported (e.g., a serialized list of supported versions).
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// A new Extension structure with the given parameters.
|
|
||||||
pub fn new(etype: u16, data: Bytes) -> Self {
|
pub fn new(etype: u16, data: Bytes) -> Self {
|
||||||
Self {
|
Self {
|
||||||
etype,
|
etype,
|
||||||
@@ -50,16 +39,7 @@ impl Extension {
|
|||||||
data,
|
data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Packs an extension into a single byte array.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `etype`: The type of extension (e.g., EXT_SUPPORTED_VERSIONS).
|
|
||||||
/// * `data`: The actual data being transported (e.g., a serialized list of supported versions).
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// A single byte array containing the type and length of the extension, followed by the actual extension data.
|
|
||||||
pub fn pack(etype: u16, data: &[u8]) -> Bytes {
|
pub fn pack(etype: u16, data: &[u8]) -> Bytes {
|
||||||
let mut ext = BytesMut::with_capacity(4 + data.len());
|
let mut ext = BytesMut::with_capacity(4 + data.len());
|
||||||
ext.put_u16(etype);
|
ext.put_u16(etype);
|
||||||
@@ -139,9 +119,9 @@ impl ExtensionBuilder {
|
|||||||
|
|
||||||
pub fn key_share(&mut self, pub_key: &[u8]) {
|
pub fn key_share(&mut self, pub_key: &[u8]) {
|
||||||
let mut data = BytesMut::with_capacity(38);
|
let mut data = BytesMut::with_capacity(38);
|
||||||
data.put_u16(34); // Total Key Share List Length
|
data.put_u16(34);
|
||||||
data.put_u16(TlsGroups::X25519);
|
data.put_u16(TlsGroups::X25519);
|
||||||
data.put_u16(32); // Public Key length
|
data.put_u16(32);
|
||||||
data.put_slice(pub_key);
|
data.put_slice(pub_key);
|
||||||
self.add_extension(TlsExtensions::KEY_SHARE, &data);
|
self.add_extension(TlsExtensions::KEY_SHARE, &data);
|
||||||
}
|
}
|
||||||
@@ -152,7 +132,7 @@ impl ExtensionBuilder {
|
|||||||
let p_bytes = proto.as_bytes();
|
let p_bytes = proto.as_bytes();
|
||||||
data.put_u8(p_bytes.len() as u8);
|
data.put_u8(p_bytes.len() as u8);
|
||||||
data.put_slice(p_bytes);
|
data.put_slice(p_bytes);
|
||||||
data.put_u16(0); // Empty settings per-protocol
|
data.put_u16(0);
|
||||||
}
|
}
|
||||||
self.add_extension(TlsExtensions::ALPS, &data);
|
self.add_extension(TlsExtensions::ALPS, &data);
|
||||||
}
|
}
|
||||||
@@ -189,15 +169,15 @@ impl ExtensionBuilder {
|
|||||||
pub fn status_request(&mut self) {
|
pub fn status_request(&mut self) {
|
||||||
let mut data = BytesMut::with_capacity(5);
|
let mut data = BytesMut::with_capacity(5);
|
||||||
data.put_u8(OCSP_STATUS_TYPE);
|
data.put_u8(OCSP_STATUS_TYPE);
|
||||||
data.put_u16(0); // responder_id_list
|
data.put_u16(0);
|
||||||
data.put_u16(0); // request_extensions
|
data.put_u16(0);
|
||||||
self.add_extension(TlsExtensions::STATUS_REQUEST, &data);
|
self.add_extension(TlsExtensions::STATUS_REQUEST, &data);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ec_point_formats(&mut self) {
|
pub fn ec_point_formats(&mut self) {
|
||||||
let mut data = BytesMut::with_capacity(2);
|
let mut data = BytesMut::with_capacity(2);
|
||||||
data.put_u8(1);
|
data.put_u8(1);
|
||||||
data.put_u8(0x00); // Uncompressed
|
data.put_u8(0x00);
|
||||||
self.add_extension(TlsExtensions::EC_POINT_FORMATS, &data);
|
self.add_extension(TlsExtensions::EC_POINT_FORMATS, &data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +243,7 @@ impl ExtensionBuilder {
|
|||||||
self.add_extension(TlsExtensions::PADDING, &[]);
|
self.add_extension(TlsExtensions::PADDING, &[]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Обработка GREASE по маске
|
|
||||||
id if (id & 0x0f0f) == 0x0a0a => self.grease(),
|
id if (id & 0x0f0f) == 0x0a0a => self.grease(),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,65 +17,46 @@ pub struct HelloHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct ClientHello {
|
pub struct ClientHello {
|
||||||
/// The maximum version supported (legacy field in TLS 1.3)
|
|
||||||
pub version: ProtocolVersion,
|
pub version: ProtocolVersion,
|
||||||
/// 32 bytes of client-generated entropy
|
|
||||||
pub random: [u8; 32],
|
pub random: [u8; 32],
|
||||||
/// Legacy session ID (used in TLS 1.3 for middlebox compatibility)
|
|
||||||
pub session_id: Bytes,
|
pub session_id: Bytes,
|
||||||
/// List of cryptographic ciphers the client supports
|
|
||||||
pub cipher_suites: Vec<u16>,
|
pub cipher_suites: Vec<u16>,
|
||||||
/// Opaque block of extensions generated by ExtensionBuilder
|
|
||||||
pub extensions: Bytes,
|
pub extensions: Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientHello {
|
impl ClientHello {
|
||||||
/// Serializes the ClientHello message into its wire format.
|
|
||||||
/// Includes the 4-byte Handshake header (Type + Length).
|
|
||||||
pub fn serialize(&self) -> Bytes {
|
pub fn serialize(&self) -> Bytes {
|
||||||
let mut buf = BytesMut::with_capacity(512 + self.extensions.len());
|
let mut buf = BytesMut::with_capacity(512 + self.extensions.len());
|
||||||
|
|
||||||
// Handshake Type: 0x01 (ClientHello)
|
|
||||||
buf.put_u8(HANDSHAKE_TYPE_CLIENT_HELLO);
|
buf.put_u8(HANDSHAKE_TYPE_CLIENT_HELLO);
|
||||||
|
|
||||||
// Handshake Length Placeholder:
|
|
||||||
// Handshake messages use a 24-bit (3 byte) length field.
|
|
||||||
let length_pos = buf.len();
|
let length_pos = buf.len();
|
||||||
buf.put_bytes(0, 3);
|
buf.put_bytes(0, 3);
|
||||||
|
|
||||||
// Protocol Version:
|
|
||||||
// For TLS 1.3, this is traditionally pinned to 0x0303 (TLS 1.2)
|
|
||||||
// to prevent middleboxes from dropping the packet.
|
|
||||||
buf.put_u16(0x0303);
|
buf.put_u16(0x0303);
|
||||||
buf.put_slice(&self.random);
|
buf.put_slice(&self.random);
|
||||||
|
|
||||||
// Legacy Session ID:
|
|
||||||
// Formatted as Length (1 byte) + ID bytes.
|
|
||||||
buf.put_u8(self.session_id.len() as u8);
|
buf.put_u8(self.session_id.len() as u8);
|
||||||
buf.put_slice(&self.session_id);
|
buf.put_slice(&self.session_id);
|
||||||
|
|
||||||
// Cipher Suites:
|
|
||||||
// Formatted as Total Length (2 bytes) + Suite IDs (2 bytes each).
|
|
||||||
buf.put_u16((self.cipher_suites.len() * 2) as u16);
|
buf.put_u16((self.cipher_suites.len() * 2) as u16);
|
||||||
for &suite in &self.cipher_suites {
|
for &suite in &self.cipher_suites {
|
||||||
buf.put_u16(suite);
|
buf.put_u16(suite);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy Compression Methods:
|
|
||||||
// Always 1 byte of length (1) followed by the 'Null' method (0x00).
|
|
||||||
buf.put_u8(1);
|
buf.put_u8(1);
|
||||||
buf.put_u8(0x00);
|
buf.put_u8(0x00);
|
||||||
|
|
||||||
// Extensions Block:
|
|
||||||
// Formatted as Total Length (2 bytes) + Extension Data.
|
|
||||||
buf.put_u16(self.extensions.len() as u16);
|
buf.put_u16(self.extensions.len() as u16);
|
||||||
buf.put_slice(&self.extensions);
|
buf.put_slice(&self.extensions);
|
||||||
|
|
||||||
// Patch the Handshake Length:
|
|
||||||
// We calculate the length of everything after the 3-byte placeholder.
|
|
||||||
let total_len = (buf.len() - length_pos - 3) as u32;
|
let total_len = (buf.len() - length_pos - 3) as u32;
|
||||||
let len_bytes = total_len.to_be_bytes();
|
let len_bytes = total_len.to_be_bytes();
|
||||||
// Copy the last 3 bytes of the big-endian u32 into the placeholder.
|
|
||||||
buf[length_pos..length_pos + 3].copy_from_slice(&len_bytes[1..4]);
|
buf[length_pos..length_pos + 3].copy_from_slice(&len_bytes[1..4]);
|
||||||
|
|
||||||
let ext_len = self.extensions.len();
|
let ext_len = self.extensions.len();
|
||||||
@@ -108,10 +89,10 @@ impl ClientHello {
|
|||||||
session_id.put_slice(&[0u8; 32]);
|
session_id.put_slice(&[0u8; 32]);
|
||||||
|
|
||||||
let client_hello = ClientHello {
|
let client_hello = ClientHello {
|
||||||
version: ProtocolVersion::Tls12, // Legacy version for compatibility
|
version: ProtocolVersion::Tls12,
|
||||||
random: tls_random,
|
random: tls_random,
|
||||||
session_id: session_id.freeze(), // Standard 32-byte session ID
|
session_id: session_id.freeze(),
|
||||||
cipher_suites: vec![0x1301, 0x1302, 0x1303], // TLS 1.3 suites
|
cipher_suites: vec![0x1301, 0x1302, 0x1303],
|
||||||
extensions: extensions_bytes,
|
extensions: extensions_bytes,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -133,7 +114,6 @@ pub struct ServerHello {
|
|||||||
pub extensions: BytesMut,
|
pub extensions: BytesMut,
|
||||||
}
|
}
|
||||||
impl ServerHello {
|
impl ServerHello {
|
||||||
/// Динамически создает ServerHello на основе данных из ClientHello
|
|
||||||
pub fn from_client_hello(
|
pub fn from_client_hello(
|
||||||
client_hello: &ClientHello,
|
client_hello: &ClientHello,
|
||||||
server_public_key: &[u8],
|
server_public_key: &[u8],
|
||||||
@@ -149,17 +129,14 @@ impl ServerHello {
|
|||||||
|
|
||||||
let mut extensions = BytesMut::new();
|
let mut extensions = BytesMut::new();
|
||||||
|
|
||||||
// --- Extension: Supported Versions (0x002b) ---
|
|
||||||
extensions.put_u16(0x002b);
|
extensions.put_u16(0x002b);
|
||||||
extensions.put_u16(2);
|
extensions.put_u16(2);
|
||||||
extensions.put_u16(0x0304); // TLS 1.3
|
extensions.put_u16(0x0304);
|
||||||
|
|
||||||
// --- Extension: Key Share (0x0033) ---
|
|
||||||
// Структура: Type(2) + Length(2) + Group(2) + KeyLength(2) + Key(N)
|
|
||||||
extensions.put_u16(0x0033);
|
extensions.put_u16(0x0033);
|
||||||
extensions.put_u16(36); // Общая длина данных расширения (2+2+32)
|
extensions.put_u16(36);
|
||||||
extensions.put_u16(0x001d); // Named Group: x25519
|
extensions.put_u16(0x001d);
|
||||||
extensions.put_u16(32); // Key Length
|
extensions.put_u16(32);
|
||||||
extensions.put_slice(server_public_key);
|
extensions.put_slice(server_public_key);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@@ -171,7 +148,6 @@ impl ServerHello {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Оборачивает в TLS Record, принимая ключ
|
|
||||||
pub fn make_server_hello(
|
pub fn make_server_hello(
|
||||||
client_hello: &ClientHello,
|
client_hello: &ClientHello,
|
||||||
server_public_key: &[u8],
|
server_public_key: &[u8],
|
||||||
@@ -188,43 +164,35 @@ impl ServerHello {
|
|||||||
|
|
||||||
record.serialize()
|
record.serialize()
|
||||||
}
|
}
|
||||||
/// Сериализация самого сообщения Handshake (Type + Len gth + Body)
|
|
||||||
pub fn serialize(&self) -> Bytes {
|
pub fn serialize(&self) -> Bytes {
|
||||||
let mut buf = BytesMut::with_capacity(256 + self.extensions.len());
|
let mut buf = BytesMut::with_capacity(256 + self.extensions.len());
|
||||||
|
|
||||||
// 1. Handshake Type: 0x02 (ServerHello)
|
|
||||||
buf.put_u8(HANDSHAKE_TYPE_SERVER_HELLO);
|
buf.put_u8(HANDSHAKE_TYPE_SERVER_HELLO);
|
||||||
|
|
||||||
// 2. Placeholder для длины (u24)
|
|
||||||
let length_pos = buf.len();
|
let length_pos = buf.len();
|
||||||
buf.put_bytes(0, 3);
|
buf.put_bytes(0, 3);
|
||||||
|
|
||||||
// 3. Тело ServerHello
|
buf.put_u16(0x0303);
|
||||||
buf.put_u16(0x0303); // Legacy Version
|
|
||||||
buf.put_slice(&self.random);
|
buf.put_slice(&self.random);
|
||||||
|
|
||||||
// Session ID: Length (1 byte) + Data
|
|
||||||
buf.put_u8(self.session_id.len() as u8);
|
buf.put_u8(self.session_id.len() as u8);
|
||||||
buf.put_slice(&self.session_id);
|
buf.put_slice(&self.session_id);
|
||||||
|
|
||||||
// Selected Cipher Suite
|
|
||||||
buf.put_u16(self.cipher_suite);
|
buf.put_u16(self.cipher_suite);
|
||||||
|
|
||||||
// Compression: всегда 0x00
|
|
||||||
buf.put_u8(0x00);
|
buf.put_u8(0x00);
|
||||||
|
|
||||||
// Extensions: Length (2 bytes) + Data
|
|
||||||
buf.put_u16(self.extensions.len() as u16);
|
buf.put_u16(self.extensions.len() as u16);
|
||||||
buf.put_slice(&self.extensions);
|
buf.put_slice(&self.extensions);
|
||||||
|
|
||||||
// 4. Патчим длину Handshake сообщения (u24)
|
|
||||||
let total_len = (buf.len() - length_pos - 3) as u32;
|
let total_len = (buf.len() - length_pos - 3) as u32;
|
||||||
let len_bytes = total_len.to_be_bytes();
|
let len_bytes = total_len.to_be_bytes();
|
||||||
buf[length_pos..length_pos + 3].copy_from_slice(&len_bytes[1..4]);
|
buf[length_pos..length_pos + 3].copy_from_slice(&len_bytes[1..4]);
|
||||||
|
|
||||||
let total_handshake_len = (buf.len() - length_pos - 3) as u32;
|
let total_handshake_len = (buf.len() - length_pos - 3) as u32;
|
||||||
let ext_len = self.extensions.len();
|
let ext_len = self.extensions.len();
|
||||||
// ИНФОРМАТИВНЫЙ ЛОГ
|
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
handshake_type = "ServerHello",
|
handshake_type = "ServerHello",
|
||||||
body_len = total_handshake_len,
|
body_len = total_handshake_len,
|
||||||
@@ -233,6 +201,6 @@ impl ServerHello {
|
|||||||
"Serialized Handshake message"
|
"Serialized Handshake message"
|
||||||
);
|
);
|
||||||
|
|
||||||
buf.freeze() // Превращаем BytesMut в Bytes
|
buf.freeze()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,20 @@
|
|||||||
use crate::tlseng::types::{ExtensionOrder, TlsGroups, TlsSignatures, TlsVersions};
|
use crate::tlseng::types::{ExtensionOrder, TlsGroups, TlsSignatures, TlsVersions};
|
||||||
|
|
||||||
/// Represents a complete TLS fingerprint profile for a specific browser.
|
|
||||||
///
|
|
||||||
/// This struct contains all the necessary information to generate a TLS
|
|
||||||
/// fingerprint for a specific browser, including the groups, signatures,
|
|
||||||
/// delegated signatures, versions, ALPN, and extension order.
|
|
||||||
pub struct BrowserProfile {
|
pub struct BrowserProfile {
|
||||||
/// The name of the browser profile.
|
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
|
|
||||||
/// The groups supported by the browser.
|
|
||||||
pub groups: TlsGroups,
|
pub groups: TlsGroups,
|
||||||
|
|
||||||
/// The signatures supported by the browser.
|
|
||||||
pub signatures: TlsSignatures,
|
pub signatures: TlsSignatures,
|
||||||
|
|
||||||
/// The delegated signatures supported by the browser.
|
|
||||||
pub delegated_signatures: TlsSignatures,
|
pub delegated_signatures: TlsSignatures,
|
||||||
|
|
||||||
/// The versions of TLS supported by the browser.
|
|
||||||
pub versions: TlsVersions,
|
pub versions: TlsVersions,
|
||||||
|
|
||||||
/// The ALPN protocols supported by the browser.
|
|
||||||
pub alpn: &'static [&'static str],
|
pub alpn: &'static [&'static str],
|
||||||
|
|
||||||
/// The specific order of Extension IDs (e.g., [0x0000, 0x0017, ...])
|
|
||||||
pub extension_order: ExtensionOrder,
|
pub extension_order: ExtensionOrder,
|
||||||
|
|
||||||
/// Whether the browser is based on Chromium.
|
|
||||||
pub is_chromium: bool,
|
pub is_chromium: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +32,7 @@ impl BrowserProfile {
|
|||||||
|
|
||||||
pub const EDGE: Self = Self {
|
pub const EDGE: Self = Self {
|
||||||
name: "Edge",
|
name: "Edge",
|
||||||
groups: TlsGroups::CHROMIUM, // Edge использует тот же набор, что и Chrome
|
groups: TlsGroups::CHROMIUM,
|
||||||
signatures: TlsSignatures::BROWSER_STANDARD,
|
signatures: TlsSignatures::BROWSER_STANDARD,
|
||||||
delegated_signatures: TlsSignatures::BROWSER_STANDARD,
|
delegated_signatures: TlsSignatures::BROWSER_STANDARD,
|
||||||
versions: TlsVersions::MODERN,
|
versions: TlsVersions::MODERN,
|
||||||
@@ -56,45 +43,31 @@ impl BrowserProfile {
|
|||||||
|
|
||||||
pub const DEFAULT: Self = Self::CHROME_131;
|
pub const DEFAULT: Self = Self::CHROME_131;
|
||||||
}
|
}
|
||||||
/// Represents a TLS configuration profile for the server side.
|
|
||||||
pub struct ServerProfile {
|
pub struct ServerProfile {
|
||||||
/// Имя профиля (например, "Modern-TLS-1.3-Only" или "Compatible-Nginx-Style")
|
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
|
|
||||||
/// Поддерживаемые версии TLS. Сервер выберет высшую общую с клиентом.
|
|
||||||
pub versions: TlsVersions,
|
pub versions: TlsVersions,
|
||||||
|
|
||||||
/// Приоритетный список шифров (Cipher Suites).
|
|
||||||
/// В TLS 1.3 это обычно [0x1301, 0x1302, 0x1303].
|
|
||||||
pub cipher_suites: &'static [u16],
|
pub cipher_suites: &'static [u16],
|
||||||
|
|
||||||
/// Группы для обмена ключами (Key Exchange Groups).
|
|
||||||
pub groups: TlsGroups,
|
pub groups: TlsGroups,
|
||||||
|
|
||||||
/// Поддерживаемые алгоритмы подписи для аутентификации сервера.
|
|
||||||
pub signatures: TlsSignatures,
|
pub signatures: TlsSignatures,
|
||||||
|
|
||||||
/// Протоколы ALPN, которые сервер готов подтвердить (h2, http/1.1).
|
|
||||||
pub alpn: &'static [&'static str],
|
pub alpn: &'static [&'static str],
|
||||||
|
|
||||||
/// Настройки сессий
|
|
||||||
pub session_tickets: bool,
|
pub session_tickets: bool,
|
||||||
|
|
||||||
/// Нужно ли форсировать порядок шифров сервера (Server Preference),
|
|
||||||
/// игнорируя порядок предпочтений клиента.
|
|
||||||
pub honor_cipher_order: bool,
|
pub honor_cipher_order: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerProfile {
|
impl ServerProfile {
|
||||||
pub const MODERN: Self = Self {
|
pub const MODERN: Self = Self {
|
||||||
name: "Modern-Secure",
|
name: "Modern-Secure",
|
||||||
versions: TlsVersions::MODERN, // Допустим, у тебя есть такой хелпер
|
versions: TlsVersions::MODERN,
|
||||||
cipher_suites: &[
|
cipher_suites: &[0x1301, 0x1302, 0x1303],
|
||||||
0x1301, // TLS_AES_128_GCM_SHA256
|
groups: TlsGroups::MODERN,
|
||||||
0x1302, // TLS_AES_256_GCM_SHA384
|
|
||||||
0x1303, // TLS_CHACHA20_POLY1305_SHA256
|
|
||||||
],
|
|
||||||
groups: TlsGroups::MODERN, // X25519, P-256
|
|
||||||
signatures: TlsSignatures::BROWSER_STANDARD,
|
signatures: TlsSignatures::BROWSER_STANDARD,
|
||||||
alpn: &["h2", "http/1.1"],
|
alpn: &["h2", "http/1.1"],
|
||||||
session_tickets: true,
|
session_tickets: true,
|
||||||
|
|||||||
@@ -2,32 +2,18 @@ use bytes::{BufMut, Bytes, BytesMut};
|
|||||||
|
|
||||||
use crate::tlseng::types::{ContentType, ProtocolVersion};
|
use crate::tlseng::types::{ContentType, ProtocolVersion};
|
||||||
|
|
||||||
/// The TLS Record Layer structure.
|
|
||||||
/// This is the outer envelope that wraps all TLS messages sent over the wire.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TlsRecord {
|
pub struct TlsRecord {
|
||||||
/// The type of data contained (Handshake, ApplicationData, etc.)
|
|
||||||
pub content_type: ContentType,
|
pub content_type: ContentType,
|
||||||
/// The record layer version (usually 0x0301 for legacy support)
|
|
||||||
pub version: ProtocolVersion,
|
pub version: ProtocolVersion,
|
||||||
|
|
||||||
pub len: u16,
|
pub len: u16,
|
||||||
/// The actual data being transported (e.g., a serialized ClientHello)
|
|
||||||
pub payload: Bytes,
|
pub payload: Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TlsRecord {
|
impl TlsRecord {
|
||||||
/// Creates a new TLS Record Layer from the given parameters.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `content_type`: The type of data contained (Handshake, ApplicationData, etc.).
|
|
||||||
/// * `version`: The record layer version (usually 0x0301 for legacy support).
|
|
||||||
/// * `payload`: The actual data being transported (e.g., a serialized ClientHello).
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// A new TLS Record Layer structure with the given parameters.
|
|
||||||
pub fn new(content_type: ContentType, version: ProtocolVersion, payload: Bytes) -> Self {
|
pub fn new(content_type: ContentType, version: ProtocolVersion, payload: Bytes) -> Self {
|
||||||
Self {
|
Self {
|
||||||
content_type,
|
content_type,
|
||||||
@@ -37,8 +23,6 @@ impl TlsRecord {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serializes the Record Layer header and payload.
|
|
||||||
/// Wire Format: [Type (1)] [Version (2)] [Length (2)] [Payload (N)]
|
|
||||||
pub fn serialize(&self) -> Bytes {
|
pub fn serialize(&self) -> Bytes {
|
||||||
let mut buf = BytesMut::with_capacity(5 + self.payload.len());
|
let mut buf = BytesMut::with_capacity(5 + self.payload.len());
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,15 @@
|
|||||||
/// TLS Content Types as defined in the TLS Record Protocol.
|
|
||||||
/// These identify what is contained within the TLS Record payload.
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum ContentType {
|
pub enum ContentType {
|
||||||
/// Handshake messages (e.g., ClientHello, ServerHello)
|
|
||||||
Handshake = 0x16,
|
Handshake = 0x16,
|
||||||
/// Encrypted application data (the actual traffic)
|
|
||||||
ApplicationData = 0x17,
|
ApplicationData = 0x17,
|
||||||
/// Notification messages (e.g., CloseNotify or error signals)
|
|
||||||
Alert = 0x15,
|
Alert = 0x15,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<u8> for ContentType {
|
impl TryFrom<u8> for ContentType {
|
||||||
type Error = &'static str;
|
type Error = &'static str;
|
||||||
/// Attempts to convert a given `u8` value into a `ContentType`.
|
|
||||||
///
|
|
||||||
/// Returns `Ok(ContentType)` if the conversion is successful, and `Err(&str)` if not.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
///
|
|
||||||
|
|
||||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
match value {
|
match value {
|
||||||
@@ -31,14 +21,6 @@ impl TryFrom<u8> for ContentType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
|
||||||
/// Represents known TLS protocol versions.
|
|
||||||
///
|
|
||||||
/// Note that TLS 1.3 often uses legacy versions in headers for compatibility.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
///
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum ProtocolVersion {
|
pub enum ProtocolVersion {
|
||||||
@@ -50,66 +32,28 @@ pub enum ProtocolVersion {
|
|||||||
impl TryFrom<u16> for ProtocolVersion {
|
impl TryFrom<u16> for ProtocolVersion {
|
||||||
type Error = &'static str;
|
type Error = &'static str;
|
||||||
|
|
||||||
/// Attempts to convert a given `u16` value into a `ProtocolVersion`.
|
|
||||||
///
|
|
||||||
/// Returns `Ok(ProtocolVersion)` if the conversion is successful, and `Err(&str)` if not.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
///
|
|
||||||
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||||
match value {
|
match value {
|
||||||
// TLS 1.0 (RFC 2246)
|
|
||||||
0x0301 => Ok(ProtocolVersion::Tls10),
|
0x0301 => Ok(ProtocolVersion::Tls10),
|
||||||
// TLS 1.2 (RFC 4346)
|
|
||||||
0x0303 => Ok(ProtocolVersion::Tls12),
|
0x0303 => Ok(ProtocolVersion::Tls12),
|
||||||
// TLS 1.3 (draft-ietf-tls-tls13-28)
|
|
||||||
0x0304 => Ok(ProtocolVersion::Tls13),
|
0x0304 => Ok(ProtocolVersion::Tls13),
|
||||||
_ => Err("This is not Protocol Version"),
|
_ => Err("This is not Protocol Version"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hello types as defined in the TLS Handshake Protocol.
|
|
||||||
///
|
|
||||||
/// These identify the type of the message in the TLS Handshake protocol.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// # Notes
|
|
||||||
///
|
|
||||||
/// The values of these enum variants are used as the first byte of the TLS
|
|
||||||
/// Handshake protocol message.
|
|
||||||
///
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum HelloType {
|
pub enum HelloType {
|
||||||
/// Client hello message type
|
|
||||||
Client = 0x01,
|
Client = 0x01,
|
||||||
/// Server hello message type
|
|
||||||
Server = 0x02,
|
Server = 0x02,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to convert a given `u8` value into a `HelloType`.
|
|
||||||
///
|
|
||||||
/// Returns `Ok(HelloType)` if the conversion is successful, and `Err(&str)` if not.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// # Notes
|
|
||||||
///
|
|
||||||
/// This function is used to convert raw bytes into a `HelloType`.
|
|
||||||
/// It is used in the `HelloHeader` parsing process.
|
|
||||||
impl TryFrom<u8> for HelloType {
|
impl TryFrom<u8> for HelloType {
|
||||||
type Error = &'static str;
|
type Error = &'static str;
|
||||||
/// Attempts to convert a given `u8` value into a `HelloType`.
|
|
||||||
///
|
|
||||||
/// Returns `Ok(HelloType)` if the conversion is successful, and `Err(&str)` if not.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
///
|
|
||||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
match value {
|
match value {
|
||||||
0x01 => Ok(HelloType::Client),
|
0x01 => Ok(HelloType::Client),
|
||||||
@@ -119,10 +63,6 @@ impl TryFrom<u8> for HelloType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A collection of supported TLS groups.
|
|
||||||
///
|
|
||||||
/// This is a list of 16-bit group identifiers that the client supports.
|
|
||||||
/// The server will select one of these groups to use for the key exchange.
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct TlsGroups(pub &'static [u16]);
|
pub struct TlsGroups(pub &'static [u16]);
|
||||||
|
|
||||||
@@ -131,17 +71,11 @@ impl TlsGroups {
|
|||||||
pub const SECP256R1: u16 = 0x0017;
|
pub const SECP256R1: u16 = 0x0017;
|
||||||
pub const SECP384R1: u16 = 0x0018;
|
pub const SECP384R1: u16 = 0x0018;
|
||||||
|
|
||||||
/// Стандартный набор для Chrome/Edge (X25519 + P-256)
|
|
||||||
pub const CHROMIUM: Self = Self(&[Self::X25519, Self::SECP256R1, Self::SECP384R1]);
|
pub const CHROMIUM: Self = Self(&[Self::X25519, Self::SECP256R1, Self::SECP384R1]);
|
||||||
|
|
||||||
/// Набор "только современные кривые"
|
|
||||||
pub const MODERN: Self = Self(&[Self::X25519, Self::SECP256R1]);
|
pub const MODERN: Self = Self(&[Self::X25519, Self::SECP256R1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A collection of supported TLS signature algorithms.
|
|
||||||
///
|
|
||||||
/// This is a list of 16-bit signature algorithm identifiers that the client supports.
|
|
||||||
/// The server will select one of these algorithms to use for the digital signature.
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct TlsSignatures(pub &'static [u16]);
|
pub struct TlsSignatures(pub &'static [u16]);
|
||||||
|
|
||||||
@@ -155,7 +89,6 @@ impl TlsSignatures {
|
|||||||
pub const RSA_PSS_RSAE_SHA512: u16 = 0x0806;
|
pub const RSA_PSS_RSAE_SHA512: u16 = 0x0806;
|
||||||
pub const RSA_PKCS1_SHA512: u16 = 0x0601;
|
pub const RSA_PKCS1_SHA512: u16 = 0x0601;
|
||||||
|
|
||||||
/// Типичный набор для современных браузеров
|
|
||||||
pub const BROWSER_STANDARD: Self = Self(&[
|
pub const BROWSER_STANDARD: Self = Self(&[
|
||||||
Self::ECDSA_SECP256R1_SHA256,
|
Self::ECDSA_SECP256R1_SHA256,
|
||||||
Self::RSA_PSS_RSAE_SHA256,
|
Self::RSA_PSS_RSAE_SHA256,
|
||||||
@@ -167,10 +100,6 @@ impl TlsSignatures {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A collection of supported TLS protocol versions.
|
|
||||||
///
|
|
||||||
/// This is a list of 16-bit protocol version identifiers that the client supports.
|
|
||||||
/// The server will select one of these versions to use for the TLS connection.
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct TlsVersions(pub &'static [u16]);
|
pub struct TlsVersions(pub &'static [u16]);
|
||||||
|
|
||||||
@@ -218,7 +147,7 @@ impl<'a> IntoIterator for &'a ExtensionOrder {
|
|||||||
|
|
||||||
impl ExtensionOrder {
|
impl ExtensionOrder {
|
||||||
pub const CHROMIUM_131: Self = Self(&[
|
pub const CHROMIUM_131: Self = Self(&[
|
||||||
0xaaaa, // GREASE
|
0xaaaa,
|
||||||
TlsExtensions::SNI,
|
TlsExtensions::SNI,
|
||||||
TlsExtensions::EMS,
|
TlsExtensions::EMS,
|
||||||
TlsExtensions::SESSION_TICKET,
|
TlsExtensions::SESSION_TICKET,
|
||||||
@@ -237,7 +166,7 @@ impl ExtensionOrder {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
pub const EDGE_130: Self = Self(&[
|
pub const EDGE_130: Self = Self(&[
|
||||||
0x1a1a, // GREASE
|
0x1a1a,
|
||||||
TlsExtensions::SNI,
|
TlsExtensions::SNI,
|
||||||
TlsExtensions::EMS,
|
TlsExtensions::EMS,
|
||||||
TlsExtensions::SESSION_TICKET,
|
TlsExtensions::SESSION_TICKET,
|
||||||
@@ -254,6 +183,6 @@ impl ExtensionOrder {
|
|||||||
TlsExtensions::SCT,
|
TlsExtensions::SCT,
|
||||||
TlsExtensions::DELEGATED_CREDENTIAL,
|
TlsExtensions::DELEGATED_CREDENTIAL,
|
||||||
TlsExtensions::PADDING,
|
TlsExtensions::PADDING,
|
||||||
0x3a3a, // GREASE
|
0x3a3a,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,50 +4,21 @@ use bytes::Buf;
|
|||||||
pub struct U24([u8; 3]);
|
pub struct U24([u8; 3]);
|
||||||
|
|
||||||
impl U24 {
|
impl U24 {
|
||||||
/// Creates a new `U24` from a given `u32` value.
|
|
||||||
///
|
|
||||||
/// The `u32` value is converted to big-endian byte order and then split into
|
|
||||||
/// three bytes, which are used to initialize the `U24`.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
///
|
|
||||||
pub fn from_u32(value: u32) -> Self {
|
pub fn from_u32(value: u32) -> Self {
|
||||||
let b = value.to_be_bytes();
|
let b = value.to_be_bytes();
|
||||||
U24([b[1], b[2], b[3]])
|
U24([b[1], b[2], b[3]])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the `U24` into a `u32` value.
|
|
||||||
///
|
|
||||||
/// The `U24` is converted from big-endian byte order to a `u32` value.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
///
|
|
||||||
pub fn to_u32(&self) -> u32 {
|
pub fn to_u32(&self) -> u32 {
|
||||||
u32::from_be_bytes([0, self.0[0], self.0[1], self.0[2]])
|
u32::from_be_bytes([0, self.0[0], self.0[1], self.0[2]])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a slice of three bytes into a `u32` value.
|
|
||||||
///
|
|
||||||
/// The slice is interpreted as a big-endian byte order, and the resulting `u32` value is
|
|
||||||
/// computed by combining the three bytes into a single 32-bit value.
|
|
||||||
pub fn from_slice(slice: &[u8]) -> u32 {
|
pub fn from_slice(slice: &[u8]) -> u32 {
|
||||||
u32::from_be_bytes([0, slice[0], slice[1], slice[2]])
|
u32::from_be_bytes([0, slice[0], slice[1], slice[2]])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait BufExt: Buf {
|
pub trait BufExt: Buf {
|
||||||
/// Reads three bytes from the buffer and returns a `u32` value.
|
|
||||||
///
|
|
||||||
/// The bytes are read in big-endian byte order and then combined into a single `u32` value.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// let mut buf = BytesMut::from(&[0x12, 0x34, 0x56][..]);
|
|
||||||
/// let val = buf.get_u24();
|
|
||||||
/// assert_eq!(val, 0x123456);
|
|
||||||
fn get_u24(&mut self) -> u32 {
|
fn get_u24(&mut self) -> u32 {
|
||||||
let b1 = self.get_u8() as u32;
|
let b1 = self.get_u8() as u32;
|
||||||
let b2 = self.get_u8() as u32;
|
let b2 = self.get_u8() as u32;
|
||||||
|
|||||||
@@ -7,11 +7,9 @@ use clap::Parser;
|
|||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about = "Netrunner Proxy Server")]
|
#[command(author, version, about = "Netrunner Proxy Server")]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// Порт, который будет слушать сервер
|
|
||||||
#[arg(short, long, default_value_t = 8080)]
|
#[arg(short, long, default_value_t = 8080)]
|
||||||
port: u16,
|
port: u16,
|
||||||
|
|
||||||
/// IP адрес для привязки
|
|
||||||
#[arg(long, default_value = "0.0.0.0")]
|
#[arg(long, default_value = "0.0.0.0")]
|
||||||
host: String,
|
host: String,
|
||||||
}
|
}
|
||||||
@@ -21,10 +19,8 @@ fn main() {
|
|||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
let net = Network::new(args.host, args.port, ConnectionRole::Server, None);
|
let net = Network::new(args.host, args.port, ConnectionRole::Server, None);
|
||||||
|
|
||||||
// Создаем движок (Runtime)
|
|
||||||
let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
|
let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
|
||||||
|
|
||||||
// "Блокируем" основной поток, пока работает асинхронный код
|
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
net.run().await;
|
net.run().await;
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user