Split IP pattern code in 3 files

This commit is contained in:
ppom 2025-07-30 12:00:00 +02:00
commit 0a9c7f97df
No known key found for this signature in database
4 changed files with 370 additions and 352 deletions

2
TODO
View file

@ -8,3 +8,5 @@ test Filter::regex conformity after setup
should an ipv6-mapped ipv4 match a pattern of type ipv6?
should it be normalized as ipv4 then?
duplicate: deduplicate when loading database

View file

@ -0,0 +1,239 @@
use std::{
fmt::Display,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
str::FromStr,
};
use super::*;
/// Stores an IP and an associated mask.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Cidr {
IPv4((Ipv4Addr, Ipv4Addr)),
IPv6((Ipv6Addr, Ipv6Addr)),
}
impl FromStr for Cidr {
type Err = String;
fn from_str(cidr: &str) -> Result<Self, Self::Err> {
let (ip, mask) = cidr.split_once('/').ok_or(format!(
"malformed IP/MASK. '{cidr}' doesn't contain any '/'"
))?;
let ip = normalize(ip).map_err(|err| format!("malformed IP '{ip}' in '{cidr}': {err}"))?;
let mask_count = u8::from_str(mask)
.map_err(|err| format!("malformed mask '{mask}' in '{cidr}': {err}"))?;
// Let's accept any mask size for now, as useless as it may seem
// if mask_count < 2 {
// return Err("Can't have a network mask of 0 or 1. You're either ignoring all Internet or half of it.".into());
// } else if mask_count
// < (match ip {
// IpAddr::V4(_) => 8,
// IpAddr::V6(_) => 16,
// })
// {
// warn!("With a mask of {mask_count}, you're ignoring a big part of Internet. Are you sure you want to do this?");
// }
Self::from_ip_and_mask(ip, mask_count)
}
}
impl Display for Cidr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}/{}", self.network(), self.mask())
}
}
impl Cidr {
fn from_ip_and_mask(ip: IpAddr, mask_count: u8) -> Result<Self, String> {
match ip {
IpAddr::V4(mut ipv4_addr) => {
// Create bitmask
let mask = mask_to_ipv4(mask_count)?;
// Normalize IP from mask
ipv4_addr &= mask;
Ok(Cidr::IPv4((ipv4_addr, mask)))
}
IpAddr::V6(mut ipv6_addr) => {
let mask = mask_to_ipv6(mask_count)?;
// Normalize IP from mask
ipv6_addr &= mask;
Ok(Cidr::IPv6((ipv6_addr, mask)))
}
}
}
/// Whether an IP is included in this IP CIDR.
/// If IP is not the same version as CIDR, returns always false.
pub fn includes(&self, ip: &IpAddr) -> bool {
let ip = normalize_ip(*ip);
match self {
Cidr::IPv4((network_ipv4, mask)) => match ip {
IpAddr::V6(_) => false,
IpAddr::V4(ipv4_addr) => *network_ipv4 == ipv4_addr & mask,
},
Cidr::IPv6((network_ipv6, mask)) => match ip {
IpAddr::V4(_) => false,
IpAddr::V6(ipv6_addr) => *network_ipv6 == ipv6_addr & mask,
},
}
}
fn network(&self) -> IpAddr {
match self {
Cidr::IPv4((network, _)) => IpAddr::from(*network),
Cidr::IPv6((network, _)) => IpAddr::from(*network),
}
}
fn mask(&self) -> u8 {
let mut raw_mask = match self {
Cidr::IPv4((_, mask)) => mask.to_bits() as u128,
Cidr::IPv6((_, mask)) => mask.to_bits(),
};
let mut ret = 0;
for _ in 0..128 {
if raw_mask % 2 == 1 {
ret += 1;
}
raw_mask >>= 1;
}
ret
}
}
#[cfg(test)]
mod cidr_tests {
use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr},
str::FromStr,
};
use super::Cidr;
#[test]
fn cidrv4_from_str() {
assert_eq!(
Ok(Cidr::IPv4((Ipv4Addr::new(192, 168, 1, 4), u32::MAX.into()))),
Cidr::from_str("192.168.1.4/32")
);
// Test IP normalization from mask
assert_eq!(
Ok(Cidr::IPv4((
Ipv4Addr::new(192, 168, 1, 0),
Ipv4Addr::new(255, 255, 255, 0),
))),
Cidr::from_str("192.168.1.4/24")
);
// Another ok-test "pour la route"
assert_eq!(
Ok(Cidr::IPv4((
Ipv4Addr::new(1, 1, 0, 0),
Ipv4Addr::new(255, 255, 0, 0),
))),
Cidr::from_str("1.1.248.25/16")
);
// Errors
assert!(Cidr::from_str("256.1.1.1/8").is_err());
// assert!(Cidr::from_str("1.1.1.1/0").is_err());
// assert!(Cidr::from_str("1.1.1.1/1").is_err());
// assert!(Cidr::from_str("1.1.1.1.1").is_err());
assert!(Cidr::from_str("1.1.1.1/16/16").is_err());
}
#[test]
fn cidrv6_from_str() {
assert_eq!(
Ok(Cidr::IPv6((
Ipv6Addr::new(0xfe80, 0, 0, 0, 0xdf68, 0x2ee, 0xe4f9, 0xe68),
u128::MAX.into()
))),
Cidr::from_str("fe80::df68:2ee:e4f9:e68/128")
);
// Test IP normalization from mask
assert_eq!(
Ok(Cidr::IPv6((
Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x9de5, 0, 0, 0, 0),
Ipv6Addr::new(u16::MAX, u16::MAX, u16::MAX, u16::MAX, 0, 0, 0, 0),
))),
Cidr::from_str("2001:db8:85a3:9de5::8a2e:370:7334/64")
);
// Another ok-test "pour la route"
assert_eq!(
Ok(Cidr::IPv6((
Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x9d00, 0, 0, 0, 0),
Ipv6Addr::new(
u16::MAX,
u16::MAX,
u16::MAX,
u16::MAX - u8::MAX as u16,
0,
0,
0,
0
),
))),
Cidr::from_str("2001:db8:85a3:9d00::8a2e:370:7334/56")
);
assert!(Cidr::from_str("2001:db8:85a3:0:0:8a2e:370:7334/56").is_ok());
assert!(Cidr::from_str("2001:DB8:85A3:0:0:8A2E:370:7334/56").is_ok());
// Errors
assert!(Cidr::from_str("2001:db8:85a3:0:0:8a2e:370:g334/56").is_err());
// assert!(Cidr::from_str("2001:db8:85a3:0:0:8a2e:370:7334/0").is_err());
// assert!(Cidr::from_str("2001:db8:85a3:0:0:8a2e:370:7334/1").is_err());
assert!(Cidr::from_str("2001:db8:85a3:0:0:8a2e:370:7334:11/56").is_err());
assert!(Cidr::from_str("2001:db8:85a3:0:0:8a2e:370:7334/11/56").is_err());
}
#[test]
fn cidrv4_includes() {
let cidr = Cidr::from_str("192.168.1.0/24").unwrap();
assert!(cidr.includes(&IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))));
assert!(cidr.includes(&IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))));
assert!(cidr.includes(&IpAddr::V4(Ipv4Addr::new(192, 168, 1, 234))));
assert!(!cidr.includes(&IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1))));
assert!(!cidr.includes(&IpAddr::V6(Ipv6Addr::new(
0xfe80, 0, 0, 0, 0xdf68, 0x2ee, 0xe4f9, 0xe68
),)));
}
#[test]
fn cidrv6_includes() {
let cidr = Cidr::from_str("2001:db8:85a3:9d00:0:8a2e:370:7334/56").unwrap();
assert!(cidr.includes(&IpAddr::V6(Ipv6Addr::new(
0x2001, 0x0db8, 0x85a3, 0x9d00, 0, 0, 0, 0
))));
assert!(cidr.includes(&IpAddr::V6(Ipv6Addr::new(
0x2001, 0x0db8, 0x85a3, 0x9da4, 0x34fc, 0x0d8b, 0xffff, 0x1111
))));
assert!(!cidr.includes(&IpAddr::V6(Ipv6Addr::new(
0x2001, 0x0db8, 0x85a3, 0xad00, 0, 0, 0, 1
))));
assert!(!cidr.includes(&IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))));
}
#[test]
fn cidr_display() {
let cidrs = [
("192.168.1.4/32", "192.168.1.4/32"),
("192.168.1.4/24", "192.168.1.0/24"),
("1.1.248.25/16", "1.1.0.0/16"),
("fe80::df68:2ee:e4f9:e68/128", "fe80::df68:2ee:e4f9:e68/128"),
(
"2001:db8:85a3:9de5::8a2e:370:7334/64",
"2001:db8:85a3:9de5::/64",
),
(
"2001:db8:85a3:9d00::8a2e:370:7334/56",
"2001:db8:85a3:9d00::/56",
),
];
for (from, to) in cidrs {
assert_eq!(Cidr::from_str(from).unwrap().to_string(), to);
}
}
}

View file

@ -1,12 +1,17 @@
use std::{
fmt::Display,
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr},
net::{IpAddr, Ipv4Addr, Ipv6Addr},
str::FromStr,
};
use serde::{Deserialize, Serialize};
use tracing::warn;
use cidr::Cidr;
use utils::*;
mod cidr;
mod utils;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
pub enum PatternType {
#[default]
@ -283,11 +288,11 @@ mod patternip_tests {
use tokio::{fs::read_to_string, task::JoinSet};
use crate::{
concepts::{pattern::ip::Cidr, Action, Duplicate, Filter, Pattern},
concepts::{Action, Duplicate, Filter, Pattern},
daemon::{tests::TestBed, React},
};
use super::{PatternIp, PatternType};
use super::{Cidr, PatternIp, PatternType};
#[test]
fn test_setup_type_regex() {
@ -716,351 +721,3 @@ mod patternip_tests {
join_set.join_all().await;
}
}
/// Normalize a string as an IP address.
/// IPv6-mapped IPv4 addresses are casted to IPv4.
fn normalize(ip: &str) -> Result<IpAddr, AddrParseError> {
IpAddr::from_str(ip).map(normalize_ip)
}
/// Normalize a string as an IP address.
/// IPv6-mapped IPv4 addresses are casted to IPv4.
fn normalize_ip(ip: IpAddr) -> IpAddr {
match ip {
IpAddr::V4(_) => ip,
IpAddr::V6(ipv6) => match ipv6.to_ipv4_mapped() {
Some(ipv4) => IpAddr::V4(ipv4),
None => ip,
},
}
}
/// Creates an [`Ipv4Addr`] from a mask
fn mask_to_ipv4(mask_count: u8) -> Result<Ipv4Addr, String> {
if mask_count > 32 {
Err(format!(
"an IPv4 mask must be 32 max. {mask_count} is too big."
))
} else {
let mask = match mask_count {
0 => 0u32,
n => u32::MAX << (32 - n),
};
let mask = Ipv4Addr::from_bits(mask);
Ok(mask)
}
}
/// Creates an [`Ipv4Addr`] from a mask
fn mask_to_ipv6(mask_count: u8) -> Result<Ipv6Addr, String> {
if mask_count > 128 {
Err(format!(
"an IPv4 mask must be 128 max. {mask_count} is too big."
))
} else {
let mask = match mask_count {
0 => 0u128,
n => u128::MAX << (128 - n),
};
let mask = Ipv6Addr::from_bits(mask);
Ok(mask)
}
}
#[cfg(test)]
mod utils_tests {
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use super::{mask_to_ipv4, mask_to_ipv6, normalize};
#[test]
fn test_normalize_ip() {
assert_eq!(
normalize("83.44.23.14"),
Ok(IpAddr::V4(Ipv4Addr::new(83, 44, 23, 14)))
);
assert_eq!(
normalize("2001:db8:85a3::8a2e:370:7334"),
Ok(IpAddr::V6(Ipv6Addr::new(
0x2001, 0xdb8, 0x85a3, 0x0, 0x0, 0x8a2e, 0x370, 0x7334
)))
);
assert_eq!(
normalize("::ffff:192.168.1.34"),
Ok(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 34)))
);
assert_eq!(
normalize("::ffff:1.2.3.4"),
Ok(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)))
);
}
#[test]
fn test_mask_to_ipv4() {
assert!(mask_to_ipv4(33).is_err());
assert!(mask_to_ipv4(100).is_err());
assert_eq!(mask_to_ipv4(16), Ok(Ipv4Addr::new(255, 255, 0, 0)));
assert_eq!(mask_to_ipv4(24), Ok(Ipv4Addr::new(255, 255, 255, 0)));
assert_eq!(mask_to_ipv4(25), Ok(Ipv4Addr::new(255, 255, 255, 128)));
assert_eq!(mask_to_ipv4(26), Ok(Ipv4Addr::new(255, 255, 255, 192)));
assert_eq!(mask_to_ipv4(32), Ok(Ipv4Addr::new(255, 255, 255, 255)));
}
#[test]
fn test_mask_to_ipv6() {
assert!(mask_to_ipv6(129).is_err());
assert!(mask_to_ipv6(254).is_err());
assert_eq!(
mask_to_ipv6(56),
Ok(Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xff00, 0, 0, 0, 0))
);
assert_eq!(
mask_to_ipv6(64),
Ok(Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0, 0))
);
assert_eq!(
mask_to_ipv6(112),
Ok(Ipv6Addr::new(
0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0
))
);
assert_eq!(
mask_to_ipv6(128),
Ok(Ipv6Addr::new(
0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff
))
);
}
}
/// Stores an IP and an associated mask.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Cidr {
IPv4((Ipv4Addr, Ipv4Addr)),
IPv6((Ipv6Addr, Ipv6Addr)),
}
impl FromStr for Cidr {
type Err = String;
fn from_str(cidr: &str) -> Result<Self, Self::Err> {
let (ip, mask) = cidr.split_once('/').ok_or(format!(
"malformed IP/MASK. '{cidr}' doesn't contain any '/'"
))?;
let ip = normalize(ip).map_err(|err| format!("malformed IP '{ip}' in '{cidr}': {err}"))?;
let mask_count = u8::from_str(mask)
.map_err(|err| format!("malformed mask '{mask}' in '{cidr}': {err}"))?;
// Let's accept any mask size for now, as useless as it may seem
// if mask_count < 2 {
// return Err("Can't have a network mask of 0 or 1. You're either ignoring all Internet or half of it.".into());
// } else if mask_count
// < (match ip {
// IpAddr::V4(_) => 8,
// IpAddr::V6(_) => 16,
// })
// {
// warn!("With a mask of {mask_count}, you're ignoring a big part of Internet. Are you sure you want to do this?");
// }
Self::from_ip_and_mask(ip, mask_count)
}
}
impl Display for Cidr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}/{}", self.network(), self.mask())
}
}
impl Cidr {
fn from_ip_and_mask(ip: IpAddr, mask_count: u8) -> Result<Self, String> {
match ip {
IpAddr::V4(mut ipv4_addr) => {
// Create bitmask
let mask = mask_to_ipv4(mask_count)?;
// Normalize IP from mask
ipv4_addr &= mask;
Ok(Cidr::IPv4((ipv4_addr, mask)))
}
IpAddr::V6(mut ipv6_addr) => {
let mask = mask_to_ipv6(mask_count)?;
// Normalize IP from mask
ipv6_addr &= mask;
Ok(Cidr::IPv6((ipv6_addr, mask)))
}
}
}
/// Whether an IP is included in this IP CIDR.
/// If IP is not the same version as CIDR, returns always false.
fn includes(&self, ip: &IpAddr) -> bool {
let ip = normalize_ip(*ip);
match self {
Cidr::IPv4((network_ipv4, mask)) => match ip {
IpAddr::V6(_) => false,
IpAddr::V4(ipv4_addr) => *network_ipv4 == ipv4_addr & mask,
},
Cidr::IPv6((network_ipv6, mask)) => match ip {
IpAddr::V4(_) => false,
IpAddr::V6(ipv6_addr) => *network_ipv6 == ipv6_addr & mask,
},
}
}
fn network(&self) -> IpAddr {
match self {
Cidr::IPv4((network, _)) => IpAddr::from(*network),
Cidr::IPv6((network, _)) => IpAddr::from(*network),
}
}
fn mask(&self) -> u8 {
let mut raw_mask = match self {
Cidr::IPv4((_, mask)) => mask.to_bits() as u128,
Cidr::IPv6((_, mask)) => mask.to_bits(),
};
let mut ret = 0;
for _ in 0..128 {
if raw_mask % 2 == 1 {
ret += 1;
}
raw_mask >>= 1;
}
ret
}
}
#[cfg(test)]
mod cidr_tests {
use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr},
str::FromStr,
};
use super::Cidr;
#[test]
fn cidrv4_from_str() {
assert_eq!(
Ok(Cidr::IPv4((Ipv4Addr::new(192, 168, 1, 4), u32::MAX.into()))),
Cidr::from_str("192.168.1.4/32")
);
// Test IP normalization from mask
assert_eq!(
Ok(Cidr::IPv4((
Ipv4Addr::new(192, 168, 1, 0),
Ipv4Addr::new(255, 255, 255, 0),
))),
Cidr::from_str("192.168.1.4/24")
);
// Another ok-test "pour la route"
assert_eq!(
Ok(Cidr::IPv4((
Ipv4Addr::new(1, 1, 0, 0),
Ipv4Addr::new(255, 255, 0, 0),
))),
Cidr::from_str("1.1.248.25/16")
);
// Errors
assert!(Cidr::from_str("256.1.1.1/8").is_err());
// assert!(Cidr::from_str("1.1.1.1/0").is_err());
// assert!(Cidr::from_str("1.1.1.1/1").is_err());
// assert!(Cidr::from_str("1.1.1.1.1").is_err());
assert!(Cidr::from_str("1.1.1.1/16/16").is_err());
}
#[test]
fn cidrv6_from_str() {
assert_eq!(
Ok(Cidr::IPv6((
Ipv6Addr::new(0xfe80, 0, 0, 0, 0xdf68, 0x2ee, 0xe4f9, 0xe68),
u128::MAX.into()
))),
Cidr::from_str("fe80::df68:2ee:e4f9:e68/128")
);
// Test IP normalization from mask
assert_eq!(
Ok(Cidr::IPv6((
Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x9de5, 0, 0, 0, 0),
Ipv6Addr::new(u16::MAX, u16::MAX, u16::MAX, u16::MAX, 0, 0, 0, 0),
))),
Cidr::from_str("2001:db8:85a3:9de5::8a2e:370:7334/64")
);
// Another ok-test "pour la route"
assert_eq!(
Ok(Cidr::IPv6((
Ipv6Addr::new(0x2001, 0xdb8, 0x85a3, 0x9d00, 0, 0, 0, 0),
Ipv6Addr::new(
u16::MAX,
u16::MAX,
u16::MAX,
u16::MAX - u8::MAX as u16,
0,
0,
0,
0
),
))),
Cidr::from_str("2001:db8:85a3:9d00::8a2e:370:7334/56")
);
assert!(Cidr::from_str("2001:db8:85a3:0:0:8a2e:370:7334/56").is_ok());
assert!(Cidr::from_str("2001:DB8:85A3:0:0:8A2E:370:7334/56").is_ok());
// Errors
assert!(Cidr::from_str("2001:db8:85a3:0:0:8a2e:370:g334/56").is_err());
// assert!(Cidr::from_str("2001:db8:85a3:0:0:8a2e:370:7334/0").is_err());
// assert!(Cidr::from_str("2001:db8:85a3:0:0:8a2e:370:7334/1").is_err());
assert!(Cidr::from_str("2001:db8:85a3:0:0:8a2e:370:7334:11/56").is_err());
assert!(Cidr::from_str("2001:db8:85a3:0:0:8a2e:370:7334/11/56").is_err());
}
#[test]
fn cidrv4_includes() {
let cidr = Cidr::from_str("192.168.1.0/24").unwrap();
assert!(cidr.includes(&IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))));
assert!(cidr.includes(&IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))));
assert!(cidr.includes(&IpAddr::V4(Ipv4Addr::new(192, 168, 1, 234))));
assert!(!cidr.includes(&IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1))));
assert!(!cidr.includes(&IpAddr::V6(Ipv6Addr::new(
0xfe80, 0, 0, 0, 0xdf68, 0x2ee, 0xe4f9, 0xe68
),)));
}
#[test]
fn cidrv6_includes() {
let cidr = Cidr::from_str("2001:db8:85a3:9d00:0:8a2e:370:7334/56").unwrap();
assert!(cidr.includes(&IpAddr::V6(Ipv6Addr::new(
0x2001, 0x0db8, 0x85a3, 0x9d00, 0, 0, 0, 0
))));
assert!(cidr.includes(&IpAddr::V6(Ipv6Addr::new(
0x2001, 0x0db8, 0x85a3, 0x9da4, 0x34fc, 0x0d8b, 0xffff, 0x1111
))));
assert!(!cidr.includes(&IpAddr::V6(Ipv6Addr::new(
0x2001, 0x0db8, 0x85a3, 0xad00, 0, 0, 0, 1
))));
assert!(!cidr.includes(&IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))));
}
#[test]
fn cidr_display() {
let cidrs = [
("192.168.1.4/32", "192.168.1.4/32"),
("192.168.1.4/24", "192.168.1.0/24"),
("1.1.248.25/16", "1.1.0.0/16"),
("fe80::df68:2ee:e4f9:e68/128", "fe80::df68:2ee:e4f9:e68/128"),
(
"2001:db8:85a3:9de5::8a2e:370:7334/64",
"2001:db8:85a3:9de5::/64",
),
(
"2001:db8:85a3:9d00::8a2e:370:7334/56",
"2001:db8:85a3:9d00::/56",
),
];
for (from, to) in cidrs {
assert_eq!(Cidr::from_str(from).unwrap().to_string(), to);
}
}
}

View file

@ -0,0 +1,120 @@
use std::{
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr},
str::FromStr,
};
/// Normalize a string as an IP address.
/// IPv6-mapped IPv4 addresses are casted to IPv4.
pub fn normalize(ip: &str) -> Result<IpAddr, AddrParseError> {
IpAddr::from_str(ip).map(normalize_ip)
}
/// Normalize a string as an IP address.
/// IPv6-mapped IPv4 addresses are casted to IPv4.
pub fn normalize_ip(ip: IpAddr) -> IpAddr {
match ip {
IpAddr::V4(_) => ip,
IpAddr::V6(ipv6) => match ipv6.to_ipv4_mapped() {
Some(ipv4) => IpAddr::V4(ipv4),
None => ip,
},
}
}
/// Creates an [`Ipv4Addr`] from a mask
pub fn mask_to_ipv4(mask_count: u8) -> Result<Ipv4Addr, String> {
if mask_count > 32 {
Err(format!(
"an IPv4 mask must be 32 max. {mask_count} is too big."
))
} else {
let mask = match mask_count {
0 => 0u32,
n => u32::MAX << (32 - n),
};
let mask = Ipv4Addr::from_bits(mask);
Ok(mask)
}
}
/// Creates an [`Ipv4Addr`] from a mask
pub fn mask_to_ipv6(mask_count: u8) -> Result<Ipv6Addr, String> {
if mask_count > 128 {
Err(format!(
"an IPv4 mask must be 128 max. {mask_count} is too big."
))
} else {
let mask = match mask_count {
0 => 0u128,
n => u128::MAX << (128 - n),
};
let mask = Ipv6Addr::from_bits(mask);
Ok(mask)
}
}
#[cfg(test)]
mod utils_tests {
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use super::{mask_to_ipv4, mask_to_ipv6, normalize};
#[test]
fn test_normalize_ip() {
assert_eq!(
normalize("83.44.23.14"),
Ok(IpAddr::V4(Ipv4Addr::new(83, 44, 23, 14)))
);
assert_eq!(
normalize("2001:db8:85a3::8a2e:370:7334"),
Ok(IpAddr::V6(Ipv6Addr::new(
0x2001, 0xdb8, 0x85a3, 0x0, 0x0, 0x8a2e, 0x370, 0x7334
)))
);
assert_eq!(
normalize("::ffff:192.168.1.34"),
Ok(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 34)))
);
assert_eq!(
normalize("::ffff:1.2.3.4"),
Ok(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)))
);
}
#[test]
fn test_mask_to_ipv4() {
assert!(mask_to_ipv4(33).is_err());
assert!(mask_to_ipv4(100).is_err());
assert_eq!(mask_to_ipv4(16), Ok(Ipv4Addr::new(255, 255, 0, 0)));
assert_eq!(mask_to_ipv4(24), Ok(Ipv4Addr::new(255, 255, 255, 0)));
assert_eq!(mask_to_ipv4(25), Ok(Ipv4Addr::new(255, 255, 255, 128)));
assert_eq!(mask_to_ipv4(26), Ok(Ipv4Addr::new(255, 255, 255, 192)));
assert_eq!(mask_to_ipv4(32), Ok(Ipv4Addr::new(255, 255, 255, 255)));
}
#[test]
fn test_mask_to_ipv6() {
assert!(mask_to_ipv6(129).is_err());
assert!(mask_to_ipv6(254).is_err());
assert_eq!(
mask_to_ipv6(56),
Ok(Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xff00, 0, 0, 0, 0))
);
assert_eq!(
mask_to_ipv6(64),
Ok(Ipv6Addr::new(0xffff, 0xffff, 0xffff, 0xffff, 0, 0, 0, 0))
);
assert_eq!(
mask_to_ipv6(112),
Ok(Ipv6Addr::new(
0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0
))
);
assert_eq!(
mask_to_ipv6(128),
Ok(Ipv6Addr::new(
0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff
))
);
}
}