feat: serve multiple domains with per-domain keys, ports and databases
This commit is contained in:
parent
931933c423
commit
dfb57b265c
8 changed files with 891 additions and 120 deletions
117
src/server.rs
117
src/server.rs
|
|
@ -1,11 +1,12 @@
|
|||
//! TCP accept loop, per-connection handling, and the background purge loop.
|
||||
|
||||
use std::net::TcpStream;
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::bind::TransportBindValues;
|
||||
use crate::channel::{handshake, Channel};
|
||||
use crate::config::DomainConfig;
|
||||
use crate::crypto::{b32, derive_public};
|
||||
use crate::proto::{FETCH_BUDGET, HEADER_TIMEOUT_SECS, KEY_LEN, MALFORMED, PURGE_INTERVAL_SECS};
|
||||
use crate::ratelimit::RateLimiter;
|
||||
|
|
@ -29,41 +30,97 @@ pub struct ServeArgs {
|
|||
pub rate_tokens: u32,
|
||||
}
|
||||
|
||||
/// The single-domain CLI path: one anonymous domain.
|
||||
pub fn run(args: ServeArgs) -> anyhow::Result<()> {
|
||||
let static_key = std::fs::read(&args.key_path)
|
||||
.map_err(|e| anyhow::anyhow!("cannot read server key: {e}"))?;
|
||||
anyhow::ensure!(
|
||||
static_key.len() == KEY_LEN,
|
||||
"server key must be {KEY_LEN} raw bytes, got {}",
|
||||
static_key.len()
|
||||
);
|
||||
|
||||
let key_array: [u8; KEY_LEN] = static_key.clone().try_into().unwrap();
|
||||
let server_static = derive_public(&key_array);
|
||||
|
||||
let config = Arc::new(ServerConfig {
|
||||
serve_domains(vec![DomainConfig {
|
||||
name: "default".to_string(),
|
||||
key_path: args.key_path,
|
||||
db_path: args.db_path,
|
||||
host: args.host,
|
||||
port: args.port,
|
||||
max_envelope: args.max_envelope,
|
||||
fetch_budget: FETCH_BUDGET,
|
||||
main_quota: args.quota,
|
||||
quota: args.quota,
|
||||
requests_quota: args.requests_quota,
|
||||
retention_days: args.retention_days,
|
||||
requests_retention_days: args.requests_retention_days,
|
||||
max_tokens: args.max_tokens,
|
||||
invite_token: args.invite_token.map(String::into_bytes),
|
||||
conn_limiter: RateLimiter::new(args.rate_connections),
|
||||
send_limiter: RateLimiter::new(args.rate_sends),
|
||||
token_limiter: RateLimiter::new(args.rate_tokens),
|
||||
});
|
||||
rate_connections: args.rate_connections,
|
||||
rate_sends: args.rate_sends,
|
||||
rate_tokens: args.rate_tokens,
|
||||
}])
|
||||
}
|
||||
|
||||
let main_retention_secs = args.retention_days * 86400;
|
||||
let requests_retention_secs = args.requests_retention_days * 86400;
|
||||
let purge_db_path = args.db_path.clone();
|
||||
std::thread::spawn(move || {
|
||||
purge_loop(purge_db_path, main_retention_secs, requests_retention_secs)
|
||||
});
|
||||
/// Serves every domain: one listener, key, mailbox database, config and
|
||||
/// purge loop per domain, nothing shared between them.
|
||||
pub fn serve_domains(domains: Vec<DomainConfig>) -> anyhow::Result<()> {
|
||||
// Read every key and bind every listener first, so a missing key or a
|
||||
// taken port fails the whole process before any domain serves.
|
||||
let mut bound = Vec::with_capacity(domains.len());
|
||||
for domain in &domains {
|
||||
let static_key = read_static_key(&domain.key_path)?;
|
||||
let listener = TcpListener::bind((domain.host.as_str(), domain.port))?;
|
||||
let config = Arc::new(ServerConfig {
|
||||
max_envelope: domain.max_envelope,
|
||||
fetch_budget: FETCH_BUDGET,
|
||||
main_quota: domain.quota,
|
||||
requests_quota: domain.requests_quota,
|
||||
max_tokens: domain.max_tokens,
|
||||
invite_token: domain.invite_token.clone(),
|
||||
conn_limiter: RateLimiter::new(domain.rate_connections),
|
||||
send_limiter: RateLimiter::new(domain.rate_sends),
|
||||
token_limiter: RateLimiter::new(domain.rate_tokens),
|
||||
});
|
||||
bound.push((domain, static_key, listener, config));
|
||||
}
|
||||
|
||||
let listener = std::net::TcpListener::bind((args.host.as_str(), args.port))?;
|
||||
log::info!("listening on {}:{}", args.host, args.port);
|
||||
log::info!("server public key: {}", b32(&server_static));
|
||||
for (domain, static_key, listener, config) in bound {
|
||||
log::info!(
|
||||
"domain {}: listening on {}:{}",
|
||||
domain.name,
|
||||
domain.host,
|
||||
domain.port
|
||||
);
|
||||
log::info!(
|
||||
"domain {}: server public key: {}",
|
||||
domain.name,
|
||||
b32(&derive_public(&static_key))
|
||||
);
|
||||
|
||||
let purge_db_path = domain.db_path.clone();
|
||||
let main_retention_secs = domain.retention_days * 86400;
|
||||
let requests_retention_secs = domain.requests_retention_days * 86400;
|
||||
std::thread::spawn(move || {
|
||||
purge_loop(purge_db_path, main_retention_secs, requests_retention_secs)
|
||||
});
|
||||
|
||||
let db_path = domain.db_path.clone();
|
||||
std::thread::spawn(move || accept_loop(listener, config, static_key, db_path));
|
||||
}
|
||||
|
||||
// Each domain's accept loop runs in its own thread; nothing fails here.
|
||||
loop {
|
||||
std::thread::sleep(Duration::from_secs(3600));
|
||||
}
|
||||
}
|
||||
|
||||
fn read_static_key(path: &str) -> anyhow::Result<[u8; KEY_LEN]> {
|
||||
let key =
|
||||
std::fs::read(path).map_err(|e| anyhow::anyhow!("cannot read server key {path}: {e}"))?;
|
||||
anyhow::ensure!(
|
||||
key.len() == KEY_LEN,
|
||||
"server key {path} must be {KEY_LEN} raw bytes, got {}",
|
||||
key.len()
|
||||
);
|
||||
Ok(key.try_into().unwrap())
|
||||
}
|
||||
|
||||
fn accept_loop(
|
||||
listener: TcpListener,
|
||||
config: Arc<ServerConfig>,
|
||||
static_key: [u8; KEY_LEN],
|
||||
db_path: String,
|
||||
) {
|
||||
for incoming in listener.incoming() {
|
||||
let stream = match incoming {
|
||||
Ok(s) => s,
|
||||
|
|
@ -83,15 +140,13 @@ pub fn run(args: ServeArgs) -> anyhow::Result<()> {
|
|||
}
|
||||
|
||||
let config = Arc::clone(&config);
|
||||
let static_key = key_array;
|
||||
let db_path = args.db_path.clone();
|
||||
let db_path = db_path.clone();
|
||||
std::thread::spawn(move || {
|
||||
if let Err(e) = handle_connection(stream, &config, &static_key, &db_path, &peer_ip) {
|
||||
log::info!("connection error from {peer_ip}: {e}");
|
||||
}
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Also the hostile harness's entry point: it drives real connections
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue