Files
xevion.dev/src/config.rs
Xevion 2a710d584e feat: add connection tarpit for malicious bot traffic
Implements slow-drip response handler for known bot paths (wp-admin, phpmyadmin, etc.) to waste attacker resources. Includes per-IP and global connection limits, configurable delays, and random chunking. Also applies clippy lint fixes across codebase.
2026-01-05 18:19:09 -06:00

127 lines
3.6 KiB
Rust

use clap::Parser;
use std::net::{SocketAddr, ToSocketAddrs};
use std::path::PathBuf;
use std::str::FromStr;
#[derive(Parser, Debug)]
#[command(name = "api")]
#[command(about = "xevion.dev API server with ISR caching", long_about = None)]
pub struct Args {
#[arg(long, env = "LISTEN_ADDR", value_delimiter = ',', required = true)]
pub listen: Vec<ListenAddr>,
#[arg(long, env = "DOWNSTREAM_URL", required = true)]
pub downstream: String,
#[arg(long, env = "TRUST_REQUEST_ID")]
pub trust_request_id: Option<String>,
}
#[derive(Debug, Clone)]
pub enum ListenAddr {
Tcp(SocketAddr),
Unix(PathBuf),
}
impl FromStr for ListenAddr {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with('/') || s.starts_with("./") {
return Ok(ListenAddr::Unix(PathBuf::from(s)));
}
if let Some(port_str) = s.strip_prefix(':') {
let port: u16 = port_str
.parse()
.map_err(|_| format!("Invalid port number: {port_str}"))?;
return Ok(ListenAddr::Tcp(SocketAddr::from(([127, 0, 0, 1], port))));
}
match s.parse::<SocketAddr>() {
Ok(addr) => Ok(ListenAddr::Tcp(addr)),
Err(_) => match s.to_socket_addrs() {
Ok(mut addrs) => addrs
.next()
.ok_or_else(|| format!("Could not resolve address: {s}"))
.map(ListenAddr::Tcp),
Err(_) => Err(format!(
"Invalid address '{s}'. Expected host:port, :port, or Unix socket path"
)),
},
}
}
}
impl std::fmt::Display for ListenAddr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ListenAddr::Tcp(addr) => write!(f, "{addr}"),
ListenAddr::Unix(path) => write!(f, "{}", path.display()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_shorthand_port() {
let addr: ListenAddr = ":8080".parse().unwrap();
match addr {
ListenAddr::Tcp(socket) => {
assert_eq!(socket.port(), 8080);
assert_eq!(socket.ip().to_string(), "127.0.0.1");
}
_ => panic!("Expected TCP address"),
}
}
#[test]
fn test_parse_ipv4() {
let addr: ListenAddr = "0.0.0.0:8080".parse().unwrap();
match addr {
ListenAddr::Tcp(socket) => {
assert_eq!(socket.port(), 8080);
assert_eq!(socket.ip().to_string(), "0.0.0.0");
}
_ => panic!("Expected TCP address"),
}
}
#[test]
fn test_parse_ipv6() {
let addr: ListenAddr = "[::]:8080".parse().unwrap();
match addr {
ListenAddr::Tcp(socket) => {
assert_eq!(socket.port(), 8080);
assert_eq!(socket.ip().to_string(), "::");
}
_ => panic!("Expected TCP address"),
}
}
#[test]
fn test_parse_unix_socket() {
let addr: ListenAddr = "/tmp/api.sock".parse().unwrap();
match addr {
ListenAddr::Unix(path) => {
assert_eq!(path, PathBuf::from("/tmp/api.sock"));
}
_ => panic!("Expected Unix socket"),
}
}
#[test]
fn test_parse_relative_unix_socket() {
let addr: ListenAddr = "./api.sock".parse().unwrap();
match addr {
ListenAddr::Unix(path) => {
assert_eq!(path, PathBuf::from("./api.sock"));
}
_ => panic!("Expected Unix socket"),
}
}
}