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))]
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, ToSocketAddrs},
23 pin::Pin,
24 str::FromStr,
25 task::{Context, Poll},
26 time::Duration,
27};
28use tracing::debug;
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, 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 ExternalAddr(String),
61 NetIf,
63 None,
65}
66
67impl NatResolver {
68 pub async fn external_addr(self) -> Option<IpAddr> {
70 external_addr_with(self).await
71 }
72
73 pub fn as_external_ip(self, port: u16) -> Option<IpAddr> {
78 match self {
79 Self::ExternalIp(ip) => Some(ip),
80 Self::ExternalAddr(domain) => format!("{domain}:{port}")
81 .to_socket_addrs()
82 .ok()
83 .and_then(|mut addrs| addrs.next().map(|addr| addr.ip())),
84 _ => None,
85 }
86 }
87}
88
89impl fmt::Display for NatResolver {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 match self {
92 Self::Any => f.write_str("any"),
93 Self::Upnp => f.write_str("upnp"),
94 Self::PublicIp => f.write_str("publicip"),
95 Self::ExternalIp(ip) => write!(f, "extip:{ip}"),
96 Self::ExternalAddr(domain) => write!(f, "extaddr:{domain}"),
97 Self::NetIf => f.write_str("netif"),
98 Self::None => f.write_str("none"),
99 }
100 }
101}
102
103#[derive(Debug, thiserror::Error)]
105pub enum ParseNatResolverError {
106 #[error(transparent)]
108 AddrParseError(#[from] AddrParseError),
109 #[error("Unknown Nat Resolver variant: {0}")]
111 UnknownVariant(String),
112}
113
114impl FromStr for NatResolver {
115 type Err = ParseNatResolverError;
116
117 fn from_str(s: &str) -> Result<Self, Self::Err> {
118 let r = match s {
119 "any" => Self::Any,
120 "upnp" => Self::Upnp,
121 "none" => Self::None,
122 "publicip" | "public-ip" => Self::PublicIp,
123 "netif" => Self::NetIf,
124 s => {
125 if let Some(ip) = s.strip_prefix("extip:") {
126 Self::ExternalIp(ip.parse()?)
127 } else if let Some(domain) = s.strip_prefix("extaddr:") {
128 Self::ExternalAddr(domain.to_string())
129 } else {
130 return Err(ParseNatResolverError::UnknownVariant(format!(
131 "Unknown Nat Resolver: {s}"
132 )));
133 }
134 }
135 };
136 Ok(r)
137 }
138}
139
140#[must_use = "Does nothing unless polled"]
142pub struct ResolveNatInterval {
143 resolver: NatResolver,
144 future: Option<Pin<Box<dyn Future<Output = Option<IpAddr>> + Send>>>,
145 interval: tokio::time::Interval,
146}
147
148impl fmt::Debug for ResolveNatInterval {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 f.debug_struct("ResolveNatInterval")
151 .field("resolver", &self.resolver)
152 .field("future", &self.future.as_ref().map(drop))
153 .field("interval", &self.interval)
154 .finish()
155 }
156}
157
158impl ResolveNatInterval {
159 fn with_interval(resolver: NatResolver, interval: tokio::time::Interval) -> Self {
160 Self { resolver, future: None, interval }
161 }
162
163 #[track_caller]
166 pub fn interval(resolver: NatResolver, period: Duration) -> Self {
167 let interval = tokio::time::interval(period);
168 Self::with_interval(resolver, interval)
169 }
170
171 #[track_caller]
174 pub fn interval_at(
175 resolver: NatResolver,
176 start: tokio::time::Instant,
177 period: Duration,
178 ) -> Self {
179 let interval = tokio::time::interval_at(start, period);
180 Self::with_interval(resolver, interval)
181 }
182
183 pub const fn resolver(&self) -> &NatResolver {
185 &self.resolver
186 }
187
188 pub async fn tick(&mut self) -> Option<IpAddr> {
190 poll_fn(|cx| self.poll_tick(cx)).await
191 }
192
193 pub fn poll_tick(&mut self, cx: &mut Context<'_>) -> Poll<Option<IpAddr>> {
201 if self.interval.poll_tick(cx).is_ready() {
202 self.future = Some(Box::pin(self.resolver.clone().external_addr()));
203 }
204
205 if let Some(mut fut) = self.future.take() {
206 match fut.as_mut().poll(cx) {
207 Poll::Ready(ip) => return Poll::Ready(ip),
208 Poll::Pending => self.future = Some(fut),
209 }
210 }
211
212 Poll::Pending
213 }
214}
215
216pub async fn external_ip() -> Option<IpAddr> {
218 external_addr_with(NatResolver::Any).await
219}
220
221pub async fn external_addr_with(resolver: NatResolver) -> Option<IpAddr> {
223 match resolver {
224 NatResolver::Any | NatResolver::Upnp | NatResolver::PublicIp => resolve_external_ip().await,
225 NatResolver::ExternalIp(ip) => Some(ip),
226 NatResolver::NetIf => resolve_net_if_ip(DEFAULT_NET_IF_NAME)
227 .inspect_err(|err| {
228 debug!(target: "net::nat",
229 %err,
230 "Failed to resolve network interface IP"
231 );
232 })
233 .ok(),
234 NatResolver::ExternalAddr(domain) => {
235 domain.to_socket_addrs().ok().and_then(|mut addrs| addrs.next().map(|addr| addr.ip()))
236 }
237 NatResolver::None => None,
238 }
239}
240
241async fn resolve_external_ip() -> Option<IpAddr> {
242 let futures = EXTERNAL_IP_APIS.iter().copied().map(resolve_external_ip_url_res).map(Box::pin);
243 futures_util::future::select_ok(futures)
244 .await
245 .inspect_err(|err| {
246 debug!(target: "net::nat",
247 ?err,
248 external_ip_apis=?EXTERNAL_IP_APIS,
249 "Failed to resolve external IP from any API");
250 })
251 .ok()
252 .map(|(ip, _)| ip)
253}
254
255async fn resolve_external_ip_url_res(url: &str) -> Result<IpAddr, ()> {
256 resolve_external_ip_url(url).await.ok_or(())
257}
258
259async fn resolve_external_ip_url(url: &str) -> Option<IpAddr> {
260 let client = reqwest::Client::builder().timeout(Duration::from_secs(10)).build().ok()?;
261 let response = client.get(url).send().await.ok()?;
262 let response = response.error_for_status().ok()?;
263 let text = response.text().await.ok()?;
264 text.trim().parse().ok()
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270 use std::net::{Ipv4Addr, Ipv6Addr};
271
272 #[tokio::test]
273 #[ignore]
274 async fn get_external_ip() {
275 reth_tracing::init_test_tracing();
276 let ip = external_ip().await;
277 dbg!(ip);
278 }
279
280 #[tokio::test]
281 #[ignore]
282 async fn get_external_ip_interval() {
283 reth_tracing::init_test_tracing();
284 let mut interval = ResolveNatInterval::interval(Default::default(), Duration::from_secs(5));
285
286 let ip = interval.tick().await;
287 dbg!(ip);
288 let ip = interval.tick().await;
289 dbg!(ip);
290 }
291
292 #[test]
293 fn as_external_ip_test() {
294 let resolver = NatResolver::ExternalAddr("localhost".to_string());
295 let ip = resolver.as_external_ip(30303).expect("localhost should be resolvable");
296
297 if ip.is_ipv4() {
298 assert_eq!(ip, IpAddr::V4(Ipv4Addr::LOCALHOST));
299 } else {
300 assert_eq!(ip, IpAddr::V6(Ipv6Addr::LOCALHOST));
301 }
302 }
303
304 #[test]
305 fn test_from_str() {
306 assert_eq!(NatResolver::Any, "any".parse().unwrap());
307 assert_eq!(NatResolver::None, "none".parse().unwrap());
308
309 let ip = NatResolver::ExternalIp(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
310 let s = "extip:0.0.0.0";
311 assert_eq!(ip, s.parse().unwrap());
312 assert_eq!(ip.to_string(), s);
313 }
314}