use clap::Parser;
use ip_discovery::{Config, IpVersion, Protocol, Strategy};
use std::process::ExitCode;
use std::time::Duration;
#[derive(Parser)]
#[command(name = "ipd", version, about, long_about = None)]
struct Cli {
#[arg(short = '4', long)]
ipv4: bool,
#[arg(short = '6', long)]
ipv6: bool,
#[arg(short, long, default_value = "plain")]
format: OutputFormat,
#[arg(short, long, default_value = "first")]
strategy: StrategyArg,
#[arg(short, long)]
protocol: Vec<ProtocolArg>,
#[arg(short, long, default_value = "10")]
timeout: u64,
#[arg(short = 'l', long, alias = "local")]
private: bool,
}
#[derive(Clone, clap::ValueEnum)]
enum OutputFormat {
Plain,
Json,
Verbose,
}
#[derive(Clone, clap::ValueEnum)]
enum StrategyArg {
First,
Race,
Consensus,
}
#[derive(Clone, clap::ValueEnum)]
enum ProtocolArg {
Dns,
Stun,
Http,
}
#[tokio::main]
async fn main() -> ExitCode {
let cli = Cli::parse();
if cli.private {
let opt_ip = match (cli.ipv4, cli.ipv6) {
(false, true) => ip_discovery::get_private_ipv6(),
_ => ip_discovery::get_private_ip().or_else(ip_discovery::get_private_ipv6),
};
match opt_ip {
Some(ip) => {
match cli.format {
OutputFormat::Plain => {
println!("{}", ip);
}
OutputFormat::Json => {
let json = serde_json::json!({
"ip": ip.to_string(),
"type": if ip.is_ipv4() { "IPv4" } else { "IPv6" },
"scope": "private",
});
println!(
"{}",
serde_json::to_string_pretty(&json).unwrap_or_default()
);
}
OutputFormat::Verbose => {
println!("{}", ip);
println!(" scope: private");
println!(" type: {}", if ip.is_ipv4() { "IPv4" } else { "IPv6" });
}
}
return ExitCode::SUCCESS;
}
None => {
eprintln!(
"error: no local network interface found with a valid private IP address"
);
return ExitCode::FAILURE;
}
}
}
let version = match (cli.ipv4, cli.ipv6) {
(true, false) => IpVersion::V4,
(false, true) => IpVersion::V6,
_ => IpVersion::Any,
};
let strategy = match cli.strategy {
StrategyArg::First => Strategy::First,
StrategyArg::Race => Strategy::Race,
StrategyArg::Consensus => Strategy::Consensus { min_agree: 2 },
};
let mut builder = Config::builder()
.version(version)
.strategy(strategy)
.timeout(Duration::from_secs(cli.timeout));
if !cli.protocol.is_empty() {
let protocols: Vec<Protocol> = cli
.protocol
.iter()
.map(|p| match p {
ProtocolArg::Dns => Protocol::Dns,
ProtocolArg::Stun => Protocol::Stun,
ProtocolArg::Http => Protocol::Http,
})
.collect();
builder = builder.protocols(&protocols);
}
let config = builder.build();
match ip_discovery::get_ip_with(config).await {
Ok(result) => {
match cli.format {
OutputFormat::Plain => {
println!("{}", result.ip);
}
OutputFormat::Json => {
let json = serde_json::json!({
"ip": result.ip.to_string(),
"provider": result.provider,
"protocol": format!("{}", result.protocol),
"latency_ms": result.latency.as_millis(),
});
println!(
"{}",
serde_json::to_string_pretty(&json).unwrap_or_default()
);
}
OutputFormat::Verbose => {
println!("{}", result.ip);
println!(" provider: {}", result.provider);
println!(" protocol: {}", result.protocol);
println!(" latency: {}ms", result.latency.as_millis());
}
}
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("error: {e}");
ExitCode::FAILURE
}
}
}