mirror of
https://framagit.org/ppom/reaction
synced 2026-03-14 12:45:47 +01:00
Split IP pattern code in 3 files
This commit is contained in:
parent
130607d28f
commit
0a9c7f97df
4 changed files with 370 additions and 352 deletions
2
TODO
2
TODO
|
|
@ -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
|
||||
|
|
|
|||
239
src/concepts/pattern/ip/cidr.rs
Normal file
239
src/concepts/pattern/ip/cidr.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
120
src/concepts/pattern/ip/utils.rs
Normal file
120
src/concepts/pattern/ip/utils.rs
Normal 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
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue