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