1use std::net::{IpAddr, Ipv4Addr};
16
17pub fn get_local_ip() -> Option<IpAddr> {
27 local_ip_address::local_ip()
28 .ok()
29 .or_else(|| local_ip_address::local_ipv6().ok())
30}
31
32pub fn get_local_ip_with_default() -> String {
40 get_local_ip()
41 .unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))) .to_string()
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48 use std::net::Ipv4Addr;
49
50 #[test]
51 fn test_get_local_ip_returns_some_ip() {
52 let ip = get_local_ip();
54 assert!(ip.is_some(), "Should be able to get local IP address");
55
56 if let Some(ip_addr) = ip {
57 println!("Local IP address: {ip_addr}");
58 match ip_addr {
60 IpAddr::V4(ipv4) => {
61 assert!(!ipv4.is_unspecified(), "IPv4 should not be unspecified (0.0.0.0)");
62 println!("Got IPv4 address: {ipv4}");
63 }
64 IpAddr::V6(ipv6) => {
65 assert!(!ipv6.is_unspecified(), "IPv6 should not be unspecified (::)");
66 println!("Got IPv6 address: {ipv6}");
67 }
68 }
69 }
70 }
71
72 #[test]
73 fn test_get_local_ip_with_default_never_empty() {
74 let ip_string = get_local_ip_with_default();
76 assert!(!ip_string.is_empty(), "IP string should never be empty");
77
78 let parsed_ip: Result<IpAddr, _> = ip_string.parse();
80 assert!(parsed_ip.is_ok(), "Returned string should be a valid IP address: {ip_string}");
81
82 println!("Local IP with default: {ip_string}");
83 }
84
85 #[test]
86 fn test_get_local_ip_with_default_fallback() {
87 let default_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
89 let ip_string = get_local_ip_with_default();
90
91 if get_local_ip().is_none() {
93 assert_eq!(ip_string, default_ip.to_string());
94 }
95
96 let parsed: Result<IpAddr, _> = ip_string.parse();
98 assert!(parsed.is_ok(), "Should always return a valid IP string");
99 }
100
101 #[test]
102 fn test_ip_address_types() {
103 if let Some(ip) = get_local_ip() {
105 match ip {
106 IpAddr::V4(ipv4) => {
107 println!("IPv4 address: {ipv4}");
109 assert!(!ipv4.is_multicast(), "Local IP should not be multicast");
110 assert!(!ipv4.is_broadcast(), "Local IP should not be broadcast");
111
112 let is_private = ipv4.is_private();
114 let is_loopback = ipv4.is_loopback();
115 println!("IPv4 is private: {is_private}, is loopback: {is_loopback}");
116 }
117 IpAddr::V6(ipv6) => {
118 println!("IPv6 address: {ipv6}");
120 assert!(!ipv6.is_multicast(), "Local IP should not be multicast");
121
122 let is_loopback = ipv6.is_loopback();
123 println!("IPv6 is loopback: {is_loopback}");
124 }
125 }
126 }
127 }
128
129 #[test]
130 fn test_ip_string_format() {
131 let ip_string = get_local_ip_with_default();
133
134 assert!(!ip_string.contains(' '), "IP string should not contain spaces");
136 assert!(!ip_string.is_empty(), "IP string should not be empty");
137
138 let parsed_ip: IpAddr = ip_string.parse().expect("Should parse as valid IP");
140 let back_to_string = parsed_ip.to_string();
141
142 println!("Original: {ip_string}, Parsed back: {back_to_string}");
144 }
145
146 #[test]
147 fn test_default_fallback_value() {
148 let default_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
150 assert_eq!(default_ip.to_string(), "127.0.0.1");
151
152 if let IpAddr::V4(ipv4) = default_ip {
154 assert!(ipv4.is_loopback(), "Default IP should be loopback");
155 assert!(!ipv4.is_unspecified(), "Default IP should not be unspecified");
156 assert!(!ipv4.is_multicast(), "Default IP should not be multicast");
157 }
158 }
159
160 #[test]
161 fn test_consistency_between_functions() {
162 let ip_option = get_local_ip();
164 let ip_string = get_local_ip_with_default();
165
166 match ip_option {
167 Some(ip) => {
168 assert_eq!(ip.to_string(), ip_string, "Both functions should return the same IP when available");
170 }
171 None => {
172 assert_eq!(ip_string, "127.0.0.1", "Should return default value when no IP is available");
174 }
175 }
176 }
177
178 #[test]
179 fn test_multiple_calls_consistency() {
180 let ip1 = get_local_ip();
182 let ip2 = get_local_ip();
183 let ip_str1 = get_local_ip_with_default();
184 let ip_str2 = get_local_ip_with_default();
185
186 assert_eq!(ip1, ip2, "Multiple calls to get_local_ip should return same result");
188 assert_eq!(ip_str1, ip_str2, "Multiple calls to get_local_ip_with_default should return same result");
189 }
190
191 #[cfg(feature = "integration")]
192 #[test]
193 fn test_network_connectivity() {
194 if let Some(ip) = get_local_ip() {
196 match ip {
197 IpAddr::V4(ipv4) => {
198 assert!(!ipv4.is_unspecified(), "Should not be 0.0.0.0");
200
201 if !ipv4.is_loopback() {
203 println!("Got routable IPv4: {ipv4}");
204 }
205 }
206 IpAddr::V6(ipv6) => {
207 assert!(!ipv6.is_unspecified(), "Should not be ::");
209
210 if !ipv6.is_loopback() {
211 println!("Got routable IPv6: {ipv6}");
212 }
213 }
214 }
215 }
216 }
217}