diff --git a/src/rust/lqos_config/src/etc/v15/ip_ranges.rs b/src/rust/lqos_config/src/etc/v15/ip_ranges.rs index 4dbb40aa..4e48698f 100644 --- a/src/rust/lqos_config/src/etc/v15/ip_ranges.rs +++ b/src/rust/lqos_config/src/etc/v15/ip_ranges.rs @@ -1,9 +1,14 @@ +use std::net::{Ipv4Addr, Ipv6Addr}; +use ip_network::IpNetwork; +use ip_network_table::IpNetworkTable; use serde::{Serialize, Deserialize}; #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] pub struct IpRanges { pub ignore_subnets: Vec, pub allow_subnets: Vec, + pub unknown_ip_honors_ignore: Option, + pub unknown_ip_honors_allow: Option, } impl Default for IpRanges { @@ -16,6 +21,50 @@ impl Default for IpRanges { "100.64.0.0/10".to_string(), "192.168.0.0/16".to_string(), ], + unknown_ip_honors_ignore: Some(true), + unknown_ip_honors_allow: Some(true), } } +} + +impl IpRanges { + /// Maps the ignored IP ranges to an LPM table. + pub fn ignored_network_table(&self) -> IpNetworkTable { + let mut ignored = IpNetworkTable::new(); + for excluded_ip in self.ignore_subnets.iter() { + let split: Vec<_> = excluded_ip.split('/').collect(); + if split[0].contains(':') { + // It's IPv6 + let ip_network: Ipv6Addr = split[0].parse().unwrap(); + let ip = IpNetwork::new(ip_network, split[1].parse().unwrap()).unwrap(); + ignored.insert(ip, true); + } else { + // It's IPv4 + let ip_network: Ipv4Addr = split[0].parse().unwrap(); + let ip = IpNetwork::new(ip_network, split[1].parse().unwrap()).unwrap(); + ignored.insert(ip, true); + } + } + ignored + } + + /// Maps the allowed IP ranges to an LPM table. + pub fn allowed_network_table(&self) -> IpNetworkTable { + let mut allowed = IpNetworkTable::new(); + for allowed_ip in self.allow_subnets.iter() { + let split: Vec<_> = allowed_ip.split('/').collect(); + if split[0].contains(':') { + // It's IPv6 + let ip_network: Ipv6Addr = split[0].parse().unwrap(); + let ip = IpNetwork::new(ip_network, split[1].parse().unwrap()).unwrap(); + allowed.insert(ip, true); + } else { + // It's IPv4 + let ip_network: Ipv4Addr = split[0].parse().unwrap(); + let ip = IpNetwork::new(ip_network, split[1].parse().unwrap()).unwrap(); + allowed.insert(ip, true); + } + } + allowed + } } \ No newline at end of file diff --git a/src/rust/lqosd/src/node_manager/local_api/unknown_ips.rs b/src/rust/lqosd/src/node_manager/local_api/unknown_ips.rs index a370d830..41fd7cb1 100644 --- a/src/rust/lqosd/src/node_manager/local_api/unknown_ips.rs +++ b/src/rust/lqosd/src/node_manager/local_api/unknown_ips.rs @@ -1,6 +1,8 @@ use std::time::Duration; use itertools::Itertools; use serde::Serialize; +use tracing::warn; +use lqos_config::load_config; use lqos_utils::units::DownUpOrder; use lqos_utils::unix_time::time_since_boot; use crate::shaped_devices_tracker::SHAPED_DEVICES; @@ -17,6 +19,13 @@ pub struct UnknownIp { pub fn get_unknown_ips() -> Vec { const FIVE_MINUTES_IN_NANOS: u64 = 5 * 60 * 1_000_000_000; + let Ok(config) = load_config() else { + warn!("Failed to load config"); + return vec![]; + }; + let allowed_ips = config.ip_ranges.allowed_network_table(); + let ignored_ips = config.ip_ranges.ignored_network_table(); + let now = Duration::from(time_since_boot().unwrap()).as_nanos() as u64; let sd_reader = SHAPED_DEVICES.load(); THROUGHPUT_TRACKER @@ -24,12 +33,25 @@ pub fn get_unknown_ips() -> Vec { .lock() .unwrap() .iter() + // Remove all loopback devices .filter(|(k,_v)| !k.as_ip().is_loopback()) + // Remove any items that have a tc_handle of 0 .filter(|(_k,d)| d.tc_handle.as_u32() == 0) + // Remove any items that are matched by the shaped devices file .filter(|(k,_d)| { let ip = k.as_ip(); - !sd_reader.trie.longest_match(ip).is_some() + // If the IP is in the ignored list, ignore it + if config.ip_ranges.unknown_ip_honors_ignore.unwrap_or(true) && ignored_ips.longest_match(ip).is_some() { + return false; + } + // If the IP is not in the allowed list, ignore it + if config.ip_ranges.unknown_ip_honors_allow.unwrap_or(true) && allowed_ips.longest_match(ip).is_none() { + return false; + } + // If the IP is in shaped devices, ignore it + sd_reader.trie.longest_match(ip).is_none() }) + // Convert to UnknownIp .map(|(k,d)| { UnknownIp { ip: k.as_ip().to_string(), @@ -38,6 +60,7 @@ pub fn get_unknown_ips() -> Vec { current_bytes: d.bytes_per_second, } }) + // Remove any items that have not been seen in the last 5 minutes .filter(|u| u.last_seen_nanos