ip_network/
ip_network.rs

1use std::cmp::Ordering;
2use std::fmt;
3use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
4use std::str::FromStr;
5use crate::{IpNetworkError, IpNetworkParseError};
6use crate::helpers;
7use crate::{Ipv4Network, Ipv6Network};
8
9/// Holds IPv4 or IPv6 network.
10#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash, PartialOrd, Ord)]
11pub enum IpNetwork {
12    V4(Ipv4Network),
13    V6(Ipv6Network),
14}
15
16impl IpNetwork {
17    /// Constructs new `IpNetwork` based on [`IpAddr`] and `netmask`.
18    ///
19    /// [`IpAddr`]: https://doc.rust-lang.org/std/net/enum.IpAddr.html
20    ///
21    /// # Examples
22    ///
23    /// ```
24    /// use std::net::{IpAddr, Ipv4Addr};
25    /// use std::str::FromStr;
26    /// use ip_network::{IpNetwork, Ipv4Network};
27    ///
28    /// let network_address = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0));
29    /// let ip_network = IpNetwork::new(network_address, 24)?;
30    /// assert_eq!(ip_network.network_address(), IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0)));
31    /// assert_eq!(ip_network.netmask(), 24);
32    /// # Ok::<(), ip_network::IpNetworkError>(())
33    /// ```
34    #[allow(clippy::new_ret_no_self)]
35    pub fn new<I: Into<IpAddr>>(network_address: I, netmask: u8) -> Result<Self, IpNetworkError> {
36        Ok(match network_address.into() {
37            IpAddr::V4(ip) => IpNetwork::V4(Ipv4Network::new(ip, netmask)?),
38            IpAddr::V6(ip) => IpNetwork::V6(Ipv6Network::new(ip, netmask)?),
39        })
40    }
41
42    /// Constructs new `IpNetwork` based on [`IpAddr`] and `netmask` with truncating host bits
43    /// from given `network_address`.
44    ///
45    /// Returns error if netmask is bigger than 32 for IPv4 and 128 for IPv6.
46    ///
47    /// [`Ipv4Addr`]: https://doc.rust-lang.org/std/net/struct.IpAddr.html
48    ///
49    /// # Examples
50    ///
51    /// ```
52    /// use std::net::{IpAddr, Ipv4Addr};
53    /// use ip_network::IpNetwork;
54    ///
55    /// let network_address = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 128));
56    /// let ip_network = IpNetwork::new_truncate(network_address, 24)?;
57    /// assert_eq!(ip_network.network_address(), IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0)));
58    /// assert_eq!(ip_network.netmask(), 24);
59    /// # Ok::<(), ip_network::IpNetworkError>(())
60    /// ```
61    pub fn new_truncate<I: Into<IpAddr>>(
62        network_address: I,
63        netmask: u8,
64    ) -> Result<Self, IpNetworkError> {
65        Ok(match network_address.into() {
66            IpAddr::V4(ip) => IpNetwork::V4(Ipv4Network::new_truncate(ip, netmask)?),
67            IpAddr::V6(ip) => IpNetwork::V6(Ipv6Network::new_truncate(ip, netmask)?),
68        })
69    }
70
71    /// Returns network IP address.
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// use std::net::{IpAddr, Ipv4Addr};
77    /// use ip_network::IpNetwork;
78    ///
79    /// let ip_network = IpNetwork::new(Ipv4Addr::new(192, 168, 1, 0), 24)?;
80    /// assert_eq!(ip_network.network_address(), IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0)));
81    /// # Ok::<(), ip_network::IpNetworkError>(())
82    /// ```
83    pub fn network_address(&self) -> IpAddr {
84        match self {
85            IpNetwork::V4(ip_network) => IpAddr::V4(ip_network.network_address()),
86            IpNetwork::V6(ip_network) => IpAddr::V6(ip_network.network_address()),
87        }
88    }
89
90    /// Returns network mask as integer.
91    ///
92    /// # Examples
93    ///
94    /// ```
95    /// use std::net::{IpAddr, Ipv4Addr};
96    /// use ip_network::IpNetwork;
97    ///
98    /// let ip_network = IpNetwork::new(Ipv4Addr::new(192, 168, 1, 0), 24)?;
99    /// assert_eq!(ip_network.netmask(), 24);
100    /// # Ok::<(), ip_network::IpNetworkError>(())
101    /// ```
102    pub fn netmask(&self) -> u8 {
103        match self {
104            IpNetwork::V4(ip_network) => ip_network.netmask(),
105            IpNetwork::V6(ip_network) => ip_network.netmask(),
106        }
107    }
108
109    /// Returns `true` if `IpNetwork` contains `Ipv4Network` struct.
110    pub fn is_ipv4(&self) -> bool {
111        match self {
112            IpNetwork::V4(_) => true,
113            IpNetwork::V6(_) => false,
114        }
115    }
116
117    /// Returns `true` if `IpNetwork` contains `Ipv6Network` struct.
118    pub fn is_ipv6(&self) -> bool {
119        !self.is_ipv4()
120    }
121
122    /// Returns `true` if `IpNetwork` contains `IpAddr`. For different network type
123    /// (for example IpNetwork is IPv6 and IpAddr is IPv4) always returns `false`.
124    ///
125    /// # Examples
126    ///
127    /// ```
128    /// use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
129    /// use ip_network::IpNetwork;
130    ///
131    /// let ip_network = IpNetwork::new(Ipv4Addr::new(192, 168, 1, 0), 24)?;
132    /// assert!(ip_network.contains(Ipv4Addr::new(192, 168, 1, 25)));
133    /// assert!(!ip_network.contains(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 1, 0, 0)));
134    /// # Ok::<(), ip_network::IpNetworkError>(())
135    /// ```
136    pub fn contains<I: Into<IpAddr>>(&self, ip: I) -> bool {
137        match (self, ip.into()) {
138            (IpNetwork::V4(network), IpAddr::V4(ip)) => network.contains(ip),
139            (IpNetwork::V6(network), IpAddr::V6(ip)) => network.contains(ip),
140            _ => false,
141        }
142    }
143
144    /// Returns `true` if the network is default route, that contains all IP addresses.
145    pub fn is_default_route(&self) -> bool {
146        match self {
147            IpNetwork::V4(ip_network) => ip_network.is_default_route(),
148            IpNetwork::V6(ip_network) => ip_network.is_default_route(),
149        }
150    }
151
152    /// Returns `true` if the network is part of multicast network range.
153    pub fn is_multicast(&self) -> bool {
154        match self {
155            IpNetwork::V4(ip_network) => ip_network.is_multicast(),
156            IpNetwork::V6(ip_network) => ip_network.is_multicast(),
157        }
158    }
159
160    /// Returns `true` if this is a part of network reserved for documentation.
161    pub fn is_documentation(&self) -> bool {
162        match self {
163            IpNetwork::V4(ip_network) => ip_network.is_documentation(),
164            IpNetwork::V6(ip_network) => ip_network.is_documentation(),
165        }
166    }
167
168    /// Returns `true` if this network is inside loopback address range.
169    pub fn is_loopback(&self) -> bool {
170        match self {
171            IpNetwork::V4(ip_network) => ip_network.is_loopback(),
172            IpNetwork::V6(ip_network) => ip_network.is_loopback(),
173        }
174    }
175
176    /// Returns `true` if the network appears to be globally routable.
177    pub fn is_global(&self) -> bool {
178        match self {
179            IpNetwork::V4(ip_network) => ip_network.is_global(),
180            IpNetwork::V6(ip_network) => ip_network.is_global(),
181        }
182    }
183
184    /// Converts string in format IPv4 (X.X.X.X/Y) or IPv6 (X:X::X/Y) CIDR notation to `IpNetwork`.
185    ///
186    /// # Examples
187    ///
188    /// ```
189    /// use std::net::Ipv4Addr;
190    /// use ip_network::{IpNetwork, Ipv4Network};
191    ///
192    /// let ip_network = IpNetwork::from_str_truncate("192.168.1.1/24").unwrap();
193    /// assert_eq!(ip_network, IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(192, 168, 1, 0), 24).unwrap()));
194    /// ```
195    pub fn from_str_truncate(s: &str) -> Result<Self, IpNetworkParseError> {
196        let (ip, netmask) =
197            helpers::split_ip_netmask(s).ok_or(IpNetworkParseError::InvalidFormatError)?;
198
199        let network_address =
200            IpAddr::from_str(ip).map_err(|_| IpNetworkParseError::AddrParseError)?;
201        let netmask =
202            u8::from_str(netmask).map_err(|_| IpNetworkParseError::InvalidNetmaskFormat)?;
203
204        IpNetwork::new_truncate(network_address, netmask)
205            .map_err(IpNetworkParseError::IpNetworkError)
206    }
207
208    /// Return an iterator of the collapsed IpNetworks.
209    pub fn collapse_addresses(addresses: &[Self]) -> Vec<Self> {
210        let mut ipv4_networks = vec![];
211        let mut ipv6_networks = vec![];
212        for address in addresses {
213            match address {
214                IpNetwork::V4(ip_network) => ipv4_networks.push(*ip_network),
215                IpNetwork::V6(ip_network) => ipv6_networks.push(*ip_network),
216            }
217        }
218
219        let mut collapsed = Ipv4Network::collapse_addresses(&ipv4_networks)
220            .into_iter()
221            .map(IpNetwork::from)
222            .collect::<Vec<_>>();
223        collapsed.extend(
224            Ipv6Network::collapse_addresses(&ipv6_networks)
225                .into_iter()
226                .map(IpNetwork::from),
227        );
228        collapsed
229    }
230}
231
232impl fmt::Display for IpNetwork {
233    /// Converts `IpNetwork` to string in format X.X.X.X/Y for IPv4 and X:X::X/Y for IPv6 (CIDR notation).
234    ///
235    /// # Examples
236    ///
237    /// ```
238    /// use std::net::Ipv4Addr;
239    /// use ip_network::{IpNetwork, Ipv4Network};
240    ///
241    /// let ip_network = IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(192, 168, 1, 0), 24)?);
242    /// assert_eq!(ip_network.to_string(), "192.168.1.0/24");
243    /// # Ok::<(), ip_network::IpNetworkError>(())
244    /// ```
245    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
246        match *self {
247            IpNetwork::V4(ref network) => network.fmt(f),
248            IpNetwork::V6(ref network) => network.fmt(f),
249        }
250    }
251}
252
253impl FromStr for IpNetwork {
254    type Err = IpNetworkParseError;
255
256    /// Converts string in format IPv4 (X.X.X.X/Y) or IPv6 (X:X::X/Y) CIDR notation to `IpNetwork`.
257    ///
258    /// # Examples
259    ///
260    /// ```
261    /// use std::net::Ipv4Addr;
262    /// use std::str::FromStr;
263    /// use ip_network::{IpNetwork, Ipv4Network};
264    ///
265    /// let ip_network = IpNetwork::from_str("192.168.1.0/24").unwrap();
266    /// assert_eq!(ip_network, IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(192, 168, 1, 0), 24).unwrap()));
267    /// ```
268    fn from_str(s: &str) -> Result<IpNetwork, IpNetworkParseError> {
269        let (ip, netmask) =
270            helpers::split_ip_netmask(s).ok_or(IpNetworkParseError::InvalidFormatError)?;
271
272        let network_address =
273            IpAddr::from_str(ip).map_err(|_| IpNetworkParseError::AddrParseError)?;
274        let netmask =
275            u8::from_str(netmask).map_err(|_| IpNetworkParseError::InvalidNetmaskFormat)?;
276
277        IpNetwork::new(network_address, netmask).map_err(IpNetworkParseError::IpNetworkError)
278    }
279}
280
281impl From<Ipv4Addr> for IpNetwork {
282    /// Converts `Ipv4Addr` to `IpNetwork` with netmask 32.
283    #[inline]
284    fn from(ip: Ipv4Addr) -> Self {
285        IpNetwork::V4(Ipv4Network::from(ip))
286    }
287}
288
289impl From<Ipv6Addr> for IpNetwork {
290    /// Converts `Ipv6Addr` to `IpNetwork` with netmask 128.
291    #[inline]
292    fn from(ip: Ipv6Addr) -> Self {
293        IpNetwork::V6(Ipv6Network::from(ip))
294    }
295}
296
297impl From<IpAddr> for IpNetwork {
298    /// Converts `IpAddr` to `IpNetwork` with netmask 32 for IPv4 address and 128 for IPv6 address.
299    fn from(ip: IpAddr) -> Self {
300        match ip {
301            IpAddr::V4(ip) => IpNetwork::from(ip),
302            IpAddr::V6(ip) => IpNetwork::from(ip),
303        }
304    }
305}
306
307impl From<Ipv4Network> for IpNetwork {
308    #[inline]
309    fn from(network: Ipv4Network) -> Self {
310        IpNetwork::V4(network)
311    }
312}
313
314impl From<Ipv6Network> for IpNetwork {
315    #[inline]
316    fn from(network: Ipv6Network) -> Self {
317        IpNetwork::V6(network)
318    }
319}
320
321impl PartialEq<Ipv4Network> for IpNetwork {
322    fn eq(&self, other: &Ipv4Network) -> bool {
323        match self {
324            IpNetwork::V4(v4) => v4 == other,
325            IpNetwork::V6(_) => false,
326        }
327    }
328}
329
330impl PartialEq<Ipv6Network> for IpNetwork {
331    fn eq(&self, other: &Ipv6Network) -> bool {
332        match self {
333            IpNetwork::V4(_) => false,
334            IpNetwork::V6(v6) => v6 == other,
335        }
336    }
337}
338
339impl PartialEq<IpNetwork> for Ipv4Network {
340    fn eq(&self, other: &IpNetwork) -> bool {
341        match other {
342            IpNetwork::V4(v4) => self == v4,
343            IpNetwork::V6(_) => false,
344        }
345    }
346}
347
348impl PartialEq<IpNetwork> for Ipv6Network {
349    fn eq(&self, other: &IpNetwork) -> bool {
350        match other {
351            IpNetwork::V4(_) => false,
352            IpNetwork::V6(v6) => self == v6,
353        }
354    }
355}
356
357impl PartialOrd<Ipv4Network> for IpNetwork {
358    fn partial_cmp(&self, other: &Ipv4Network) -> Option<Ordering> {
359        match self {
360            IpNetwork::V4(v4) => v4.partial_cmp(other),
361            IpNetwork::V6(_) => Some(Ordering::Greater),
362        }
363    }
364}
365
366impl PartialOrd<IpNetwork> for Ipv4Network {
367    fn partial_cmp(&self, other: &IpNetwork) -> Option<Ordering> {
368        match other {
369            IpNetwork::V4(v4) => self.partial_cmp(v4),
370            IpNetwork::V6(_) => Some(Ordering::Less),
371        }
372    }
373}
374
375impl PartialOrd<Ipv6Network> for IpNetwork {
376    fn partial_cmp(&self, other: &Ipv6Network) -> Option<Ordering> {
377        match self {
378            IpNetwork::V4(_) => Some(Ordering::Less),
379            IpNetwork::V6(v6) => v6.partial_cmp(other),
380        }
381    }
382}
383
384impl PartialOrd<IpNetwork> for Ipv6Network {
385    fn partial_cmp(&self, other: &IpNetwork) -> Option<Ordering> {
386        match other {
387            IpNetwork::V4(_) => Some(Ordering::Greater),
388            IpNetwork::V6(v6) => self.partial_cmp(v6),
389        }
390    }
391}
392
393#[cfg(test)]
394mod tests {
395    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
396    use crate::{IpNetwork, IpNetworkParseError, IpNetworkError, Ipv4Network, Ipv6Network};
397    use std::str::FromStr;
398
399    fn return_test_ipv4_network() -> Ipv4Network {
400        Ipv4Network::new(Ipv4Addr::new(192, 168, 0, 0), 16).unwrap()
401    }
402
403    fn return_test_ipv6_network() -> Ipv6Network {
404        Ipv6Network::new(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0), 32).unwrap()
405    }
406
407    #[test]
408    fn network_address_ipv4() {
409        let ip_network = IpNetwork::V4(return_test_ipv4_network());
410        assert_eq!(
411            IpAddr::V4(Ipv4Addr::new(192, 168, 0, 0)),
412            ip_network.network_address()
413        );
414    }
415
416    #[test]
417    fn network_address_ipv6() {
418        let ip_network = IpNetwork::V6(return_test_ipv6_network());
419        assert_eq!(
420            IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)),
421            ip_network.network_address()
422        );
423    }
424
425    #[test]
426    fn is_ipv4() {
427        let ip_network = IpNetwork::V4(return_test_ipv4_network());
428        assert!(ip_network.is_ipv4());
429        assert!(!ip_network.is_ipv6());
430    }
431
432    #[test]
433    fn is_ipv6() {
434        let ip_network = IpNetwork::V6(return_test_ipv6_network());
435        assert!(ip_network.is_ipv6());
436        assert!(!ip_network.is_ipv4());
437    }
438
439    #[test]
440    fn parse_ipv4() {
441        let ip_network: IpNetwork = "192.168.0.0/16".parse().unwrap();
442        assert_eq!(ip_network, IpNetwork::V4(return_test_ipv4_network()));
443    }
444
445    #[test]
446    fn parse_ipv6() {
447        let ip_network: IpNetwork = "2001:db8::/32".parse().unwrap();
448        assert_eq!(ip_network, IpNetwork::V6(return_test_ipv6_network()));
449    }
450
451    #[test]
452    fn parse_empty() {
453        let ip_network = "".parse::<IpNetwork>();
454        assert!(ip_network.is_err());
455        assert_eq!(
456            IpNetworkParseError::InvalidFormatError,
457            ip_network.unwrap_err()
458        );
459    }
460
461    #[test]
462    fn parse_invalid_netmask() {
463        let ip_network = "192.168.0.0/a".parse::<IpNetwork>();
464        assert!(ip_network.is_err());
465        assert_eq!(
466            IpNetworkParseError::InvalidNetmaskFormat,
467            ip_network.unwrap_err()
468        );
469    }
470
471    #[test]
472    fn parse_invalid_ip() {
473        let ip_network = "192.168.0.0a/16".parse::<IpNetwork>();
474        assert!(ip_network.is_err());
475        assert_eq!(IpNetworkParseError::AddrParseError, ip_network.unwrap_err());
476    }
477
478    #[test]
479    fn parse_ipv4_host_bits_set() {
480        let ip_network = "192.168.0.1/16".parse::<IpNetwork>();
481        assert!(ip_network.is_err());
482        assert_eq!(
483            IpNetworkParseError::IpNetworkError(IpNetworkError::HostBitsSet),
484            ip_network.unwrap_err()
485        );
486    }
487
488    #[test]
489    fn parse_ipv6_host_bits_set() {
490        let ip_network = "2001:db8::1/32".parse::<IpNetwork>();
491        assert!(ip_network.is_err());
492        assert_eq!(
493            IpNetworkParseError::IpNetworkError(IpNetworkError::HostBitsSet),
494            ip_network.unwrap_err()
495        );
496    }
497
498    #[test]
499    fn format_ipv4() {
500        let ip_network = IpNetwork::V4(return_test_ipv4_network());
501        assert_eq!(ip_network.to_string(), "192.168.0.0/16");
502    }
503
504    #[test]
505    fn format_ipv6() {
506        let ip_network = IpNetwork::V6(return_test_ipv6_network());
507        assert_eq!(ip_network.to_string(), "2001:db8::/32");
508    }
509
510    #[test]
511    fn from_ipv4addr() {
512        let ipv4addr = Ipv4Addr::new(1, 2, 3, 4);
513        let ip_network = IpNetwork::from(ipv4addr);
514        assert_eq!(IpAddr::V4(ipv4addr), ip_network.network_address());
515        assert_eq!(32, ip_network.netmask());
516    }
517
518    #[test]
519    fn from_ipv6addr() {
520        let ipv6addr = Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0);
521        let ip_network = IpNetwork::from(ipv6addr);
522        assert_eq!(IpAddr::V6(ipv6addr), ip_network.network_address());
523        assert_eq!(128, ip_network.netmask());
524    }
525
526    #[test]
527    fn from_ipaddr() {
528        let ipaddr = IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0));
529        let ip_network = IpNetwork::from(ipaddr);
530        assert_eq!(ipaddr, ip_network.network_address());
531        assert_eq!(128, ip_network.netmask());
532    }
533
534    #[test]
535    fn from_ipv4_network() {
536        let ipv4_network = return_test_ipv4_network();
537        let ip_network = IpNetwork::from(ipv4_network);
538        assert_eq!(
539            IpAddr::V4(ipv4_network.network_address()),
540            ip_network.network_address()
541        );
542        assert_eq!(ipv4_network.netmask(), ip_network.netmask());
543    }
544
545    #[test]
546    fn from_ipv6_network() {
547        let ipv6_network = return_test_ipv6_network();
548        let ip_network = IpNetwork::from(ipv6_network);
549        assert_eq!(
550            IpAddr::V6(ipv6_network.network_address()),
551            ip_network.network_address()
552        );
553        assert_eq!(ipv6_network.netmask(), ip_network.netmask());
554    }
555
556    #[test]
557    fn equal_ip_network_ipv4_network() {
558        let ip_network = IpNetwork::V4(return_test_ipv4_network());
559        let ipv4_network = return_test_ipv4_network();
560        let different = Ipv4Network::new(Ipv4Addr::new(1, 2, 3, 4), 32).unwrap();
561        assert_eq!(ip_network, ipv4_network);
562        assert_eq!(ipv4_network, ip_network);
563        assert_ne!(ip_network, different);
564        assert_ne!(different, ip_network);
565        assert_ne!(ip_network, return_test_ipv6_network());
566        assert_ne!(return_test_ipv6_network(), ip_network);
567    }
568
569    #[test]
570    fn equal_ip_network_ipv6_network() {
571        let ip_network = IpNetwork::V6(return_test_ipv6_network());
572        let ipv6_network = return_test_ipv6_network();
573        let different = Ipv6Network::new(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), 128).unwrap();
574        assert_eq!(ip_network, ipv6_network);
575        assert_eq!(ipv6_network, ip_network);
576        assert_ne!(ip_network, different);
577        assert_ne!(different, ip_network);
578        assert_ne!(ip_network, return_test_ipv4_network());
579        assert_ne!(return_test_ipv4_network(), ip_network);
580    }
581
582    #[test]
583    fn compare_ip_network_ipv4_network_same() {
584        let ip_network = IpNetwork::V4(return_test_ipv4_network());
585        let ipv4_network = return_test_ipv4_network();
586        assert!(ip_network <= ipv4_network);
587        assert!(ip_network >= ipv4_network);
588        assert!(ipv4_network <= ip_network);
589        assert!(ipv4_network >= ip_network);
590    }
591
592    #[test]
593    fn compare_ip_network_ipv6_network_same() {
594        let ip_network = IpNetwork::V6(return_test_ipv6_network());
595        let ipv6_network = return_test_ipv6_network();
596        assert!(ip_network <= ipv6_network);
597        assert!(ip_network >= ipv6_network);
598        assert!(ipv6_network <= ip_network);
599        assert!(ipv6_network >= ip_network);
600    }
601
602    #[test]
603    fn compare_ip_network_v4_ip_network_v6() {
604        let ip_network_v4 = IpNetwork::V4(return_test_ipv4_network());
605        let ip_network_v6 = IpNetwork::V6(return_test_ipv6_network());
606        assert!(ip_network_v4 < ip_network_v6);
607        assert!(ip_network_v6 > ip_network_v4);
608    }
609
610    #[test]
611    fn collapse_addresses() {
612        let addresses: Vec<_> = [
613            "192.0.2.0/25",
614            "192.0.2.128/25",
615            "2001::/100",
616            "2001::/120",
617            "2001::/96",
618        ]
619        .iter()
620        .map(|i| IpNetwork::from_str(i).unwrap())
621        .collect();
622        let collapsed = IpNetwork::collapse_addresses(&addresses);
623        assert_eq!(2, collapsed.len());
624    }
625}