From 067924aaea5aa458b7c871cfbb0133be22274e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Sun, 22 Jun 2025 15:49:29 +0200 Subject: [PATCH 01/13] extract largest possible subnet when merging address ranges --- .../defguard_core/src/enterprise/firewall.rs | 184 +++++++++++++++++- 1 file changed, 175 insertions(+), 9 deletions(-) diff --git a/crates/defguard_core/src/enterprise/firewall.rs b/crates/defguard_core/src/enterprise/firewall.rs index 570400ef9e..5516bf983f 100644 --- a/crates/defguard_core/src/enterprise/firewall.rs +++ b/crates/defguard_core/src/enterprise/firewall.rs @@ -539,7 +539,6 @@ fn process_alias_destination_addrs( (ipv4_dest_addrs, ipv6_dest_addrs) } -#[cfg(test)] fn get_last_ip_in_v6_subnet(subnet: &ipnetwork::Ipv6Network) -> IpAddr { // get subnet IP portion as u128 let first_ip = subnet.ip().to_bits(); @@ -549,6 +548,88 @@ fn get_last_ip_in_v6_subnet(subnet: &ipnetwork::Ipv6Network) -> IpAddr { IpAddr::V6(last_ip.into()) } +/// Finds the largest subnet that fits within the given IP address range. +/// Returns None if no valid subnet can be found. +fn find_largest_subnet_in_range(start: IpAddr, end: IpAddr) -> Option { + match (start, end) { + (IpAddr::V4(start_v4), IpAddr::V4(end_v4)) => { + find_largest_ipv4_subnet_in_range(start_v4, end_v4) + } + (IpAddr::V6(start_v6), IpAddr::V6(end_v6)) => { + find_largest_ipv6_subnet_in_range(start_v6, end_v6) + } + _ => None, // Mixed IP versions + } +} + +/// Finds the largest IPv4 subnet that fits within the given range. +fn find_largest_ipv4_subnet_in_range(start: Ipv4Addr, end: Ipv4Addr) -> Option { + let start_bits = u32::from(start); + let end_bits = u32::from(end); + + if start_bits > end_bits { + return None; + } + + // Find the largest prefix length where the subnet fits in the range + for prefix_len in 0..=32 { + let (mask, broadcast_addr) = if prefix_len == 0 { + (0u32, u32::MAX) + } else if prefix_len == 32 { + (u32::MAX, start_bits) + } else { + let mask = !((1u32 << (32 - prefix_len)) - 1); + let network_addr = start_bits & mask; + let broadcast_addr = network_addr | ((1u32 << (32 - prefix_len)) - 1); + (mask, broadcast_addr) + }; + + let network_addr = start_bits & mask; + + if network_addr >= start_bits && broadcast_addr <= end_bits { + if let Ok(network) = IpNetwork::new(IpAddr::V4(Ipv4Addr::from(network_addr)), prefix_len) { + return Some(network); + } + } + } + + None +} + +/// Finds the largest IPv6 subnet that fits within the given range. +fn find_largest_ipv6_subnet_in_range(start: Ipv6Addr, end: Ipv6Addr) -> Option { + let start_bits = u128::from(start); + let end_bits = u128::from(end); + + if start_bits > end_bits { + return None; + } + + // Find the largest prefix length where the subnet fits in the range + for prefix_len in 0..=128 { + let (mask, broadcast_addr) = if prefix_len == 0 { + (0u128, u128::MAX) + } else if prefix_len == 128 { + (u128::MAX, start_bits) + } else { + let mask = !((1u128 << (128 - prefix_len)) - 1); + let network_addr = start_bits & mask; + let broadcast_addr = network_addr | ((1u128 << (128 - prefix_len)) - 1); + (mask, broadcast_addr) + }; + + let network_addr = start_bits & mask; + + if network_addr >= start_bits && broadcast_addr <= end_bits { + if let Ok(network) = IpNetwork::new(IpAddr::V6(Ipv6Addr::from(network_addr)), prefix_len) { + return Some(network); + } + } + } + + None +} + /// Converts an arbitrary list of IP address ranges into the smallest possible list /// of non-overlapping elements which can be used in a firewall rule. /// It assumes that all ranges with an invalid IP version have already been filtered out. @@ -566,14 +647,99 @@ fn merge_addrs(addr_ranges: Vec>) -> Vec { address: Some(Address::Ip(range_start.to_string())), }); } else { - // TODO: find largest subnet in range - // address range - result.push(IpAddress { - address: Some(Address::IpRange(IpRange { - start: range_start.to_string(), - end: range_end.to_string(), - })), - }); + // Try to find the largest subnet that fits in the range + if let Some(subnet) = find_largest_subnet_in_range(range_start, range_end) { + let subnet_start = subnet.network(); + let subnet_end = if subnet.is_ipv4() { + subnet.broadcast() + } else { + // For IPv6, calculate the last IP in the subnet + let ipv6_net = match subnet { + IpNetwork::V6(net) => net, + _ => unreachable!(), // We already checked is_ipv4() is false + }; + get_last_ip_in_v6_subnet(&ipv6_net) + }; + + // Check if the subnet covers the entire range + if subnet_start == range_start && subnet_end == range_end { + // Use subnet notation for the entire range + result.push(IpAddress { + address: Some(Address::IpSubnet(subnet.to_string())), + }); + } else { + // Subnet is found within the range, append both subnet and remaining ranges + + // Add range before subnet (if any) + if range_start < subnet_start { + let prev_ip = match subnet_start { + IpAddr::V4(ip) => { + let ip_u32 = u32::from(ip); + if ip_u32 > 0 { + IpAddr::V4(Ipv4Addr::from(ip_u32 - 1)) + } else { + range_start // shouldn't happen in practice + } + } + IpAddr::V6(ip) => { + let ip_u128 = u128::from(ip); + if ip_u128 > 0 { + IpAddr::V6(Ipv6Addr::from(ip_u128 - 1)) + } else { + range_start // shouldn't happen in practice + } + } + }; + result.push(IpAddress { + address: Some(Address::IpRange(IpRange { + start: range_start.to_string(), + end: prev_ip.to_string(), + })), + }); + } + + // Add the subnet + result.push(IpAddress { + address: Some(Address::IpSubnet(subnet.to_string())), + }); + + // Add range after subnet (if any) + if subnet_end < range_end { + let next_ip = match subnet_end { + IpAddr::V4(ip) => { + let ip_u32 = u32::from(ip); + if ip_u32 < u32::MAX { + IpAddr::V4(Ipv4Addr::from(ip_u32 + 1)) + } else { + range_end // shouldn't happen in practice + } + } + IpAddr::V6(ip) => { + let ip_u128 = u128::from(ip); + if ip_u128 < u128::MAX { + IpAddr::V6(Ipv6Addr::from(ip_u128 + 1)) + } else { + range_end // shouldn't happen in practice + } + } + }; + result.push(IpAddress { + address: Some(Address::IpRange(IpRange { + start: next_ip.to_string(), + end: range_end.to_string(), + })), + }); + } + } + } else { + // Fall back to range notation + result.push(IpAddress { + address: Some(Address::IpRange(IpRange { + start: range_start.to_string(), + end: range_end.to_string(), + })), + }); + } } } From 976c1bf8a2b7a23859c3f773d66788faf2daf241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Sun, 22 Jun 2025 16:21:58 +0200 Subject: [PATCH 02/13] don't process networks separately --- .../src/enterprise/db/models/acl.rs | 12 -- .../defguard_core/src/enterprise/firewall.rs | 148 +++++++----------- 2 files changed, 59 insertions(+), 101 deletions(-) diff --git a/crates/defguard_core/src/enterprise/db/models/acl.rs b/crates/defguard_core/src/enterprise/db/models/acl.rs index 9f539119ee..d4e6cd7d1d 100644 --- a/crates/defguard_core/src/enterprise/db/models/acl.rs +++ b/crates/defguard_core/src/enterprise/db/models/acl.rs @@ -1892,12 +1892,6 @@ pub struct AclRuleDestinationRange { pub end: IpAddr, } -impl AclRuleDestinationRange { - pub(crate) fn fits_in_network(&self, ipnet: &IpNetwork) -> bool { - ipnet.contains(self.start) && ipnet.contains(self.end) - } -} - impl AclRuleDestinationRange { pub async fn save<'e, E>(self, executor: E) -> Result, SqlxError> where @@ -1936,12 +1930,6 @@ pub struct AclAliasDestinationRange { pub end: IpAddr, } -impl AclAliasDestinationRange { - pub(crate) fn fits_in_network(&self, ipnet: &IpNetwork) -> bool { - ipnet.contains(self.start) && ipnet.contains(self.end) - } -} - impl AclAliasDestinationRange { pub async fn save<'e, E>(self, executor: E) -> Result, SqlxError> where diff --git a/crates/defguard_core/src/enterprise/firewall.rs b/crates/defguard_core/src/enterprise/firewall.rs index 5516bf983f..6d9522a322 100644 --- a/crates/defguard_core/src/enterprise/firewall.rs +++ b/crates/defguard_core/src/enterprise/firewall.rs @@ -67,6 +67,7 @@ pub async fn generate_firewall_rules_from_acls( // get network IPs for devices belonging to those users let user_device_ips = get_user_device_ips(&users, location_id, &mut *conn).await?; + // separate IPv4 and IPv6 user-device addresses let user_device_ips = user_device_ips .iter() @@ -84,6 +85,7 @@ pub async fn generate_firewall_rules_from_acls( get_source_network_devices(allowed_network_devices, &denied_network_devices); let network_device_ips = get_network_device_ips(&network_devices, location_id, &mut *conn).await?; + // separate IPv4 and IPv6 network-device addresses let network_device_ips = network_device_ips .iter() @@ -129,7 +131,7 @@ pub async fn generate_firewall_rules_from_acls( // prepare destination addresses let (dest_addrs_v4, dest_addrs_v6) = - process_destination_addrs(&destination, destination_ranges); + process_destination_addrs(&destination, &destination_ranges); // prepare destination ports let destination_ports = merge_port_ranges(ports); @@ -199,7 +201,7 @@ pub async fn generate_firewall_rules_from_acls( // combine destination addrs let (dest_addrs_v4, dest_addrs_v6) = - process_alias_destination_addrs(&alias.destination, alias_destination_ranges); + process_alias_destination_addrs(&alias.destination, &alias_destination_ranges); // process alias ports let alias_ports = alias.ports.into_iter().map(Into::into).collect::>(); @@ -424,56 +426,38 @@ fn get_source_addrs( /// first field and IPv6 addresses in the second. fn process_destination_addrs( dest_ipnets: &[IpNetwork], - mut dest_ranges: Vec>, + dest_ranges: &[AclRuleDestinationRange], ) -> (Vec, Vec) { - // Remove all IP address ranges that fit in the networks. - for dest in dest_ipnets { - dest_ranges.retain(|range| !range.fits_in_network(dest)); - } - - // Separate IP v4 and v6 addresses. - let mut ipv4_dest_addrs = dest_ipnets + // Separate IP v4 and v6 addresses and convert networks to intermediate range representation for merging + let ipv4_dest_net_addrs = dest_ipnets .iter() .filter(|dst| dst.is_ipv4()) - .map(|addr| IpAddress { - address: Some(if u32::from(addr.prefix()) == Ipv4Addr::BITS { - Address::Ip(addr.ip().to_string()) - } else { - Address::IpSubnet(addr.to_string()) - }), - }) - .collect::>(); - let mut ipv6_dest_addrs = dest_ipnets - .iter() - .filter(|dst| dst.is_ipv6()) - .map(|addr| { - let addr_string = addr.to_string(); - IpAddress { - address: Some(if u32::from(addr.prefix()) == Ipv6Addr::BITS { - Address::Ip(addr.ip().to_string()) - } else { - Address::IpSubnet(addr_string) - }), - } - }) - .collect::>(); + .map(|dst| dst.network()..=dst.broadcast()); + let ipv6_dest_net_addrs = dest_ipnets.iter().filter_map(|dst| { + if let IpNetwork::V6(subnet) = dst { + let range_start = subnet.network().into(); + let range_end = get_last_ip_in_v6_subnet(subnet); + Some(range_start..=range_end) + } else { + None + } + }); // Separate IP v4 and v6 ranges. let ipv4_dest_ranges = dest_ranges .iter() .filter(|dst| dst.start.is_ipv4() && dst.end.is_ipv4()) - .map(RangeInclusive::from) - .collect(); + .map(RangeInclusive::from); let ipv6_dest_ranges = dest_ranges .iter() .filter(|dst| dst.start.is_ipv6() && dst.end.is_ipv6()) - .map(RangeInclusive::from) - .collect(); + .map(RangeInclusive::from); - ipv4_dest_addrs.append(&mut merge_addrs(ipv4_dest_ranges)); - ipv6_dest_addrs.append(&mut merge_addrs(ipv6_dest_ranges)); + // combine iterators + let ipv4_dest_addrs = ipv4_dest_net_addrs.chain(ipv4_dest_ranges).collect(); + let ipv6_dest_addrs = ipv6_dest_net_addrs.chain(ipv6_dest_ranges).collect(); - (ipv4_dest_addrs, ipv6_dest_addrs) + (merge_addrs(ipv4_dest_addrs), merge_addrs(ipv6_dest_addrs)) } /// Convert destination networks and ranges configured in an ACL alias @@ -487,56 +471,38 @@ fn process_destination_addrs( /// first field and IPv6 addresses in the second. fn process_alias_destination_addrs( dest_ipnets: &[IpNetwork], - mut dest_ranges: Vec>, + dest_ranges: &[AclAliasDestinationRange], ) -> (Vec, Vec) { - // Remove all IP address ranges that fit in the networks. - for dest in dest_ipnets { - dest_ranges.retain(|range| !range.fits_in_network(dest)); - } - - // Separate IP v4 and v6 addresses. - let mut ipv4_dest_addrs = dest_ipnets + // Separate IP v4 and v6 addresses and convert networks to intermediate range representation for merging + let ipv4_dest_net_addrs = dest_ipnets .iter() .filter(|dst| dst.is_ipv4()) - .map(|addr| IpAddress { - address: Some(if u32::from(addr.prefix()) == Ipv4Addr::BITS { - Address::Ip(addr.ip().to_string()) - } else { - Address::IpSubnet(addr.to_string()) - }), - }) - .collect::>(); - let mut ipv6_dest_addrs = dest_ipnets - .iter() - .filter(|dst| dst.is_ipv6()) - .map(|addr| { - let addr_string = addr.to_string(); - IpAddress { - address: Some(if u32::from(addr.prefix()) == Ipv6Addr::BITS { - Address::Ip(addr.ip().to_string()) - } else { - Address::IpSubnet(addr_string) - }), - } - }) - .collect::>(); + .map(|dst| dst.network()..=dst.broadcast()); + let ipv6_dest_net_addrs = dest_ipnets.iter().filter_map(|dst| { + if let IpNetwork::V6(subnet) = dst { + let range_start = subnet.network().into(); + let range_end = get_last_ip_in_v6_subnet(subnet); + Some(range_start..=range_end) + } else { + None + } + }); // Separate IP v4 and v6 ranges. let ipv4_dest_ranges = dest_ranges .iter() .filter(|dst| dst.start.is_ipv4() && dst.end.is_ipv4()) - .map(RangeInclusive::from) - .collect(); + .map(RangeInclusive::from); let ipv6_dest_ranges = dest_ranges .iter() .filter(|dst| dst.start.is_ipv6() && dst.end.is_ipv6()) - .map(RangeInclusive::from) - .collect(); + .map(RangeInclusive::from); - ipv4_dest_addrs.append(&mut merge_addrs(ipv4_dest_ranges)); - ipv6_dest_addrs.append(&mut merge_addrs(ipv6_dest_ranges)); + // combine iterators + let ipv4_dest_addrs = ipv4_dest_net_addrs.chain(ipv4_dest_ranges).collect(); + let ipv6_dest_addrs = ipv6_dest_net_addrs.chain(ipv6_dest_ranges).collect(); - (ipv4_dest_addrs, ipv6_dest_addrs) + (merge_addrs(ipv4_dest_addrs), merge_addrs(ipv6_dest_addrs)) } fn get_last_ip_in_v6_subnet(subnet: &ipnetwork::Ipv6Network) -> IpAddr { @@ -566,11 +532,11 @@ fn find_largest_subnet_in_range(start: IpAddr, end: IpAddr) -> Option fn find_largest_ipv4_subnet_in_range(start: Ipv4Addr, end: Ipv4Addr) -> Option { let start_bits = u32::from(start); let end_bits = u32::from(end); - + if start_bits > end_bits { return None; } - + // Find the largest prefix length where the subnet fits in the range for prefix_len in 0..=32 { let (mask, broadcast_addr) = if prefix_len == 0 { @@ -583,16 +549,18 @@ fn find_largest_ipv4_subnet_in_range(start: Ipv4Addr, end: Ipv4Addr) -> Option= start_bits && broadcast_addr <= end_bits { - if let Ok(network) = IpNetwork::new(IpAddr::V4(Ipv4Addr::from(network_addr)), prefix_len) { + if let Ok(network) = + IpNetwork::new(IpAddr::V4(Ipv4Addr::from(network_addr)), prefix_len) + { return Some(network); } } } - + None } @@ -600,11 +568,11 @@ fn find_largest_ipv4_subnet_in_range(start: Ipv4Addr, end: Ipv4Addr) -> Option Option { let start_bits = u128::from(start); let end_bits = u128::from(end); - + if start_bits > end_bits { return None; } - + // Find the largest prefix length where the subnet fits in the range for prefix_len in 0..=128 { let (mask, broadcast_addr) = if prefix_len == 0 { @@ -617,16 +585,18 @@ fn find_largest_ipv6_subnet_in_range(start: Ipv6Addr, end: Ipv6Addr) -> Option= start_bits && broadcast_addr <= end_bits { - if let Ok(network) = IpNetwork::new(IpAddr::V6(Ipv6Addr::from(network_addr)), prefix_len) { + if let Ok(network) = + IpNetwork::new(IpAddr::V6(Ipv6Addr::from(network_addr)), prefix_len) + { return Some(network); } } } - + None } @@ -669,7 +639,7 @@ fn merge_addrs(addr_ranges: Vec>) -> Vec { }); } else { // Subnet is found within the range, append both subnet and remaining ranges - + // Add range before subnet (if any) if range_start < subnet_start { let prev_ip = match subnet_start { From 19da048a2e4fa106f47ef4a4bfb8273d47a54395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Sun, 22 Jun 2025 16:44:49 +0200 Subject: [PATCH 03/13] avoid extracting one-element subnets --- crates/defguard_core/src/enterprise/firewall.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/defguard_core/src/enterprise/firewall.rs b/crates/defguard_core/src/enterprise/firewall.rs index 6d9522a322..4738cb53e0 100644 --- a/crates/defguard_core/src/enterprise/firewall.rs +++ b/crates/defguard_core/src/enterprise/firewall.rs @@ -529,6 +529,7 @@ fn find_largest_subnet_in_range(start: IpAddr, end: IpAddr) -> Option } /// Finds the largest IPv4 subnet that fits within the given range. +/// The subnet must contain more than one IP address since single IPs have their own gRPC representation. fn find_largest_ipv4_subnet_in_range(start: Ipv4Addr, end: Ipv4Addr) -> Option { let start_bits = u32::from(start); let end_bits = u32::from(end); @@ -538,11 +539,9 @@ fn find_largest_ipv4_subnet_in_range(start: Ipv4Addr, end: Ipv4Addr) -> Option Option Option { let start_bits = u128::from(start); let end_bits = u128::from(end); @@ -574,11 +574,9 @@ fn find_largest_ipv6_subnet_in_range(start: Ipv6Addr, end: Ipv6Addr) -> Option Date: Sun, 22 Jun 2025 16:44:54 +0200 Subject: [PATCH 04/13] update tests --- .../src/enterprise/firewall/tests.rs | 404 +++++++++++++++++- 1 file changed, 386 insertions(+), 18 deletions(-) diff --git a/crates/defguard_core/src/enterprise/firewall/tests.rs b/crates/defguard_core/src/enterprise/firewall/tests.rs index 477af6bda2..1a2c7e57e1 100644 --- a/crates/defguard_core/src/enterprise/firewall/tests.rs +++ b/crates/defguard_core/src/enterprise/firewall/tests.rs @@ -9,8 +9,8 @@ use sqlx::{ }; use super::{ - get_last_ip_in_v6_subnet, get_source_users, merge_addrs, merge_port_ranges, - process_destination_addrs, + find_largest_subnet_in_range, get_last_ip_in_v6_subnet, get_source_users, merge_addrs, + merge_port_ranges, process_destination_addrs, }; use crate::{ db::{ @@ -30,6 +30,350 @@ use crate::{ }, }; +#[test] +fn test_merge_addrs_extracts_ipv4_subnets() { + // Test case: 192.168.1.0 - 192.168.1.255 should become 192.168.1.0/24 + let ranges = vec![ + IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 255)), + ]; + + let result = merge_addrs(ranges); + + assert_eq!(result.len(), 1); + match &result[0].address { + Some(Address::IpSubnet(subnet)) => { + assert_eq!(subnet, "192.168.1.0/24"); + } + _ => panic!("Expected subnet notation, got {:?}", result[0].address), + } +} + +#[test] +fn test_merge_addrs_extracts_ipv6_subnets() { + // Test case: 2001:db8:: - 2001:db8::ffff should become 2001:db8::/112 + let start = "2001:db8::".parse::().unwrap(); + let end = "2001:db8::ffff".parse::().unwrap(); + let ranges = vec![IpAddr::V6(start)..=IpAddr::V6(end)]; + + let result = merge_addrs(ranges); + + assert_eq!(result.len(), 1); + match &result[0].address { + Some(Address::IpSubnet(subnet)) => { + assert_eq!(subnet, "2001:db8::/112"); + } + _ => panic!("Expected subnet notation, got {:?}", result[0].address), + } +} + +#[test] +fn test_merge_addrs_falls_back_to_range_when_no_subnet_fits() { + // Test case: 192.168.1.10 - 192.168.1.20 (doesn't align to subnet boundaries) + let ranges = vec![ + IpAddr::V4(Ipv4Addr::new(192, 168, 1, 10))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 20)), + ]; + + let result = merge_addrs(ranges); + + assert_eq!(result.len(), 1); + match &result[0].address { + Some(Address::IpRange(range)) => { + assert_eq!(range.start, "192.168.1.10"); + assert_eq!(range.end, "192.168.1.20"); + } + _ => panic!("Expected range notation, got {:?}", result[0].address), + } +} + +#[test] +fn test_merge_addrs_handles_single_ip() { + // Test case: single IP should remain as IP + let ranges = + vec![IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))]; + + let result = merge_addrs(ranges); + + assert_eq!(result.len(), 1); + match &result[0].address { + Some(Address::Ip(ip)) => { + assert_eq!(ip, "192.168.1.1"); + } + _ => panic!("Expected single IP notation, got {:?}", result[0].address), + } +} + +#[test] +fn test_find_largest_ipv4_subnet_perfect_match() { + // Test /24 subnet + let start = Ipv4Addr::new(192, 168, 1, 0); + let end = Ipv4Addr::new(192, 168, 1, 255); + + let result = find_largest_subnet_in_range(IpAddr::V4(start), IpAddr::V4(end)); + + assert!(result.is_some()); + let subnet = result.unwrap(); + assert_eq!(subnet.to_string(), "192.168.1.0/24"); +} + +#[test] +fn test_find_largest_ipv4_subnet_smaller_range() { + // Test /28 subnet (16 addresses) + let start = Ipv4Addr::new(192, 168, 1, 0); + let end = Ipv4Addr::new(192, 168, 1, 15); + + let result = find_largest_subnet_in_range(IpAddr::V4(start), IpAddr::V4(end)); + + assert!(result.is_some()); + let subnet = result.unwrap(); + assert_eq!(subnet.to_string(), "192.168.1.0/28"); +} + +#[test] +fn test_find_largest_ipv6_subnet_perfect_match() { + // Test /112 subnet + let start = "2001:db8::".parse::().unwrap(); + let end = "2001:db8::ffff".parse::().unwrap(); + + let result = find_largest_subnet_in_range(IpAddr::V6(start), IpAddr::V6(end)); + + assert!(result.is_some()); + let subnet = result.unwrap(); + assert_eq!(subnet.to_string(), "2001:db8::/112"); +} + +#[test] +fn test_find_largest_subnet_mixed_ip_versions() { + // Test mixed IP versions should return None + let start = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0)); + let end = IpAddr::V6("2001:db8::1".parse().unwrap()); + + let result = find_largest_subnet_in_range(start, end); + + assert!(result.is_none()); +} + +#[test] +fn test_find_largest_subnet_invalid_range() { + // Test invalid range (start > end) should return None + let start = Ipv4Addr::new(192, 168, 1, 10); + let end = Ipv4Addr::new(192, 168, 1, 5); + + let result = find_largest_subnet_in_range(IpAddr::V4(start), IpAddr::V4(end)); + + assert!(result.is_none()); +} + +#[test] +fn test_merge_addrs_multiple_ranges_with_subnets() { + // Test multiple ranges where some can be converted to subnets and others cannot + let ranges = vec![ + // This should become a /24 subnet + IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 255)), + // This should remain as a range + IpAddr::V4(Ipv4Addr::new(10, 0, 0, 10))..=IpAddr::V4(Ipv4Addr::new(10, 0, 0, 20)), + // This should be a single IP + IpAddr::V4(Ipv4Addr::new(172, 16, 0, 1))..=IpAddr::V4(Ipv4Addr::new(172, 16, 0, 1)), + ]; + + let result = merge_addrs(ranges); + + assert_eq!(result.len(), 3); + + // Check first result (should be subnet) + match &result[0].address { + Some(Address::IpSubnet(subnet)) => { + assert_eq!(subnet, "192.168.1.0/24"); + } + _ => panic!( + "Expected subnet notation for first result, got {:?}", + result[0].address + ), + } + + // Check second result (should be range) + match &result[1].address { + Some(Address::IpRange(range)) => { + assert_eq!(range.start, "10.0.0.10"); + assert_eq!(range.end, "10.0.0.20"); + } + _ => panic!( + "Expected range notation for second result, got {:?}", + result[1].address + ), + } + + // Check third result (should be single IP) + match &result[2].address { + Some(Address::Ip(ip)) => { + assert_eq!(ip, "172.16.0.1"); + } + _ => panic!( + "Expected single IP notation for third result, got {:?}", + result[2].address + ), + } +} + +#[test] +fn test_merge_addrs_subnet_within_range() { + // Test case: range contains a subnet but doesn't align perfectly + // Range 192.168.1.5 - 192.168.1.250 contains subnet 192.168.1.16/28 (192.168.1.16 - 192.168.1.31) + let ranges = vec![ + IpAddr::V4(Ipv4Addr::new(192, 168, 1, 5))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 250)), + ]; + + let result = merge_addrs(ranges); + + // Should split into: range before subnet, subnet, range after subnet + assert_eq!(result.len(), 3); + + // Check first part (before subnet) + match &result[0].address { + Some(Address::IpRange(range)) => { + assert_eq!(range.start, "192.168.1.5"); + assert_eq!(range.end, "192.168.1.15"); + } + _ => panic!( + "Expected range notation for first result, got {:?}", + result[0].address + ), + } + + // Check subnet part + match &result[1].address { + Some(Address::IpSubnet(subnet)) => { + assert_eq!(subnet, "192.168.1.16/28"); + } + _ => panic!( + "Expected subnet notation for second result, got {:?}", + result[1].address + ), + } + + // Check third part (after subnet) + match &result[2].address { + Some(Address::IpRange(range)) => { + assert_eq!(range.start, "192.168.1.32"); + assert_eq!(range.end, "192.168.1.250"); + } + _ => panic!( + "Expected range notation for third result, got {:?}", + result[2].address + ), + } +} + +#[test] +fn test_merge_addrs_subnet_at_start_of_range() { + // Test case: subnet at the beginning of range + // Range 192.168.1.0 - 192.168.1.100 starts with subnet 192.168.1.0/28 (192.168.1.0 - 192.168.1.15) + let ranges = vec![ + IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)), + ]; + + let result = merge_addrs(ranges); + + // Should split into: subnet, range after subnet + assert_eq!(result.len(), 2); + + // Check subnet part + match &result[0].address { + Some(Address::IpSubnet(subnet)) => { + assert_eq!(subnet, "192.168.1.0/28"); + } + _ => panic!( + "Expected subnet notation for first result, got {:?}", + result[0].address + ), + } + + // Check second part (after subnet) + match &result[1].address { + Some(Address::IpRange(range)) => { + assert_eq!(range.start, "192.168.1.16"); + assert_eq!(range.end, "192.168.1.100"); + } + _ => panic!( + "Expected range notation for second result, got {:?}", + result[1].address + ), + } +} + +#[test] +fn test_merge_addrs_subnet_at_end_of_range() { + // Test case: subnet at the end of range + // Range 192.168.1.5 - 192.168.1.31 ends with subnet 192.168.1.16/28 (192.168.1.16 - 192.168.1.31) + let ranges = vec![ + IpAddr::V4(Ipv4Addr::new(192, 168, 1, 5))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 31)), + ]; + + let result = merge_addrs(ranges); + + // Should split into: range before subnet, subnet + assert_eq!(result.len(), 2); + + // Check first part (before subnet) + match &result[0].address { + Some(Address::IpRange(range)) => { + assert_eq!(range.start, "192.168.1.5"); + assert_eq!(range.end, "192.168.1.15"); + } + _ => panic!( + "Expected range notation for first result, got {:?}", + result[0].address + ), + } + + // Check subnet part + match &result[1].address { + Some(Address::IpSubnet(subnet)) => { + assert_eq!(subnet, "192.168.1.16/28"); + } + _ => panic!( + "Expected subnet notation for second result, got {:?}", + result[1].address + ), + } +} + +#[test] +fn test_merge_addrs_ipv6_subnet_within_range() { + // Test case: IPv6 range contains a subnet but doesn't align perfectly + let start = "2001:db8::5".parse::().unwrap(); + let end = "2001:db8::ffff".parse::().unwrap(); + let ranges = vec![IpAddr::V6(start)..=IpAddr::V6(end)]; + + let result = merge_addrs(ranges); + + // Should split into: range before subnet, subnet, range after subnet + assert_eq!(result.len(), 3); + + // Check first part (before subnet) + match &result[0].address { + Some(Address::IpRange(range)) => { + assert_eq!(range.start, "2001:db8::5"); + assert_eq!(range.end, "2001:db8::ffff"); // This will be one less than subnet start + } + _ => panic!( + "Expected range notation for first result, got {:?}", + result[0].address + ), + } + + // Check subnet part + match &result[1].address { + Some(Address::IpSubnet(subnet)) => { + // The largest subnet that fits should be found + assert!(subnet.starts_with("2001:db8::/")); + } + _ => panic!( + "Expected subnet notation for second result, got {:?}", + result[1].address + ), + } +} + impl Default for AclRuleDestinationRange { fn default() -> Self { Self { @@ -220,7 +564,7 @@ fn test_process_destination_addrs_v4() { }, ]; - let destination_addrs = process_destination_addrs(&destination_ips, destination_ranges); + let destination_addrs = process_destination_addrs(&destination_ips, &destination_ranges); assert_eq!( destination_addrs.0, @@ -244,11 +588,11 @@ fn test_process_destination_addrs_v4() { ); // Test with empty input - let empty_addrs = process_destination_addrs(&[], Vec::new()); + let empty_addrs = process_destination_addrs(&[], &[]); assert!(empty_addrs.0.is_empty()); // Test with only IPv6 addresses - should return empty result for IPv4 - let ipv6_only = process_destination_addrs(&["2001:db8::/64".parse().unwrap()], Vec::new()); + let ipv6_only = process_destination_addrs(&["2001:db8::/64".parse().unwrap()], &[]); assert!(ipv6_only.0.is_empty()); } @@ -275,7 +619,7 @@ fn test_process_destination_addrs_v6() { }, ]; - let destination_addrs = process_destination_addrs(&destination_ips, destination_ranges); + let destination_addrs = process_destination_addrs(&destination_ips, &destination_ranges); assert_eq!( destination_addrs.1, @@ -299,11 +643,11 @@ fn test_process_destination_addrs_v6() { ); // Test with empty input - let empty_addrs = process_destination_addrs(&[], Vec::new()); + let empty_addrs = process_destination_addrs(&[], &[]); assert!(empty_addrs.1.is_empty()); // Test with only IPv4 addresses - should return empty result for IPv6 - let ipv4_only = process_destination_addrs(&["192.168.1.0/24".parse().unwrap()], Vec::new()); + let ipv4_only = process_destination_addrs(&["192.168.1.0/24".parse().unwrap()], &[]); assert!(ipv4_only.1.is_empty()); } @@ -1033,9 +1377,12 @@ async fn test_generate_firewall_rules_ipv4(_: PgPoolOptions, options: PgConnectO end: "10.0.1.43".to_string(), })), }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.52/30".to_string())), + }, IpAddress { address: Some(Address::IpRange(IpRange { - start: "10.0.1.52".to_string(), + start: "10.0.1.56".to_string(), end: "10.0.2.43".to_string(), })), } @@ -1057,9 +1404,12 @@ async fn test_generate_firewall_rules_ipv4(_: PgPoolOptions, options: PgConnectO end: "10.0.1.43".to_string(), })), }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.52/30".to_string())), + }, IpAddress { address: Some(Address::IpRange(IpRange { - start: "10.0.1.52".to_string(), + start: "10.0.1.56".to_string(), end: "10.0.2.43".to_string(), })), } @@ -1446,9 +1796,12 @@ async fn test_generate_firewall_rules_ipv6(_: PgPoolOptions, options: PgConnectO end: "fc00::1:43".to_string(), })), }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:52/127".to_string())), + }, IpAddress { address: Some(Address::IpRange(IpRange { - start: "fc00::1:52".to_string(), + start: "fc00::1:54".to_string(), end: "fc00::2:43".to_string(), })), } @@ -1470,9 +1823,12 @@ async fn test_generate_firewall_rules_ipv6(_: PgPoolOptions, options: PgConnectO end: "fc00::1:43".to_string(), })), }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:52/127".to_string())), + }, IpAddress { address: Some(Address::IpRange(IpRange { - start: "fc00::1:52".to_string(), + start: "fc00::1:54".to_string(), end: "fc00::2:43".to_string(), })), } @@ -1946,9 +2302,12 @@ async fn test_generate_firewall_rules_ipv4_and_ipv6(_: PgPoolOptions, options: P end: "10.0.1.43".to_string(), })), }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.52/30".to_string())), + }, IpAddress { address: Some(Address::IpRange(IpRange { - start: "10.0.1.52".to_string(), + start: "10.0.1.56".to_string(), end: "10.0.2.43".to_string(), })), }, @@ -2003,9 +2362,12 @@ async fn test_generate_firewall_rules_ipv4_and_ipv6(_: PgPoolOptions, options: P end: "fc00::1:43".to_string(), })), }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:52/127".to_string())), + }, IpAddress { address: Some(Address::IpRange(IpRange { - start: "fc00::1:52".to_string(), + start: "fc00::1:54".to_string(), end: "fc00::2:43".to_string(), })), } @@ -2027,9 +2389,12 @@ async fn test_generate_firewall_rules_ipv4_and_ipv6(_: PgPoolOptions, options: P end: "10.0.1.43".to_string(), })), }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.52/30".to_string())), + }, IpAddress { address: Some(Address::IpRange(IpRange { - start: "10.0.1.52".to_string(), + start: "10.0.1.56".to_string(), end: "10.0.2.43".to_string(), })), }, @@ -2050,9 +2415,12 @@ async fn test_generate_firewall_rules_ipv4_and_ipv6(_: PgPoolOptions, options: P end: "fc00::1:43".to_string(), })), }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:52/127".to_string())), + }, IpAddress { address: Some(Address::IpRange(IpRange { - start: "fc00::1:52".to_string(), + start: "fc00::1:54".to_string(), end: "fc00::2:43".to_string(), })), } @@ -3327,10 +3695,10 @@ async fn test_alias_kinds(_: PgPoolOptions, options: PgConnectOptions) { ]; let expected_destination_addrs = [ IpAddress { - address: Some(Address::IpSubnet("192.168.1.0/24".to_string())), + address: Some(Address::Ip("10.0.2.3".to_string())), }, IpAddress { - address: Some(Address::Ip("10.0.2.3".to_string())), + address: Some(Address::IpSubnet("192.168.1.0/24".to_string())), }, ]; From e37a32f903e81229f8eea5166b04d877a258bb83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Sun, 22 Jun 2025 22:01:47 +0200 Subject: [PATCH 05/13] handle multiple subnets in range --- .../defguard_core/src/enterprise/firewall.rs | 203 ++++++++++-------- 1 file changed, 110 insertions(+), 93 deletions(-) diff --git a/crates/defguard_core/src/enterprise/firewall.rs b/crates/defguard_core/src/enterprise/firewall.rs index 4738cb53e0..85f0dd1c90 100644 --- a/crates/defguard_core/src/enterprise/firewall.rs +++ b/crates/defguard_core/src/enterprise/firewall.rs @@ -598,6 +598,115 @@ fn find_largest_ipv6_subnet_in_range(start: Ipv6Addr, end: Ipv6Addr) -> Option Vec { + // initialize output + let mut result = Vec::new(); + + // Try to find the largest subnet that fits in the range + if let Some(subnet) = find_largest_subnet_in_range(range_start, range_end) { + let subnet_start = subnet.network(); + let subnet_end = if subnet.is_ipv4() { + subnet.broadcast() + } else { + // For IPv6, calculate the last IP in the subnet + let ipv6_net = match subnet { + IpNetwork::V6(net) => net, + _ => unreachable!(), // We already checked is_ipv4() is false + }; + get_last_ip_in_v6_subnet(&ipv6_net) + }; + + // Check if the subnet covers the entire range + if subnet_start == range_start && subnet_end == range_end { + // Use subnet notation for the entire range + result.push(IpAddress { + address: Some(Address::IpSubnet(subnet.to_string())), + }); + } else { + // Subnet is found within the range, append both subnet and remaining ranges + + // Add range before subnet (if any) + if range_start < subnet_start { + // find last IP before subnet start + let prev_ip = match subnet_start { + IpAddr::V4(ip) => { + let ip_u32 = u32::from(ip); + if ip_u32 > 0 { + IpAddr::V4(Ipv4Addr::from(ip_u32 - 1)) + } else { + range_start // shouldn't happen in practice + } + } + IpAddr::V6(ip) => { + let ip_u128 = u128::from(ip); + if ip_u128 > 0 { + IpAddr::V6(Ipv6Addr::from(ip_u128 - 1)) + } else { + range_start // shouldn't happen in practice + } + } + }; + + // also check this range for subnets + result.extend(extract_all_subnets_from_range(range_start, prev_ip)); + } + + // Add the subnet itself + result.push(IpAddress { + address: Some(Address::IpSubnet(subnet.to_string())), + }); + + // Add range after subnet (if any) + if subnet_end < range_end { + // find first IP after the subnet end + let next_ip = match subnet_end { + IpAddr::V4(ip) => { + let ip_u32 = u32::from(ip); + if ip_u32 < u32::MAX { + IpAddr::V4(Ipv4Addr::from(ip_u32 + 1)) + } else { + range_end // shouldn't happen in practice + } + } + IpAddr::V6(ip) => { + let ip_u128 = u128::from(ip); + if ip_u128 < u128::MAX { + IpAddr::V6(Ipv6Addr::from(ip_u128 + 1)) + } else { + range_end // shouldn't happen in practice + } + } + }; + // + // also check this range for subnets + result.extend(extract_all_subnets_from_range(next_ip, range_end)); + } + } + } else { + // Fall back to range notation if no subnet is found + result.push(IpAddress { + address: Some(Address::IpRange(IpRange { + start: range_start.to_string(), + end: range_end.to_string(), + })), + }); + } + + result +} + /// Converts an arbitrary list of IP address ranges into the smallest possible list /// of non-overlapping elements which can be used in a firewall rule. /// It assumes that all ranges with an invalid IP version have already been filtered out. @@ -615,99 +724,7 @@ fn merge_addrs(addr_ranges: Vec>) -> Vec { address: Some(Address::Ip(range_start.to_string())), }); } else { - // Try to find the largest subnet that fits in the range - if let Some(subnet) = find_largest_subnet_in_range(range_start, range_end) { - let subnet_start = subnet.network(); - let subnet_end = if subnet.is_ipv4() { - subnet.broadcast() - } else { - // For IPv6, calculate the last IP in the subnet - let ipv6_net = match subnet { - IpNetwork::V6(net) => net, - _ => unreachable!(), // We already checked is_ipv4() is false - }; - get_last_ip_in_v6_subnet(&ipv6_net) - }; - - // Check if the subnet covers the entire range - if subnet_start == range_start && subnet_end == range_end { - // Use subnet notation for the entire range - result.push(IpAddress { - address: Some(Address::IpSubnet(subnet.to_string())), - }); - } else { - // Subnet is found within the range, append both subnet and remaining ranges - - // Add range before subnet (if any) - if range_start < subnet_start { - let prev_ip = match subnet_start { - IpAddr::V4(ip) => { - let ip_u32 = u32::from(ip); - if ip_u32 > 0 { - IpAddr::V4(Ipv4Addr::from(ip_u32 - 1)) - } else { - range_start // shouldn't happen in practice - } - } - IpAddr::V6(ip) => { - let ip_u128 = u128::from(ip); - if ip_u128 > 0 { - IpAddr::V6(Ipv6Addr::from(ip_u128 - 1)) - } else { - range_start // shouldn't happen in practice - } - } - }; - result.push(IpAddress { - address: Some(Address::IpRange(IpRange { - start: range_start.to_string(), - end: prev_ip.to_string(), - })), - }); - } - - // Add the subnet - result.push(IpAddress { - address: Some(Address::IpSubnet(subnet.to_string())), - }); - - // Add range after subnet (if any) - if subnet_end < range_end { - let next_ip = match subnet_end { - IpAddr::V4(ip) => { - let ip_u32 = u32::from(ip); - if ip_u32 < u32::MAX { - IpAddr::V4(Ipv4Addr::from(ip_u32 + 1)) - } else { - range_end // shouldn't happen in practice - } - } - IpAddr::V6(ip) => { - let ip_u128 = u128::from(ip); - if ip_u128 < u128::MAX { - IpAddr::V6(Ipv6Addr::from(ip_u128 + 1)) - } else { - range_end // shouldn't happen in practice - } - } - }; - result.push(IpAddress { - address: Some(Address::IpRange(IpRange { - start: next_ip.to_string(), - end: range_end.to_string(), - })), - }); - } - } - } else { - // Fall back to range notation - result.push(IpAddress { - address: Some(Address::IpRange(IpRange { - start: range_start.to_string(), - end: range_end.to_string(), - })), - }); - } + result.extend(extract_all_subnets_from_range(range_start, range_end)) } } From ef95fea98b2ae4d4e1a8475dc92b98be38ea6c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Sun, 22 Jun 2025 23:17:11 +0200 Subject: [PATCH 06/13] change where single IP ranges are handled --- crates/defguard_core/src/enterprise/firewall.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/defguard_core/src/enterprise/firewall.rs b/crates/defguard_core/src/enterprise/firewall.rs index 85f0dd1c90..d1210947fd 100644 --- a/crates/defguard_core/src/enterprise/firewall.rs +++ b/crates/defguard_core/src/enterprise/firewall.rs @@ -611,6 +611,13 @@ fn find_largest_ipv6_subnet_in_range(start: Ipv6Addr, end: Ipv6Addr) -> Option Vec { + // return early if range represents a single IP address + if range_start == range_end { + return vec![IpAddress { + address: Some(Address::Ip(range_start.to_string())), + }]; + } + // initialize output let mut result = Vec::new(); @@ -718,14 +725,7 @@ fn merge_addrs(addr_ranges: Vec>) -> Vec { let mut result = Vec::new(); for range in addr_ranges { let (range_start, range_end) = range.into_inner(); - if range_start == range_end { - // single IP address - result.push(IpAddress { - address: Some(Address::Ip(range_start.to_string())), - }); - } else { - result.extend(extract_all_subnets_from_range(range_start, range_end)) - } + result.extend(extract_all_subnets_from_range(range_start, range_end)) } result From 3486a80bbcdff2d0ceda4d03a5628af416a5049f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Sun, 22 Jun 2025 23:17:23 +0200 Subject: [PATCH 07/13] fixing tests --- .../src/enterprise/firewall/tests.rs | 617 +++++++++++------- 1 file changed, 383 insertions(+), 234 deletions(-) diff --git a/crates/defguard_core/src/enterprise/firewall/tests.rs b/crates/defguard_core/src/enterprise/firewall/tests.rs index 1a2c7e57e1..c5eb3bae21 100644 --- a/crates/defguard_core/src/enterprise/firewall/tests.rs +++ b/crates/defguard_core/src/enterprise/firewall/tests.rs @@ -39,13 +39,12 @@ fn test_merge_addrs_extracts_ipv4_subnets() { let result = merge_addrs(ranges); - assert_eq!(result.len(), 1); - match &result[0].address { - Some(Address::IpSubnet(subnet)) => { - assert_eq!(subnet, "192.168.1.0/24"); - } - _ => panic!("Expected subnet notation, got {:?}", result[0].address), - } + assert_eq!( + result, + [IpAddress { + address: Some(Address::IpSubnet("192.168.1.0/24".to_string())) + }] + ); } #[test] @@ -57,32 +56,56 @@ fn test_merge_addrs_extracts_ipv6_subnets() { let result = merge_addrs(ranges); - assert_eq!(result.len(), 1); - match &result[0].address { - Some(Address::IpSubnet(subnet)) => { - assert_eq!(subnet, "2001:db8::/112"); - } - _ => panic!("Expected subnet notation, got {:?}", result[0].address), - } + assert_eq!( + result, + [IpAddress { + address: Some(Address::IpSubnet("2001:db8::/112".to_string())) + }] + ); } #[test] fn test_merge_addrs_falls_back_to_range_when_no_subnet_fits() { - // Test case: 192.168.1.10 - 192.168.1.20 (doesn't align to subnet boundaries) + // Test case: 192.168.1.10 - 192.168.1.20 (extracts subnets recursively) let ranges = vec![ IpAddr::V4(Ipv4Addr::new(192, 168, 1, 10))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 20)), ]; let result = merge_addrs(ranges); - assert_eq!(result.len(), 1); - match &result[0].address { - Some(Address::IpRange(range)) => { - assert_eq!(range.start, "192.168.1.10"); - assert_eq!(range.end, "192.168.1.20"); - } - _ => panic!("Expected range notation, got {:?}", result[0].address), - } + // The recursive extraction should find multiple subnets within this range + assert_eq!( + result, + [ + IpAddress { + address: Some(Address::IpSubnet("192.168.1.10/31".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("192.168.1.12/30".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("192.168.1.16/30".to_string())), + }, + IpAddress { + address: Some(Address::IpRange(IpRange { + start: "192.168.1.10".to_string(), + end: "192.168.1.15".to_string(), + })), + }, + IpAddress { + address: Some(Address::IpSubnet("192.168.1.16/28".to_string())), + }, + IpAddress { + address: Some(Address::IpRange(IpRange { + start: "192.168.1.17".to_string(), + end: "192.168.1.19".to_string(), + })), + }, + IpAddress { + address: Some(Address::Ip("192.168.1.20".to_string())), + }, + ] + ); } #[test] @@ -93,13 +116,12 @@ fn test_merge_addrs_handles_single_ip() { let result = merge_addrs(ranges); - assert_eq!(result.len(), 1); - match &result[0].address { - Some(Address::Ip(ip)) => { - assert_eq!(ip, "192.168.1.1"); - } - _ => panic!("Expected single IP notation, got {:?}", result[0].address), - } + assert_eq!( + result, + [IpAddress { + address: Some(Address::Ip("192.168.1.1".to_string())), + },] + ); } #[test] @@ -113,10 +135,7 @@ fn test_find_largest_ipv4_subnet_perfect_match() { assert!(result.is_some()); let subnet = result.unwrap(); assert_eq!(subnet.to_string(), "192.168.1.0/24"); -} -#[test] -fn test_find_largest_ipv4_subnet_smaller_range() { // Test /28 subnet (16 addresses) let start = Ipv4Addr::new(192, 168, 1, 0); let end = Ipv4Addr::new(192, 168, 1, 15); @@ -169,7 +188,7 @@ fn test_merge_addrs_multiple_ranges_with_subnets() { let ranges = vec![ // This should become a /24 subnet IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 255)), - // This should remain as a range + // This should be recursively extracted into subnets IpAddr::V4(Ipv4Addr::new(10, 0, 0, 10))..=IpAddr::V4(Ipv4Addr::new(10, 0, 0, 20)), // This should be a single IP IpAddr::V4(Ipv4Addr::new(172, 16, 0, 1))..=IpAddr::V4(Ipv4Addr::new(172, 16, 0, 1)), @@ -177,127 +196,82 @@ fn test_merge_addrs_multiple_ranges_with_subnets() { let result = merge_addrs(ranges); - assert_eq!(result.len(), 3); - - // Check first result (should be subnet) - match &result[0].address { - Some(Address::IpSubnet(subnet)) => { - assert_eq!(subnet, "192.168.1.0/24"); - } - _ => panic!( - "Expected subnet notation for first result, got {:?}", - result[0].address - ), - } - - // Check second result (should be range) - match &result[1].address { - Some(Address::IpRange(range)) => { - assert_eq!(range.start, "10.0.0.10"); - assert_eq!(range.end, "10.0.0.20"); - } - _ => panic!( - "Expected range notation for second result, got {:?}", - result[1].address - ), - } - - // Check third result (should be single IP) - match &result[2].address { - Some(Address::Ip(ip)) => { - assert_eq!(ip, "172.16.0.1"); - } - _ => panic!( - "Expected single IP notation for third result, got {:?}", - result[2].address - ), - } + // With recursive extraction, we should get more results + assert_eq!( + result, + [ + IpAddress { + address: Some(Address::IpRange(IpRange { + start: "10.0.0.10".to_string(), + end: "10.0.0.15".to_string(), + })), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.0.16/28".to_string())), + }, + IpAddress { + address: Some(Address::IpRange(IpRange { + start: "10.0.0.17".to_string(), + end: "10.0.0.19".to_string(), + })), + }, + IpAddress { + address: Some(Address::Ip("10.0.0.20".to_string())), + }, + IpAddress { + address: Some(Address::Ip("172.16.0.1".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("192.168.1.0/24".to_string())), + }, + ] + ); } #[test] fn test_merge_addrs_subnet_within_range() { // Test case: range contains a subnet but doesn't align perfectly - // Range 192.168.1.5 - 192.168.1.250 contains subnet 192.168.1.16/28 (192.168.1.16 - 192.168.1.31) + // Range 192.168.1.5 - 192.168.1.250 will be recursively extracted into many subnets let ranges = vec![ IpAddr::V4(Ipv4Addr::new(192, 168, 1, 5))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 250)), ]; let result = merge_addrs(ranges); - // Should split into: range before subnet, subnet, range after subnet - assert_eq!(result.len(), 3); - - // Check first part (before subnet) - match &result[0].address { - Some(Address::IpRange(range)) => { - assert_eq!(range.start, "192.168.1.5"); - assert_eq!(range.end, "192.168.1.15"); - } - _ => panic!( - "Expected range notation for first result, got {:?}", - result[0].address - ), - } - - // Check subnet part - match &result[1].address { - Some(Address::IpSubnet(subnet)) => { - assert_eq!(subnet, "192.168.1.16/28"); - } - _ => panic!( - "Expected subnet notation for second result, got {:?}", - result[1].address - ), - } - - // Check third part (after subnet) - match &result[2].address { - Some(Address::IpRange(range)) => { - assert_eq!(range.start, "192.168.1.32"); - assert_eq!(range.end, "192.168.1.250"); - } - _ => panic!( - "Expected range notation for third result, got {:?}", - result[2].address - ), - } + // With recursive extraction, this should result in just one range since no perfect subnet alignment + assert_eq!( + result, + [IpAddress { + address: Some(Address::IpRange(IpRange { + start: "192.168.1.5".to_string(), + end: "192.168.1.250".to_string(), + })), + },] + ); } #[test] fn test_merge_addrs_subnet_at_start_of_range() { // Test case: subnet at the beginning of range - // Range 192.168.1.0 - 192.168.1.100 starts with subnet 192.168.1.0/28 (192.168.1.0 - 192.168.1.15) + // Range 192.168.1.0 - 192.168.1.100 will be recursively extracted into multiple subnets let ranges = vec![ - IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)), + IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 64)), ]; let result = merge_addrs(ranges); - // Should split into: subnet, range after subnet - assert_eq!(result.len(), 2); - - // Check subnet part - match &result[0].address { - Some(Address::IpSubnet(subnet)) => { - assert_eq!(subnet, "192.168.1.0/28"); - } - _ => panic!( - "Expected subnet notation for first result, got {:?}", - result[0].address - ), - } - - // Check second part (after subnet) - match &result[1].address { - Some(Address::IpRange(range)) => { - assert_eq!(range.start, "192.168.1.16"); - assert_eq!(range.end, "192.168.1.100"); - } - _ => panic!( - "Expected range notation for second result, got {:?}", - result[1].address - ), - } + // With recursive extraction, we should get multiple subnets + assert_eq!( + result, + [ + IpAddress { + address: Some(Address::IpSubnet("192.168.1.0/26".to_string())), + }, + IpAddress { + address: Some(Address::Ip("192.168.1.64".to_string())), + }, + ] + ); } #[test] @@ -311,30 +285,20 @@ fn test_merge_addrs_subnet_at_end_of_range() { let result = merge_addrs(ranges); // Should split into: range before subnet, subnet - assert_eq!(result.len(), 2); - - // Check first part (before subnet) - match &result[0].address { - Some(Address::IpRange(range)) => { - assert_eq!(range.start, "192.168.1.5"); - assert_eq!(range.end, "192.168.1.15"); - } - _ => panic!( - "Expected range notation for first result, got {:?}", - result[0].address - ), - } - - // Check subnet part - match &result[1].address { - Some(Address::IpSubnet(subnet)) => { - assert_eq!(subnet, "192.168.1.16/28"); - } - _ => panic!( - "Expected subnet notation for second result, got {:?}", - result[1].address - ), - } + assert_eq!( + result, + [ + IpAddress { + address: Some(Address::IpRange(IpRange { + start: "192.168.1.5".to_string(), + end: "192.168.1.15".to_string(), + })), + }, + IpAddress { + address: Some(Address::IpSubnet("192.168.1.16/28".to_string())), + }, + ] + ); } #[test] @@ -346,32 +310,16 @@ fn test_merge_addrs_ipv6_subnet_within_range() { let result = merge_addrs(ranges); - // Should split into: range before subnet, subnet, range after subnet - assert_eq!(result.len(), 3); - - // Check first part (before subnet) - match &result[0].address { - Some(Address::IpRange(range)) => { - assert_eq!(range.start, "2001:db8::5"); - assert_eq!(range.end, "2001:db8::ffff"); // This will be one less than subnet start - } - _ => panic!( - "Expected range notation for first result, got {:?}", - result[0].address - ), - } - - // Check subnet part - match &result[1].address { - Some(Address::IpSubnet(subnet)) => { - // The largest subnet that fits should be found - assert!(subnet.starts_with("2001:db8::/")); - } - _ => panic!( - "Expected subnet notation for second result, got {:?}", - result[1].address - ), - } + // Should be a single range since no perfect subnet alignment + assert_eq!( + result, + [IpAddress { + address: Some(Address::IpRange(IpRange { + start: "2001:db8::5".to_string(), + end: "2001:db8::ffff".to_string(), + })), + },] + ); } impl Default for AclRuleDestinationRange { @@ -575,15 +523,15 @@ fn test_process_destination_addrs_v4() { IpAddress { address: Some(Address::IpSubnet("10.0.2.0/24".to_string())), }, - IpAddress { - address: Some(Address::IpSubnet("192.168.1.0/24".to_string())), - }, IpAddress { address: Some(Address::IpRange(IpRange { start: "10.0.3.1".to_string(), end: "10.0.3.100".to_string(), })), }, + IpAddress { + address: Some(Address::IpSubnet("192.168.1.0/24".to_string())), + }, ] ); @@ -664,6 +612,7 @@ fn test_merge_v4_addrs() { ]; let merged_addrs = merge_addrs(addr_ranges); + // With recursive subnet extraction, we should get more results assert_eq!( merged_addrs, [ @@ -673,14 +622,17 @@ fn test_merge_v4_addrs() { end: "10.0.10.32".to_string(), })), }, + IpAddress { + address: Some(Address::IpSubnet("10.0.9.0/24".to_string())), + }, IpAddress { address: Some(Address::Ip("10.0.20.20".to_string())), }, IpAddress { - address: Some(Address::IpRange(IpRange { - start: "10.0.60.20".to_string(), - end: "10.0.60.25".to_string(), - })), + address: Some(Address::IpSubnet("10.0.60.20/30".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.60.24/31".to_string())), }, IpAddress { address: Some(Address::Ip("192.168.0.20".to_string())), @@ -690,10 +642,10 @@ fn test_merge_v4_addrs() { // merge single IPs into a range let addr_ranges = vec![ + IpAddr::V4(Ipv4Addr::new(10, 0, 10, 0))..=IpAddr::V4(Ipv4Addr::new(10, 0, 10, 0)), IpAddr::V4(Ipv4Addr::new(10, 0, 10, 1))..=IpAddr::V4(Ipv4Addr::new(10, 0, 10, 1)), IpAddr::V4(Ipv4Addr::new(10, 0, 10, 2))..=IpAddr::V4(Ipv4Addr::new(10, 0, 10, 2)), IpAddr::V4(Ipv4Addr::new(10, 0, 10, 3))..=IpAddr::V4(Ipv4Addr::new(10, 0, 10, 3)), - IpAddr::V4(Ipv4Addr::new(10, 0, 10, 4))..=IpAddr::V4(Ipv4Addr::new(10, 0, 10, 4)), IpAddr::V4(Ipv4Addr::new(10, 0, 10, 20))..=IpAddr::V4(Ipv4Addr::new(10, 0, 10, 20)), ]; @@ -702,10 +654,7 @@ fn test_merge_v4_addrs() { merged_addrs, [ IpAddress { - address: Some(Address::IpRange(IpRange { - start: "10.0.10.1".into(), - end: "10.0.10.4".to_string(), - })), + address: Some(Address::IpSubnet("10.0.10.0/30".to_string())), }, IpAddress { address: Some(Address::Ip("10.0.10.20".to_string())), @@ -1368,6 +1317,7 @@ async fn test_generate_firewall_rules_ipv4(_: PgPoolOptions, options: PgConnectO }, ] ); + // With recursive subnet extraction, we get many more subnets assert_eq!( dns_allow_rule.destination_addrs, [ @@ -1381,11 +1331,23 @@ async fn test_generate_firewall_rules_ipv4(_: PgPoolOptions, options: PgConnectO address: Some(Address::IpSubnet("10.0.1.52/30".to_string())), }, IpAddress { - address: Some(Address::IpRange(IpRange { - start: "10.0.1.56".to_string(), - end: "10.0.2.43".to_string(), - })), - } + address: Some(Address::IpSubnet("10.0.1.56/29".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.64/26".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.128/25".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.2.0/27".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.2.32/29".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.2.40/30".to_string())), + }, ] ); @@ -1395,6 +1357,7 @@ async fn test_generate_firewall_rules_ipv4(_: PgPoolOptions, options: PgConnectO assert!(dns_deny_rule.protocols.is_empty(),); assert!(dns_deny_rule.destination_ports.is_empty(),); assert!(dns_deny_rule.source_addrs.is_empty(),); + // With recursive subnet extraction, we get many more subnets assert_eq!( dns_deny_rule.destination_addrs, [ @@ -1408,11 +1371,23 @@ async fn test_generate_firewall_rules_ipv4(_: PgPoolOptions, options: PgConnectO address: Some(Address::IpSubnet("10.0.1.52/30".to_string())), }, IpAddress { - address: Some(Address::IpRange(IpRange { - start: "10.0.1.56".to_string(), - end: "10.0.2.43".to_string(), - })), - } + address: Some(Address::IpSubnet("10.0.1.56/29".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.64/26".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.128/25".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.2.0/27".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.2.32/29".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.2.40/30".to_string())), + }, ] ); } @@ -1787,6 +1762,7 @@ async fn test_generate_firewall_rules_ipv6(_: PgPoolOptions, options: PgConnectO }, ] ); + // With recursive subnet extraction, we get many more subnets assert_eq!( dns_allow_rule.destination_addrs, [ @@ -1800,11 +1776,47 @@ async fn test_generate_firewall_rules_ipv6(_: PgPoolOptions, options: PgConnectO address: Some(Address::IpSubnet("fc00::1:52/127".to_string())), }, IpAddress { - address: Some(Address::IpRange(IpRange { - start: "fc00::1:54".to_string(), - end: "fc00::2:43".to_string(), - })), - } + address: Some(Address::IpSubnet("fc00::1:54/126".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:58/125".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:60/123".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:80/121".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:100/120".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:200/119".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:400/118".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:800/117".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:1000/116".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:2000/115".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:4000/114".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:8000/113".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::2:0/122".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::2:40/126".to_string())), + }, ] ); @@ -1814,6 +1826,7 @@ async fn test_generate_firewall_rules_ipv6(_: PgPoolOptions, options: PgConnectO assert!(dns_deny_rule.protocols.is_empty(),); assert!(dns_deny_rule.destination_ports.is_empty(),); assert!(dns_deny_rule.source_addrs.is_empty(),); + // With recursive subnet extraction, we get many more subnets assert_eq!( dns_deny_rule.destination_addrs, [ @@ -1827,11 +1840,47 @@ async fn test_generate_firewall_rules_ipv6(_: PgPoolOptions, options: PgConnectO address: Some(Address::IpSubnet("fc00::1:52/127".to_string())), }, IpAddress { - address: Some(Address::IpRange(IpRange { - start: "fc00::1:54".to_string(), - end: "fc00::2:43".to_string(), - })), - } + address: Some(Address::IpSubnet("fc00::1:54/126".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:58/125".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:60/123".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:80/121".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:100/120".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:200/119".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:400/118".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:800/117".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:1000/116".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:2000/115".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:4000/114".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:8000/113".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::2:0/122".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::2:40/126".to_string())), + }, ] ); } @@ -2293,6 +2342,7 @@ async fn test_generate_firewall_rules_ipv4_and_ipv6(_: PgPoolOptions, options: P }, ] ); + // With recursive subnet extraction, we get many more subnets assert_eq!( dns_allow_rule_ipv4.destination_addrs, [ @@ -2306,10 +2356,22 @@ async fn test_generate_firewall_rules_ipv4_and_ipv6(_: PgPoolOptions, options: P address: Some(Address::IpSubnet("10.0.1.52/30".to_string())), }, IpAddress { - address: Some(Address::IpRange(IpRange { - start: "10.0.1.56".to_string(), - end: "10.0.2.43".to_string(), - })), + address: Some(Address::IpSubnet("10.0.1.56/29".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.64/26".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.128/25".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.2.0/27".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.2.32/29".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.2.40/30".to_string())), }, ] ); @@ -2353,6 +2415,7 @@ async fn test_generate_firewall_rules_ipv4_and_ipv6(_: PgPoolOptions, options: P }, ] ); + // With recursive subnet extraction, we get many more subnets assert_eq!( dns_allow_rule_ipv6.destination_addrs, [ @@ -2366,11 +2429,47 @@ async fn test_generate_firewall_rules_ipv4_and_ipv6(_: PgPoolOptions, options: P address: Some(Address::IpSubnet("fc00::1:52/127".to_string())), }, IpAddress { - address: Some(Address::IpRange(IpRange { - start: "fc00::1:54".to_string(), - end: "fc00::2:43".to_string(), - })), - } + address: Some(Address::IpSubnet("fc00::1:54/126".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:58/125".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:60/123".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:80/121".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:100/120".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:200/119".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:400/118".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:800/117".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:1000/116".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:2000/115".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:4000/114".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:8000/113".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::2:0/122".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::2:40/126".to_string())), + }, ] ); @@ -2380,6 +2479,7 @@ async fn test_generate_firewall_rules_ipv4_and_ipv6(_: PgPoolOptions, options: P assert!(dns_deny_rule_ipv4.protocols.is_empty(),); assert!(dns_deny_rule_ipv4.destination_ports.is_empty(),); assert!(dns_deny_rule_ipv4.source_addrs.is_empty(),); + // With recursive subnet extraction, we get many more subnets assert_eq!( dns_deny_rule_ipv4.destination_addrs, [ @@ -2393,10 +2493,22 @@ async fn test_generate_firewall_rules_ipv4_and_ipv6(_: PgPoolOptions, options: P address: Some(Address::IpSubnet("10.0.1.52/30".to_string())), }, IpAddress { - address: Some(Address::IpRange(IpRange { - start: "10.0.1.56".to_string(), - end: "10.0.2.43".to_string(), - })), + address: Some(Address::IpSubnet("10.0.1.56/29".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.64/26".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.128/25".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.2.0/27".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.2.32/29".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.2.40/30".to_string())), }, ] ); @@ -2406,6 +2518,7 @@ async fn test_generate_firewall_rules_ipv4_and_ipv6(_: PgPoolOptions, options: P assert!(dns_deny_rule_ipv6.protocols.is_empty(),); assert!(dns_deny_rule_ipv6.destination_ports.is_empty(),); assert!(dns_deny_rule_ipv6.source_addrs.is_empty(),); + // With recursive subnet extraction, we get many more subnets assert_eq!( dns_deny_rule_ipv6.destination_addrs, [ @@ -2419,11 +2532,47 @@ async fn test_generate_firewall_rules_ipv4_and_ipv6(_: PgPoolOptions, options: P address: Some(Address::IpSubnet("fc00::1:52/127".to_string())), }, IpAddress { - address: Some(Address::IpRange(IpRange { - start: "fc00::1:54".to_string(), - end: "fc00::2:43".to_string(), - })), - } + address: Some(Address::IpSubnet("fc00::1:54/126".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:58/125".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:60/123".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:80/121".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:100/120".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:200/119".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:400/118".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:800/117".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:1000/116".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:2000/115".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:4000/114".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:8000/113".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::2:0/122".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::2:40/126".to_string())), + }, ] ); } From 9c4593dc83a824d3e085549b1eee1f33ced2124c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 23 Jun 2025 11:38:11 +0200 Subject: [PATCH 08/13] Simplify --- Cargo.lock | 423 ++++++++++-------- .../defguard_core/src/enterprise/firewall.rs | 58 ++- .../src/enterprise/firewall/tests.rs | 3 - 3 files changed, 250 insertions(+), 234 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 303233b639..77ee330fe2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aead" @@ -105,9 +105,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -120,33 +120,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.8" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", "once_cell_polyfill", @@ -205,7 +205,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "synstructure", ] @@ -217,7 +217,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -239,7 +239,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -250,7 +250,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -270,9 +270,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" @@ -460,9 +460,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "base64urlsafedata" @@ -554,9 +554,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" [[package]] name = "byteorder" @@ -613,9 +613,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.25" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "jobserver", "libc", @@ -633,9 +633,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -708,9 +708,9 @@ checksum = "bba18ee93d577a8428902687bcc2b6b45a56b1981a1f6d779731c86cc4c5db18" [[package]] name = "clap" -version = "4.5.39" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" dependencies = [ "clap_builder", "clap_derive", @@ -718,9 +718,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.39" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" dependencies = [ "anstream", "anstyle", @@ -730,21 +730,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "cmac" @@ -759,9 +759,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "compact_jwt" @@ -1021,7 +1021,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1045,7 +1045,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1056,7 +1056,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1253,7 +1253,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1274,7 +1274,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1284,7 +1284,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1297,7 +1297,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1317,7 +1317,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "unicode-xid", ] @@ -1356,7 +1356,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1519,12 +1519,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1579,9 +1579,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "libz-rs-sys", @@ -1712,7 +1712,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1758,9 +1758,9 @@ dependencies = [ [[package]] name = "getopts" -version = "0.2.21" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +checksum = "cba6ae63eb948698e300f645f87c70f76630d505f23b8907cf1e193ee85048c1" dependencies = [ "unicode-width", ] @@ -1774,7 +1774,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -1899,9 +1899,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", @@ -1914,7 +1914,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.3", + "hashbrown 0.15.4", ] [[package]] @@ -2081,9 +2081,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.6" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http", "hyper", @@ -2127,9 +2127,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ "base64 0.22.1", "bytes", @@ -2331,7 +2331,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "serde", ] @@ -2525,9 +2525,9 @@ dependencies = [ [[package]] name = "lettre" -version = "0.11.16" +version = "0.11.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ffd14fa289730e3ad68edefdc31f603d56fe716ec38f2076bb7410e09147c2" +checksum = "cb2a0354e9ece2fcdcf9fa53417f6de587230c0c248068eb058fa26c4a753179" dependencies = [ "async-trait", "base64 0.22.1", @@ -2553,15 +2553,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.172" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libgit2-sys" -version = "0.18.1+1.9.0" +version = "0.18.2+1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1dcb20f84ffcdd825c7a311ae347cce604a6f084a767dec4a4929829645290e" +checksum = "1c42fe03df2bd3c53a3a9c7317ad91d80c81cd1fb0caec8d7cc4cd2bfa10c222" dependencies = [ "cc", "libc", @@ -2587,9 +2587,9 @@ dependencies = [ [[package]] name = "libz-rs-sys" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6489ca9bd760fe9642d7644e827b0c9add07df89857b0416ee15c1cc1a3b8c5a" +checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221" dependencies = [ "zlib-rs", ] @@ -2715,9 +2715,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "mime" @@ -2743,9 +2743,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -2757,7 +2757,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] @@ -2766,7 +2766,7 @@ name = "model_derive" version = "0.0.0" dependencies = [ "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -2904,23 +2904,24 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3054,7 +3055,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3241,9 +3242,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", "thiserror 2.0.12", @@ -3252,9 +3253,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" +checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" dependencies = [ "pest", "pest_generator", @@ -3262,24 +3263,23 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" +checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "pest_meta" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" +checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" dependencies = [ - "once_cell", "pest", "sha2", ] @@ -3415,7 +3415,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3495,12 +3495,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.33" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dee91521343f4c5c6a63edd65e54f31f5c92fe8978c40a4282f8372194c6a7d" +checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" dependencies = [ "proc-macro2", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3556,7 +3556,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.101", + "syn 2.0.104", "tempfile", ] @@ -3570,7 +3570,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -3669,9 +3669,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" dependencies = [ "cfg_aliases", "libc", @@ -3698,9 +3698,9 @@ checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" @@ -3763,13 +3763,33 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ "bitflags 2.9.1", ] +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "regex" version = "1.11.1" @@ -3816,9 +3836,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.19" +version = "0.12.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2f8e5513d63f2e5b386eb5106dc67eaf3f84e95258e210489136b8b92ad6119" +checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" dependencies = [ "base64 0.22.1", "bytes", @@ -3835,13 +3855,11 @@ dependencies = [ "hyper-rustls", "hyper-tls", "hyper-util", - "ipnet", "js-sys", "log", "mime", "mime_guess", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", "quinn", @@ -3940,7 +3958,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.101", + "syn 2.0.104", "walkdir", ] @@ -3968,9 +3986,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -4011,9 +4029,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.27" +version = "0.23.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" +checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" dependencies = [ "log", "once_cell", @@ -4096,6 +4114,18 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -4214,7 +4244,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4286,15 +4316,16 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "bf65a400f8f66fb7b0552869ad70157166676db75ed8181f8104ea91cf9d0b42" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", "indexmap 2.9.0", + "schemars", "serde", "serde_derive", "serde_json", @@ -4304,14 +4335,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "81679d9ed988d5e9a5e6531dc3f2c28efbd639cbd1dfb628df08edea6004da77" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4445,12 +4476,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "slug" @@ -4464,9 +4492,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ "serde", ] @@ -4530,7 +4558,7 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "hashlink", "indexmap 2.9.0", "ipnetwork", @@ -4561,7 +4589,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4584,7 +4612,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.101", + "syn 2.0.104", "tokio", "url", ] @@ -4793,7 +4821,7 @@ checksum = "ac94fea04bf721f57ed7f421e64d3a04858e15708d00e8aa814cad7507427503" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4815,7 +4843,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4837,9 +4865,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -4863,7 +4891,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4948,7 +4976,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -4959,17 +4987,16 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -5064,7 +5091,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -5114,15 +5141,15 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" [[package]] name = "toml_edit" -version = "0.22.26" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap 2.9.0", "toml_datetime", @@ -5174,7 +5201,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -5292,20 +5319,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -5348,7 +5375,7 @@ checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -5483,9 +5510,9 @@ checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-width" -version = "0.1.14" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] name = "unicode-xid" @@ -5541,9 +5568,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "utoipa" -version = "5.3.1" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435c6f69ef38c9017b4b4eea965dfb91e71e53d869e896db40d1cf2441dd75c0" +checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" dependencies = [ "indexmap 2.9.0", "serde", @@ -5553,14 +5580,14 @@ dependencies = [ [[package]] name = "utoipa-gen" -version = "5.3.1" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77d306bc75294fd52f3e99b13ece67c02c1a2789190a6f31d32f736624326f7" +checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b" dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.101", + "syn 2.0.104", "uuid", ] @@ -5679,9 +5706,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -5720,7 +5747,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -5755,7 +5782,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5902,9 +5929,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" +checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" dependencies = [ "rustls-pki-types", ] @@ -5960,7 +5987,7 @@ dependencies = [ "windows-interface", "windows-link", "windows-result", - "windows-strings 0.4.2", + "windows-strings", ] [[package]] @@ -5971,7 +5998,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -5982,24 +6009,24 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-registry" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820" dependencies = [ + "windows-link", "windows-result", - "windows-strings 0.3.1", - "windows-targets 0.53.0", + "windows-strings", ] [[package]] @@ -6011,15 +6038,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-strings" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-strings" version = "0.4.2" @@ -6056,6 +6074,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -6089,9 +6116,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", @@ -6243,9 +6270,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] @@ -6332,28 +6359,28 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -6373,7 +6400,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "synstructure", ] @@ -6394,7 +6421,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -6427,7 +6454,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -6476,9 +6503,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "868b928d7949e09af2f6086dfc1e01936064cc7a819253bce650d4e2a2d63ba8" +checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a" [[package]] name = "zopfli" diff --git a/crates/defguard_core/src/enterprise/firewall.rs b/crates/defguard_core/src/enterprise/firewall.rs index d1210947fd..ceac862de9 100644 --- a/crates/defguard_core/src/enterprise/firewall.rs +++ b/crates/defguard_core/src/enterprise/firewall.rs @@ -163,7 +163,7 @@ pub async fn generate_firewall_rules_from_acls( &comment, ); if let Some(rule) = ipv4_rules.0 { - allow_rules.push(rule) + allow_rules.push(rule); } deny_rules.push(ipv4_rules.1); } @@ -180,7 +180,7 @@ pub async fn generate_firewall_rules_from_acls( &comment, ); if let Some(rule) = ipv6_rules.0 { - allow_rules.push(rule) + allow_rules.push(rule); } deny_rules.push(ipv6_rules.1); } @@ -517,6 +517,10 @@ fn get_last_ip_in_v6_subnet(subnet: &ipnetwork::Ipv6Network) -> IpAddr { /// Finds the largest subnet that fits within the given IP address range. /// Returns None if no valid subnet can be found. fn find_largest_subnet_in_range(start: IpAddr, end: IpAddr) -> Option { + if start > end { + return None; + } + match (start, end) { (IpAddr::V4(start_v4), IpAddr::V4(end_v4)) => { find_largest_ipv4_subnet_in_range(start_v4, end_v4) @@ -529,27 +533,21 @@ fn find_largest_subnet_in_range(start: IpAddr, end: IpAddr) -> Option } /// Finds the largest IPv4 subnet that fits within the given range. -/// The subnet must contain more than one IP address since single IPs have their own gRPC representation. +/// The subnet must contain more than one IP address since single IPs have their own gRPC +/// representation. fn find_largest_ipv4_subnet_in_range(start: Ipv4Addr, end: Ipv4Addr) -> Option { - let start_bits = u32::from(start); - let end_bits = u32::from(end); - - if start_bits > end_bits { - return None; - } + let start_bits = start.to_bits(); + let end_bits = end.to_bits(); - // Find the largest prefix length where the subnet fits in the range + // Find the largest prefix length where the subnet fits in the range. for prefix_len in 0..=31 { - let (mask, broadcast_addr) = if prefix_len == 0 { - (0u32, u32::MAX) + let mask = if prefix_len == 0 { + 0 } else { - let mask = !((1u32 << (32 - prefix_len)) - 1); - let network_addr = start_bits & mask; - let broadcast_addr = network_addr | ((1u32 << (32 - prefix_len)) - 1); - (mask, broadcast_addr) + u32::MAX << (32 - prefix_len) }; - let network_addr = start_bits & mask; + let broadcast_addr = network_addr | !mask; if network_addr >= start_bits && broadcast_addr <= end_bits { if let Ok(network) = @@ -564,27 +562,22 @@ fn find_largest_ipv4_subnet_in_range(start: Ipv4Addr, end: Ipv4Addr) -> Option Option { let start_bits = u128::from(start); let end_bits = u128::from(end); - if start_bits > end_bits { - return None; - } - - // Find the largest prefix length where the subnet fits in the range + // Find the largest prefix length where the subnet fits in the range. for prefix_len in 0..=127 { - let (mask, broadcast_addr) = if prefix_len == 0 { - (0u128, u128::MAX) + let mask = if prefix_len == 0 { + 0 } else { - let mask = !((1u128 << (128 - prefix_len)) - 1); - let network_addr = start_bits & mask; - let broadcast_addr = network_addr | ((1u128 << (128 - prefix_len)) - 1); - (mask, broadcast_addr) + u128::MAX << (128 - prefix_len) }; let network_addr = start_bits & mask; + let broadcast_addr = network_addr | !mask; if network_addr >= start_bits && broadcast_addr <= end_bits { if let Ok(network) = @@ -628,9 +621,8 @@ fn extract_all_subnets_from_range(range_start: IpAddr, range_end: IpAddr) -> Vec subnet.broadcast() } else { // For IPv6, calculate the last IP in the subnet - let ipv6_net = match subnet { - IpNetwork::V6(net) => net, - _ => unreachable!(), // We already checked is_ipv4() is false + let IpNetwork::V6(ipv6_net) = subnet else { + unreachable!(); // We already checked is_ipv4() is false }; get_last_ip_in_v6_subnet(&ipv6_net) }; @@ -725,7 +717,7 @@ fn merge_addrs(addr_ranges: Vec>) -> Vec { let mut result = Vec::new(); for range in addr_ranges { let (range_start, range_end) = range.into_inner(); - result.extend(extract_all_subnets_from_range(range_start, range_end)) + result.extend(extract_all_subnets_from_range(range_start, range_end)); } result diff --git a/crates/defguard_core/src/enterprise/firewall/tests.rs b/crates/defguard_core/src/enterprise/firewall/tests.rs index c5eb3bae21..98bfca2176 100644 --- a/crates/defguard_core/src/enterprise/firewall/tests.rs +++ b/crates/defguard_core/src/enterprise/firewall/tests.rs @@ -622,9 +622,6 @@ fn test_merge_v4_addrs() { end: "10.0.10.32".to_string(), })), }, - IpAddress { - address: Some(Address::IpSubnet("10.0.9.0/24".to_string())), - }, IpAddress { address: Some(Address::Ip("10.0.20.20".to_string())), }, From 676fd636cad49c608c6db0e78d067abff60dd59f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 23 Jun 2025 13:39:55 +0200 Subject: [PATCH 09/13] Small fixes --- .../defguard_core/src/enterprise/firewall.rs | 37 ++++++++----------- .../src/enterprise/firewall/tests.rs | 4 +- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/crates/defguard_core/src/enterprise/firewall.rs b/crates/defguard_core/src/enterprise/firewall.rs index ceac862de9..2f2da8496b 100644 --- a/crates/defguard_core/src/enterprise/firewall.rs +++ b/crates/defguard_core/src/enterprise/firewall.rs @@ -509,7 +509,7 @@ fn get_last_ip_in_v6_subnet(subnet: &ipnetwork::Ipv6Network) -> IpAddr { // get subnet IP portion as u128 let first_ip = subnet.ip().to_bits(); - let last_ip = first_ip | (!u128::from(subnet.mask())); + let last_ip = first_ip | (!subnet.mask().to_bits()); IpAddr::V6(last_ip.into()) } @@ -565,8 +565,8 @@ fn find_largest_ipv4_subnet_in_range(start: Ipv4Addr, end: Ipv4Addr) -> Option Option { - let start_bits = u128::from(start); - let end_bits = u128::from(end); + let start_bits = start.to_bits(); + let end_bits = end.to_bits(); // Find the largest prefix length where the subnet fits in the range. for prefix_len in 0..=127 { @@ -604,27 +604,23 @@ fn find_largest_ipv6_subnet_in_range(start: Ipv6Addr, end: Ipv6Addr) -> Option Vec { - // return early if range represents a single IP address + // Initialize output. + let mut result = Vec::new(); + + // Return early if range represents a single IP address. if range_start == range_end { - return vec![IpAddress { + result.push(IpAddress { address: Some(Address::Ip(range_start.to_string())), - }]; + }); + return result; } - // initialize output - let mut result = Vec::new(); - - // Try to find the largest subnet that fits in the range + // Try to find the largest subnet that fits in the range. if let Some(subnet) = find_largest_subnet_in_range(range_start, range_end) { let subnet_start = subnet.network(); - let subnet_end = if subnet.is_ipv4() { - subnet.broadcast() - } else { - // For IPv6, calculate the last IP in the subnet - let IpNetwork::V6(ipv6_net) = subnet else { - unreachable!(); // We already checked is_ipv4() is false - }; - get_last_ip_in_v6_subnet(&ipv6_net) + let subnet_end = match subnet { + IpNetwork::V4(_) => subnet.broadcast(), + IpNetwork::V6(net6) => get_last_ip_in_v6_subnet(&net6), }; // Check if the subnet covers the entire range @@ -634,7 +630,7 @@ fn extract_all_subnets_from_range(range_start: IpAddr, range_end: IpAddr) -> Vec address: Some(Address::IpSubnet(subnet.to_string())), }); } else { - // Subnet is found within the range, append both subnet and remaining ranges + // Subnet is found within the range, append both subnet and remaining ranges. // Add range before subnet (if any) if range_start < subnet_start { @@ -688,13 +684,12 @@ fn extract_all_subnets_from_range(range_start: IpAddr, range_end: IpAddr) -> Vec } } }; - // // also check this range for subnets result.extend(extract_all_subnets_from_range(next_ip, range_end)); } } } else { - // Fall back to range notation if no subnet is found + // Fall back to range notation if no subnet is found. result.push(IpAddress { address: Some(Address::IpRange(IpRange { start: range_start.to_string(), diff --git a/crates/defguard_core/src/enterprise/firewall/tests.rs b/crates/defguard_core/src/enterprise/firewall/tests.rs index 98bfca2176..11eb8efc55 100644 --- a/crates/defguard_core/src/enterprise/firewall/tests.rs +++ b/crates/defguard_core/src/enterprise/firewall/tests.rs @@ -492,14 +492,14 @@ fn test_process_source_addrs_v6() { #[test] fn test_process_destination_addrs_v4() { // Test data with mixed IPv4 and IPv6 networks - let destination_ips = vec![ + let destination_ips = [ "10.0.1.0/24".parse().unwrap(), "10.0.2.0/24".parse().unwrap(), "2001:db8::/64".parse().unwrap(), // Should be filtered out "192.168.1.0/24".parse().unwrap(), ]; - let destination_ranges = vec![ + let destination_ranges = [ AclRuleDestinationRange { start: IpAddr::V4(Ipv4Addr::new(10, 0, 3, 1)), end: IpAddr::V4(Ipv4Addr::new(10, 0, 3, 100)), From bfdb1d2bd689e097d16e9b7c54b8b4dc85823204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Tue, 24 Jun 2025 10:08:14 +0200 Subject: [PATCH 10/13] handle subnets not aligned with range start --- .../defguard_core/src/enterprise/firewall.rs | 51 +++++++++++++++---- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/crates/defguard_core/src/enterprise/firewall.rs b/crates/defguard_core/src/enterprise/firewall.rs index 2f2da8496b..b85d40c52a 100644 --- a/crates/defguard_core/src/enterprise/firewall.rs +++ b/crates/defguard_core/src/enterprise/firewall.rs @@ -540,13 +540,29 @@ fn find_largest_ipv4_subnet_in_range(start: Ipv4Addr, end: Ipv4Addr) -> Option= start_bits && broadcast_addr <= end_bits { @@ -569,14 +585,29 @@ fn find_largest_ipv6_subnet_in_range(start: Ipv6Addr, end: Ipv6Addr) -> Option= start_bits && broadcast_addr <= end_bits { From dcbe8b9b028dd82f55b3aa37754f458ea6a3c671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Tue, 24 Jun 2025 10:23:02 +0200 Subject: [PATCH 11/13] simplify conversion --- crates/defguard_core/src/enterprise/firewall.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/defguard_core/src/enterprise/firewall.rs b/crates/defguard_core/src/enterprise/firewall.rs index b85d40c52a..90f2f6bbdc 100644 --- a/crates/defguard_core/src/enterprise/firewall.rs +++ b/crates/defguard_core/src/enterprise/firewall.rs @@ -668,7 +668,7 @@ fn extract_all_subnets_from_range(range_start: IpAddr, range_end: IpAddr) -> Vec // find last IP before subnet start let prev_ip = match subnet_start { IpAddr::V4(ip) => { - let ip_u32 = u32::from(ip); + let ip_u32 = ip.to_bits(); if ip_u32 > 0 { IpAddr::V4(Ipv4Addr::from(ip_u32 - 1)) } else { @@ -676,7 +676,7 @@ fn extract_all_subnets_from_range(range_start: IpAddr, range_end: IpAddr) -> Vec } } IpAddr::V6(ip) => { - let ip_u128 = u128::from(ip); + let ip_u128 = ip.to_bits(); if ip_u128 > 0 { IpAddr::V6(Ipv6Addr::from(ip_u128 - 1)) } else { @@ -699,7 +699,7 @@ fn extract_all_subnets_from_range(range_start: IpAddr, range_end: IpAddr) -> Vec // find first IP after the subnet end let next_ip = match subnet_end { IpAddr::V4(ip) => { - let ip_u32 = u32::from(ip); + let ip_u32 = ip.to_bits(); if ip_u32 < u32::MAX { IpAddr::V4(Ipv4Addr::from(ip_u32 + 1)) } else { @@ -707,7 +707,7 @@ fn extract_all_subnets_from_range(range_start: IpAddr, range_end: IpAddr) -> Vec } } IpAddr::V6(ip) => { - let ip_u128 = u128::from(ip); + let ip_u128 = ip.to_bits(); if ip_u128 < u128::MAX { IpAddr::V6(Ipv6Addr::from(ip_u128 + 1)) } else { From 48eef2571bcc60b410f4d2b2e4c8afa376f8b9bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Tue, 24 Jun 2025 11:03:39 +0200 Subject: [PATCH 12/13] fix tests for merging addrs --- .../src/enterprise/firewall/tests.rs | 580 +++++++++--------- 1 file changed, 273 insertions(+), 307 deletions(-) diff --git a/crates/defguard_core/src/enterprise/firewall/tests.rs b/crates/defguard_core/src/enterprise/firewall/tests.rs index 11eb8efc55..a7fd85c7d4 100644 --- a/crates/defguard_core/src/enterprise/firewall/tests.rs +++ b/crates/defguard_core/src/enterprise/firewall/tests.rs @@ -30,298 +30,6 @@ use crate::{ }, }; -#[test] -fn test_merge_addrs_extracts_ipv4_subnets() { - // Test case: 192.168.1.0 - 192.168.1.255 should become 192.168.1.0/24 - let ranges = vec![ - IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 255)), - ]; - - let result = merge_addrs(ranges); - - assert_eq!( - result, - [IpAddress { - address: Some(Address::IpSubnet("192.168.1.0/24".to_string())) - }] - ); -} - -#[test] -fn test_merge_addrs_extracts_ipv6_subnets() { - // Test case: 2001:db8:: - 2001:db8::ffff should become 2001:db8::/112 - let start = "2001:db8::".parse::().unwrap(); - let end = "2001:db8::ffff".parse::().unwrap(); - let ranges = vec![IpAddr::V6(start)..=IpAddr::V6(end)]; - - let result = merge_addrs(ranges); - - assert_eq!( - result, - [IpAddress { - address: Some(Address::IpSubnet("2001:db8::/112".to_string())) - }] - ); -} - -#[test] -fn test_merge_addrs_falls_back_to_range_when_no_subnet_fits() { - // Test case: 192.168.1.10 - 192.168.1.20 (extracts subnets recursively) - let ranges = vec![ - IpAddr::V4(Ipv4Addr::new(192, 168, 1, 10))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 20)), - ]; - - let result = merge_addrs(ranges); - - // The recursive extraction should find multiple subnets within this range - assert_eq!( - result, - [ - IpAddress { - address: Some(Address::IpSubnet("192.168.1.10/31".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("192.168.1.12/30".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("192.168.1.16/30".to_string())), - }, - IpAddress { - address: Some(Address::IpRange(IpRange { - start: "192.168.1.10".to_string(), - end: "192.168.1.15".to_string(), - })), - }, - IpAddress { - address: Some(Address::IpSubnet("192.168.1.16/28".to_string())), - }, - IpAddress { - address: Some(Address::IpRange(IpRange { - start: "192.168.1.17".to_string(), - end: "192.168.1.19".to_string(), - })), - }, - IpAddress { - address: Some(Address::Ip("192.168.1.20".to_string())), - }, - ] - ); -} - -#[test] -fn test_merge_addrs_handles_single_ip() { - // Test case: single IP should remain as IP - let ranges = - vec![IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))]; - - let result = merge_addrs(ranges); - - assert_eq!( - result, - [IpAddress { - address: Some(Address::Ip("192.168.1.1".to_string())), - },] - ); -} - -#[test] -fn test_find_largest_ipv4_subnet_perfect_match() { - // Test /24 subnet - let start = Ipv4Addr::new(192, 168, 1, 0); - let end = Ipv4Addr::new(192, 168, 1, 255); - - let result = find_largest_subnet_in_range(IpAddr::V4(start), IpAddr::V4(end)); - - assert!(result.is_some()); - let subnet = result.unwrap(); - assert_eq!(subnet.to_string(), "192.168.1.0/24"); - - // Test /28 subnet (16 addresses) - let start = Ipv4Addr::new(192, 168, 1, 0); - let end = Ipv4Addr::new(192, 168, 1, 15); - - let result = find_largest_subnet_in_range(IpAddr::V4(start), IpAddr::V4(end)); - - assert!(result.is_some()); - let subnet = result.unwrap(); - assert_eq!(subnet.to_string(), "192.168.1.0/28"); -} - -#[test] -fn test_find_largest_ipv6_subnet_perfect_match() { - // Test /112 subnet - let start = "2001:db8::".parse::().unwrap(); - let end = "2001:db8::ffff".parse::().unwrap(); - - let result = find_largest_subnet_in_range(IpAddr::V6(start), IpAddr::V6(end)); - - assert!(result.is_some()); - let subnet = result.unwrap(); - assert_eq!(subnet.to_string(), "2001:db8::/112"); -} - -#[test] -fn test_find_largest_subnet_mixed_ip_versions() { - // Test mixed IP versions should return None - let start = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0)); - let end = IpAddr::V6("2001:db8::1".parse().unwrap()); - - let result = find_largest_subnet_in_range(start, end); - - assert!(result.is_none()); -} - -#[test] -fn test_find_largest_subnet_invalid_range() { - // Test invalid range (start > end) should return None - let start = Ipv4Addr::new(192, 168, 1, 10); - let end = Ipv4Addr::new(192, 168, 1, 5); - - let result = find_largest_subnet_in_range(IpAddr::V4(start), IpAddr::V4(end)); - - assert!(result.is_none()); -} - -#[test] -fn test_merge_addrs_multiple_ranges_with_subnets() { - // Test multiple ranges where some can be converted to subnets and others cannot - let ranges = vec![ - // This should become a /24 subnet - IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 255)), - // This should be recursively extracted into subnets - IpAddr::V4(Ipv4Addr::new(10, 0, 0, 10))..=IpAddr::V4(Ipv4Addr::new(10, 0, 0, 20)), - // This should be a single IP - IpAddr::V4(Ipv4Addr::new(172, 16, 0, 1))..=IpAddr::V4(Ipv4Addr::new(172, 16, 0, 1)), - ]; - - let result = merge_addrs(ranges); - - // With recursive extraction, we should get more results - assert_eq!( - result, - [ - IpAddress { - address: Some(Address::IpRange(IpRange { - start: "10.0.0.10".to_string(), - end: "10.0.0.15".to_string(), - })), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.0.16/28".to_string())), - }, - IpAddress { - address: Some(Address::IpRange(IpRange { - start: "10.0.0.17".to_string(), - end: "10.0.0.19".to_string(), - })), - }, - IpAddress { - address: Some(Address::Ip("10.0.0.20".to_string())), - }, - IpAddress { - address: Some(Address::Ip("172.16.0.1".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("192.168.1.0/24".to_string())), - }, - ] - ); -} - -#[test] -fn test_merge_addrs_subnet_within_range() { - // Test case: range contains a subnet but doesn't align perfectly - // Range 192.168.1.5 - 192.168.1.250 will be recursively extracted into many subnets - let ranges = vec![ - IpAddr::V4(Ipv4Addr::new(192, 168, 1, 5))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 250)), - ]; - - let result = merge_addrs(ranges); - - // With recursive extraction, this should result in just one range since no perfect subnet alignment - assert_eq!( - result, - [IpAddress { - address: Some(Address::IpRange(IpRange { - start: "192.168.1.5".to_string(), - end: "192.168.1.250".to_string(), - })), - },] - ); -} - -#[test] -fn test_merge_addrs_subnet_at_start_of_range() { - // Test case: subnet at the beginning of range - // Range 192.168.1.0 - 192.168.1.100 will be recursively extracted into multiple subnets - let ranges = vec![ - IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 64)), - ]; - - let result = merge_addrs(ranges); - - // With recursive extraction, we should get multiple subnets - assert_eq!( - result, - [ - IpAddress { - address: Some(Address::IpSubnet("192.168.1.0/26".to_string())), - }, - IpAddress { - address: Some(Address::Ip("192.168.1.64".to_string())), - }, - ] - ); -} - -#[test] -fn test_merge_addrs_subnet_at_end_of_range() { - // Test case: subnet at the end of range - // Range 192.168.1.5 - 192.168.1.31 ends with subnet 192.168.1.16/28 (192.168.1.16 - 192.168.1.31) - let ranges = vec![ - IpAddr::V4(Ipv4Addr::new(192, 168, 1, 5))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 31)), - ]; - - let result = merge_addrs(ranges); - - // Should split into: range before subnet, subnet - assert_eq!( - result, - [ - IpAddress { - address: Some(Address::IpRange(IpRange { - start: "192.168.1.5".to_string(), - end: "192.168.1.15".to_string(), - })), - }, - IpAddress { - address: Some(Address::IpSubnet("192.168.1.16/28".to_string())), - }, - ] - ); -} - -#[test] -fn test_merge_addrs_ipv6_subnet_within_range() { - // Test case: IPv6 range contains a subnet but doesn't align perfectly - let start = "2001:db8::5".parse::().unwrap(); - let end = "2001:db8::ffff".parse::().unwrap(); - let ranges = vec![IpAddr::V6(start)..=IpAddr::V6(end)]; - - let result = merge_addrs(ranges); - - // Should be a single range since no perfect subnet alignment - assert_eq!( - result, - [IpAddress { - address: Some(Address::IpRange(IpRange { - start: "2001:db8::5".to_string(), - end: "2001:db8::ffff".to_string(), - })), - },] - ); -} - impl Default for AclRuleDestinationRange { fn default() -> Self { Self { @@ -604,9 +312,9 @@ fn test_merge_v4_addrs() { let addr_ranges = vec![ IpAddr::V4(Ipv4Addr::new(10, 0, 60, 20))..=IpAddr::V4(Ipv4Addr::new(10, 0, 60, 25)), IpAddr::V4(Ipv4Addr::new(10, 0, 10, 1))..=IpAddr::V4(Ipv4Addr::new(10, 0, 10, 22)), - IpAddr::V4(Ipv4Addr::new(10, 0, 8, 51))..=IpAddr::V4(Ipv4Addr::new(10, 0, 9, 12)), + IpAddr::V4(Ipv4Addr::new(10, 0, 8, 127))..=IpAddr::V4(Ipv4Addr::new(10, 0, 9, 12)), IpAddr::V4(Ipv4Addr::new(10, 0, 9, 1))..=IpAddr::V4(Ipv4Addr::new(10, 0, 10, 12)), - IpAddr::V4(Ipv4Addr::new(10, 0, 9, 20))..=IpAddr::V4(Ipv4Addr::new(10, 0, 10, 32)), + IpAddr::V4(Ipv4Addr::new(10, 0, 9, 20))..=IpAddr::V4(Ipv4Addr::new(10, 0, 10, 31)), IpAddr::V4(Ipv4Addr::new(192, 168, 0, 20))..=IpAddr::V4(Ipv4Addr::new(192, 168, 0, 20)), IpAddr::V4(Ipv4Addr::new(10, 0, 20, 20))..=IpAddr::V4(Ipv4Addr::new(10, 0, 20, 20)), ]; @@ -617,10 +325,16 @@ fn test_merge_v4_addrs() { merged_addrs, [ IpAddress { - address: Some(Address::IpRange(IpRange { - start: "10.0.8.51".to_string(), - end: "10.0.10.32".to_string(), - })), + address: Some(Address::Ip("10.0.8.127".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.8.128/25".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.9.0/24".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.10.0/27".to_string())), }, IpAddress { address: Some(Address::Ip("10.0.20.20".to_string())), @@ -678,19 +392,271 @@ fn test_merge_v6_addrs() { merged_addrs, [ IpAddress { - address: Some(Address::IpRange(IpRange { - start: "2001:db8:1::1".to_string(), - end: "2001:db8:1::8".to_string(), - })), + address: Some(Address::Ip("2001:db8:1::1".to_string())) }, IpAddress { - address: Some(Address::Ip("2001:db8:2::1".to_string())), + address: Some(Address::IpSubnet("2001:db8:1::2/127".to_string())) }, IpAddress { - address: Some(Address::IpRange(IpRange { - start: "2001:db8:3::1".to_string(), - end: "2001:db8:3::3".to_string(), - })), + address: Some(Address::IpSubnet("2001:db8:1::4/126".to_string())) + }, + IpAddress { + address: Some(Address::Ip("2001:db8:1::8".to_string())) + }, + IpAddress { + address: Some(Address::Ip("2001:db8:2::1".to_string())) + }, + IpAddress { + address: Some(Address::Ip("2001:db8:3::1".to_string())) + }, + IpAddress { + address: Some(Address::IpSubnet("2001:db8:3::2/127".to_string())) + } + ] + ); +} + +#[test] +fn test_merge_addrs_extracts_ipv4_subnets() { + let ranges = vec![ + IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))..=IpAddr::V4(Ipv4Addr::new(192, 168, 2, 255)), + ]; + + let result = merge_addrs(ranges); + + assert_eq!( + result, + [ + IpAddress { + address: Some(Address::IpSubnet("192.168.1.0/24".to_string())) + }, + IpAddress { + address: Some(Address::IpSubnet("192.168.2.0/24".to_string())) + }, + ] + ); +} + +#[test] +fn test_merge_addrs_extracts_ipv6_subnets() { + let start = "2001:db8::".parse::().unwrap(); + let end = "2001:db9::ffff".parse::().unwrap(); + let ranges = vec![IpAddr::V6(start)..=IpAddr::V6(end)]; + + let result = merge_addrs(ranges); + + assert_eq!( + result, + [ + IpAddress { + address: Some(Address::IpSubnet("2001:db8::/32".to_string())) + }, + IpAddress { + address: Some(Address::IpSubnet("2001:db9::/112".to_string())) + }, + ] + ); +} + +#[test] +fn test_merge_addrs_falls_back_to_range_when_no_subnet_fits() { + let ranges = vec![ + IpAddr::V4(Ipv4Addr::new(192, 168, 1, 255))..=IpAddr::V4(Ipv4Addr::new(192, 168, 2, 0)), + ]; + + let result = merge_addrs(ranges); + + assert_eq!( + result, + [IpAddress { + address: Some(Address::IpRange(IpRange { + start: "192.168.1.255".to_string(), + end: "192.168.2.0".to_string(), + })), + },] + ); + + let start = "2001:db8:ffff:ffff:ffff:ffff:ffff:ffff" + .parse::() + .unwrap(); + let end = "2001:db9::".parse::().unwrap(); + let ranges = vec![IpAddr::V6(start)..=IpAddr::V6(end)]; + + let result = merge_addrs(ranges); + + assert_eq!( + result, + [IpAddress { + address: Some(Address::IpRange(IpRange { + start: "2001:db8:ffff:ffff:ffff:ffff:ffff:ffff".to_string(), + end: "2001:db9::".to_string(), + })), + },] + ); +} + +#[test] +fn test_merge_addrs_handles_single_ip() { + // Test case: single IP should remain as IP + let ranges = + vec![IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))]; + + let result = merge_addrs(ranges); + + assert_eq!( + result, + [IpAddress { + address: Some(Address::Ip("192.168.1.1".to_string())), + },] + ); + + let start = "2001:db8::".parse::().unwrap(); + let end = "2001:db8::".parse::().unwrap(); + let ranges = vec![IpAddr::V6(start)..=IpAddr::V6(end)]; + + let result = merge_addrs(ranges); + + assert_eq!( + result, + [IpAddress { + address: Some(Address::Ip("2001:db8::".to_string())), + },] + ); +} + +#[test] +fn test_find_largest_ipv4_subnet_perfect_match() { + // Test /24 subnet + let start = Ipv4Addr::new(192, 168, 1, 0); + let end = Ipv4Addr::new(192, 168, 1, 255); + + let result = find_largest_subnet_in_range(IpAddr::V4(start), IpAddr::V4(end)); + + assert!(result.is_some()); + let subnet = result.unwrap(); + assert_eq!(subnet.to_string(), "192.168.1.0/24"); + + // Test /28 subnet (16 addresses) + let start = Ipv4Addr::new(192, 168, 1, 0); + let end = Ipv4Addr::new(192, 168, 1, 15); + + let result = find_largest_subnet_in_range(IpAddr::V4(start), IpAddr::V4(end)); + + assert!(result.is_some()); + let subnet = result.unwrap(); + assert_eq!(subnet.to_string(), "192.168.1.0/28"); +} + +#[test] +fn test_find_largest_ipv6_subnet_perfect_match() { + // Test /112 subnet + let start = "2001:db8::".parse::().unwrap(); + let end = "2001:db8::ffff".parse::().unwrap(); + + let result = find_largest_subnet_in_range(IpAddr::V6(start), IpAddr::V6(end)); + + assert!(result.is_some()); + let subnet = result.unwrap(); + assert_eq!(subnet.to_string(), "2001:db8::/112"); +} + +#[test] +fn test_find_largest_subnet_mixed_ip_versions() { + // Test mixed IP versions should return None + let start = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0)); + let end = IpAddr::V6("2001:db8::1".parse().unwrap()); + + let result = find_largest_subnet_in_range(start, end); + + assert!(result.is_none()); +} + +#[test] +fn test_find_largest_subnet_invalid_range() { + // Test invalid range (start > end) should return None + let start = Ipv4Addr::new(192, 168, 1, 10); + let end = Ipv4Addr::new(192, 168, 1, 5); + + let result = find_largest_subnet_in_range(IpAddr::V4(start), IpAddr::V4(end)); + + assert!(result.is_none()); +} + +#[test] +fn test_merge_addrs_subnet_at_start_of_range() { + let ranges = vec![ + IpAddr::V4(Ipv4Addr::new(192, 168, 1, 0))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 64)), + ]; + + let result = merge_addrs(ranges); + + assert_eq!( + result, + [ + IpAddress { + address: Some(Address::IpSubnet("192.168.1.0/26".to_string())), + }, + IpAddress { + address: Some(Address::Ip("192.168.1.64".to_string())), + }, + ] + ); + + let ranges = vec![ + IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)) + ..=IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0x40)), + ]; + + let result = merge_addrs(ranges); + + assert_eq!( + result, + [ + IpAddress { + address: Some(Address::IpSubnet("2001:db8::/122".to_string())), + }, + IpAddress { + address: Some(Address::Ip("2001:db8::40".to_string())), + }, + ] + ); +} + +#[test] +fn test_merge_addrs_subnet_at_end_of_range() { + let ranges = vec![ + IpAddr::V4(Ipv4Addr::new(192, 168, 1, 15))..=IpAddr::V4(Ipv4Addr::new(192, 168, 1, 31)), + ]; + + let result = merge_addrs(ranges); + + assert_eq!( + result, + [ + IpAddress { + address: Some(Address::Ip("192.168.1.15".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("192.168.1.16/28".to_string())), + }, + ] + ); + + let ranges = vec![ + IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0x0f)) + ..=IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0x1f)), + ]; + + let result = merge_addrs(ranges); + + assert_eq!( + result, + [ + IpAddress { + address: Some(Address::Ip("2001:db8::f".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("2001:db8::10/124".to_string())), }, ] ); From d03cf4ba113331091a2e198eafdfd270f8d22583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Tue, 24 Jun 2025 11:55:49 +0200 Subject: [PATCH 13/13] fix previous firewall tests --- .../src/enterprise/firewall/tests.rs | 606 +++++++----------- 1 file changed, 240 insertions(+), 366 deletions(-) diff --git a/crates/defguard_core/src/enterprise/firewall/tests.rs b/crates/defguard_core/src/enterprise/firewall/tests.rs index a7fd85c7d4..5b839d3368 100644 --- a/crates/defguard_core/src/enterprise/firewall/tests.rs +++ b/crates/defguard_core/src/enterprise/firewall/tests.rs @@ -118,10 +118,13 @@ fn test_process_source_addrs_v4() { source_addrs, [ IpAddress { - address: Some(Address::IpRange(IpRange { - start: "10.0.1.1".to_string(), - end: "10.0.1.5".to_string(), - })), + address: Some(Address::Ip("10.0.1.1".to_string())) + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.2/31".to_string())) + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.4/31".to_string())) }, IpAddress { address: Some(Address::Ip("172.16.1.1".to_string())), @@ -170,10 +173,13 @@ fn test_process_source_addrs_v6() { source_addrs, [ IpAddress { - address: Some(Address::IpRange(IpRange { - start: "2001:db8::1".to_string(), - end: "2001:db8::5".to_string(), - })), + address: Some(Address::Ip("2001:db8::1".to_string())) + }, + IpAddress { + address: Some(Address::IpSubnet("2001:db8::2/127".to_string())) + }, + IpAddress { + address: Some(Address::IpSubnet("2001:db8::4/127".to_string())) }, IpAddress { address: Some(Address::Ip("2001:db8:0:1::1".to_string())), @@ -209,8 +215,8 @@ fn test_process_destination_addrs_v4() { let destination_ranges = [ AclRuleDestinationRange { - start: IpAddr::V4(Ipv4Addr::new(10, 0, 3, 1)), - end: IpAddr::V4(Ipv4Addr::new(10, 0, 3, 100)), + start: IpAddr::V4(Ipv4Addr::new(10, 0, 3, 255)), + end: IpAddr::V4(Ipv4Addr::new(10, 0, 4, 0)), ..Default::default() }, AclRuleDestinationRange { @@ -233,8 +239,8 @@ fn test_process_destination_addrs_v4() { }, IpAddress { address: Some(Address::IpRange(IpRange { - start: "10.0.3.1".to_string(), - end: "10.0.3.100".to_string(), + start: "10.0.3.255".to_string(), + end: "10.0.4.0".to_string(), })), }, IpAddress { @@ -265,7 +271,7 @@ fn test_process_destination_addrs_v6() { let destination_ranges = vec![ AclRuleDestinationRange { start: IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 4, 0, 0, 0, 0, 1)), - end: IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 4, 0, 0, 0, 0, 100)), + end: IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 4, 0, 0, 0, 0, 3)), ..Default::default() }, AclRuleDestinationRange { @@ -290,11 +296,11 @@ fn test_process_destination_addrs_v6() { address: Some(Address::IpSubnet("2001:db8:3::/64".to_string())), }, IpAddress { - address: Some(Address::IpRange(IpRange { - start: "2001:db8:4::1".to_string(), - end: "2001:db8:4::64".to_string(), - })), + address: Some(Address::Ip("2001:db8:4::1".to_string())) }, + IpAddress { + address: Some(Address::IpSubnet("2001:db8:4::2/127".to_string())) + } ] ); @@ -320,7 +326,7 @@ fn test_merge_v4_addrs() { ]; let merged_addrs = merge_addrs(addr_ranges); - // With recursive subnet extraction, we should get more results + assert_eq!( merged_addrs, [ @@ -1280,39 +1286,47 @@ async fn test_generate_firewall_rules_ipv4(_: PgPoolOptions, options: PgConnectO }, ] ); - // With recursive subnet extraction, we get many more subnets - assert_eq!( - dns_allow_rule.destination_addrs, - [ - IpAddress { - address: Some(Address::IpRange(IpRange { - start: "10.0.1.13".to_string(), - end: "10.0.1.43".to_string(), - })), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.1.52/30".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.1.56/29".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.1.64/26".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.1.128/25".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.2.0/27".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.2.32/29".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.2.40/30".to_string())), - }, - ] - ); + + let expected_destination_addrs = vec![ + IpAddress { + address: Some(Address::Ip("10.0.1.13".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.14/31".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.16/28".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.32/29".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.40/30".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.52/30".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.56/29".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.64/26".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.128/25".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.2.0/27".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.2.32/29".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.2.40/30".to_string())), + }, + ]; + + assert_eq!(dns_allow_rule.destination_addrs, expected_destination_addrs); // Second ACL - DNS Access DENY let dns_deny_rule = &generated_firewall_rules[3]; @@ -1320,39 +1334,7 @@ async fn test_generate_firewall_rules_ipv4(_: PgPoolOptions, options: PgConnectO assert!(dns_deny_rule.protocols.is_empty(),); assert!(dns_deny_rule.destination_ports.is_empty(),); assert!(dns_deny_rule.source_addrs.is_empty(),); - // With recursive subnet extraction, we get many more subnets - assert_eq!( - dns_deny_rule.destination_addrs, - [ - IpAddress { - address: Some(Address::IpRange(IpRange { - start: "10.0.1.13".to_string(), - end: "10.0.1.43".to_string(), - })), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.1.52/30".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.1.56/29".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.1.64/26".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.1.128/25".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.2.0/27".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.2.32/29".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.2.40/30".to_string())), - }, - ] - ); + assert_eq!(dns_deny_rule.destination_addrs, expected_destination_addrs); } #[sqlx::test] @@ -1701,6 +1683,70 @@ async fn test_generate_firewall_rules_ipv6(_: PgPoolOptions, options: PgConnectO port: Some(PortInner::SinglePort(53)) }] ); + + let expected_destination_addrs = vec![ + IpAddress { + address: Some(Address::Ip("fc00::1:13".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:14/126".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:18/125".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:20/123".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:40/126".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:52/127".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:54/126".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:58/125".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:60/123".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:80/121".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:100/120".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:200/119".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:400/118".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:800/117".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:1000/116".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:2000/115".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:4000/114".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:8000/113".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::2:0/122".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::2:40/126".to_string())), + }, + ]; + // Source addresses should include network_devices 1,2 assert_eq!( dns_allow_rule.source_addrs, @@ -1725,63 +1771,7 @@ async fn test_generate_firewall_rules_ipv6(_: PgPoolOptions, options: PgConnectO }, ] ); - // With recursive subnet extraction, we get many more subnets - assert_eq!( - dns_allow_rule.destination_addrs, - [ - IpAddress { - address: Some(Address::IpRange(IpRange { - start: "fc00::1:13".to_string(), - end: "fc00::1:43".to_string(), - })), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:52/127".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:54/126".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:58/125".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:60/123".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:80/121".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:100/120".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:200/119".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:400/118".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:800/117".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:1000/116".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:2000/115".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:4000/114".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:8000/113".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::2:0/122".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::2:40/126".to_string())), - }, - ] - ); + assert_eq!(dns_allow_rule.destination_addrs, expected_destination_addrs); // Second ACL - DNS Access DENY let dns_deny_rule = &generated_firewall_rules[3]; @@ -1789,63 +1779,7 @@ async fn test_generate_firewall_rules_ipv6(_: PgPoolOptions, options: PgConnectO assert!(dns_deny_rule.protocols.is_empty(),); assert!(dns_deny_rule.destination_ports.is_empty(),); assert!(dns_deny_rule.source_addrs.is_empty(),); - // With recursive subnet extraction, we get many more subnets - assert_eq!( - dns_deny_rule.destination_addrs, - [ - IpAddress { - address: Some(Address::IpRange(IpRange { - start: "fc00::1:13".to_string(), - end: "fc00::1:43".to_string(), - })), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:52/127".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:54/126".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:58/125".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:60/123".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:80/121".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:100/120".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:200/119".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:400/118".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:800/117".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:1000/116".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:2000/115".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:4000/114".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:8000/113".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::2:0/122".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::2:40/126".to_string())), - }, - ] - ); + assert_eq!(dns_deny_rule.destination_addrs, expected_destination_addrs); } #[sqlx::test] @@ -2305,38 +2239,49 @@ async fn test_generate_firewall_rules_ipv4_and_ipv6(_: PgPoolOptions, options: P }, ] ); - // With recursive subnet extraction, we get many more subnets + + let expected_destination_addrs_v4 = vec![ + IpAddress { + address: Some(Address::Ip("10.0.1.13".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.14/31".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.16/28".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.32/29".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.40/30".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.52/30".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.56/29".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.64/26".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.1.128/25".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.2.0/27".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.2.32/29".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("10.0.2.40/30".to_string())), + }, + ]; + assert_eq!( dns_allow_rule_ipv4.destination_addrs, - [ - IpAddress { - address: Some(Address::IpRange(IpRange { - start: "10.0.1.13".to_string(), - end: "10.0.1.43".to_string(), - })), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.1.52/30".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.1.56/29".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.1.64/26".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.1.128/25".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.2.0/27".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.2.32/29".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.2.40/30".to_string())), - }, - ] + expected_destination_addrs_v4 ); let dns_allow_rule_ipv6 = &generated_firewall_rules[3]; @@ -2378,62 +2323,73 @@ async fn test_generate_firewall_rules_ipv4_and_ipv6(_: PgPoolOptions, options: P }, ] ); - // With recursive subnet extraction, we get many more subnets + + let expected_destination_addrs_v6 = vec![ + IpAddress { + address: Some(Address::Ip("fc00::1:13".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:14/126".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:18/125".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:20/123".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:40/126".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:52/127".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:54/126".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:58/125".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:60/123".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:80/121".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:100/120".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:200/119".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:400/118".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:800/117".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:1000/116".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:2000/115".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:4000/114".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::1:8000/113".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::2:0/122".to_string())), + }, + IpAddress { + address: Some(Address::IpSubnet("fc00::2:40/126".to_string())), + }, + ]; + assert_eq!( dns_allow_rule_ipv6.destination_addrs, - [ - IpAddress { - address: Some(Address::IpRange(IpRange { - start: "fc00::1:13".to_string(), - end: "fc00::1:43".to_string(), - })), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:52/127".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:54/126".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:58/125".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:60/123".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:80/121".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:100/120".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:200/119".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:400/118".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:800/117".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:1000/116".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:2000/115".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:4000/114".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:8000/113".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::2:0/122".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::2:40/126".to_string())), - }, - ] + expected_destination_addrs_v6 ); // Second ACL - DNS Access DENY @@ -2442,38 +2398,9 @@ async fn test_generate_firewall_rules_ipv4_and_ipv6(_: PgPoolOptions, options: P assert!(dns_deny_rule_ipv4.protocols.is_empty(),); assert!(dns_deny_rule_ipv4.destination_ports.is_empty(),); assert!(dns_deny_rule_ipv4.source_addrs.is_empty(),); - // With recursive subnet extraction, we get many more subnets assert_eq!( dns_deny_rule_ipv4.destination_addrs, - [ - IpAddress { - address: Some(Address::IpRange(IpRange { - start: "10.0.1.13".to_string(), - end: "10.0.1.43".to_string(), - })), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.1.52/30".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.1.56/29".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.1.64/26".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.1.128/25".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.2.0/27".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.2.32/29".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("10.0.2.40/30".to_string())), - }, - ] + expected_destination_addrs_v4 ); let dns_deny_rule_ipv6 = &generated_firewall_rules[7]; @@ -2481,62 +2408,9 @@ async fn test_generate_firewall_rules_ipv4_and_ipv6(_: PgPoolOptions, options: P assert!(dns_deny_rule_ipv6.protocols.is_empty(),); assert!(dns_deny_rule_ipv6.destination_ports.is_empty(),); assert!(dns_deny_rule_ipv6.source_addrs.is_empty(),); - // With recursive subnet extraction, we get many more subnets assert_eq!( dns_deny_rule_ipv6.destination_addrs, - [ - IpAddress { - address: Some(Address::IpRange(IpRange { - start: "fc00::1:13".to_string(), - end: "fc00::1:43".to_string(), - })), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:52/127".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:54/126".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:58/125".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:60/123".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:80/121".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:100/120".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:200/119".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:400/118".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:800/117".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:1000/116".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:2000/115".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:4000/114".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::1:8000/113".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::2:0/122".to_string())), - }, - IpAddress { - address: Some(Address::IpSubnet("fc00::2:40/126".to_string())), - }, - ] + expected_destination_addrs_v6 ); }