netsim_embed_core/
range.rs

1use crate::addr::{Ipv4AddrClass, Ipv4AddrExt};
2use std::net::Ipv4Addr;
3use std::str::FromStr;
4use thiserror::Error;
5
6/// A range of IPv4 addresses with a common prefix
7#[derive(Clone, Copy, PartialEq, Eq)]
8pub struct Ipv4Range {
9    addr: Ipv4Addr,
10    bits: u8,
11}
12
13impl std::fmt::Debug for Ipv4Range {
14    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
15        write!(f, "{}/{}", self.addr, self.bits)
16    }
17}
18
19impl Ipv4Range {
20    /// Create an IPv4 range with the given base address and netmask prefix length.
21    ///
22    /// # Example
23    ///
24    /// Create the subnet 192.168.0.0/24 with `Ipv4Range::new("192.168.0.0".parse().unwrap(), 24)`
25    pub fn new(addr: Ipv4Addr, bits: u8) -> Self {
26        let mask = !((!0u32).checked_shr(u32::from(bits)).unwrap_or(0));
27        Ipv4Range {
28            addr: Ipv4Addr::from(u32::from(addr) & mask),
29            bits,
30        }
31    }
32
33    /// Return the entire IPv4 range, eg. 0.0.0.0/0
34    pub fn global() -> Self {
35        Ipv4Range {
36            addr: Ipv4Addr::new(0, 0, 0, 0),
37            bits: 0,
38        }
39    }
40
41    /// Returns the local network subnet 10.0.0.0/8
42    pub fn local_subnet_10() -> Self {
43        Ipv4Range {
44            addr: Ipv4Addr::new(10, 0, 0, 0),
45            bits: 8,
46        }
47    }
48
49    /// Returns a local network subnet 172.(16 | x).0.0/16 where x is a 4-bit number given by
50    /// `block`
51    ///
52    /// # Panics
53    ///
54    /// If `block & 0xf0 != 0`
55    pub fn local_subnet_172(block: u8) -> Self {
56        assert!(block < 16);
57        Ipv4Range {
58            addr: Ipv4Addr::new(172, 16 | block, 0, 0),
59            bits: 16,
60        }
61    }
62
63    /// Returns the local subnet 192.168.x.0/24 where x is given by `block`.
64    pub fn local_subnet_192(block: u8) -> Self {
65        Ipv4Range {
66            addr: Ipv4Addr::new(192, 168, block, 0),
67            bits: 24,
68        }
69    }
70
71    /// Returns a random local network subnet from one of the ranges 10.0.0.0, 172.16.0.0 or
72    /// 192.168.0.0
73    pub fn random_local_subnet() -> Self {
74        match rand::random::<u8>() % 3 {
75            0 => Ipv4Range::local_subnet_10(),
76            1 => Ipv4Range::local_subnet_172(rand::random::<u8>() & 0x0f),
77            2 => Ipv4Range::local_subnet_192(rand::random()),
78            _ => unreachable!(),
79        }
80    }
81
82    /// Get the netmask as an IP address
83    pub fn netmask(&self) -> Ipv4Addr {
84        Ipv4Addr::from(!((!0u32).checked_shr(u32::from(self.bits)).unwrap_or(0)))
85    }
86
87    /// Get the number of netmask prefix bits
88    pub fn netmask_prefix_length(&self) -> u8 {
89        self.bits
90    }
91
92    /// Get the base address of the range, ie. the lowest IP address which is part of the range.
93    pub fn base_addr(&self) -> Ipv4Addr {
94        self.addr
95    }
96
97    /// Get a default IP address for the range's gateway. This is one higher than the base address
98    /// of the range. eg. for 10.0.0.0/8, the default address for the gateway will be 10.0.0.1
99    pub fn gateway_addr(&self) -> Ipv4Addr {
100        Ipv4Addr::from(u32::from(self.addr) | 1)
101    }
102
103    /// Get the broadcast address, ie. the highest IP address which is part of the range.
104    pub fn broadcast_addr(&self) -> Ipv4Addr {
105        Ipv4Addr::from(!(!0 >> self.bits) | u32::from(self.addr))
106    }
107
108    /// Get a random IP address from the range which is not the base address or the default
109    /// for the gateway address.
110    pub fn random_client_addr(&self) -> Ipv4Addr {
111        let mask = !0 >> self.bits;
112        assert!(mask > 1);
113        let class = if self.bits == 0 {
114            Ipv4AddrClass::Global
115        } else {
116            self.addr.class()
117        };
118
119        loop {
120            let x = rand::random::<u32>() & mask;
121            if x < 2 {
122                continue;
123            }
124            let addr = Ipv4Addr::from(u32::from(self.addr) | x);
125            if class != addr.class() {
126                continue;
127            }
128            return addr;
129        }
130    }
131
132    /// Generate an IP address for a device.
133    pub fn address_for(&self, device: u32) -> Ipv4Addr {
134        let mask = !0 >> self.bits;
135        assert!(mask > 1);
136        let addr = Ipv4Addr::from(u32::from(self.addr) | ((device & mask) + 2));
137        assert_ne!(addr, self.broadcast_addr());
138        addr
139    }
140
141    /// Check whether this range contains the given IP address
142    pub fn contains(&self, ip: Ipv4Addr) -> bool {
143        let base_addr = u32::from(self.addr);
144        let test_addr = u32::from(ip);
145        (base_addr ^ test_addr).leading_zeros() >= u32::from(self.bits)
146    }
147
148    /// Split a range into `num` sub-ranges
149    ///
150    /// # Panics
151    ///
152    /// If the range is too small to be split up that much.
153    pub fn split(self, num: u32) -> Vec<Self> {
154        let mut ret = Vec::with_capacity(num as usize);
155        let mut n = 0u32;
156        let class = if self.bits == 0 {
157            Ipv4AddrClass::Global
158        } else {
159            self.addr.class()
160        };
161        loop {
162            let mut n_reversed = 0;
163            for i in 0..32 {
164                if n & (1 << i) != 0 {
165                    n_reversed |= 0x8000_0000u32 >> i;
166                }
167            }
168            let base_addr = u32::from(self.addr);
169            let ip = base_addr | (n_reversed >> self.bits);
170            let ip = Ipv4Addr::from(ip);
171            if class != ip.class() {
172                n += 1;
173                continue;
174            }
175            ret.push(Ipv4Range { addr: ip, bits: 0 });
176            if ret.len() == num as usize {
177                break;
178            }
179            n += 1;
180        }
181        let extra_bits = (32 - n.leading_zeros()) as u8;
182        let bits = self.bits + extra_bits;
183        for range in &mut ret {
184            range.bits = bits;
185        }
186        ret
187    }
188}
189
190/// Errors returned by `SubnetV*::from_str`
191#[derive(Debug, Error)]
192pub enum IpRangeParseError {
193    /// Missing '/' delimiter
194    #[error("missing '/' delimiter")]
195    MissingDelimiter,
196    /// More than one '/' delimiter
197    #[error("more than one '/' delimiter")]
198    ExtraDelimiter,
199    /// error parsing IP address
200    #[error("error parsing IP address: {0}")]
201    ParseAddr(std::net::AddrParseError),
202    /// error parsing netmask prefix length
203    #[error("error parsing netmask prefix length: {0}")]
204    ParseNetmaskPrefixLength(std::num::ParseIntError),
205}
206
207impl FromStr for Ipv4Range {
208    type Err = IpRangeParseError;
209
210    fn from_str(s: &str) -> Result<Ipv4Range, IpRangeParseError> {
211        let mut split = s.split('/');
212        let addr = split.next().unwrap();
213        let bits = match split.next() {
214            Some(bits) => bits,
215            None => return Err(IpRangeParseError::MissingDelimiter),
216        };
217        if split.next().is_some() {
218            return Err(IpRangeParseError::ExtraDelimiter);
219        }
220        let addr = match Ipv4Addr::from_str(addr) {
221            Ok(addr) => addr,
222            Err(e) => return Err(IpRangeParseError::ParseAddr(e)),
223        };
224        let bits = match u8::from_str(bits) {
225            Ok(bits) => bits,
226            Err(e) => return Err(IpRangeParseError::ParseNetmaskPrefixLength(e)),
227        };
228        Ok(Ipv4Range::new(addr, bits))
229    }
230}
231
232impl From<Ipv4Addr> for Ipv4Range {
233    fn from(addr: Ipv4Addr) -> Self {
234        Self::new(addr, 32)
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241
242    #[test]
243    fn it_creates_address_range() {
244        let addrs = Ipv4Range::new("1.2.3.0".parse().unwrap(), 24);
245
246        assert!(addrs.contains("1.2.3.5".parse().unwrap()));
247        assert!(addrs.contains("1.2.3.255".parse().unwrap()));
248        assert!(!addrs.contains("1.2.4.5".parse().unwrap()));
249    }
250}