montana/Русский/Сеть/исходники/lib.rs

558 lines
24 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! # 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<usize, Vec<СетевойАдрес>>,
/// Проверенные адреса
проверенные: HashMap<usize, Vec<СетевойАдрес>>,
/// Секретный ключ для бакетирования
секретный_ключ: [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::<bool>();
if использовать_проверенные {
self.выбрать_из_проверенных().or_else(|| self.выбрать_из_новых())
} else {
self.выбрать_из_новых().or_else(|| self.выбрать_из_проверенных())
}
}
/// Выбрать из проверенных
fn выбрать_из_проверенных(&self) -> Option<СетевойАдрес> {
if self.проверенные.is_empty() {
return None;
}
let индекс_бакета = rand::random::<usize>() % self.количество_проверенных_бакетов;
self.проверенные.get(&индекс_бакета).and_then(|адреса| {
if адреса.is_empty() {
None
} else {
let индекс = rand::random::<usize>() % адреса.len();
Some(адреса[индекс].clone())
}
})
}
/// Выбрать из новых
fn выбрать_из_новых(&self) -> Option<СетевойАдрес> {
if self.новые.is_empty() {
return None;
}
let индекс_бакета = rand::random::<usize>() % self.количествоовых_бакетов;
self.новые.get(&индекс_бакета).and_then(|адреса| {
if адреса.is_empty() {
None
} else {
let индекс = rand::random::<usize>() % адреса.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);
}
}