//! # P2P Сетевой модуль //! //! Распространение подписей присутствия между узлами. //! //! ## Русский код //! Все идентификаторы на русском, как будто писал дух русского языка. use montana_crypto::{sha3_256 as хеш256, secure_random_bytes as случайные_байты}; use montana_acp::PresenceProof as ДоказательствоПрисутствия; use std::collections::{HashMap, HashSet, VecDeque}; use std::net::IpAddr; // ═══════════════════════════════════════════════════════════════════════════════ // ЛИМИТЫ СОЕДИНЕНИЙ // ═══════════════════════════════════════════════════════════════════════════════ /// Лимиты соединений /// Защита от перегрузки и Eclipse-атак #[derive(Clone, Copy, Debug)] pub struct ЛимитыСоединений { /// Максимум входящих соединений pub максимум_входящих: usize, /// Максимум исходящих соединений pub максимум_исходящих: usize, /// Максимум соединений на подсеть /16 pub максимум_на_подсеть: usize, /// Минимум различных подсетей pub минимум_подсетей: usize, } impl Default for ЛимитыСоединений { fn default() -> Self { Self { максимум_входящих: 117, максимум_исходящих: 11, максимум_на_подсеть: 2, минимум_подсетей: 4, } } } // ═══════════════════════════════════════════════════════════════════════════════ // СЕТЕВОЙ АДРЕС // ═══════════════════════════════════════════════════════════════════════════════ /// Сетевой адрес узла #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct СетевойАдрес { /// IP адрес pub адрес: IpAddr, /// Порт pub порт: u16, /// Временная метка последнего контакта pub метка_времени: u64, /// Сервисы узла pub сервисы: u64, } impl СетевойАдрес { /// Создать новый адрес pub fn новый(адрес: IpAddr, порт: u16) -> Self { let метка_времени = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs(); Self { адрес, порт, метка_времени, сервисы: 0, } } /// Проверить роутабельность адреса pub fn маршрутизируемый(&self) -> bool { match self.адрес { IpAddr::V4(ip) => { !ip.is_private() && !ip.is_loopback() && !ip.is_link_local() && !ip.is_broadcast() && !ip.is_documentation() && !ip.is_unspecified() } IpAddr::V6(ip) => { !ip.is_loopback() && !ip.is_unspecified() } } } /// Получить ключ группы (для бакетирования) pub fn ключ_группы(&self) -> [u8; 4] { match self.адрес { IpAddr::V4(ip) => { let октеты = ip.octets(); [октеты[0], октеты[1], 0, 0] } IpAddr::V6(ip) => { let сегменты = ip.segments(); let байты1 = сегменты[0].to_be_bytes(); let байты2 = сегменты[1].to_be_bytes(); [байты1[0], байты1[1], байты2[0], байты2[1]] } } } } // ═══════════════════════════════════════════════════════════════════════════════ // МЕНЕДЖЕР АДРЕСОВ // ═══════════════════════════════════════════════════════════════════════════════ /// Менеджер адресов /// Защита от Eclipse через криптографическое бакетирование pub struct МенеджерАдресов { /// Новые адреса (непроверенные) новые: HashMap>, /// Проверенные адреса проверенные: HashMap>, /// Секретный ключ для бакетирования секретный_ключ: [u8; 32], /// Количество новых бакетов количество_новых_бакетов: usize, /// Количество проверенных бакетов количество_проверенных_бакетов: usize, /// Размер бакета размер_бакета: usize, } impl МенеджерАдресов { /// Создать новый менеджер pub fn новый() -> Self { Self { новые: HashMap::new(), проверенные: HashMap::new(), секретный_ключ: случайные_байты(), количество_новых_бакетов: 1024, количество_проверенных_бакетов: 256, размер_бакета: 64, } } /// Вычислить бакет для адреса fn вычислить_бакет(&self, адрес: &СетевойАдрес, источник: &СетевойАдрес, новый: bool) -> usize { let mut данные = Vec::new(); данные.extend_from_slice(&self.секретный_ключ); данные.extend_from_slice(&адрес.ключ_группы()); данные.extend_from_slice(&источник.ключ_группы()); let хеш = хеш256(&данные); let количество = if новый { self.количество_новых_бакетов } else { self.количество_проверенных_бакетов }; (u64::from_le_bytes(хеш[0..8].try_into().unwrap()) as usize) % количество } /// Добавить адрес в таблицу новых pub fn добавить_новый(&mut self, адрес: СетевойАдрес, источник: &СетевойАдрес) -> bool { if !адрес.маршрутизируемый() { return false; } let бакет = self.вычислить_бакет(&адрес, источник, true); let адреса_бакета = self.новые.entry(бакет).or_insert_with(Vec::new); if адреса_бакета.len() >= self.размер_бакета { if let Some(индекс_старого) = адреса_бакета .iter() .enumerate() .min_by_key(|(_, а)| а.метка_времени) .map(|(и, _)| и) { адреса_бакета.remove(индекс_старого); } } if !адреса_бакета.contains(&адрес) { адреса_бакета.push(адрес); true } else { false } } /// Отметить адрес как хороший pub fn отметить_хорошим(&mut self, адрес: &СетевойАдрес) { for адреса_бакета in self.новые.values_mut() { if let Some(позиция) = адреса_бакета.iter().position(|а| а == адрес) { адреса_бакета.remove(позиция); break; } } let пустой_источник = СетевойАдрес::новый(адрес.адрес, 0); let бакет = self.вычислить_бакет(адрес, &пустой_источник, false); let адреса_бакета = self.проверенные.entry(бакет).or_insert_with(Vec::new); if адреса_бакета.len() < self.размер_бакета && !адреса_бакета.contains(адрес) { адреса_бакета.push(адрес.clone()); } } /// Выбрать адрес для подключения pub fn выбрать_для_подключения(&self) -> Option<СетевойАдрес> { let использовать_проверенные = rand::random::(); if использовать_проверенные { self.выбрать_из_проверенных().or_else(|| self.выбрать_из_новых()) } else { self.выбрать_из_новых().or_else(|| self.выбрать_из_проверенных()) } } /// Выбрать из проверенных fn выбрать_из_проверенных(&self) -> Option<СетевойАдрес> { if self.проверенные.is_empty() { return None; } let индекс_бакета = rand::random::() % self.количество_проверенных_бакетов; self.проверенные.get(&индекс_бакета).and_then(|адреса| { if адреса.is_empty() { None } else { let индекс = rand::random::() % адреса.len(); Some(адреса[индекс].clone()) } }) } /// Выбрать из новых fn выбрать_из_новых(&self) -> Option<СетевойАдрес> { if self.новые.is_empty() { return None; } let индекс_бакета = rand::random::() % self.количество_новых_бакетов; self.новые.get(&индекс_бакета).and_then(|адреса| { if адреса.is_empty() { None } else { let индекс = rand::random::() % адреса.len(); Some(адреса[индекс].clone()) } }) } /// Получить количество адресов pub fn количество(&self) -> (usize, usize) { let новых: usize = self.новые.values().map(|v| v.len()).sum(); let проверенных: usize = self.проверенные.values().map(|v| v.len()).sum(); (новых, проверенных) } } impl Default for МенеджерАдресов { fn default() -> Self { Self::новый() } } // ═══════════════════════════════════════════════════════════════════════════════ // СТАТИСТИКА ПИРА // ═══════════════════════════════════════════════════════════════════════════════ /// Идентификатор пира pub type ИдПира = [u8; 32]; /// Статистика пира #[derive(Clone, Debug, Default)] pub struct СтатистикаПира { /// Количество валидных сообщений pub валидных: u64, /// Количество невалидных сообщений pub невалидных: u64, /// Время последнего сообщения pub последнее_сообщение: u64, /// Средняя задержка (мс) pub средняя_задержка_мс: u64, } // ═══════════════════════════════════════════════════════════════════════════════ // РАСПРОСТРАНЕНИЕ ПОДПИСЕЙ // ═══════════════════════════════════════════════════════════════════════════════ /// Протокол распространения подписей pub struct РаспространениеПодписей { /// Очередь подписей для отправки очередь_исходящих: VecDeque<ДоказательствоПрисутствия>, /// Фильтр виденных подписей виденные: HashSet<[u8; 32]>, /// Статистика по пирам статистика_пиров: HashMap<ИдПира, СтатистикаПира>, /// Локальный пул подписей локальный_пул: Vec<ДоказательствоПрисутствия>, /// Максимум подписей в пуле максимум_пула: usize, } impl РаспространениеПодписей { /// Создать новый протокол pub fn новый() -> Self { Self { очередь_исходящих: VecDeque::new(), виденные: HashSet::new(), статистика_пиров: HashMap::new(), локальный_пул: Vec::new(), максимум_пула: 10_000, } } /// Обработать входящую подпись pub fn при_подписи(&mut self, подпись: ДоказательствоПрисутствия, от: ИдПира, текущий_τ2: u64) -> bool { let хеш_подписи = подпись.hash(); if self.виденные.contains(&хеш_подписи) { return false; } self.виденные.insert(хеш_подписи); if !подпись.verify(текущий_τ2) { if let Some(статистика) = self.статистика_пиров.get_mut(&от) { статистика.невалидных += 1; } return false; } if let Some(статистика) = self.статистика_пиров.get_mut(&от) { статистика.валидных += 1; статистика.последнее_сообщение = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs(); } if self.локальный_пул.len() < self.максимум_пула { self.локальный_пул.push(подпись.clone()); } self.очередь_исходящих.push_back(подпись); true } /// Получить следующую подпись для отправки pub fn следующая_исходящая(&mut self) -> Option<ДоказательствоПрисутствия> { self.очередь_исходящих.pop_front() } /// Зарегистрировать пира pub fn зарегистрировать_пира(&mut self, ид_пира: ИдПира) { self.статистика_пиров.insert(ид_пира, СтатистикаПира::default()); } /// Удалить пира pub fn удалить_пира(&mut self, ид_пира: &ИдПира) { self.статистика_пиров.remove(ид_пира); } /// Получить пул pub fn пул(&self) -> &[ДоказательствоПрисутствия] { &self.локальный_пул } /// Очистить пул pub fn очистить_пул(&mut self) { self.локальный_пул.clear(); self.виденные.clear(); } /// Получить статистику пира pub fn статистика_пира(&self, ид_пира: &ИдПира) -> Option<&СтатистикаПира> { self.статистика_пиров.get(ид_пира) } } impl Default for РаспространениеПодписей { fn default() -> Self { Self::новый() } } // ═══════════════════════════════════════════════════════════════════════════════ // ЗДОРОВЬЕ СЕТИ // ═══════════════════════════════════════════════════════════════════════════════ /// Метрики здоровья сети #[derive(Clone, Debug)] pub struct ЗдоровьеСети { /// Количество активных соединений pub соединений: usize, /// Количество различных подсетей pub подсетей: usize, /// Среднее время отклика (мс) pub средняя_задержка_мс: u64, /// Количество подписей за последний τ₂ pub подписей_за_τ2: usize, } impl ЗдоровьеСети { /// Оценка здоровья сети (0.0 - 1.0) pub fn оценка(&self) -> f64 { let лимиты = ЛимитыСоединений::default(); let оценка_соединений = (self.соединений as f64 / 10.0).min(1.0); let оценка_подсетей = (self.подсетей as f64 / лимиты.минимум_подсетей as f64).min(1.0); let оценка_задержки = if self.средняя_задержка_мс == 0 { 0.5 } else { (500.0 / self.средняя_задержка_мс as f64).min(1.0) }; (оценка_соединений + оценка_подсетей + оценка_задержки) / 3.0 } /// Сеть здорова? pub fn здорова(&self) -> bool { self.оценка() > 0.6 } } // ═══════════════════════════════════════════════════════════════════════════════ // P2P СООБЩЕНИЯ // ═══════════════════════════════════════════════════════════════════════════════ /// Типы P2P сообщений #[derive(Clone, Debug)] pub enum P2PСообщение { /// Рукопожатие Версия { версия: u32, сервисы: u64, метка_времени: u64, одноразовый_номер: u64, }, /// Подпись присутствия Присутствие(ДоказательствоПрисутствия), /// Адреса узлов Адреса(Vec<СетевойАдрес>), /// Запрос адресов ЗапросАдресов, /// Пинг Пинг(u64), /// Понг Понг(u64), } impl P2PСообщение { /// Получить тип сообщения pub fn тип(&self) -> &'static str { match self { Self::Версия { .. } => "ВЕРСИЯ", Self::Присутствие(_) => "ПРИСУТСТВИЕ", Self::Адреса(_) => "АДРЕСА", Self::ЗапросАдресов => "ЗАПРОС_АДРЕСОВ", Self::Пинг(_) => "ПИНГ", Self::Понг(_) => "ПОНГ", } } } // ═══════════════════════════════════════════════════════════════════════════════ // СОВМЕСТИМЫЕ ПСЕВДОНИМЫ // ═══════════════════════════════════════════════════════════════════════════════ pub use СетевойАдрес as NetAddr; pub use МенеджерАдресов as AddrManager; pub use РаспространениеПодписей as SignatureGossip; pub use ЗдоровьеСети as NetworkHealth; // Совместимость: английские методы для экономического модуля impl МенеджерАдресов { pub fn new() -> Self { Self::новый() } pub fn count(&self) -> (usize, usize) { self.количество() } } impl РаспространениеПодписей { pub fn new() -> Self { Self::новый() } } impl ЗдоровьеСети { pub fn connections(&self) -> usize { self.соединений } pub fn netgroups(&self) -> usize { self.подсетей } pub fn avg_latency_ms(&self) -> u64 { self.средняя_задержка_мс } pub fn signatures_per_tau2(&self) -> usize { self.подписей_за_τ2 } } // ═══════════════════════════════════════════════════════════════════════════════ // ТЕСТЫ // ═══════════════════════════════════════════════════════════════════════════════ #[cfg(test)] mod тесты { use super::*; use std::net::Ipv4Addr; #[test] fn тест_маршрутизируемости_адреса() { let публичный = СетевойАдрес::новый(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)), 8333); let приватный = СетевойАдрес::новый(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 8333); let локальный = СетевойАдрес::новый(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8333); assert!(публичный.маршрутизируемый()); assert!(!приватный.маршрутизируемый()); assert!(!локальный.маршрутизируемый()); } #[test] fn тест_менеджера_адресов() { let mut менеджер = МенеджерАдресов::новый(); let источник = СетевойАдрес::новый(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)), 8333); let адрес = СетевойАдрес::новый(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)), 8333); assert!(менеджер.добавить_новый(адрес.clone(), &источник)); let (новых, _) = менеджер.количество(); assert_eq!(новых, 1); } #[test] fn тест_здоровья_сети() { let здоровье = ЗдоровьеСети { соединений: 10, подсетей: 5, средняя_задержка_мс: 100, подписей_за_τ2: 1000, }; assert!(здоровье.здорова()); assert!(здоровье.оценка() > 0.6); } }