amadeus_utils/
ip_resolver.rs1use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
2use std::time::Duration;
3use tokio::{net, time};
4
5const STUN_MAGIC_COOKIE: u32 = 0x2112A442;
6
7pub async fn resolve_public_ipv4() -> Option<Ipv4Addr> {
12 if let Some(ip) = get_ip_stun().await.ok().flatten() {
14 return Some(ip);
15 }
16
17 match get_ip_http().await {
19 Ok(Some(ip_str)) => ip_str.parse().ok(),
20 _ => None,
21 }
22}
23
24pub async fn resolve_public_ipv4_string() -> Option<String> {
26 resolve_public_ipv4().await.map(|ip| ip.to_string())
27}
28
29fn build_stun_binding_request(txid: &[u8; 12]) -> [u8; 20] {
30 let mut buf = [0u8; 20];
31 buf[0] = 0x00;
33 buf[1] = 0x01;
34 buf[2] = 0x00;
36 buf[3] = 0x00;
37 buf[4..8].copy_from_slice(&STUN_MAGIC_COOKIE.to_be_bytes());
39 buf[8..20].copy_from_slice(txid);
41 buf
42}
43
44pub fn parse_xor_mapped_v4(resp: &[u8], _txid: &[u8; 12]) -> Option<Ipv4Addr> {
46 if resp.len() < 20 {
47 return None;
48 }
49 let msg_len = u16::from_be_bytes([resp[2], resp[3]]) as usize;
50 if resp.len() < 20 + msg_len {
51 return None;
52 }
53 let mut offset = 20;
54 while offset + 4 <= 20 + msg_len {
55 let atype = u16::from_be_bytes([resp[offset], resp[offset + 1]]);
56 let alen = u16::from_be_bytes([resp[offset + 2], resp[offset + 3]]) as usize;
57 offset += 4;
58 if offset + alen > resp.len() {
59 return None;
60 }
61 if atype == 0x0020 {
62 if alen < 8 {
64 return None;
65 }
66 let family = resp[offset + 1];
67 if family != 0x01 {
68 return None;
69 }
70 let xport = u16::from_be_bytes([resp[offset + 2], resp[offset + 3]]);
71 let _port = xport ^ ((STUN_MAGIC_COOKIE >> 16) as u16);
72 let mut xaddr = [0u8; 4];
73 xaddr.copy_from_slice(&resp[offset + 4..offset + 8]);
74 let addr_be = u32::from_be_bytes(xaddr) ^ STUN_MAGIC_COOKIE;
75 let octets = addr_be.to_be_bytes();
76 return Some(Ipv4Addr::new(octets[0], octets[1], octets[2], octets[3]));
77 }
78 let pad = (4 - (alen % 4)) % 4;
79 offset += alen + pad;
80 }
81 None
82}
83
84async fn get_ip_stun() -> Result<Option<Ipv4Addr>, std::io::Error> {
85 use rand::RngCore;
86 let mut txid = [0u8; 12];
87 rand::rng().fill_bytes(&mut txid);
88 let req = build_stun_binding_request(&txid);
89
90 let local = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0));
91 let socket = net::UdpSocket::bind(local).await?;
92
93 let addrs: Vec<SocketAddr> = net::lookup_host(("stun.l.google.com", 19302)).await?.collect();
95 if let Some(target) = addrs.iter().find(|sa| sa.is_ipv4()).cloned().or_else(|| addrs.get(0).cloned()) {
96 let _ = socket.send_to(&req, target).await?;
97 let mut buf = [0u8; 1500];
98 match time::timeout(Duration::from_millis(6000), socket.recv_from(&mut buf)).await {
99 Ok(Ok((n, _addr))) => Ok(parse_xor_mapped_v4(&buf[..n], &txid)),
100 _ => Ok(None),
101 }
102 } else {
103 Ok(None)
104 }
105}
106
107async fn get_ip_http() -> Result<Option<String>, std::io::Error> {
108 use tokio::io::{AsyncReadExt, AsyncWriteExt};
109 let host = "api.myip.la";
110 let port = 80;
111 let req = b"GET /en?json HTTP/1.1\r\nHost: api.myip.la\r\nConnection: close\r\n\r\n";
112
113 let stream = match time::timeout(Duration::from_millis(6000), net::TcpStream::connect((host, port))).await {
114 Ok(Ok(s)) => s,
115 _ => return Ok(None),
116 };
117
118 let mut stream = stream;
119 if time::timeout(Duration::from_millis(6000), stream.write_all(req)).await.is_err() {
120 return Ok(None);
121 }
122
123 let mut buf = Vec::with_capacity(4096);
124 match time::timeout(Duration::from_millis(6000), async {
125 let mut tmp = [0u8; 2048];
126 loop {
127 let n = stream.read(&mut tmp).await?;
128 if n == 0 {
129 break;
130 }
131 buf.extend_from_slice(&tmp[..n]);
132 }
133 Ok::<(), std::io::Error>(())
134 })
135 .await
136 {
137 Ok(Ok(())) => {}
138 _ => return Ok(None),
139 }
140
141 let mut body = &buf[..];
143 if let Some(pos) = buf.windows(4).position(|w| w == b"\r\n\r\n") {
144 body = &buf[pos + 4..];
145 }
146
147 let is_chunked = {
149 let headers = &buf[..buf.len().saturating_sub(body.len() + 4)];
150 let headers_str = String::from_utf8_lossy(headers).to_ascii_lowercase();
151 headers_str.contains("transfer-encoding: chunked")
152 };
153
154 let body_bytes = if is_chunked {
155 match decode_chunked(body) {
156 Some(b) => b,
157 None => body.to_vec(),
158 }
159 } else {
160 body.to_vec()
161 };
162
163 if let Ok(v) = serde_json::from_slice::<serde_json::Value>(&body_bytes) {
164 if let Some(ip) = v.get("ip").and_then(|x| x.as_str()) {
165 return Ok(Some(ip.to_string()));
166 }
167 }
168 Ok(None)
169}
170
171fn decode_chunked(mut body: &[u8]) -> Option<Vec<u8>> {
172 let mut out = Vec::new();
173 loop {
174 let pos = body.windows(2).position(|w| w == b"\r\n")?;
176 let size_str = std::str::from_utf8(&body[..pos]).ok()?.trim();
177 let size = usize::from_str_radix(size_str.trim_end_matches(|c: char| c.is_ascii_whitespace()), 16).ok()?;
178 body = &body[pos + 2..];
179 if size == 0 {
180 break;
181 }
182 if body.len() < size + 2 {
183 return None;
184 }
185 out.extend_from_slice(&body[..size]);
186 body = &body[size + 2..]; }
188 Some(out)
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn xor_mapped_v4_parsing() {
197 let txid = [1u8; 12];
199 let mut resp = Vec::new();
200 resp.extend_from_slice(&[0x01, 0x01, 0x00, 0x0c]); resp.extend_from_slice(&STUN_MAGIC_COOKIE.to_be_bytes());
203 resp.extend_from_slice(&txid);
204 resp.extend_from_slice(&[0x00, 0x20, 0x00, 0x08]);
206 let port: u16 = 54321;
208 let xport = port ^ ((STUN_MAGIC_COOKIE >> 16) as u16);
209 resp.extend_from_slice(&[0x00, 0x01]);
210 resp.extend_from_slice(&xport.to_be_bytes());
211 let ip = Ipv4Addr::new(8, 8, 8, 8);
212 let xaddr = u32::from_be_bytes(ip.octets()) ^ STUN_MAGIC_COOKIE;
213 resp.extend_from_slice(&xaddr.to_be_bytes());
214
215 let parsed = parse_xor_mapped_v4(&resp, &txid).unwrap();
216 assert_eq!(parsed, ip);
217 }
218}