1#![doc(
8 html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
9 html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
10 issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
11)]
12#![cfg_attr(not(test), warn(unused_crate_dependencies))]
13#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
14
15pub mod net_if;
16
17pub use net_if::{NetInterfaceError, DEFAULT_NET_IF_NAME};
18
19use std::{
20 fmt,
21 future::{poll_fn, Future},
22 net::{AddrParseError, IpAddr},
23 pin::Pin,
24 str::FromStr,
25 task::{Context, Poll},
26 time::Duration,
27};
28use tracing::{debug, error};
29
30use crate::net_if::resolve_net_if_ip;
31#[cfg(feature = "serde")]
32use serde_with::{DeserializeFromStr, SerializeDisplay};
33
34const EXTERNAL_IP_APIS: &[&str] =
38 &["https://ipinfo.io/ip", "https://icanhazip.com", "https://ifconfig.me"];
39
40#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Hash)]
42#[cfg_attr(feature = "serde", derive(SerializeDisplay, DeserializeFromStr))]
43pub enum NatResolver {
44 #[default]
46 Any,
47 Upnp,
49 PublicIp,
51 ExternalIp(IpAddr),
53 NetIf,
55 None,
57}
58
59impl NatResolver {
60 pub async fn external_addr(self) -> Option<IpAddr> {
62 external_addr_with(self).await
63 }
64
65 pub const fn as_external_ip(self) -> Option<IpAddr> {
67 match self {
68 Self::ExternalIp(ip) => Some(ip),
69 _ => None,
70 }
71 }
72}
73
74impl fmt::Display for NatResolver {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 match self {
77 Self::Any => f.write_str("any"),
78 Self::Upnp => f.write_str("upnp"),
79 Self::PublicIp => f.write_str("publicip"),
80 Self::ExternalIp(ip) => write!(f, "extip:{ip}"),
81 Self::NetIf => f.write_str("netif"),
82 Self::None => f.write_str("none"),
83 }
84 }
85}
86
87#[derive(Debug, thiserror::Error)]
89pub enum ParseNatResolverError {
90 #[error(transparent)]
92 AddrParseError(#[from] AddrParseError),
93 #[error("Unknown Nat Resolver variant: {0}")]
95 UnknownVariant(String),
96}
97
98impl FromStr for NatResolver {
99 type Err = ParseNatResolverError;
100
101 fn from_str(s: &str) -> Result<Self, Self::Err> {
102 let r = match s {
103 "any" => Self::Any,
104 "upnp" => Self::Upnp,
105 "none" => Self::None,
106 "publicip" | "public-ip" => Self::PublicIp,
107 "netif" => Self::NetIf,
108 s => {
109 let Some(ip) = s.strip_prefix("extip:") else {
110 return Err(ParseNatResolverError::UnknownVariant(format!(
111 "Unknown Nat Resolver: {s}"
112 )))
113 };
114 Self::ExternalIp(ip.parse()?)
115 }
116 };
117 Ok(r)
118 }
119}
120
121#[must_use = "Does nothing unless polled"]
123pub struct ResolveNatInterval {
124 resolver: NatResolver,
125 future: Option<Pin<Box<dyn Future<Output = Option<IpAddr>> + Send>>>,
126 interval: tokio::time::Interval,
127}
128
129impl fmt::Debug for ResolveNatInterval {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 f.debug_struct("ResolveNatInterval")
132 .field("resolver", &self.resolver)
133 .field("future", &self.future.as_ref().map(drop))
134 .field("interval", &self.interval)
135 .finish()
136 }
137}
138
139impl ResolveNatInterval {
140 fn with_interval(resolver: NatResolver, interval: tokio::time::Interval) -> Self {
141 Self { resolver, future: None, interval }
142 }
143
144 #[track_caller]
147 pub fn interval(resolver: NatResolver, period: Duration) -> Self {
148 let interval = tokio::time::interval(period);
149 Self::with_interval(resolver, interval)
150 }
151
152 #[track_caller]
155 pub fn interval_at(
156 resolver: NatResolver,
157 start: tokio::time::Instant,
158 period: Duration,
159 ) -> Self {
160 let interval = tokio::time::interval_at(start, period);
161 Self::with_interval(resolver, interval)
162 }
163
164 pub const fn resolver(&self) -> &NatResolver {
166 &self.resolver
167 }
168
169 pub async fn tick(&mut self) -> Option<IpAddr> {
171 poll_fn(|cx| self.poll_tick(cx)).await
172 }
173
174 pub fn poll_tick(&mut self, cx: &mut Context<'_>) -> Poll<Option<IpAddr>> {
182 if self.interval.poll_tick(cx).is_ready() {
183 self.future = Some(Box::pin(self.resolver.external_addr()));
184 }
185
186 if let Some(mut fut) = self.future.take() {
187 match fut.as_mut().poll(cx) {
188 Poll::Ready(ip) => return Poll::Ready(ip),
189 Poll::Pending => self.future = Some(fut),
190 }
191 }
192
193 Poll::Pending
194 }
195}
196
197pub async fn external_ip() -> Option<IpAddr> {
199 external_addr_with(NatResolver::Any).await
200}
201
202pub async fn external_addr_with(resolver: NatResolver) -> Option<IpAddr> {
204 match resolver {
205 NatResolver::Any | NatResolver::Upnp | NatResolver::PublicIp => resolve_external_ip().await,
206 NatResolver::ExternalIp(ip) => Some(ip),
207 NatResolver::NetIf => resolve_net_if_ip(DEFAULT_NET_IF_NAME)
208 .inspect_err(|err| {
209 debug!(target: "net::nat",
210 %err,
211 "Failed to resolve network interface IP"
212 );
213 })
214 .ok(),
215 NatResolver::None => None,
216 }
217}
218
219async fn resolve_external_ip() -> Option<IpAddr> {
220 let futures = EXTERNAL_IP_APIS.iter().copied().map(resolve_external_ip_url_res).map(Box::pin);
221 futures_util::future::select_ok(futures)
222 .await
223 .inspect_err(|err| {
224 debug!(target: "net::nat",
225 ?err,
226 external_ip_apis=?EXTERNAL_IP_APIS,
227 "Failed to resolve external IP from any API");
228 })
229 .ok()
230 .map(|(ip, _)| ip)
231}
232
233async fn resolve_external_ip_url_res(url: &str) -> Result<IpAddr, ()> {
234 resolve_external_ip_url(url).await.ok_or(())
235}
236
237async fn resolve_external_ip_url(url: &str) -> Option<IpAddr> {
238 let client = reqwest::Client::builder().timeout(Duration::from_secs(10)).build().ok()?;
239 let response = client.get(url).send().await.ok()?;
240 let response = response.error_for_status().ok()?;
241 let text = response.text().await.ok()?;
242 text.trim().parse().ok()
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use std::net::Ipv4Addr;
249
250 #[tokio::test]
251 #[ignore]
252 async fn get_external_ip() {
253 reth_tracing::init_test_tracing();
254 let ip = external_ip().await;
255 dbg!(ip);
256 }
257
258 #[tokio::test]
259 #[ignore]
260 async fn get_external_ip_interval() {
261 reth_tracing::init_test_tracing();
262 let mut interval = ResolveNatInterval::interval(Default::default(), Duration::from_secs(5));
263
264 let ip = interval.tick().await;
265 dbg!(ip);
266 let ip = interval.tick().await;
267 dbg!(ip);
268 }
269
270 #[test]
271 fn test_from_str() {
272 assert_eq!(NatResolver::Any, "any".parse().unwrap());
273 assert_eq!(NatResolver::None, "none".parse().unwrap());
274
275 let ip = NatResolver::ExternalIp(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
276 let s = "extip:0.0.0.0";
277 assert_eq!(ip, s.parse().unwrap());
278 assert_eq!(ip.to_string().as_str(), s);
279 }
280}