302 lines
9.5 KiB
Rust
302 lines
9.5 KiB
Rust
//! Command-line interface definitions (clap).
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use clap::{Args, Parser, Subcommand};
|
|
|
|
/// With a node but no subcommand, the top-level args run the full auto pipeline
|
|
/// (ping → latency → speed → export). With no arguments at all (e.g. a
|
|
/// double-clicked binary), the local web UI launches.
|
|
#[derive(Parser)]
|
|
#[command(
|
|
name = "fast-xray",
|
|
about = "Fast Cloudflare IP optimizer for CDN-fronted Xray/VLESS nodes.",
|
|
args_conflicts_with_subcommands = true
|
|
)]
|
|
pub(crate) struct Cli {
|
|
#[command(subcommand)]
|
|
pub(crate) command: Option<Command>,
|
|
|
|
#[command(flatten)]
|
|
pub(crate) auto: AutoArgs,
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
pub(crate) enum Command {
|
|
/// Probe Cloudflare IPs over TCP 443 and collect the fastest reachable ones.
|
|
Ping(PingArgs),
|
|
/// Measure real proxy latency for candidate IPs through the node.
|
|
Latency(LatencyArgs),
|
|
/// Download through the node to measure real speed.
|
|
Speed(SpeedArgs),
|
|
/// Build importable vless:// nodes from a valid-IP list and a node.
|
|
Export(ExportArgs),
|
|
/// Find one IP fast enough to hit a target speed, then stop.
|
|
Easy(EasyArgs),
|
|
/// Launch the local web UI (zero-install front end for `easy`).
|
|
Web(WebArgs),
|
|
}
|
|
|
|
#[derive(Args)]
|
|
pub(crate) struct AutoArgs {
|
|
/// Input vless:// node URL (or use --node-file).
|
|
pub(crate) node: Option<String>,
|
|
|
|
/// Read the vless:// node URL from a file (avoids shell escaping of `&`).
|
|
#[arg(long)]
|
|
pub(crate) node_file: Option<PathBuf>,
|
|
|
|
/// Stage 1: how many valid IPs the ping stage collects.
|
|
#[arg(short = 'n', long, default_value_t = 50)]
|
|
pub(crate) count: usize,
|
|
|
|
/// Stage 2: keep the fastest N after latency (feeds the speed stage).
|
|
/// 0 = keep all that pass.
|
|
#[arg(long, default_value_t = 0)]
|
|
pub(crate) lat_top: usize,
|
|
|
|
/// Stage 3: final node count written to result.txt. 0 = keep all.
|
|
#[arg(long, default_value_t = 0)]
|
|
pub(crate) speed_top: usize,
|
|
|
|
/// Stage 3: concurrent downloads during the speed test (1 = most accurate).
|
|
#[arg(long, default_value_t = 1)]
|
|
pub(crate) speed_concurrency: usize,
|
|
|
|
/// Also test IPv6 ranges (off by default).
|
|
#[arg(short = '6', long = "ipv6")]
|
|
pub(crate) ipv6: bool,
|
|
|
|
/// Stage 1: drop IPs whose TCP ping exceeds this (ms).
|
|
#[arg(long, default_value_t = 300.0)]
|
|
pub(crate) ping_max: f64,
|
|
|
|
/// Stage 2: drop IPs whose real latency exceeds this (ms). 0 = off.
|
|
#[arg(long, default_value_t = 0.0)]
|
|
pub(crate) lat_max: f64,
|
|
|
|
/// Quality gate: drop IPs slower than this (MB/s). 0 = off.
|
|
#[arg(long, default_value_t = 0.0)]
|
|
pub(crate) min_speed: f64,
|
|
|
|
/// Output directory for all intermediate files and result.txt.
|
|
#[arg(short = 'o', long, default_value = "result")]
|
|
pub(crate) output: PathBuf,
|
|
|
|
/// Print each stage's result table.
|
|
#[arg(short = 'v', long)]
|
|
pub(crate) verbose: bool,
|
|
}
|
|
|
|
#[derive(Args)]
|
|
pub(crate) struct PingArgs {
|
|
/// Target number of valid (reachable) IPs to collect.
|
|
#[arg(short = 'n', long, default_value_t = 50)]
|
|
pub(crate) count: usize,
|
|
|
|
/// TCP connect timeout in seconds; slower than this counts as unreachable.
|
|
#[arg(short = 't', long, default_value_t = 3.0)]
|
|
pub(crate) timeout: f64,
|
|
|
|
/// Concurrent probes.
|
|
#[arg(short = 'c', long, default_value_t = 100)]
|
|
pub(crate) concurrency: usize,
|
|
|
|
/// Also test IPv6 ranges (off by default).
|
|
#[arg(short = '6', long = "ipv6")]
|
|
pub(crate) ipv6: bool,
|
|
|
|
/// TCP port to probe.
|
|
#[arg(short = 'p', long, default_value_t = 443)]
|
|
pub(crate) port: u16,
|
|
|
|
/// Safety cap: stop after probing this many IPs. Defaults to count * 100.
|
|
#[arg(long)]
|
|
pub(crate) max_probe: Option<usize>,
|
|
|
|
/// TCP probes per IP. >1 enables packet-loss filtering.
|
|
#[arg(long, default_value_t = 1)]
|
|
pub(crate) times: usize,
|
|
|
|
/// Reject IPs with average latency below this (ms).
|
|
#[arg(long = "min", default_value_t = 10.0)]
|
|
pub(crate) min_latency: f64,
|
|
|
|
/// Reject IPs with average latency above this (ms).
|
|
#[arg(long = "max", default_value_t = 300.0)]
|
|
pub(crate) max_latency: f64,
|
|
|
|
/// Reject IPs with loss rate above this (0.0-1.0). Default 0 = no loss.
|
|
#[arg(long, default_value_t = 0.0)]
|
|
pub(crate) max_loss: f64,
|
|
|
|
/// Output directory. Writes <dir>/ip.txt (plain IPs, fastest first).
|
|
#[arg(short = 'o', long, default_value = "result")]
|
|
pub(crate) output: PathBuf,
|
|
|
|
/// Print the full result table (IP + latency) at the end.
|
|
#[arg(short = 'v', long)]
|
|
pub(crate) verbose: bool,
|
|
}
|
|
|
|
#[derive(Args)]
|
|
pub(crate) struct LatencyArgs {
|
|
/// Input vless:// node URL (or use --node-file).
|
|
pub(crate) node: Option<String>,
|
|
|
|
/// Read the vless:// node URL from a file (avoids shell escaping of `&`).
|
|
#[arg(long)]
|
|
pub(crate) node_file: Option<PathBuf>,
|
|
|
|
/// IP list to test (one per line), e.g. the ping stage output.
|
|
#[arg(short = 'i', long, default_value = "result/ip.txt")]
|
|
pub(crate) input: PathBuf,
|
|
|
|
/// Concurrent tunnels.
|
|
#[arg(short = 'c', long, default_value_t = 50)]
|
|
pub(crate) concurrency: usize,
|
|
|
|
/// Per-IP timeout in seconds for the whole tunnel + request.
|
|
#[arg(short = 't', long, default_value_t = 5.0)]
|
|
pub(crate) timeout: f64,
|
|
|
|
/// Reject IPs whose real latency exceeds this (ms). 0 = timeout only.
|
|
#[arg(long = "max", default_value_t = 0.0)]
|
|
pub(crate) max_latency: f64,
|
|
|
|
/// Keep only the fastest N results. 0 = keep all that pass.
|
|
#[arg(short = 'p', long, default_value_t = 0)]
|
|
pub(crate) top: usize,
|
|
|
|
/// Output directory. Writes <dir>/latency.csv (IP + latency) and
|
|
/// <dir>/latency.txt (plain IPs, fastest first).
|
|
#[arg(short = 'o', long, default_value = "result")]
|
|
pub(crate) output: PathBuf,
|
|
|
|
/// Print the full result table at the end.
|
|
#[arg(short = 'v', long)]
|
|
pub(crate) verbose: bool,
|
|
}
|
|
|
|
#[derive(Args)]
|
|
pub(crate) struct SpeedArgs {
|
|
/// Input vless:// node URL (or use --node-file).
|
|
pub(crate) node: Option<String>,
|
|
|
|
/// Read the vless:// node URL from a file (avoids shell escaping of `&`).
|
|
#[arg(long)]
|
|
pub(crate) node_file: Option<PathBuf>,
|
|
|
|
/// Input CSV from the latency stage (IP + latency).
|
|
#[arg(short = 'i', long, default_value = "result/latency.csv")]
|
|
pub(crate) input: PathBuf,
|
|
|
|
/// Concurrent downloads. Default 1 for accurate throughput.
|
|
#[arg(short = 'c', long, default_value_t = 1)]
|
|
pub(crate) concurrency: usize,
|
|
|
|
/// Per-IP download time cap in seconds.
|
|
#[arg(short = 't', long, default_value_t = 10.0)]
|
|
pub(crate) timeout: f64,
|
|
|
|
/// Bytes to download per IP before stopping.
|
|
#[arg(long, default_value_t = 10_000_000)]
|
|
pub(crate) bytes: u64,
|
|
|
|
/// Reject IPs slower than this (MB/s). 0 = keep all.
|
|
#[arg(long = "min", default_value_t = 0.0)]
|
|
pub(crate) min_speed: f64,
|
|
|
|
/// Keep only the best N results. 0 means keep all.
|
|
#[arg(short = 'p', long, default_value_t = 0)]
|
|
pub(crate) top: usize,
|
|
|
|
/// Output directory. Writes <dir>/speed.csv and <dir>/speed.txt.
|
|
#[arg(short = 'o', long, default_value = "result")]
|
|
pub(crate) output: PathBuf,
|
|
|
|
/// Print the full result table at the end.
|
|
#[arg(short = 'v', long)]
|
|
pub(crate) verbose: bool,
|
|
}
|
|
|
|
#[derive(Args)]
|
|
pub(crate) struct EasyArgs {
|
|
/// Input vless:// node URL (or use --node-file).
|
|
pub(crate) node: Option<String>,
|
|
|
|
/// Read the vless:// node URL from a file (avoids shell escaping of `&`).
|
|
#[arg(long)]
|
|
pub(crate) node_file: Option<PathBuf>,
|
|
|
|
/// Target download speed in MB/s; returns the first IP that reaches it.
|
|
/// Omit to default to 80% of the measured direct (no-proxy) speed.
|
|
#[arg(long)]
|
|
pub(crate) speed: Option<f64>,
|
|
|
|
/// Give up after this many valid IPs (those that pass both ping and
|
|
/// latency). Reaching it without a fast-enough IP means "not found".
|
|
#[arg(long, default_value_t = 100)]
|
|
pub(crate) max: usize,
|
|
|
|
/// Also search IPv6 ranges (off by default).
|
|
#[arg(short = '6', long = "ipv6")]
|
|
pub(crate) ipv6: bool,
|
|
|
|
/// Output directory. Writes <dir>/result.txt with the chosen node.
|
|
#[arg(short = 'o', long, default_value = "result")]
|
|
pub(crate) output: PathBuf,
|
|
}
|
|
|
|
impl EasyArgs {
|
|
pub(crate) fn chuan() -> Self {
|
|
Self {
|
|
node: Some(crate::easy::CHUAN_NODE.to_string()),
|
|
node_file: None,
|
|
speed: None,
|
|
max: 100,
|
|
ipv6: false,
|
|
output: PathBuf::from("result"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Args)]
|
|
pub(crate) struct WebArgs {
|
|
/// Address to bind. Use 0.0.0.0 to expose on the LAN or behind a reverse proxy.
|
|
#[arg(long, default_value = "127.0.0.1")]
|
|
pub(crate) host: String,
|
|
|
|
/// Port to listen on.
|
|
#[arg(short = 'p', long, default_value_t = 8080)]
|
|
pub(crate) port: u16,
|
|
|
|
/// Don't open the browser automatically on startup.
|
|
#[arg(long)]
|
|
pub(crate) no_open: bool,
|
|
}
|
|
|
|
impl Default for WebArgs {
|
|
fn default() -> Self {
|
|
Self { host: "127.0.0.1".to_string(), port: 8080, no_open: false }
|
|
}
|
|
}
|
|
|
|
#[derive(Args)]
|
|
pub(crate) struct ExportArgs {
|
|
/// Input vless:// node URL (or use --node-file).
|
|
pub(crate) node: Option<String>,
|
|
|
|
/// Read the vless:// node URL from a file (avoids shell escaping of `&`).
|
|
#[arg(long)]
|
|
pub(crate) node_file: Option<PathBuf>,
|
|
|
|
/// Valid IP list: plain IPs, or a CSV whose first column is the IP.
|
|
#[arg(short = 'i', long, default_value = "result/ip.txt")]
|
|
pub(crate) input: PathBuf,
|
|
|
|
/// Output directory. Writes <dir>/result.txt (importable nodes).
|
|
#[arg(short = 'o', long, default_value = "result")]
|
|
pub(crate) output: PathBuf,
|
|
}
|