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
41/// Result from a successful IP lookup
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct ProviderResult {
44    /// The detected public IP address
45    pub ip: IpAddr,
46    /// Name of the provider that returned this result
47    pub provider: String,
48    /// Protocol used for detection
49    pub protocol: Protocol,
50    /// Time taken to get the result
51    pub latency: Duration,
52}
53
54impl ProviderResult {
55    /// Extract the IPv4 address from the result, if present.
56    pub fn ipv4(&self) -> Option<std::net::Ipv4Addr> {
57        match self.ip {
58            IpAddr::V4(v4) => Some(v4),
59            _ => None,
60        }
61    }
62
63    /// Extract the IPv6 address from the result, if present.
64    pub fn ipv6(&self) -> Option<std::net::Ipv6Addr> {
65        match self.ip {
66            IpAddr::V6(v6) => Some(v6),
67            _ => None,
68        }
69    }
70}
71
72impl std::fmt::Display for ProviderResult {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        write!(
75            f,
76            "{} (via {} over {}, {:?})",
77            self.ip, self.provider, self.protocol, self.latency
78        )
79    }
80}
81
82/// Built-in IP detection providers
83///
84/// Each variant represents a specific provider service.
85/// Use with [`Config::builder()`](crate::Config::builder) to select which providers to use.
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
87#[non_exhaustive]
88pub enum BuiltinProvider {
89    // --- STUN providers ---
90    /// Google STUN server (stun.l.google.com)
91    GoogleStun,
92    /// Google STUN server 1 (stun1.l.google.com)
93    GoogleStun1,
94    /// Google STUN server 2 (stun2.l.google.com)
95    GoogleStun2,
96    /// Cloudflare STUN server (stun.cloudflare.com)
97    CloudflareStun,
98
99    // --- DNS providers ---
100    /// Google DNS via o-o.myaddr.l.google.com TXT
101    GoogleDns,
102    /// Cloudflare DNS via whoami.cloudflare TXT/CH
103    CloudflareDns,
104    /// OpenDNS via myip.opendns.com
105    OpenDns,
106
107    // --- HTTP providers ---
108    /// Cloudflare 1.1.1.1/cdn-cgi/trace
109    CloudflareHttp,
110    /// AWS checkip.amazonaws.com
111    Aws,
112}
113
114impl BuiltinProvider {
115    /// Get the protocol this provider uses
116    pub fn protocol(&self) -> Protocol {
117        match self {
118            Self::GoogleStun | Self::GoogleStun1 | Self::GoogleStun2 | Self::CloudflareStun => {
119                Protocol::Stun
120            }
121            Self::GoogleDns | Self::CloudflareDns | Self::OpenDns => Protocol::Dns,
122            Self::CloudflareHttp | Self::Aws => Protocol::Http,
123        }
124    }
125
126    /// All available built-in providers, ordered by expected performance.
127    ///
128    /// Providers with both IPv4 and IPv6 support are listed first, followed by
129    /// IPv4-only providers. Within each tier, UDP-based protocols (STUN, DNS)
130    /// are preferred over HTTP due to lower overhead (no TLS handshake).
131    ///
132    /// This order is used by [`Strategy::First`](crate::Strategy::First).
133    /// Run the benchmark example to find the optimal order for your network:
134    /// `cargo run --example benchmark --all-features`
135    pub const ALL: &'static [BuiltinProvider] = &[
136        // Tier 1: UDP-based, IPv4 + IPv6
137        Self::CloudflareStun,
138        Self::CloudflareDns,
139        Self::GoogleStun,
140        Self::GoogleStun1,
141        Self::GoogleStun2,
142        Self::GoogleDns,
143        // Tier 2: IPv4-only (fallback)
144        Self::OpenDns,
145        Self::CloudflareHttp,
146        Self::Aws,
147    ];
148
149    /// Create the boxed provider instance
150    pub(crate) fn to_boxed(self) -> crate::provider::BoxedProvider {
151        match self {
152            #[cfg(feature = "stun")]
153            Self::GoogleStun => Box::new(crate::stun::providers::google()),
154            #[cfg(feature = "stun")]
155            Self::GoogleStun1 => Box::new(crate::stun::providers::google1()),
156            #[cfg(feature = "stun")]
157            Self::GoogleStun2 => Box::new(crate::stun::providers::google2()),
158            #[cfg(feature = "stun")]
159            Self::CloudflareStun => Box::new(crate::stun::providers::cloudflare()),
160
161            #[cfg(feature = "dns")]
162            Self::GoogleDns => Box::new(crate::dns::providers::google()),
163            #[cfg(feature = "dns")]
164            Self::CloudflareDns => Box::new(crate::dns::providers::cloudflare()),
165            #[cfg(feature = "dns")]
166            Self::OpenDns => Box::new(crate::dns::providers::opendns()),
167
168            #[cfg(feature = "http")]
169            Self::CloudflareHttp => Box::new(crate::http::providers::cloudflare()),
170            #[cfg(feature = "http")]
171            Self::Aws => Box::new(crate::http::providers::aws()),
172
173            // Feature not enabled — create a stub that always errors
174            #[allow(unreachable_patterns)]
175            other => Box::new(super::provider::DisabledProvider(format!("{:?}", other))),
176        }
177    }
178}
179
180impl std::fmt::Display for BuiltinProvider {
181    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182        match self {
183            Self::GoogleStun => write!(f, "Google STUN"),
184            Self::GoogleStun1 => write!(f, "Google STUN 1"),
185            Self::GoogleStun2 => write!(f, "Google STUN 2"),
186            Self::CloudflareStun => write!(f, "Cloudflare STUN"),
187            Self::GoogleDns => write!(f, "Google DNS"),
188            Self::CloudflareDns => write!(f, "Cloudflare DNS"),
189            Self::OpenDns => write!(f, "OpenDNS"),
190            Self::CloudflareHttp => write!(f, "Cloudflare"),
191            Self::Aws => write!(f, "AWS"),
192        }
193    }
194}