oxnet/
ipnet.rs

1// Copyright 2025 Oxide Computer Company
2
3use std::{
4    net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr},
5    num::ParseIntError,
6};
7
8/// A prefix error during the creation of an [IpNet], [Ipv4Net], or [Ipv6Net]
9#[derive(Debug, Clone)]
10pub struct IpNetPrefixError(u8);
11
12impl std::fmt::Display for IpNetPrefixError {
13    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14        write!(f, "invalid network prefix {}", self.0)
15    }
16}
17impl std::error::Error for IpNetPrefixError {}
18
19/// An error during the parsing of an [IpNet], [Ipv4Net], or [Ipv6Net]
20#[derive(Debug, Clone)]
21pub enum IpNetParseError {
22    /// Failure to parse the address
23    InvalidAddr(AddrParseError),
24    /// Bad prefix value
25    PrefixValue(IpNetPrefixError),
26    /// No slash to indicate the prefix
27    NoPrefix,
28    /// Prefix parse error
29    InvalidPrefix(ParseIntError),
30}
31
32impl std::fmt::Display for IpNetParseError {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        match self {
35            IpNetParseError::InvalidAddr(e) => e.fmt(f),
36            IpNetParseError::PrefixValue(e) => {
37                write!(f, "invalid prefix value: {e}")
38            }
39            IpNetParseError::NoPrefix => write!(f, "missing '/' character"),
40            IpNetParseError::InvalidPrefix(e) => e.fmt(f),
41        }
42    }
43}
44impl std::error::Error for IpNetParseError {}
45
46/// A subnet, either IPv4 or IPv6
47#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
48#[cfg_attr(feature = "serde", serde(untagged))]
49#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
50pub enum IpNet {
51    /// An IPv4 subnet
52    V4(Ipv4Net),
53    /// An IPv6 subnet
54    V6(Ipv6Net),
55}
56
57impl IpNet {
58    /// Create an IpNet with the given address and prefix width.
59    pub fn new(addr: IpAddr, prefix: u8) -> Result<Self, IpNetPrefixError> {
60        match addr {
61            IpAddr::V4(addr) => Ok(Self::V4(Ipv4Net::new(addr, prefix)?)),
62            IpAddr::V6(addr) => Ok(Self::V6(Ipv6Net::new(addr, prefix)?)),
63        }
64    }
65
66    /// Create an IpNet with the given address and prefix width with no checks
67    /// for the validity of the prefix length.
68    pub const fn new_unchecked(addr: IpAddr, prefix: u8) -> Self {
69        match addr {
70            IpAddr::V4(addr) => Self::V4(Ipv4Net::new_unchecked(addr, prefix)),
71            IpAddr::V6(addr) => Self::V6(Ipv6Net::new_unchecked(addr, prefix)),
72        }
73    }
74
75    /// Create an IpNet that contains *exclusively* the given address.
76    pub fn host_net(addr: IpAddr) -> Self {
77        match addr {
78            IpAddr::V4(addr) => Self::V4(Ipv4Net::host_net(addr)),
79            IpAddr::V6(addr) => Self::V6(Ipv6Net::host_net(addr)),
80        }
81    }
82
83    /// Return the base address.
84    pub const fn addr(&self) -> IpAddr {
85        match self {
86            IpNet::V4(inner) => IpAddr::V4(inner.addr()),
87            IpNet::V6(inner) => IpAddr::V6(inner.addr()),
88        }
89    }
90
91    /// Return the prefix address (the base address with the mask applied).
92    pub fn prefix(&self) -> IpAddr {
93        match self {
94            IpNet::V4(inner) => inner.prefix().into(),
95            IpNet::V6(inner) => inner.prefix().into(),
96        }
97    }
98
99    /// Return the prefix length.
100    pub const fn width(&self) -> u8 {
101        match self {
102            IpNet::V4(inner) => inner.width(),
103            IpNet::V6(inner) => inner.width(),
104        }
105    }
106
107    /// Return the netmask address derived from prefix length.
108    pub fn mask_addr(&self) -> IpAddr {
109        match self {
110            IpNet::V4(inner) => inner.mask_addr().into(),
111            IpNet::V6(inner) => inner.mask_addr().into(),
112        }
113    }
114
115    /// Return `true` iff the subnet contains only the base address i.e. the
116    /// size is exactly one address.
117    pub const fn is_host_net(&self) -> bool {
118        match self {
119            IpNet::V4(inner) => inner.is_host_net(),
120            IpNet::V6(inner) => inner.is_host_net(),
121        }
122    }
123
124    /// Return `true` iff the base address corresponds to the all-zeroes host
125    /// ID in the subnet.
126    pub fn is_network_address(&self) -> bool {
127        match self {
128            IpNet::V4(inner) => inner.is_network_address(),
129            IpNet::V6(inner) => inner.is_network_address(),
130        }
131    }
132
133    /// Return `true` iff this subnet is in a multicast address range.
134    pub const fn is_multicast(&self) -> bool {
135        match self {
136            IpNet::V4(inner) => inner.is_multicast(),
137            IpNet::V6(inner) => inner.is_multicast(),
138        }
139    }
140
141    /// Return `true` iff this subnet is in an administratively scoped
142    /// multicast address range with boundaries that are administratively
143    /// configured.
144    ///
145    /// "Admin" is short for "administratively"; these scopes have boundaries
146    /// configured by network administrators, unlike well-known scopes like
147    /// link-local or global.
148    ///
149    /// For IPv4, this is 239.0.0.0/8 as defined in [RFC 2365] and [RFC 5771].
150    /// For IPv6, this includes scopes 4, 5, and 8 (admin-local, site-local,
151    /// organization-local) as defined in [RFC 7346] and [RFC 4291].
152    ///
153    /// [RFC 2365]: https://tools.ietf.org/html/rfc2365
154    /// [RFC 5771]: https://tools.ietf.org/html/rfc5771
155    /// [RFC 7346]: https://tools.ietf.org/html/rfc7346
156    /// [RFC 4291]: https://tools.ietf.org/html/rfc4291
157    pub const fn is_admin_scoped_multicast(&self) -> bool {
158        match self {
159            IpNet::V4(inner) => inner.is_admin_scoped_multicast(),
160            IpNet::V6(inner) => inner.is_admin_scoped_multicast(),
161        }
162    }
163
164    /// Return `true` iff this subnet is in an admin-local multicast address
165    /// range (scope 4) as defined in [RFC 7346] and [RFC 4291].
166    /// This is only defined for IPv6. IPv4 does not have an equivalent
167    /// "admin-local" scope.
168    ///
169    /// [RFC 7346]: https://tools.ietf.org/html/rfc7346
170    /// [RFC 4291]: https://tools.ietf.org/html/rfc4291
171    pub const fn is_admin_local_multicast(&self) -> bool {
172        match self {
173            IpNet::V4(_inner) => false,
174            IpNet::V6(inner) => inner.is_admin_local_multicast(),
175        }
176    }
177
178    /// Return `true` iff this subnet is in a local multicast address range.
179    /// For IPv4, this is 239.255.0.0/16 (IPv4 Local Scope) as defined in
180    /// [RFC 2365]. IPv6 does not have an equivalent "local" scope.
181    ///
182    /// [RFC 2365]: https://tools.ietf.org/html/rfc2365
183    pub const fn is_local_multicast(&self) -> bool {
184        match self {
185            IpNet::V4(inner) => inner.is_local_multicast(),
186            IpNet::V6(_inner) => false,
187        }
188    }
189
190    /// Return `true` iff this subnet is in a site-local multicast address
191    /// range. This is only defined for IPv6 (scope 5) as defined in [RFC 7346]
192    /// and [RFC 4291]. IPv4 does not have a site-local multicast scope.
193    ///
194    /// [RFC 7346]: https://tools.ietf.org/html/rfc7346
195    /// [RFC 4291]: https://tools.ietf.org/html/rfc4291
196    pub const fn is_site_local_multicast(&self) -> bool {
197        match self {
198            IpNet::V4(_inner) => false,
199            IpNet::V6(inner) => inner.is_site_local_multicast(),
200        }
201    }
202
203    /// Return `true` iff this subnet is in an organization-local multicast
204    /// address range.
205    ///
206    /// For IPv4, this is 239.192.0.0/14 as defined in [RFC 2365].
207    /// For IPv6, this is scope 8 as defined in [RFC 7346] and [RFC 4291].
208    ///
209    /// [RFC 2365]: https://tools.ietf.org/html/rfc2365
210    /// [RFC 7346]: https://tools.ietf.org/html/rfc7346
211    /// [RFC 4291]: https://tools.ietf.org/html/rfc4291
212    pub const fn is_org_local_multicast(&self) -> bool {
213        match self {
214            IpNet::V4(inner) => inner.is_org_local_multicast(),
215            IpNet::V6(inner) => inner.is_org_local_multicast(),
216        }
217    }
218
219    /// Return `true` iff this subnet is in a Unique Local Address range.
220    /// This is only valid for IPv6 addresses.
221    pub const fn is_unique_local(&self) -> bool {
222        match self {
223            IpNet::V4(_inner) => false, // IPv4 does not support ULA
224            IpNet::V6(inner) => inner.is_unique_local(),
225        }
226    }
227
228    /// Return `true` iff this subnet is in a loopback address range.
229    pub const fn is_loopback(&self) -> bool {
230        match self {
231            IpNet::V4(inner) => inner.is_loopback(),
232            IpNet::V6(inner) => inner.is_loopback(),
233        }
234    }
235
236    /// Return `true` if the provided address is contained in self.
237    ///
238    /// This returns `false` if the address and the network are of different IP
239    /// families.
240    pub fn contains(&self, addr: IpAddr) -> bool {
241        match (self, addr) {
242            (IpNet::V4(net), IpAddr::V4(ip)) => net.contains(ip),
243            (IpNet::V6(net), IpAddr::V6(ip)) => net.contains(ip),
244            (_, _) => false,
245        }
246    }
247
248    /// Returns `true` iff this subnet is wholly contained within `other`.
249    ///
250    /// This returns `false` if the address and the network are of different IP
251    /// families.
252    pub fn is_subnet_of(&self, other: &Self) -> bool {
253        match (self, other) {
254            (IpNet::V4(net), IpNet::V4(other)) => net.is_subnet_of(other),
255            (IpNet::V6(net), IpNet::V6(other)) => net.is_subnet_of(other),
256            (_, _) => false,
257        }
258    }
259
260    /// Returns `true` iff `other` is wholly contained within this subnet.
261    ///
262    /// This returns `false` if the address and the network are of different IP
263    /// families.
264    pub fn is_supernet_of(&self, other: &Self) -> bool {
265        other.is_subnet_of(self)
266    }
267
268    /// Return `true` if the provided `IpNet` shares any IP addresses with
269    /// `self` (e.g., `self.is_subnet_of(other)`, or vice-versa).
270    ///
271    /// This returns `false` if the networks are of different IP families.
272    pub fn overlaps(&self, other: &Self) -> bool {
273        match (self, other) {
274            (IpNet::V4(net), IpNet::V4(other)) => net.overlaps(other),
275            (IpNet::V6(net), IpNet::V6(other)) => net.overlaps(other),
276            (_, _) => false,
277        }
278    }
279
280    /// Return `true` if this is an IPv4 network.
281    pub const fn is_ipv4(&self) -> bool {
282        matches!(self, IpNet::V4(_))
283    }
284
285    /// Return `true` if this is an IPv6 network.
286    pub const fn is_ipv6(&self) -> bool {
287        matches!(self, IpNet::V6(_))
288    }
289}
290
291impl From<Ipv4Net> for IpNet {
292    fn from(n: Ipv4Net) -> IpNet {
293        IpNet::V4(n)
294    }
295}
296
297impl From<Ipv6Net> for IpNet {
298    fn from(n: Ipv6Net) -> IpNet {
299        IpNet::V6(n)
300    }
301}
302
303impl std::fmt::Display for IpNet {
304    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305        match self {
306            IpNet::V4(inner) => write!(f, "{inner}"),
307            IpNet::V6(inner) => write!(f, "{inner}"),
308        }
309    }
310}
311
312impl std::str::FromStr for IpNet {
313    type Err = IpNetParseError;
314
315    fn from_str(s: &str) -> Result<Self, Self::Err> {
316        let Some((addr_str, prefix_str)) = s.split_once('/') else {
317            return Err(IpNetParseError::NoPrefix);
318        };
319
320        let prefix = prefix_str.parse().map_err(IpNetParseError::InvalidPrefix)?;
321        let addr = addr_str.parse().map_err(IpNetParseError::InvalidAddr)?;
322        IpNet::new(addr, prefix).map_err(IpNetParseError::PrefixValue)
323    }
324}
325
326#[cfg(feature = "schemars")]
327impl schemars::JsonSchema for IpNet {
328    fn schema_name() -> String {
329        "IpNet".to_string()
330    }
331
332    fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
333        use crate::schema_util::label_schema;
334        schemars::schema::SchemaObject {
335            subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
336                one_of: Some(vec![
337                    label_schema("v4", gen.subschema_for::<Ipv4Net>()),
338                    label_schema("v6", gen.subschema_for::<Ipv6Net>()),
339                ]),
340                ..Default::default()
341            })),
342            extensions: crate::schema_util::extension("IpNet", "0.1.0"),
343            ..Default::default()
344        }
345        .into()
346    }
347}
348
349/// The maximum width of an IPv4 subnet
350pub const IPV4_NET_WIDTH_MAX: u8 = 32;
351
352/// An IPv4 subnet
353#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
354pub struct Ipv4Net {
355    addr: Ipv4Addr,
356    width: u8,
357}
358
359impl Ipv4Net {
360    /// Create an Ipv4Net with the given address and prefix width.
361    pub fn new(addr: Ipv4Addr, width: u8) -> Result<Self, IpNetPrefixError> {
362        if width > IPV4_NET_WIDTH_MAX {
363            Err(IpNetPrefixError(width))
364        } else {
365            Ok(Self { addr, width })
366        }
367    }
368
369    /// Create an Ipv4Net with the given address and prefix width with no
370    /// checks for the validity of the prefix length.
371    pub const fn new_unchecked(addr: Ipv4Addr, width: u8) -> Self {
372        Self { addr, width }
373    }
374
375    /// Create an Ipv4Net that contains *exclusively* the given address.
376    pub const fn host_net(addr: Ipv4Addr) -> Self {
377        Self {
378            addr,
379            width: IPV4_NET_WIDTH_MAX,
380        }
381    }
382
383    /// Return the base address used to create this subnet.
384    pub const fn addr(&self) -> Ipv4Addr {
385        self.addr
386    }
387
388    /// Return the prefix width.
389    pub const fn width(&self) -> u8 {
390        self.width
391    }
392
393    pub(crate) fn mask(&self) -> u32 {
394        u32::MAX
395            .checked_shl((IPV4_NET_WIDTH_MAX - self.width) as u32)
396            .unwrap_or(0)
397    }
398
399    /// Return the netmask address derived from prefix length.
400    pub fn mask_addr(&self) -> Ipv4Addr {
401        Ipv4Addr::from(self.mask())
402    }
403
404    /// Return true iff the subnet contains only the base address i.e. the
405    /// size is exactly one address.
406    pub const fn is_host_net(&self) -> bool {
407        self.width == IPV4_NET_WIDTH_MAX
408    }
409
410    /// Return `true` iff the base address corresponds to the all-zeroes host
411    /// ID in the subnet.
412    pub fn is_network_address(&self) -> bool {
413        self.addr == self.prefix()
414    }
415
416    /// Return `true` iff this subnet is in a multicast address range.
417    pub const fn is_multicast(&self) -> bool {
418        self.addr.is_multicast()
419    }
420
421    /// Return `true` iff this subnet is in an administratively scoped multicast
422    /// address range (239.0.0.0/8) as defined in [RFC 2365] and [RFC 5771].
423    ///
424    /// "Admin" is short for "administratively"; these scopes have boundaries
425    /// configured by network administrators.
426    ///
427    /// [RFC 2365]: https://tools.ietf.org/html/rfc2365
428    /// [RFC 5771]: https://tools.ietf.org/html/rfc5771
429    pub const fn is_admin_scoped_multicast(&self) -> bool {
430        // RFC 2365/RFC 5771, ยง10: The administratively scoped IPv4 multicast
431        // space is 239/8
432        // IPv4 multicast is 224.0.0.0/4, so 239/8 is a subset of that
433        self.addr.octets()[0] == 239
434    }
435
436    /// Return `true` iff this subnet is in a local multicast address range
437    /// (239.255.0.0/16) as defined in [RFC 2365]. This is the IPv4 Local
438    /// Scope.
439    ///
440    /// [RFC 2365]: https://tools.ietf.org/html/rfc2365
441    pub const fn is_local_multicast(&self) -> bool {
442        // RFC 2365: 239.255.0.0/16 is defined to be the IPv4 Local Scope
443        let octets = self.addr.octets();
444        octets[0] == 239 && octets[1] == 255
445    }
446
447    /// Return `true` iff this subnet is in an organization-local multicast
448    /// address range (239.192.0.0/14) as defined in [RFC 2365].
449    ///
450    /// [RFC 2365]: https://tools.ietf.org/html/rfc2365
451    pub const fn is_org_local_multicast(&self) -> bool {
452        // RFC 2365: The IPv4 Organization Local Scope is 239.192.0.0/14
453        // This is 239.192.0.0 - 239.195.255.255
454        let octets = self.addr.octets();
455        octets[0] == 239 && (octets[1] >= 192 && octets[1] <= 195)
456    }
457
458    /// Return `true` iff this subnet is in a loopback address range.
459    pub const fn is_loopback(&self) -> bool {
460        self.addr.is_loopback()
461    }
462
463    /// Return the number of addresses contained within this subnet or None for
464    /// a /0 subnet whose value would be one larger than can be represented in
465    /// a `u32`.
466    pub const fn size(&self) -> Option<u32> {
467        1u32.checked_shl((IPV4_NET_WIDTH_MAX - self.width) as u32)
468    }
469
470    /// Return the prefix address (the base address with the mask applied).
471    pub fn prefix(&self) -> Ipv4Addr {
472        self.first_addr()
473    }
474
475    /// Return the network address for subnets as applicable; /31 and /32
476    /// subnets return `None`.
477    pub fn network(&self) -> Option<Ipv4Addr> {
478        (self.width < 31).then(|| self.first_addr())
479    }
480
481    /// Return the broadcast address for subnets as applicable; /31 and /32
482    /// subnets return `None`.
483    pub fn broadcast(&self) -> Option<Ipv4Addr> {
484        (self.width < 31).then(|| self.last_addr())
485    }
486
487    /// Return the first address within this subnet.
488    pub fn first_addr(&self) -> Ipv4Addr {
489        let addr: u32 = self.addr.into();
490        Ipv4Addr::from(addr & self.mask())
491    }
492
493    /// Return the last address within this subnet.
494    pub fn last_addr(&self) -> Ipv4Addr {
495        let addr: u32 = self.addr.into();
496        Ipv4Addr::from(addr | !self.mask())
497    }
498
499    /// Return the first host address within this subnet. For /31 and /32
500    /// subnets that is the first address; for wider subnets this returns
501    /// the address immediately after the network address.
502    pub fn first_host(&self) -> Ipv4Addr {
503        let mask = self.mask();
504        let addr: u32 = self.addr.into();
505        let first = addr & mask;
506        if self.width == 31 || self.width == 32 {
507            Ipv4Addr::from(first)
508        } else {
509            Ipv4Addr::from(first + 1)
510        }
511    }
512
513    /// Return the last host address within this subnet. For /31 and /32
514    /// subnets that is the last address (there is no broadcast address); for
515    /// wider subnets this returns the address immediately before the broadcast
516    /// address.
517    pub fn last_host(&self) -> Ipv4Addr {
518        let mask = self.mask();
519        let addr: u32 = self.addr.into();
520        let last = addr | !mask;
521        if self.width == 31 || self.width == 32 {
522            Ipv4Addr::from(last)
523        } else {
524            Ipv4Addr::from(last - 1)
525        }
526    }
527
528    /// Return `true` iff the given IP address is within the subnet.
529    pub fn contains(&self, other: Ipv4Addr) -> bool {
530        let mask = self.mask();
531        let addr: u32 = self.addr.into();
532        let other: u32 = other.into();
533
534        (addr & mask) == (other & mask)
535    }
536
537    /// Return the nth address within this subnet or none if `n` is larger than
538    /// the size of the subnet.
539    pub fn nth(&self, n: usize) -> Option<Ipv4Addr> {
540        let addr: u32 = self.addr.into();
541        let nth = addr.checked_add(n.try_into().ok()?)?;
542        (nth <= self.last_addr().into()).then_some(nth.into())
543    }
544
545    /// Produce an iterator over all addresses within this subnet.
546    pub fn addr_iter(&self) -> impl Iterator<Item = Ipv4Addr> {
547        Ipv4NetIter {
548            next: Some(self.first_addr().into()),
549            last: self.last_addr().into(),
550        }
551    }
552
553    /// Produce an iterator over all hosts within this subnet. For /31 and /32
554    /// subnets, this is all addresses; for all larger subnets this excludes
555    /// the first (network) and last (broadcast) addresses.
556    pub fn host_iter(&self) -> impl Iterator<Item = Ipv4Addr> {
557        Ipv4NetIter {
558            next: Some(self.first_host().into()),
559            last: self.last_host().into(),
560        }
561    }
562
563    /// Returns `true` iff this subnet is wholly contained within `other`.
564    pub fn is_subnet_of(&self, other: &Self) -> bool {
565        other.first_addr() <= self.first_addr() && other.last_addr() >= self.last_addr()
566    }
567
568    /// Returns `true` iff `other` is wholly contained within this subnet.
569    pub fn is_supernet_of(&self, other: &Self) -> bool {
570        other.is_subnet_of(self)
571    }
572
573    /// Return `true` if the `other` shares any IP addresses with `self`
574    /// (e.g., `self.is_subnet_of(other)`, or vice-versa).
575    pub fn overlaps(&self, other: &Self) -> bool {
576        let (parent, child) = if self.width <= other.width {
577            (self, other)
578        } else {
579            (other, self)
580        };
581
582        child.is_subnet_of(parent)
583    }
584}
585
586impl std::fmt::Display for Ipv4Net {
587    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
588        write!(f, "{}/{}", &self.addr, self.width)
589    }
590}
591
592impl std::str::FromStr for Ipv4Net {
593    type Err = IpNetParseError;
594
595    fn from_str(s: &str) -> Result<Self, Self::Err> {
596        let Some((addr_str, prefix_str)) = s.split_once('/') else {
597            return Err(IpNetParseError::NoPrefix);
598        };
599
600        let prefix = prefix_str.parse().map_err(IpNetParseError::InvalidPrefix)?;
601        let addr = addr_str.parse().map_err(IpNetParseError::InvalidAddr)?;
602        Ipv4Net::new(addr, prefix).map_err(IpNetParseError::PrefixValue)
603    }
604}
605
606#[cfg(feature = "serde")]
607impl<'de> serde::Deserialize<'de> for Ipv4Net {
608    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
609    where
610        D: serde::Deserializer<'de>,
611    {
612        String::deserialize(deserializer)?
613            .parse()
614            .map_err(<D::Error as serde::de::Error>::custom)
615    }
616}
617
618#[cfg(feature = "serde")]
619impl serde::Serialize for Ipv4Net {
620    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
621    where
622        S: serde::Serializer,
623    {
624        serializer.serialize_str(&format!("{self}"))
625    }
626}
627
628#[cfg(feature = "schemars")]
629const IPV4_NET_REGEX: &str = concat!(
630    r#"^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}"#,
631    r#"([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"#,
632    r#"/([0-9]|1[0-9]|2[0-9]|3[0-2])$"#,
633);
634
635#[cfg(feature = "schemars")]
636impl schemars::JsonSchema for Ipv4Net {
637    fn schema_name() -> String {
638        "Ipv4Net".to_string()
639    }
640
641    fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
642        schemars::schema::SchemaObject {
643            metadata: Some(Box::new(schemars::schema::Metadata {
644                title: Some("An IPv4 subnet".to_string()),
645                description: Some("An IPv4 subnet, including prefix and prefix length".to_string()),
646                examples: vec!["192.168.1.0/24".into()],
647                ..Default::default()
648            })),
649            instance_type: Some(schemars::schema::InstanceType::String.into()),
650            string: Some(Box::new(schemars::schema::StringValidation {
651                pattern: Some(IPV4_NET_REGEX.to_string()),
652                ..Default::default()
653            })),
654            extensions: crate::schema_util::extension("Ipv4Net", "0.1.0"),
655            ..Default::default()
656        }
657        .into()
658    }
659}
660
661/// The highest value for an IPv6 subnet prefix
662pub const IPV6_NET_WIDTH_MAX: u8 = 128;
663
664/// IPv6 multicast scope values as defined in [RFC 4291] and [RFC 7346].
665///
666/// [RFC 4291]: https://tools.ietf.org/html/rfc4291
667/// [RFC 7346]: https://tools.ietf.org/html/rfc7346
668#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
669#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
670pub enum MulticastScopeV6 {
671    /// Interface-local scope (0x1)
672    InterfaceLocal = 0x1,
673    /// Link-local scope (0x2)
674    LinkLocal = 0x2,
675    /// Admin-local scope (0x4) - administratively configured
676    AdminLocal = 0x4,
677    /// Site-local scope (0x5) - administratively configured
678    SiteLocal = 0x5,
679    /// Organization-local scope (0x8) - administratively configured
680    OrganizationLocal = 0x8,
681    /// Global scope (0xE)
682    Global = 0xE,
683}
684
685impl MulticastScopeV6 {
686    /// Returns `true` if this scope is administratively configured
687    /// (scopes 4, 5, 8).
688    pub const fn is_admin_scoped_multicast(&self) -> bool {
689        matches!(
690            self,
691            MulticastScopeV6::AdminLocal
692                | MulticastScopeV6::SiteLocal
693                | MulticastScopeV6::OrganizationLocal
694        )
695    }
696
697    /// Create a `MulticastScopeV6` from a raw scope value.
698    /// Returns `None` if the scope value is not recognized.
699    pub const fn from_u8(scope: u8) -> Option<Self> {
700        match scope {
701            0x1 => Some(MulticastScopeV6::InterfaceLocal),
702            0x2 => Some(MulticastScopeV6::LinkLocal),
703            0x4 => Some(MulticastScopeV6::AdminLocal),
704            0x5 => Some(MulticastScopeV6::SiteLocal),
705            0x8 => Some(MulticastScopeV6::OrganizationLocal),
706            0xE => Some(MulticastScopeV6::Global),
707            _ => None,
708        }
709    }
710}
711
712/// An IPv6 subnet
713#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
714pub struct Ipv6Net {
715    addr: Ipv6Addr,
716    width: u8,
717}
718
719impl Ipv6Net {
720    /// Create an Ipv6Net with the given base address and prefix width.
721    pub fn new(addr: Ipv6Addr, width: u8) -> Result<Self, IpNetPrefixError> {
722        if width > IPV6_NET_WIDTH_MAX {
723            Err(IpNetPrefixError(width))
724        } else {
725            Ok(Self { addr, width })
726        }
727    }
728
729    /// Create an Ipv6Net with the given address and prefix width with no
730    /// checks for the validity of the prefix length.
731    pub const fn new_unchecked(addr: Ipv6Addr, width: u8) -> Self {
732        Self { addr, width }
733    }
734
735    /// Create an Ipv6Net that contains *exclusively* the given address.
736    pub const fn host_net(addr: Ipv6Addr) -> Self {
737        Self {
738            addr,
739            width: IPV6_NET_WIDTH_MAX,
740        }
741    }
742
743    /// Return the base address used to create this subnet.
744    pub const fn addr(&self) -> Ipv6Addr {
745        self.addr
746    }
747
748    /// Return the prefix width.
749    pub const fn width(&self) -> u8 {
750        self.width
751    }
752
753    pub(crate) fn mask(&self) -> u128 {
754        u128::MAX
755            .checked_shl((IPV6_NET_WIDTH_MAX - self.width) as u32)
756            .unwrap_or(0)
757    }
758
759    /// Return the netmask address derived from prefix length.
760    pub fn mask_addr(&self) -> Ipv6Addr {
761        Ipv6Addr::from(self.mask())
762    }
763
764    /// Return true iff the subnet contains only the base address i.e. the
765    /// size is exactly one address.
766    pub const fn is_host_net(&self) -> bool {
767        self.width == IPV6_NET_WIDTH_MAX
768    }
769
770    /// Return `true` iff the base address corresponds to the all-zeroes host
771    /// ID in the subnet.
772    pub fn is_network_address(&self) -> bool {
773        self.addr == self.prefix()
774    }
775
776    /// Return `true` iff this subnet is in a multicast address range.
777    pub const fn is_multicast(&self) -> bool {
778        self.addr.is_multicast()
779    }
780
781    /// Return the IPv6 multicast scope if this subnet is a multicast address,
782    /// or `None` otherwise. This extracts the scope field from the multicast
783    /// address as defined in [RFC 4291] and [RFC 7346].
784    ///
785    /// [RFC 4291]: https://tools.ietf.org/html/rfc4291
786    /// [RFC 7346]: https://tools.ietf.org/html/rfc7346
787    pub const fn multicast_scope(&self) -> Option<MulticastScopeV6> {
788        if !self.addr.is_multicast() {
789            return None;
790        }
791
792        // Extract the scope field (bits 4-7 of the second byte)
793        let segments = self.addr.segments();
794        let scope = (segments[0] & 0x000F) as u8;
795
796        MulticastScopeV6::from_u8(scope)
797    }
798
799    /// Return `true` iff this subnet is in an administratively scoped
800    /// multicast address range with boundaries that are administratively
801    /// configured.
802    ///
803    /// "Admin" is short for "administratively"; these scopes have boundaries
804    /// configured by network administrators, unlike well-known scopes like
805    /// link-local or global.
806    ///
807    /// For IPv6, this includes scopes 4, 5, and 8 (admin-local, site-local,
808    /// organization-local) as defined in [RFC 7346] and [RFC 4291].
809    ///
810    /// [RFC 7346]: https://tools.ietf.org/html/rfc7346
811    /// [RFC 4291]: https://tools.ietf.org/html/rfc4291
812    pub const fn is_admin_scoped_multicast(&self) -> bool {
813        match self.multicast_scope() {
814            Some(scope) => scope.is_admin_scoped_multicast(),
815            None => false,
816        }
817    }
818
819    /// Return `true` iff this address is an admin-local multicast address
820    /// (scope 4) as defined in [RFC 7346] and [RFC 4291].
821    ///
822    /// [RFC 7346]: https://tools.ietf.org/html/rfc7346
823    /// [RFC 4291]: https://tools.ietf.org/html/rfc4291
824    pub const fn is_admin_local_multicast(&self) -> bool {
825        matches!(self.multicast_scope(), Some(MulticastScopeV6::AdminLocal))
826    }
827
828    /// Return `true` iff this address is a site-local multicast address
829    /// (scope 5) as defined in [RFC 7346] and [RFC 4291].
830    ///
831    /// [RFC 7346]: https://tools.ietf.org/html/rfc7346
832    /// [RFC 4291]: https://tools.ietf.org/html/rfc4291
833    pub const fn is_site_local_multicast(&self) -> bool {
834        matches!(self.multicast_scope(), Some(MulticastScopeV6::SiteLocal))
835    }
836
837    /// Return `true` iff this address is an organization-local multicast
838    /// address (scope 8) as defined in [RFC 7346] and [RFC 4291].
839    ///
840    /// [RFC 7346]: https://tools.ietf.org/html/rfc7346
841    /// [RFC 4291]: https://tools.ietf.org/html/rfc4291
842    pub const fn is_org_local_multicast(&self) -> bool {
843        matches!(
844            self.multicast_scope(),
845            Some(MulticastScopeV6::OrganizationLocal)
846        )
847    }
848
849    /// Return `true` iff this subnet is in a loopback address range.
850    pub const fn is_loopback(&self) -> bool {
851        self.addr.is_loopback()
852    }
853
854    /// Return the number of addresses contained within this subnet or None for
855    /// a /0 subnet whose value would be one larger than can be represented in
856    /// a `u128`.
857    pub const fn size(&self) -> Option<u128> {
858        1u128.checked_shl((IPV6_NET_WIDTH_MAX - self.width) as u32)
859    }
860
861    /// Return the prefix address (the base address with the mask applied).
862    pub fn prefix(&self) -> Ipv6Addr {
863        self.first_addr()
864    }
865
866    /// Return `true` if this subnetwork is in the IPv6 Unique Local Address
867    /// range defined in [RFC 4193], e.g., `fd00:/8`.
868    ///
869    /// [RFC 4193]: https://tools.ietf.org/html/rfc4193
870    pub const fn is_unique_local(&self) -> bool {
871        self.addr.is_unique_local()
872    }
873
874    /// Return the first address within this subnet.
875    pub fn first_addr(&self) -> Ipv6Addr {
876        let addr: u128 = self.addr.into();
877        Ipv6Addr::from(addr & self.mask())
878    }
879
880    /// The broadcast address for this subnet which is also the last address.
881    pub fn last_addr(&self) -> Ipv6Addr {
882        let addr: u128 = self.addr.into();
883        Ipv6Addr::from(addr | !self.mask())
884    }
885
886    /// Return an interator over the addresses of this subnet.
887    pub fn iter(&self) -> impl Iterator<Item = Ipv6Addr> {
888        Ipv6NetIter {
889            next: Some(self.first_addr().into()),
890            last: self.last_addr().into(),
891        }
892    }
893
894    /// Return `true` if the address is within the subnet.
895    pub fn contains(&self, other: Ipv6Addr) -> bool {
896        let mask = self.mask();
897        let addr: u128 = self.addr.into();
898        let other: u128 = other.into();
899
900        (addr & mask) == (other & mask)
901    }
902
903    /// Return the nth address within this subnet or none if `n` is larger than
904    /// the size of the subnet.
905    pub fn nth(&self, n: u128) -> Option<Ipv6Addr> {
906        let addr: u128 = self.addr.into();
907        let nth = addr.checked_add(n)?;
908        (nth <= self.last_addr().into()).then_some(nth.into())
909    }
910
911    /// Returns `true` iff this subnet is wholly contained within `other`.
912    pub fn is_subnet_of(&self, other: &Self) -> bool {
913        other.first_addr() <= self.first_addr() && other.last_addr() >= self.last_addr()
914    }
915
916    /// Returns `true` iff `other` is wholly contained within this subnet.
917    pub fn is_supernet_of(&self, other: &Self) -> bool {
918        other.is_subnet_of(self)
919    }
920
921    /// Return `true` if the `other` shares any IP addresses with `self`
922    /// (e.g., `self.is_subnet_of(other)`, or vice-versa).
923    pub fn overlaps(&self, other: &Self) -> bool {
924        let (parent, child) = if self.width <= other.width {
925            (self, other)
926        } else {
927            (other, self)
928        };
929
930        child.is_subnet_of(parent)
931    }
932}
933
934impl std::fmt::Display for Ipv6Net {
935    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
936        write!(f, "{}/{}", &self.addr, self.width)
937    }
938}
939
940impl std::str::FromStr for Ipv6Net {
941    type Err = IpNetParseError;
942
943    fn from_str(s: &str) -> Result<Self, Self::Err> {
944        let Some((addr_str, prefix_str)) = s.split_once('/') else {
945            return Err(IpNetParseError::NoPrefix);
946        };
947
948        let prefix = prefix_str.parse().map_err(IpNetParseError::InvalidPrefix)?;
949        let addr = addr_str.parse().map_err(IpNetParseError::InvalidAddr)?;
950        Ipv6Net::new(addr, prefix).map_err(IpNetParseError::PrefixValue)
951    }
952}
953
954#[cfg(feature = "serde")]
955impl<'de> serde::Deserialize<'de> for Ipv6Net {
956    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
957    where
958        D: serde::Deserializer<'de>,
959    {
960        String::deserialize(deserializer)?
961            .parse()
962            .map_err(<D::Error as serde::de::Error>::custom)
963    }
964}
965
966#[cfg(feature = "serde")]
967impl serde::Serialize for Ipv6Net {
968    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
969    where
970        S: serde::Serializer,
971    {
972        serializer.serialize_str(&format!("{self}"))
973    }
974}
975
976#[cfg(feature = "schemars")]
977const IPV6_NET_REGEX: &str = concat!(
978    r#"^("#,
979    r#"([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|"#,
980    r#"([0-9a-fA-F]{1,4}:){1,7}:|"#,
981    r#"([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|"#,
982    r#"([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|"#,
983    r#"([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|"#,
984    r#"([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|"#,
985    r#"([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|"#,
986    r#"[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|"#,
987    r#":((:[0-9a-fA-F]{1,4}){1,7}|:)|"#,
988    r#"fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|"#,
989    r#"::(ffff(:0{1,4}){0,1}:){0,1}"#,
990    r#"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}"#,
991    r#"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|"#,
992    r#"([0-9a-fA-F]{1,4}:){1,4}:"#,
993    r#"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}"#,
994    r#"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"#,
995    r#")\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$"#,
996);
997
998#[cfg(feature = "schemars")]
999impl schemars::JsonSchema for Ipv6Net {
1000    fn schema_name() -> String {
1001        "Ipv6Net".to_string()
1002    }
1003
1004    fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
1005        schemars::schema::SchemaObject {
1006            metadata: Some(Box::new(schemars::schema::Metadata {
1007                title: Some("An IPv6 subnet".to_string()),
1008                description: Some("An IPv6 subnet, including prefix and subnet mask".to_string()),
1009                examples: vec!["fd12:3456::/64".into()],
1010                ..Default::default()
1011            })),
1012            instance_type: Some(schemars::schema::InstanceType::String.into()),
1013            string: Some(Box::new(schemars::schema::StringValidation {
1014                pattern: Some(IPV6_NET_REGEX.to_string()),
1015                ..Default::default()
1016            })),
1017            extensions: crate::schema_util::extension("Ipv6Net", "0.1.0"),
1018            ..Default::default()
1019        }
1020        .into()
1021    }
1022}
1023
1024pub struct Ipv4NetIter {
1025    next: Option<u32>,
1026    last: u32,
1027}
1028
1029impl Iterator for Ipv4NetIter {
1030    type Item = Ipv4Addr;
1031
1032    fn next(&mut self) -> Option<Self::Item> {
1033        let next = self.next?;
1034        if next == self.last {
1035            self.next = None;
1036        } else {
1037            self.next = Some(next + 1)
1038        }
1039        Some(next.into())
1040    }
1041
1042    fn nth(&mut self, n: usize) -> Option<Self::Item> {
1043        let next = self.next?;
1044        let nth = next.checked_add(n as u32)?;
1045        self.next = (nth <= self.last).then_some(nth);
1046        self.next()
1047    }
1048}
1049
1050pub struct Ipv6NetIter {
1051    next: Option<u128>,
1052    last: u128,
1053}
1054
1055impl Iterator for Ipv6NetIter {
1056    type Item = Ipv6Addr;
1057
1058    fn next(&mut self) -> Option<Self::Item> {
1059        let next = self.next?;
1060        if next == self.last {
1061            self.next = None;
1062        } else {
1063            self.next = Some(next + 1)
1064        }
1065        Some(next.into())
1066    }
1067
1068    fn nth(&mut self, n: usize) -> Option<Self::Item> {
1069        let next = self.next?;
1070        let nth = next.checked_add(n as u128)?;
1071        self.next = (nth <= self.last).then_some(nth);
1072        self.next()
1073    }
1074}
1075
1076#[cfg(feature = "ipnetwork")]
1077mod ipnetwork_feature {
1078    use super::*;
1079    use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
1080
1081    impl From<IpNetwork> for IpNet {
1082        fn from(value: IpNetwork) -> Self {
1083            match value {
1084                IpNetwork::V4(net) => Self::V4(net.into()),
1085                IpNetwork::V6(net) => Self::V6(net.into()),
1086            }
1087        }
1088    }
1089
1090    impl From<IpNet> for IpNetwork {
1091        fn from(value: IpNet) -> Self {
1092            match value {
1093                IpNet::V4(net) => Self::V4(net.into()),
1094                IpNet::V6(net) => Self::V6(net.into()),
1095            }
1096        }
1097    }
1098
1099    impl From<Ipv4Network> for Ipv4Net {
1100        fn from(value: Ipv4Network) -> Self {
1101            Self {
1102                addr: value.ip(),
1103                width: value.prefix(),
1104            }
1105        }
1106    }
1107
1108    impl From<Ipv4Net> for Ipv4Network {
1109        fn from(value: Ipv4Net) -> Self {
1110            Self::new(value.addr, value.width).unwrap()
1111        }
1112    }
1113
1114    impl From<Ipv6Network> for Ipv6Net {
1115        fn from(value: Ipv6Network) -> Self {
1116            Self {
1117                addr: value.ip(),
1118                width: value.prefix(),
1119            }
1120        }
1121    }
1122
1123    impl From<Ipv6Net> for Ipv6Network {
1124        fn from(value: Ipv6Net) -> Self {
1125            Self::new(value.addr, value.width).unwrap()
1126        }
1127    }
1128}
1129
1130#[cfg(test)]
1131mod tests {
1132    use super::*;
1133
1134    #[test]
1135    fn test_ipv6_regex() {
1136        let re = regress::Regex::new(IPV6_NET_REGEX).unwrap();
1137        for case in [
1138            "1:2:3:4:5:6:7:8",
1139            "1:a:2:b:3:c:4:d",
1140            "1::",
1141            "::1",
1142            "::",
1143            "1::3:4:5:6:7:8",
1144            "1:2::4:5:6:7:8",
1145            "1:2:3::5:6:7:8",
1146            "1:2:3:4::6:7:8",
1147            "1:2:3:4:5::7:8",
1148            "1:2:3:4:5:6::8",
1149            "1:2:3:4:5:6:7::",
1150            "2001::",
1151            "fd00::",
1152            "::100:1",
1153            "fd12:3456::",
1154        ] {
1155            for prefix in 0..=128 {
1156                let net = format!("{case}/{prefix}");
1157                assert!(
1158                    re.find(&net).is_some(),
1159                    "Expected to match IPv6 case: {}",
1160                    prefix,
1161                );
1162            }
1163        }
1164    }
1165
1166    #[test]
1167    fn test_ipv4_net_operations() {
1168        let x: IpNet = "0.0.0.0/0".parse().unwrap();
1169        assert_eq!(x, IpNet::V4("0.0.0.0/0".parse().unwrap()));
1170    }
1171
1172    #[test]
1173    fn test_ipnet_serde() {
1174        let net_str = "fd00:2::/32";
1175        let net: IpNet = net_str.parse().unwrap();
1176        let ser = serde_json::to_string(&net).unwrap();
1177
1178        assert_eq!(format!(r#""{}""#, net_str), ser);
1179        let net_des = serde_json::from_str::<IpNet>(&ser).unwrap();
1180        assert_eq!(net, net_des);
1181
1182        let net_str = "fd00:47::1/64";
1183        let net: IpNet = net_str.parse().unwrap();
1184        let ser = serde_json::to_string(&net).unwrap();
1185
1186        assert_eq!(format!(r#""{}""#, net_str), ser);
1187        let net_des = serde_json::from_str::<IpNet>(&ser).unwrap();
1188        assert_eq!(net, net_des);
1189
1190        let net_str = "192.168.1.1/16";
1191        let net: IpNet = net_str.parse().unwrap();
1192        let ser = serde_json::to_string(&net).unwrap();
1193
1194        assert_eq!(format!(r#""{}""#, net_str), ser);
1195        let net_des = serde_json::from_str::<IpNet>(&ser).unwrap();
1196        assert_eq!(net, net_des);
1197
1198        let net_str = "0.0.0.0/0";
1199        let net: IpNet = net_str.parse().unwrap();
1200        let ser = serde_json::to_string(&net).unwrap();
1201
1202        assert_eq!(format!(r#""{}""#, net_str), ser);
1203        let net_des = serde_json::from_str::<IpNet>(&ser).unwrap();
1204        assert_eq!(net, net_des);
1205    }
1206
1207    #[test]
1208    fn test_ipnet_size() {
1209        let net = Ipv4Net::host_net("1.2.3.4".parse().unwrap());
1210        assert_eq!(net.size(), Some(1));
1211        assert_eq!(net.width(), 32);
1212        assert_eq!(net.mask(), 0xffff_ffff);
1213        assert_eq!(net.mask_addr(), Ipv4Addr::new(0xff, 0xff, 0xff, 0xff));
1214
1215        let net = Ipv4Net::new("1.2.3.4".parse().unwrap(), 24).unwrap();
1216        assert_eq!(net.size(), Some(256));
1217        assert_eq!(net.width(), 24);
1218        assert_eq!(net.mask(), 0xffff_ff00);
1219        assert_eq!(net.mask_addr(), Ipv4Addr::new(0xff, 0xff, 0xff, 0));
1220
1221        let net = Ipv4Net::new("0.0.0.0".parse().unwrap(), 0).unwrap();
1222        assert_eq!(net.size(), None);
1223        assert_eq!(net.width(), 0);
1224        assert_eq!(net.mask(), 0);
1225        assert_eq!(net.mask_addr(), Ipv4Addr::new(0, 0, 0, 0));
1226
1227        let net = Ipv6Net::host_net("fd00:47::1".parse().unwrap());
1228        assert_eq!(net.size(), Some(1));
1229        assert_eq!(net.width(), 128);
1230        assert_eq!(net.mask(), 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff);
1231        assert_eq!(
1232            net.mask_addr(),
1233            Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff)
1234        );
1235
1236        let net = Ipv6Net::new("fd00:47::1".parse().unwrap(), 56).unwrap();
1237        assert_eq!(net.size(), Some(0x0000_0000_0000_0100_0000_0000_0000_0000));
1238        assert_eq!(net.width(), 56);
1239        assert_eq!(net.mask(), 0xffff_ffff_ffff_ff00_0000_0000_0000_0000);
1240        assert_eq!(
1241            net.mask_addr(),
1242            Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xff00, 0, 0, 0, 0)
1243        );
1244    }
1245
1246    #[test]
1247    fn test_iter() {
1248        let ipnet = Ipv4Net::new(Ipv4Addr::new(0, 0, 0, 0), 0).unwrap();
1249
1250        let actual = ipnet.addr_iter().take(5).collect::<Vec<_>>();
1251        let expected = (0..5).map(Ipv4Addr::from).collect::<Vec<_>>();
1252        assert_eq!(actual, expected);
1253
1254        let actual = ipnet.addr_iter().skip(5).take(10).collect::<Vec<_>>();
1255        let expected = (5..15).map(Ipv4Addr::from).collect::<Vec<_>>();
1256        assert_eq!(actual, expected);
1257
1258        let ipnet = Ipv6Net::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0), 0).unwrap();
1259
1260        let actual = ipnet.iter().take(5).collect::<Vec<_>>();
1261        let expected = (0..5).map(Ipv6Addr::from).collect::<Vec<_>>();
1262        assert_eq!(actual, expected);
1263
1264        let actual = ipnet.iter().skip(5).take(10).collect::<Vec<_>>();
1265        let expected = (5..15).map(Ipv6Addr::from).collect::<Vec<_>>();
1266        assert_eq!(actual, expected);
1267    }
1268
1269    #[test]
1270    fn test_contains() {
1271        let default_v4: IpNet = "0.0.0.0/0".parse().unwrap();
1272        let private_v4: IpNet = "10.0.0.0/8".parse().unwrap();
1273        let privater_v4_c0: IpNet = "10.0.0.0/9".parse().unwrap();
1274        let privater_v4_c1: IpNet = "10.128.0.0/9".parse().unwrap();
1275
1276        assert!(private_v4.is_subnet_of(&default_v4));
1277        assert!(privater_v4_c0.is_subnet_of(&default_v4));
1278        assert!(privater_v4_c0.is_subnet_of(&private_v4));
1279        assert!(privater_v4_c1.is_subnet_of(&default_v4));
1280        assert!(privater_v4_c1.is_subnet_of(&private_v4));
1281
1282        assert!(private_v4.is_supernet_of(&privater_v4_c0));
1283        assert!(private_v4.is_supernet_of(&privater_v4_c1));
1284
1285        assert!(!privater_v4_c0.overlaps(&privater_v4_c1));
1286        assert!(!privater_v4_c1.overlaps(&privater_v4_c0));
1287        assert!(privater_v4_c0.overlaps(&privater_v4_c0));
1288        assert!(privater_v4_c0.overlaps(&private_v4));
1289        assert!(private_v4.overlaps(&privater_v4_c0));
1290
1291        let child_ip: IpNet = "10.128.20.20/16".parse().unwrap();
1292        assert!(child_ip.is_subnet_of(&privater_v4_c1));
1293        assert!(!child_ip.is_subnet_of(&privater_v4_c0));
1294    }
1295
1296    #[test]
1297    fn test_is_network_addr() {
1298        let v4_net: IpNet = "127.0.0.0/8".parse().unwrap();
1299        let v4_host: IpNet = "127.0.0.1/8".parse().unwrap();
1300        let v6_net: IpNet = "fd00:1234:5678::/48".parse().unwrap();
1301        let v6_host: IpNet = "fd00:1234:5678::7777/48".parse().unwrap();
1302
1303        assert!(v4_net.is_network_address());
1304        assert!(!v4_host.is_network_address());
1305        assert!(v6_net.is_network_address());
1306        assert!(!v6_host.is_network_address());
1307
1308        // We don't return a `.network()` for a /31 or /32, but the host bits
1309        // are zero in these addresses (i.e., they're in a canonical form).
1310        let two_addr: IpNet = "10.7.7.64/31".parse().unwrap();
1311        let one_addr: IpNet = "10.7.7.64/32".parse().unwrap();
1312        assert!(two_addr.is_network_address());
1313        assert!(one_addr.is_network_address());
1314
1315        // The IpNet as used in a default route should be considered valid in
1316        // this form.
1317        let unspec: IpNet = "0.0.0.0/0".parse().unwrap();
1318        assert!(unspec.is_network_address());
1319    }
1320
1321    #[test]
1322    fn test_is_multicast_with_scopes() {
1323        // IPv4 multicast tests (224.0.0.0/4 is the IPv4 multicast range)
1324        let v4_mcast: IpNet = "224.0.0.1/32".parse().unwrap();
1325        let v4_not_mcast: IpNet = "192.168.1.1/24".parse().unwrap();
1326
1327        assert!(v4_mcast.is_multicast());
1328        assert!(!v4_not_mcast.is_multicast());
1329
1330        // IPv6 multicast tests (ff00::/8 is the IPv6 multicast range)
1331        let v6_mcast: IpNet = "ff02::1/128".parse().unwrap();
1332        let v6_not_mcast: IpNet = "2001:db8::1/64".parse().unwrap();
1333
1334        assert!(v6_mcast.is_multicast());
1335        assert!(!v6_not_mcast.is_multicast());
1336
1337        // Test for site-local multicast (scope 5)
1338        let v6_site_local_mcast: IpNet = "ff05::1/128".parse().unwrap();
1339        // Test for organization-local multicast (scope 8)
1340        let v6_org_local_mcast: IpNet = "ff08::1/128".parse().unwrap();
1341        // Test for admin-local multicast (scope 4)
1342        let v6_admin_local_mcast: IpNet = "ff04::1/128".parse().unwrap();
1343        // Test for a multicast address that is not admin scoped (link-local, scope 2)
1344        let v6_link_local_mcast: IpNet = "ff02::1/128".parse().unwrap();
1345
1346        // Test admin scoped multicast (covers scopes 4, 5, 8 for IPv6, 239/8 for IPv4)
1347        assert!(v6_admin_local_mcast.is_admin_scoped_multicast());
1348        assert!(v6_site_local_mcast.is_admin_scoped_multicast());
1349        assert!(v6_org_local_mcast.is_admin_scoped_multicast());
1350        assert!(!v6_link_local_mcast.is_admin_scoped_multicast()); // scope 2 is not administratively configured
1351        assert!(!v6_not_mcast.is_admin_scoped_multicast());
1352
1353        // Test IPv4 admin scoped (239.0.0.0/8)
1354        let v4_admin_scoped: IpNet = "239.0.0.1/32".parse().unwrap();
1355        let v4_admin_scoped_range: IpNet = "239.192.0.0/16".parse().unwrap();
1356        assert!(v4_admin_scoped.is_admin_scoped_multicast());
1357        assert!(v4_admin_scoped_range.is_admin_scoped_multicast());
1358        assert!(!v4_mcast.is_admin_scoped_multicast());
1359
1360        // Test IPv6 admin-local multicast (scope 4) - IPv4 does not have this
1361        assert!(!v6_site_local_mcast.is_admin_local_multicast());
1362        assert!(!v6_org_local_mcast.is_admin_local_multicast());
1363        assert!(v6_admin_local_mcast.is_admin_local_multicast());
1364        assert!(!v6_link_local_mcast.is_admin_local_multicast());
1365        assert!(!v6_not_mcast.is_admin_local_multicast());
1366        assert!(!v4_mcast.is_admin_local_multicast()); // IPv4 does not have admin-local
1367        assert!(!v4_admin_scoped.is_admin_local_multicast()); // IPv4 does not have admin-local
1368
1369        // Test IPv4 local multicast (239.255.0.0/16) - IPv6 does not have this
1370        let v4_local_mcast: IpNet = "239.255.0.1/32".parse().unwrap();
1371        let v4_local_mcast_range: IpNet = "239.255.128.0/24".parse().unwrap();
1372        let v4_not_local: IpNet = "239.254.255.255/32".parse().unwrap();
1373        assert!(v4_local_mcast.is_local_multicast());
1374        assert!(v4_local_mcast_range.is_local_multicast());
1375        assert!(!v4_not_local.is_local_multicast());
1376        assert!(!v4_mcast.is_local_multicast()); // 224.0.0.1 is not in 239.255/16
1377        assert!(!v6_admin_local_mcast.is_local_multicast()); // IPv6 does not have local scope
1378
1379        // Test site-local multicast (scope 5)
1380        assert!(v6_site_local_mcast.is_site_local_multicast());
1381        assert!(!v6_org_local_mcast.is_site_local_multicast());
1382        assert!(!v6_admin_local_mcast.is_site_local_multicast());
1383        assert!(!v6_link_local_mcast.is_site_local_multicast());
1384        assert!(!v6_not_mcast.is_site_local_multicast());
1385        assert!(!v4_mcast.is_site_local_multicast());
1386
1387        // Test organization-local multicast
1388        // IPv6 (scope 8)
1389        assert!(!v6_site_local_mcast.is_org_local_multicast());
1390        assert!(v6_org_local_mcast.is_org_local_multicast());
1391        assert!(!v6_admin_local_mcast.is_org_local_multicast());
1392        assert!(!v6_link_local_mcast.is_org_local_multicast());
1393        assert!(!v6_not_mcast.is_org_local_multicast());
1394
1395        // IPv4 (239.192.0.0/14)
1396        let v4_org_local_mcast: IpNet = "239.192.0.1/32".parse().unwrap();
1397        let v4_org_local_mcast_end: IpNet = "239.195.255.255/32".parse().unwrap();
1398        let v4_not_org_local: IpNet = "239.196.0.0/32".parse().unwrap();
1399        assert!(v4_org_local_mcast.is_org_local_multicast());
1400        assert!(v4_org_local_mcast_end.is_org_local_multicast());
1401        assert!(!v4_not_org_local.is_org_local_multicast());
1402        assert!(!v4_mcast.is_org_local_multicast()); // 224.0.0.1 is not in 239.192/14
1403    }
1404
1405    #[test]
1406    fn test_ipv6_multicast_scope() {
1407        use MulticastScopeV6::*;
1408
1409        let link_local: Ipv6Net = "ff02::1/128".parse().unwrap();
1410        let admin_local: Ipv6Net = "ff04::1/128".parse().unwrap();
1411        let site_local: Ipv6Net = "ff05::1/128".parse().unwrap();
1412        let org_local: Ipv6Net = "ff08::1/128".parse().unwrap();
1413        let global: Ipv6Net = "ff0e::1/128".parse().unwrap();
1414        let not_mcast: Ipv6Net = "2001:db8::1/64".parse().unwrap();
1415
1416        assert_eq!(link_local.multicast_scope(), Some(LinkLocal));
1417        assert_eq!(admin_local.multicast_scope(), Some(AdminLocal));
1418        assert_eq!(site_local.multicast_scope(), Some(SiteLocal));
1419        assert_eq!(org_local.multicast_scope(), Some(OrganizationLocal));
1420        assert_eq!(global.multicast_scope(), Some(Global));
1421        assert_eq!(not_mcast.multicast_scope(), None);
1422
1423        // Test is_admin_scoped_multicast
1424        assert!(!LinkLocal.is_admin_scoped_multicast());
1425        assert!(AdminLocal.is_admin_scoped_multicast());
1426        assert!(SiteLocal.is_admin_scoped_multicast());
1427        assert!(OrganizationLocal.is_admin_scoped_multicast());
1428        assert!(!Global.is_admin_scoped_multicast());
1429    }
1430}