108 lines
3.5 KiB
Rust
108 lines
3.5 KiB
Rust
//! CLI entry point for sn-exit.
|
|
//!
|
|
//! KEINE GARANTIE — this software is provided "as is" without warranty of
|
|
//! any kind (AGPL-3.0, sections 15/16). Users are solely responsible for
|
|
//! what they transmit or receive with it.
|
|
|
|
use std::time::Duration;
|
|
|
|
use clap::Parser;
|
|
use sn_exit::config::{self, ExitConfig, QuotaPolicy, SourceAllowlist};
|
|
|
|
/// Per-neighbor quota is disabled when 0 is passed (no limit).
|
|
const QUOTA_DISABLED: u64 = 0;
|
|
|
|
#[derive(Debug, Parser)]
|
|
#[command(
|
|
name = "sn-exit",
|
|
about = "SpiderNet exit service: quota-controlled uplink proxy for neighbors"
|
|
)]
|
|
struct Args {
|
|
/// Address to listen on — ideally the household's Yggdrasil address,
|
|
/// e.g. `[200:1234::1]:8080`.
|
|
#[arg(long, default_value = "127.0.0.1:8080")]
|
|
bind: String,
|
|
|
|
/// Neighbor addresses allowed to use this exit (repeatable, e.g.
|
|
/// `200:5678::2`). Without any `--allow`, only loopback clients are
|
|
/// accepted (development mode).
|
|
#[arg(long = "allow", value_delimiter = ',')]
|
|
allow: Vec<String>,
|
|
|
|
/// Quota per neighbor per window, in MiB. `0` disables the quota.
|
|
#[arg(long, default_value_t = 1024)]
|
|
quota_mib: u64,
|
|
|
|
/// Quota window in hours (fixed window per neighbor).
|
|
#[arg(long, default_value_t = 24)]
|
|
window_hours: u64,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
tracing_subscriber::fmt()
|
|
.with_env_filter(
|
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
|
|
)
|
|
.init();
|
|
|
|
if let Err(report) = run().await {
|
|
eprintln!("NO WARRANTY — SpiderNet sn-exit (AGPL-3.0, no liability)");
|
|
eprintln!("error: {report}");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
|
|
async fn run() -> Result<(), String> {
|
|
let args = Args::parse();
|
|
|
|
let bind = config::parse_bind(&args.bind).map_err(|err| err.to_string())?;
|
|
let allowlist = if args.allow.is_empty() {
|
|
SourceAllowlist::loopback_only()
|
|
} else {
|
|
let sources = args
|
|
.allow
|
|
.iter()
|
|
.map(|addr| config::parse_source(addr).map_err(|err| err.to_string()))
|
|
.collect::<Result<Vec<_>, String>>()?;
|
|
SourceAllowlist::new(sources)
|
|
};
|
|
let quota = if args.quota_mib == QUOTA_DISABLED {
|
|
None
|
|
} else {
|
|
let max_bytes = args.quota_mib.saturating_mul(1024 * 1024);
|
|
Some(
|
|
QuotaPolicy::try_new(max_bytes, Duration::from_secs(args.window_hours * 3600))
|
|
.map_err(|err| err.to_string())?,
|
|
)
|
|
};
|
|
|
|
let config = ExitConfig::new(bind, allowlist, quota);
|
|
let listener = tokio::net::TcpListener::bind(config.bind())
|
|
.await
|
|
.map_err(|err| format!("cannot bind {}: {err}", config.bind()))?;
|
|
|
|
println!("NO WARRANTY — SpiderNet sn-exit (AGPL-3.0, no liability)");
|
|
println!();
|
|
if config.allowlist().is_loopback_only() {
|
|
println!("DEV MODE: no --allow given — only loopback clients are accepted.");
|
|
}
|
|
match config.quota() {
|
|
Some(policy) => println!(
|
|
"quota: {} MiB per {}h per neighbor (enforced at connection start)",
|
|
policy.max_bytes() / (1024 * 1024),
|
|
policy.window().as_secs() / 3600,
|
|
),
|
|
None => println!("quota: disabled — neighbors may use unlimited uplink traffic"),
|
|
}
|
|
println!(
|
|
"sharing: this household now offers its uplink (stop sn-exit to revoke = kill-switch)"
|
|
);
|
|
println!("listen: {}", config.bind());
|
|
|
|
sn_exit::proxy::serve(config, listener)
|
|
.await
|
|
.map_err(|err| err.to_string())
|
|
}
|