1use crate::cfg::DetectCfg;
8use anyhow::{Result, anyhow};
9use reqwest::Client;
10use std::time::Duration;
11use tokio::{process::Command, time::timeout};
12use tracing::info;
13
14#[cfg(unix)]
16fn detect_iface(iface: &str) -> Result<String> {
17 use pnet_datalink::interfaces;
18 use std::net::IpAddr;
19
20 for i in interfaces() {
21 if i.name == iface {
22 for ipn in i.ips {
23 if let IpAddr::V4(v4) = ipn.ip() {
24 return Ok(v4.to_string());
25 }
26 }
27 return Err(anyhow!("interface `{iface}` has no IPv4 address"));
28 }
29 }
30 Err(anyhow!("interface `{iface}` not found"))
31}
32
33#[cfg(windows)]
34fn detect_iface(_iface: &str) -> Result<String> {
35 Err(anyhow!(
36 r#"kind = "interface" is not supported on Windows; \
37please use `http` or `command` instead"#
38 ))
39}
40
41async fn detect_http(url: &str, to: Option<u64>) -> Result<String> {
43 let fut = async {
44 Ok::<_, anyhow::Error>(
45 Client::new()
46 .get(url)
47 .send()
48 .await?
49 .text()
50 .await?
51 .trim()
52 .to_owned(),
53 )
54 };
55 match to {
56 Some(ms) => Ok(timeout(Duration::from_millis(ms), fut).await??),
57 None => fut.await,
58 }
59}
60
61async fn detect_cmd(cmd: &str, to: Option<u64>) -> Result<String> {
63 let fut = async {
64 let out = Command::new("sh").arg("-c").arg(cmd).output().await?;
65 Ok(String::from_utf8_lossy(&out.stdout).trim().to_owned())
66 };
67 match to {
68 Some(ms) => Ok(timeout(Duration::from_millis(ms), fut).await??),
69 None => fut.await,
70 }
71}
72
73pub async fn detect_ip(list: &[DetectCfg]) -> Result<String> {
75 let mut items = list.to_vec();
77 items.sort_by_key(|d| match d {
78 DetectCfg::Http { priority, .. }
79 | DetectCfg::Interface { priority, .. }
80 | DetectCfg::Command { priority, .. } => priority.unwrap_or(100),
81 });
82
83 for det in items {
84 match det {
85 DetectCfg::Http {
86 url, timeout: to, ..
87 } => {
88 if let Ok(ip) = detect_http(&url, to).await {
89 info!("detect/http {url} -> {ip}");
90 return Ok(ip);
91 }
92 }
93 DetectCfg::Interface { iface, .. } => {
94 if let Ok(ip) = detect_iface(&iface) {
95 info!("detect/iface {iface} -> {ip}");
96 return Ok(ip);
97 }
98 }
99 DetectCfg::Command {
100 cmd, timeout: to, ..
101 } => {
102 if let Ok(ip) = detect_cmd(&cmd, to).await {
103 info!("detect/cmd `{cmd}` -> {ip}");
104 return Ok(ip);
105 }
106 }
107 }
108 }
109 Err(anyhow!("all detectors failed"))
110}