// SPDX-FileCopyrightText: 2026 Dilluti0n // SPDX-License-Identifier: GPL-3.0-or-later use anyhow::{Result, anyhow, Context}; use std::sync::OnceLock; use crate::log_println; use crate::log; use log::LogLevel; static OPT_LOG_LEVEL: OnceLock = OnceLock::new(); static OPT_NO_SPLASH: OnceLock = OnceLock::new(); static OPT_FAKE: OnceLock = OnceLock::new(); static OPT_FAKE_TTL: OnceLock = OnceLock::new(); static OPT_FAKE_AUTOTTL: OnceLock = OnceLock::new(); static OPT_FAKE_BADSUM: OnceLock = OnceLock::new(); static OPT_DELAY_MS: OnceLock = OnceLock::new(); #[cfg(target_os = "linux")] static OPT_QUEUE_NUM: OnceLock = OnceLock::new(); #[cfg(target_os = "linux")] static OPT_NFT_COMMAND: OnceLock = OnceLock::new(); const DEFAULT_DAEMON: bool = false; #[cfg(debug_assertions)] const DEFAULT_LOG_LEVEL: LogLevel = LogLevel::Debug; #[cfg(not(debug_assertions))] const DEFAULT_LOG_LEVEL: LogLevel = LogLevel::Warning; const DEFAULT_NO_SPLASH: bool = false; const DEFAULT_FAKE: bool = false; const DEFAULT_FAKE_TTL: u8 = 8; const DEFAULT_FAKE_AUTOTTL: bool = false; const DEFAULT_FAKE_BADSUM: bool = false; const DEFAULT_DELAY_MS: u64 = 0; #[cfg(target_os = "linux")] const DEFAULT_QUEUE_NUM: u16 = 1; #[cfg(target_os = "linux")] const DEFAULT_NFT_COMMAND: &str = "nft"; pub struct Opt { pub daemon: bool, log_level: LogLevel, no_splash: bool, fake: bool, fake_ttl: u8, fake_autottl: bool, fake_badsum: bool, delay_ms: u64, #[cfg(target_os = "linux")] queue_num: u16, #[cfg(target_os = "linux")] nft_command: String, } impl Opt { pub fn from_args() -> Result { let mut daemon = DEFAULT_DAEMON; let mut log_level = DEFAULT_LOG_LEVEL; let mut delay_ms = DEFAULT_DELAY_MS; let mut no_splash = DEFAULT_NO_SPLASH; let mut fake = DEFAULT_FAKE; let mut fake_ttl = DEFAULT_FAKE_TTL; let mut fake_autottl = DEFAULT_FAKE_AUTOTTL; let mut fake_badsum = DEFAULT_FAKE_BADSUM; #[cfg(target_os = "linux")] let mut queue_num: u16 = DEFAULT_QUEUE_NUM; #[cfg(target_os = "linux")] let mut nft_command = String::from(DEFAULT_NFT_COMMAND); let mut args = std::env::args().skip(1); // program name let mut warned_loglevel_deprecated = false; while let Some(arg) = args.next() { let argv = arg.as_str(); match argv { "-h" | "--help" => { usage(); std::process::exit(0); } "-D" | "--daemon" => { no_splash = true; // if it is unchanged explicitly by argument, set it to info if log_level == DEFAULT_LOG_LEVEL { log_level = LogLevel::Info; } daemon = true; } "--delay-ms" => { delay_ms = take_value(&mut args, argv)?; } "--log-level" | "--loglevel" => { if argv == "--loglevel" && !warned_loglevel_deprecated { // FIXME(on release): remove this on v1.0.0 warned_loglevel_deprecated = true; eprintln!("Note: `{arg}' has been deprecated since v0.1.1. Use `--log-level' instead."); } log_level = take_value(&mut args, argv)?; } "--no-splash" => { no_splash = true; } "--fake" => { fake = true; } "--fake-ttl" => { fake = true; fake_ttl = take_value(&mut args, argv)?; } "--fake-autottl" => { fake = true; fake_autottl = true } "--fake-badsum" => { fake = true; fake_badsum = true } #[cfg(target_os = "linux")] "--queue-num" => { queue_num = take_value(&mut args, argv)?; } #[cfg(target_os = "linux")] "--nft-command" => { nft_command = take_value(&mut args, argv)?; } _ => { return Err(anyhow!("argument: unknown: {}", arg)); } } } Ok(Opt { daemon: daemon, log_level: log_level, no_splash: no_splash, fake: fake, fake_ttl: fake_ttl, fake_autottl: fake_autottl, fake_badsum: fake_badsum, delay_ms: delay_ms, #[cfg(target_os = "linux")] queue_num: queue_num, #[cfg(target_os = "linux")] nft_command: nft_command, }) } pub fn set_opt(self) -> Result<()> { set_opt("OPT_LOG_LEVEL", &OPT_LOG_LEVEL, self.log_level)?; set_opt("OPT_NO_SPLASH", &OPT_NO_SPLASH, self.no_splash)?; set_opt("OPT_DELAY_MS", &OPT_DELAY_MS, self.delay_ms)?; set_opt("OPT_FAKE", &OPT_FAKE, self.fake)?; set_opt("OPT_FAKE_TTL", &OPT_FAKE_TTL, self.fake_ttl)?; set_opt("OPT_FAKE_AUTOTTL", &OPT_FAKE_AUTOTTL, self.fake_autottl)?; set_opt("OPT_FAKE_BADSUM", &OPT_FAKE_BADSUM, self.fake_badsum)?; #[cfg(target_os = "linux")] set_opt("OPT_QUEUE_NUM", &OPT_QUEUE_NUM, self.queue_num)?; #[cfg(target_os = "linux")] set_opt("OPT_NFT_COMMAND", &OPT_NFT_COMMAND, self.nft_command)?; Ok(()) } } pub fn no_splash() -> bool { *OPT_NO_SPLASH.get().unwrap_or(&DEFAULT_NO_SPLASH) } pub fn log_level() -> LogLevel { *OPT_LOG_LEVEL.get().unwrap_or(&DEFAULT_LOG_LEVEL) } pub fn fake() -> bool { *OPT_FAKE.get().unwrap_or(&DEFAULT_FAKE) } pub fn fake_ttl() -> u8 { *OPT_FAKE_TTL.get().unwrap_or(&DEFAULT_FAKE_TTL) } pub fn fake_autottl() -> bool { *OPT_FAKE_AUTOTTL.get().unwrap_or(&DEFAULT_FAKE_AUTOTTL) } pub fn fake_badsum() -> bool { *OPT_FAKE_BADSUM.get().unwrap_or(&DEFAULT_FAKE_BADSUM) } pub fn delay_ms() -> u64 { *OPT_DELAY_MS.get().unwrap_or(&DEFAULT_DELAY_MS) } #[cfg(target_os = "linux")] pub fn queue_num() -> u16 { *OPT_QUEUE_NUM.get().unwrap_or(&DEFAULT_QUEUE_NUM) } #[cfg(target_os = "linux")] pub fn nft_command() -> &'static str { OPT_NFT_COMMAND.get().map(String::as_str).unwrap_or(DEFAULT_NFT_COMMAND) } fn take_value(args: &mut I, arg_name: &str) -> Result where T: std::str::FromStr, T::Err: std::error::Error + Send + Sync + 'static, I: Iterator, { let raw = args .next() .ok_or_else(|| anyhow!("argument: missing value after {}", arg_name))?; raw.parse::() .with_context(|| format!("argument: {}: invalid value '{}'", arg_name, raw)) } fn usage() { println!("Usage: dpibreak [OPTIONS]\n"); println!("Options:"); println!(" -D, --daemon Run as daemon; kill `pidof dpibreak` to stop."); println!(" --delay-ms (default: {DEFAULT_DELAY_MS})"); #[cfg(target_os = "linux")] println!(" --queue-num (default: {DEFAULT_QUEUE_NUM})"); #[cfg(target_os = "linux")] println!(" --nft-command (default: {DEFAULT_NFT_COMMAND})"); println!(" --log-level (default: {DEFAULT_LOG_LEVEL})"); println!(" --no-splash Do not print splash messages\n"); println!(" --fake Enable fake clienthello injection"); println!(" --fake-ttl Override ttl of fake clienthello (default: {DEFAULT_FAKE_TTL})"); println!(" --fake-autottl Override ttl of fake clienthello automatically"); println!(" --fake-badsum Modifies the TCP checksum of the fake packet to an invalid value."); println!(""); println!(" -h, --help Show this help"); } fn set_opt( name: &str, cell: &OnceLock, value: T, ) -> Result<()> { cell.set(value).map_err(|_| anyhow!("{name} already initialized"))?; let v = cell.get().expect("just set; qed"); log_println!(LogLevel::Info, "{name}: {v}"); Ok(()) }