rustfs_utils/
ip.rs

1// Copyright 2024 RustFS Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::net::{IpAddr, Ipv4Addr};
16
17/// Get the IP address of the machine
18///
19/// Priority is given to trying to get the IPv4 address, and if it fails, try to get the IPv6 address.
20/// If both fail to retrieve, None is returned.
21///
22/// # Returns
23///
24/// * `Some(IpAddr)` - Native IP address (IPv4 or IPv6)
25/// * `None` - Unable to obtain any native IP address
26pub 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
32/// Get the IP address of the machine as a string
33///
34/// If the IP address cannot be obtained, returns "127.0.0.1" as the default value.
35///
36/// # Returns
37///
38/// * `String` - Native IP address (IPv4 or IPv6) as a string, or the default value
39pub fn get_local_ip_with_default() -> String {
40    get_local_ip()
41        .unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))) // Provide a safe default value
42        .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        // Test getting local IP address, should return Some value
53        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            // Verify that the returned IP address is valid
59            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        // Test that function with default value never returns empty string
75        let ip_string = get_local_ip_with_default();
76        assert!(!ip_string.is_empty(), "IP string should never be empty");
77
78        // Verify that the returned string can be parsed as a valid IP address
79        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        // Test whether the default value is 127.0.0.1
88        let default_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
89        let ip_string = get_local_ip_with_default();
90
91        // If unable to get real IP, should return default value
92        if get_local_ip().is_none() {
93            assert_eq!(ip_string, default_ip.to_string());
94        }
95
96        // In any case, should always return a valid IP address string
97        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        // Test IP address type recognition
104        if let Some(ip) = get_local_ip() {
105            match ip {
106                IpAddr::V4(ipv4) => {
107                    // Test IPv4 address properties
108                    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                    // Check if it's a private address (usually local IP is private)
113                    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                    // Test IPv6 address properties
119                    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        // Test IP address string format
132        let ip_string = get_local_ip_with_default();
133
134        // Verify string format
135        assert!(!ip_string.contains(' '), "IP string should not contain spaces");
136        assert!(!ip_string.is_empty(), "IP string should not be empty");
137
138        // Verify round-trip conversion
139        let parsed_ip: IpAddr = ip_string.parse().expect("Should parse as valid IP");
140        let back_to_string = parsed_ip.to_string();
141
142        // For standard IP addresses, round-trip conversion should be consistent
143        println!("Original: {ip_string}, Parsed back: {back_to_string}");
144    }
145
146    #[test]
147    fn test_default_fallback_value() {
148        // Test correctness of default fallback value
149        let default_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
150        assert_eq!(default_ip.to_string(), "127.0.0.1");
151
152        // Verify default IP properties
153        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        // Test consistency between the two functions
163        let ip_option = get_local_ip();
164        let ip_string = get_local_ip_with_default();
165
166        match ip_option {
167            Some(ip) => {
168                // If get_local_ip returns Some, then get_local_ip_with_default should return the same IP
169                assert_eq!(ip.to_string(), ip_string, "Both functions should return the same IP when available");
170            }
171            None => {
172                // If get_local_ip returns None, then get_local_ip_with_default should return default value
173                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        // Test consistency of multiple calls
181        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        // Multiple calls should return the same result
187        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        // Integration test: verify that the obtained IP address can be used for network connections
195        if let Some(ip) = get_local_ip() {
196            match ip {
197                IpAddr::V4(ipv4) => {
198                    // For IPv4, check if it's a valid network address
199                    assert!(!ipv4.is_unspecified(), "Should not be 0.0.0.0");
200
201                    // If it's not a loopback address, it should be routable
202                    if !ipv4.is_loopback() {
203                        println!("Got routable IPv4: {ipv4}");
204                    }
205                }
206                IpAddr::V6(ipv6) => {
207                    // For IPv6, check if it's a valid network address
208                    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}