Skip to main content

ip_discovery/
types.rs

1//! Core types for ip-discovery
2
3use std::net::IpAddr;
4use std::time::Duration;
5
6/// Protocol used to detect public IP
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8#[non_exhaustive]
9pub enum Protocol {
10    /// DNS-based detection (e.g., OpenDNS, Cloudflare DNS)
11    Dns,
12    /// HTTP/HTTPS-based detection
13    Http,
14    /// STUN protocol (RFC 5389)
15    Stun,
16}
17
18impl std::fmt::Display for Protocol {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        match self {
21            Protocol::Dns => write!(f, "DNS"),
22            Protocol::Http => write!(f, "HTTP"),
23            Protocol::Stun => write!(f, "STUN"),
24        }
25    }
26}
27
28/// IP version preference
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
30#[non_exhaustive]
31pub enum IpVersion {
32    /// IPv4 only
33    V4,
34    /// IPv6 only
35    V6,
36    /// Any IP version (prefer IPv4)
37    #[default]
38    Any,
39}
40
41impl IpVersion {
42    pub(crate) fn matches(self, ip: IpAddr) -> bool {
43        match self {
44            Self::V4 => ip.is_ipv4(),
45            Self::V6 => ip.is_ipv6(),
46            Self::Any => true,
47        }
48    }
49}
50
51/// Result from a successful IP lookup
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct ProviderResult {
54    /// The detected public IP address
55    pub ip: IpAddr,
56    /// Name of the provider that returned this result
57    pub provider: String,
58    /// Protocol used for detection
59    pub protocol: Protocol,
60    /// Time taken to get the result
61    pub latency: Duration,
62}
63
64impl ProviderResult {
65    /// Extract the IPv4 address from the result, if present.
66    pub fn ipv4(&self) -> Option<std::net::Ipv4Addr> {
67        match self.ip {
68            IpAddr::V4(v4) => Some(v4),
69            _ => None,
70        }
71    }
72
73    /// Extract the IPv6 address from the result, if present.
74    pub fn ipv6(&self) -> Option<std::net::Ipv6Addr> {
75        match self.ip {
76            IpAddr::V6(v6) => Some(v6),
77            _ => None,
78        }
79    }
80}
81
82impl std::fmt::Display for ProviderResult {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        write!(
85            f,
86            "{} (via {} over {}, {:?})",
87            self.ip, self.provider, self.protocol, self.latency
88        )
89    }
90}
91
92/// Built-in IP detection providers
93///
94/// Each variant represents a specific provider service.
95/// Use with [`Config::builder()`](crate::Config::builder) to select which providers to use.
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
97#[non_exhaustive]
98pub enum BuiltinProvider {
99    // --- STUN providers ---
100    /// Google STUN server (stun.l.google.com)
101    GoogleStun,
102    /// Google STUN server 1 (stun1.l.google.com)
103    GoogleStun1,
104    /// Google STUN server 2 (stun2.l.google.com)
105    GoogleStun2,
106    /// Cloudflare STUN server (stun.cloudflare.com)
107    CloudflareStun,
108
109    // --- DNS providers ---
110    /// Google DNS via o-o.myaddr.l.google.com TXT
111    GoogleDns,
112    /// Cloudflare DNS via whoami.cloudflare TXT/CH
113    CloudflareDns,
114    /// OpenDNS via myip.opendns.com
115    OpenDns,
116
117    // --- HTTP providers ---
118    /// Cloudflare 1.1.1.1/cdn-cgi/trace
119    CloudflareHttp,
120    /// AWS checkip.amazonaws.com
121    Aws,
122}
123
124impl BuiltinProvider {
125    /// Get the protocol this provider uses
126    pub fn protocol(&self) -> Protocol {
127        match self {
128            Self::GoogleStun | Self::GoogleStun1 | Self::GoogleStun2 | Self::CloudflareStun => {
129                Protocol::Stun
130            }
131            Self::GoogleDns | Self::CloudflareDns | Self::OpenDns => Protocol::Dns,
132            Self::CloudflareHttp | Self::Aws => Protocol::Http,
133        }
134    }
135
136    /// All available built-in providers, ordered by expected performance.
137    ///
138    /// Providers with both IPv4 and IPv6 support are listed first, followed by
139    /// IPv4-only providers. Within each tier, UDP-based protocols (STUN, DNS)
140    /// are preferred over HTTP due to lower overhead (no TLS handshake).
141    ///
142    /// This order is used by [`Strategy::First`](crate::Strategy::First).
143    /// Run the benchmark example to find the optimal order for your network:
144    /// `cargo run --example benchmark --all-features`
145    pub const ALL: &'static [BuiltinProvider] = &[
146        // Tier 1: UDP-based, IPv4 + IPv6
147        Self::CloudflareStun,
148        Self::CloudflareDns,
149        Self::GoogleStun,
150        Self::GoogleStun1,
151        Self::GoogleStun2,
152        Self::GoogleDns,
153        // Tier 2: IPv4-only (fallback)
154        Self::OpenDns,
155        Self::CloudflareHttp,
156        Self::Aws,
157    ];
158
159    /// Create the boxed blocking provider instance
160    pub(crate) fn to_boxed_blocking(self) -> crate::provider::BoxedBlockingProvider {
161        match self {
162            #[cfg(feature = "stun")]
163            Self::GoogleStun => Box::new(crate::stun::providers::google()),
164            #[cfg(feature = "stun")]
165            Self::GoogleStun1 => Box::new(crate::stun::providers::google1()),
166            #[cfg(feature = "stun")]
167            Self::GoogleStun2 => Box::new(crate::stun::providers::google2()),
168            #[cfg(feature = "stun")]
169            Self::CloudflareStun => Box::new(crate::stun::providers::cloudflare()),
170
171            #[cfg(feature = "dns")]
172            Self::GoogleDns => Box::new(crate::dns::providers::google()),
173            #[cfg(feature = "dns")]
174            Self::CloudflareDns => Box::new(crate::dns::providers::cloudflare()),
175            #[cfg(feature = "dns")]
176            Self::OpenDns => Box::new(crate::dns::providers::opendns()),
177
178            #[cfg(feature = "http")]
179            Self::CloudflareHttp => Box::new(crate::http::providers::cloudflare()),
180            #[cfg(feature = "http")]
181            Self::Aws => Box::new(crate::http::providers::aws()),
182
183            // Feature not enabled — create a stub that always errors
184            #[allow(unreachable_patterns)]
185            other => Box::new(super::provider::DisabledProvider(format!("{:?}", other))),
186        }
187    }
188
189    /// Create the boxed async provider instance
190    #[cfg(feature = "tokio")]
191    pub(crate) fn to_boxed(self) -> crate::provider::BoxedProvider {
192        match self {
193            #[cfg(feature = "stun")]
194            Self::GoogleStun => Box::new(crate::stun::providers::google()),
195            #[cfg(feature = "stun")]
196            Self::GoogleStun1 => Box::new(crate::stun::providers::google1()),
197            #[cfg(feature = "stun")]
198            Self::GoogleStun2 => Box::new(crate::stun::providers::google2()),
199            #[cfg(feature = "stun")]
200            Self::CloudflareStun => Box::new(crate::stun::providers::cloudflare()),
201
202            #[cfg(feature = "dns")]
203            Self::GoogleDns => Box::new(crate::dns::providers::google()),
204            #[cfg(feature = "dns")]
205            Self::CloudflareDns => Box::new(crate::dns::providers::cloudflare()),
206            #[cfg(feature = "dns")]
207            Self::OpenDns => Box::new(crate::dns::providers::opendns()),
208
209            #[cfg(feature = "http")]
210            Self::CloudflareHttp => Box::new(crate::http::providers::cloudflare()),
211            #[cfg(feature = "http")]
212            Self::Aws => Box::new(crate::http::providers::aws()),
213
214            // Feature not enabled — create a stub that always errors
215            #[allow(unreachable_patterns)]
216            other => Box::new(super::provider::DisabledProvider(format!("{:?}", other))),
217        }
218    }
219}
220
221impl std::fmt::Display for BuiltinProvider {
222    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223        match self {
224            Self::GoogleStun => write!(f, "Google STUN"),
225            Self::GoogleStun1 => write!(f, "Google STUN 1"),
226            Self::GoogleStun2 => write!(f, "Google STUN 2"),
227            Self::CloudflareStun => write!(f, "Cloudflare STUN"),
228            Self::GoogleDns => write!(f, "Google DNS"),
229            Self::CloudflareDns => write!(f, "Cloudflare DNS"),
230            Self::OpenDns => write!(f, "OpenDNS"),
231            Self::CloudflareHttp => write!(f, "Cloudflare"),
232            Self::Aws => write!(f, "AWS"),
233        }
234    }
235}