diff --git a/Cargo.lock b/Cargo.lock index e2dff4f..a8dcddb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -463,6 +463,7 @@ dependencies = [ "chacha20poly1305", "clap", "dashmap", + "directories", "ed25519-dalek", "env_logger", "futures", @@ -475,6 +476,8 @@ dependencies = [ "russh-keys 0.45.0", "rustls", "rustls-pemfile", + "sea-orm", + "sea-orm-migration", "serde", "tokio", "toml 0.9.8", @@ -2030,13 +2033,34 @@ dependencies = [ "zeroize", ] +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys 0.4.1", +] + [[package]] name = "dirs" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys", + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.4.6", + "windows-sys 0.48.0", ] [[package]] @@ -2047,7 +2071,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users", + "redox_users 0.5.2", "windows-sys 0.61.2", ] @@ -4106,6 +4130,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ + "cc", "pkg-config", "vcpkg", ] @@ -4234,6 +4259,17 @@ dependencies = [ "time", ] +[[package]] +name = "mac_address" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" +dependencies = [ + "nix 0.29.0", + "serde", + "winapi", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -4598,6 +4634,19 @@ dependencies = [ "libc", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + [[package]] name = "nix" version = "0.30.1" @@ -6172,6 +6221,17 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -6917,9 +6977,9 @@ dependencies = [ [[package]] name = "sea-orm" -version = "1.1.19" +version = "1.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d945f62558fac19e5988680d2fdf747b734c2dbc6ce2cb81ba33ed8dde5b103" +checksum = "2dc312fedd460a47ea563911761d254a84e7b51d8cc73ec92c929e78f33fa957" dependencies = [ "async-stream", "async-trait", @@ -6928,6 +6988,7 @@ dependencies = [ "derive_more", "futures-util", "log", + "mac_address", "ouroboros", "pgvector", "rust_decimal", @@ -6947,9 +7008,9 @@ dependencies = [ [[package]] name = "sea-orm-cli" -version = "1.1.19" +version = "1.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c94492e2ab6c045b4cc38013809ce255d14c3d352c9f0d11e6b920e2adc948ad" +checksum = "da80ebcdb44571e86f03a2bdcb5532136a87397f366f38bbce64673fc5e6a450" dependencies = [ "chrono", "clap", @@ -6963,9 +7024,9 @@ dependencies = [ [[package]] name = "sea-orm-macros" -version = "1.1.19" +version = "1.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c2e64a50a9cc8339f10a27577e10062c7f995488e469f2c95762c5ee847832" +checksum = "9b9a3f90e336ec74803e8eb98c61bc98754c1adfba3b4f84d946237b752b1c88" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -6977,9 +7038,9 @@ dependencies = [ [[package]] name = "sea-orm-migration" -version = "1.1.19" +version = "1.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7315c0cadb7e60fb17ee2bb282aa27d01911fc2a7e5836ec1d4ac37d19250bb4" +checksum = "07c577f2959277e936c1d08109acd1e08fc36a95ef29ec028190ba82cad8f96e" dependencies = [ "async-trait", "clap", diff --git a/anet-server/.vscode/launch.json b/anet-server/.vscode/launch.json new file mode 100644 index 0000000..12bcef3 --- /dev/null +++ b/anet-server/.vscode/launch.json @@ -0,0 +1,61 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug anet-server", + "cargo": { + "args": [ + "build", + "--bin", + "anet-server" + ], + "filter": { + "name": "anet-server", + "kind": "bin" + } + }, + "args": [ + "-c", + "/home/lis/Documents/coded/anet-fork/contrib/dev/server.toml" + ], + "cwd": "${workspaceFolder}" + }, + { + "name": "Attach anet-server", + "type": "lldb", + "request": "attach", + "program": "${workspaceFolder}/target/debug/anet-server", + "pid": "${command:pickProcess}" + }, +{ + "name": "Debug anet-server (sudo)", + "type": "lldb", + "request": "launch", + + "program": "/home/lis/Documents/coded/anet-fork/target/debug/anet-server", + + "args": [ + "-c", + "/home/lis/Documents/coded/anet-fork/contrib/dev/server.toml" + ], + + "cwd": "/home/lis/Documents/coded/anet-fork", + + "preLaunchTask": "cargo build", + + "initCommands": [ + "platform select remote-linux" + ], + + "pipeTransport": { + "pipeProgram": "sudo", + "pipeArgs": [ + "-E" + ], + "debuggerPath": "/usr/bin/lldb-server" + } + } + ] +} \ No newline at end of file diff --git a/anet-server/Cargo.toml b/anet-server/Cargo.toml index 6fe81b6..67ad302 100644 --- a/anet-server/Cargo.toml +++ b/anet-server/Cargo.toml @@ -25,6 +25,10 @@ ed25519-dalek = {workspace = true} async-trait = { workspace = true } futures = { workspace = true } +sea-orm = { version = "1", features = ["sqlx-postgres", "sqlx-sqlite", "runtime-tokio-rustls"] } + +directories = "5" + russh = "0.45" russh-keys = "0.45" @@ -33,6 +37,7 @@ arc-swap = "1.6" reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } anet-common = { path = "../anet-common" } +sea-orm-migration = "1.1.20" [[bin]] name = "anet-server" diff --git a/anet-server/src/auth_handler.rs b/anet-server/src/auth_handler.rs index 3578881..eca0dbe 100644 --- a/anet-server/src/auth_handler.rs +++ b/anet-server/src/auth_handler.rs @@ -184,7 +184,7 @@ impl ServerAuthHandler { }; if req.client_id != temp_info.client_fingerprint { return Err(anyhow::anyhow!("Client ID mismatch")); } - let assigned_ip = self.registry.allocate_ip().context("IP POOL FOOL")?.to_string(); + let assigned_ip = self.registry.allocate_ip(temp_info.client_fingerprint.clone()).await.context("IP POOL FOOL")?.to_string(); let session_id = generate_seid(); let nonce_prefix = generate_unique_nonce_prefix(self.registry.clone()); diff --git a/anet-server/src/client_registry.rs b/anet-server/src/client_registry.rs index a0affdf..ff91e1e 100644 --- a/anet-server/src/client_registry.rs +++ b/anet-server/src/client_registry.rs @@ -73,14 +73,9 @@ impl ClientRegistry { let client_ip = &client_info.assigned_ip; let remote_addr = **client_info.remote_addr.load(); - self.quic_router.remove(client_ip); self.clients_by_prefix.remove(&client_info.nonce_prefix); self.clients_by_addr.remove(&remote_addr); - if let Ok(ip_addr) = client_ip.parse::() { - self.ip_pool.release(ip_addr); - } - // dec sessions let ap = self.auth_provider.clone(); let fp = client_info.fingerprint.clone(); @@ -92,8 +87,8 @@ impl ClientRegistry { info!("[Registry] Client {} removed.", client_ip); } - pub fn allocate_ip(&self) -> Option { - self.ip_pool.allocate() + pub async fn allocate_ip(&self, fingerprint: String) -> Result { + self.ip_pool.allocate(fingerprint).await } pub fn get_by_addr(&self, remote_addr: &SocketAddr) -> Option> { diff --git a/anet-server/src/config.rs b/anet-server/src/config.rs index 310d8be..21453c2 100644 --- a/anet-server/src/config.rs +++ b/anet-server/src/config.rs @@ -97,6 +97,7 @@ pub struct ServerCoreConfig { pub ssh_bind_to: String, pub vnc_bind_to: String, pub ssh_host_key: String, + pub db_url: Option, } impl Default for ServerCoreConfig { @@ -106,6 +107,7 @@ impl Default for ServerCoreConfig { ssh_bind_to: "0.0.0.0:822".to_string(), vnc_bind_to: "0.0.0.0:5900".to_string(), ssh_host_key: "/etc/ssh/ssh_host_rsa_key".to_string(), + db_url: None, } } } @@ -129,6 +131,7 @@ pub struct Config { #[serde(default)] pub stealth: StealthConfig, + } #[derive(Debug, Parser)] diff --git a/anet-server/src/db.rs b/anet-server/src/db.rs new file mode 100644 index 0000000..33586f1 --- /dev/null +++ b/anet-server/src/db.rs @@ -0,0 +1,43 @@ +use anyhow::Result; +use directories::ProjectDirs; +use sea_orm::{Database, DatabaseConnection}; +use std::fs; +use sea_orm_migration::MigratorTrait; + +use crate::migration; + +#[derive(Clone)] +pub struct AnetDB { +} + +impl AnetDB { + + pub async fn connect_db(url : Option) -> Result { + let db_url = match url{ + Some(url) => url, + None => { + // ~/.local/share/myapp/ + let proj_dirs = ProjectDirs::from("org", "alco","anet") + .expect("failed to get project dirs"); + + let data_dir = proj_dirs.data_dir(); + + fs::create_dir_all(data_dir).expect("failed to create data dir"); + + let db_path = data_dir.join("db.sqlite"); + + format!("sqlite://{}?mode=rwc", db_path.display()) + } + }; + + println!("DB: {}", db_url); + + + let db: DatabaseConnection = + Database::connect(db_url) + .await?; + + migration::Migrator::up(&db, None).await?; + Ok(db) + } +} diff --git a/anet-server/src/entities/clients.rs b/anet-server/src/entities/clients.rs new file mode 100644 index 0000000..4c6cae3 --- /dev/null +++ b/anet-server/src/entities/clients.rs @@ -0,0 +1,17 @@ +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] +#[sea_orm(table_name = "clients")] +pub struct Model { + #[sea_orm(primary_key, column_type = "Text")] + pub fingerprint: String, + pub ip: i64, + + pub created_at: DateTime, + pub updated_at: DateTime, +} +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} \ No newline at end of file diff --git a/anet-server/src/entities/mod.rs b/anet-server/src/entities/mod.rs new file mode 100644 index 0000000..a84242e --- /dev/null +++ b/anet-server/src/entities/mod.rs @@ -0,0 +1 @@ +pub mod clients; \ No newline at end of file diff --git a/anet-server/src/ip_pool.rs b/anet-server/src/ip_pool.rs index 44ea355..33eb824 100644 --- a/anet-server/src/ip_pool.rs +++ b/anet-server/src/ip_pool.rs @@ -1,6 +1,8 @@ -use dashmap::DashSet; +use anyhow::Error; +use sea_orm::{ActiveValue::Set, DatabaseConnection, QuerySelect, TransactionTrait, sqlx::types::chrono}; use std::net::Ipv4Addr; -use std::sync::Arc; +use crate::entities::clients; +use sea_orm::{EntityTrait}; #[derive(Clone)] pub struct IpPool { @@ -9,7 +11,7 @@ pub struct IpPool { pub gateway: Ipv4Addr, pub server: Ipv4Addr, pub mtu: u16, - used: Arc>, + db: DatabaseConnection, } impl IpPool { @@ -19,6 +21,7 @@ impl IpPool { gateway: Ipv4Addr, server: Ipv4Addr, mtu: u16, + db: DatabaseConnection, ) -> Self { Self { network, @@ -26,38 +29,104 @@ impl IpPool { gateway, server, mtu, - used: Arc::new(DashSet::new()), + db, } } - pub fn allocate(&self) -> Option { + pub async fn allocate( + &self, + fingerprint: String, + ) -> Result { + + let txn = self.db.begin().await?; + + // 1. уже есть IP + if let Some(ip) = self.get_ip(&txn, fingerprint.clone()).await? { + txn.commit().await?; + return Ok(ip); + } + + // 2. ищем свободный + let ip = self.assign_ip(&txn).await?; + + // 3. сохраняем + self.set_ip(&txn, fingerprint, ip).await?; + + txn.commit().await?; + + Ok(ip) + } + + async fn get_ip( + &self, + txn: &sea_orm::DatabaseTransaction, + fingerprint: String, + ) -> Result, Error> { + + let model = clients::Entity::find_by_id(fingerprint) + .one(txn) + .await?; + + Ok(model.map(|m| Ipv4Addr::from(m.ip as u32))) + } + + async fn assign_ip( + &self, + txn: &sea_orm::DatabaseTransaction, + ) -> Result { + + let used: Vec = clients::Entity::find() + .select_only() + .column(clients::Column::Ip) + .into_tuple() + .all(txn) + .await?; + + let used: std::collections::HashSet = + used.into_iter().map(|v| v as u32).collect(); + let net = u32::from(self.network); let mask = u32::from(self.netmask); - let gw = self.gateway; - let srv = self.server; - for host in 1..=u32::MAX { + for host in 1..=254u32 { let candidate = net | host; + if (candidate & mask) != (net & mask) { break; } + let ip = Ipv4Addr::from(candidate); - if ip == gw || ip == srv { + if ip == self.gateway || ip == self.server { continue; } - if self.used.contains(&ip) { + + if used.contains(&candidate) { continue; } - self.used.insert(ip); - return Some(ip); + return Ok(ip); } - None + + Err(anyhow::anyhow!("no free IPs in pool")) } - pub fn release(&self, ip: Ipv4Addr) -> bool { - self.used.remove(&ip); - self.used.contains(&ip) + async fn set_ip( + &self, + txn: &sea_orm::DatabaseTransaction, + fingerprint: String, + ip: Ipv4Addr, + ) -> Result<(), Error> { + + clients::Entity::insert(clients::ActiveModel { + fingerprint: Set(fingerprint), + ip: Set(u32::from(ip) as i64), + created_at: Set(chrono::Utc::now().naive_utc()), + updated_at: Set(chrono::Utc::now().naive_utc()), + }) + .exec(txn) + .await?; + + Ok(()) } } diff --git a/anet-server/src/lib.rs b/anet-server/src/lib.rs index c7fc051..4d32ba8 100644 --- a/anet-server/src/lib.rs +++ b/anet-server/src/lib.rs @@ -7,3 +7,6 @@ pub(crate) mod multikey_udp_socket; pub mod server; pub(crate) mod utils; pub mod servers; +pub mod db; +pub(crate) mod migration; +pub(crate) mod entities; diff --git a/anet-server/src/main.rs b/anet-server/src/main.rs index dbfd593..e4f7a7c 100644 --- a/anet-server/src/main.rs +++ b/anet-server/src/main.rs @@ -1,7 +1,9 @@ use anet_server::config::load; use anet_server::server; use anyhow::Result; +use sea_orm::DatabaseConnection; use server::ANetServer; +use anet_server::db::AnetDB; // #[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "multi_thread", worker_threads = 4)] @@ -17,7 +19,10 @@ async fn main() -> Result<()> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); let cfg = load().await?; - let mut server = ANetServer::new(&cfg)?; + let db: DatabaseConnection = AnetDB::connect_db(cfg.server.db_url.clone()).await?; + + + let mut server = ANetServer::new(&cfg, db)?; server.run().await?; Ok(()) diff --git a/anet-server/src/migration/m20260513_132524_create_leases.rs b/anet-server/src/migration/m20260513_132524_create_leases.rs new file mode 100644 index 0000000..b554368 --- /dev/null +++ b/anet-server/src/migration/m20260513_132524_create_leases.rs @@ -0,0 +1,59 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + + manager + .create_table( + Table::create() + .table(Clients::Table) + .if_not_exists() + .col( + ColumnDef::new(Clients::Fingerprint) + .string() + .not_null() + .primary_key() + ) + .col( + ColumnDef::new(Clients::Ip) + .big_integer() + .not_null() + .unique_key() + ) + .col( + ColumnDef::new(Clients::CreatedAt) + .timestamp() + .not_null() + .default(Expr::current_timestamp()) + ) + .col( + ColumnDef::new(Clients::UpdatedAt) + .timestamp() + .not_null() + .default(Expr::current_timestamp()) + ) + .to_owned() + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + + manager + .drop_table(Table::drop().table(Clients::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Clients { + Table, + Fingerprint, + Ip, + CreatedAt, + UpdatedAt, +} diff --git a/anet-server/src/migration/mod.rs b/anet-server/src/migration/mod.rs new file mode 100644 index 0000000..8a6fefb --- /dev/null +++ b/anet-server/src/migration/mod.rs @@ -0,0 +1,14 @@ +mod m20260513_132524_create_leases; + +use sea_orm_migration::prelude::*; + +pub struct Migrator; + +#[async_trait::async_trait] +impl MigratorTrait for Migrator { + fn migrations() -> Vec> { + vec![ + Box::new(m20260513_132524_create_leases::Migration), + ] + } +} \ No newline at end of file diff --git a/anet-server/src/server.rs b/anet-server/src/server.rs index cc8a54e..b503fec 100644 --- a/anet-server/src/server.rs +++ b/anet-server/src/server.rs @@ -13,6 +13,7 @@ use base64::prelude::*; use dashmap::DashMap; use ed25519_dalek::SigningKey; use log::error; +use sea_orm::DatabaseConnection; use std::sync::Arc; use std::time::Duration; @@ -25,7 +26,7 @@ pub struct ANetServer { } impl ANetServer { - pub fn new(cfg_ref: &Config) -> Result { + pub fn new(cfg_ref: &Config, db: DatabaseConnection) -> Result { let t_prm = TunParams { netmask: cfg_ref.network.mask.parse()?, gateway: cfg_ref.network.gateway.parse()?, address: cfg_ref.network.self_ip.parse()?, name: cfg_ref.network.if_name.clone(), @@ -35,6 +36,7 @@ impl ANetServer { let pl = IpPool::new( cfg_ref.network.net.parse()?, cfg_ref.network.mask.parse()?, cfg_ref.network.gateway.parse()?, cfg_ref.network.self_ip.parse()?, cfg_ref.network.mtu, + db ); let sk_bytes = BASE64_STANDARD.decode(&cfg_ref.crypto.server_signing_key)?;