Skip to content

Commit

Permalink
FIXES #635
Browse files Browse the repository at this point in the history
Configuration gains two optional items in the `[ip_ranges]` section:

* `unknown_ip_honors_ignore` will remove any unknown IPs that fall into the "unknown IP" ranges.
* `unknown_ip_honors_allow` will remove any unknown IPs that aren't explicitly in the allowed IP range.

Extends the unknown IP list to apply these policies. Both default to "true" if not specified.
  • Loading branch information
thebracket committed Jan 22, 2025
1 parent 0f1ff6e commit f6d00c6
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 1 deletion.
49 changes: 49 additions & 0 deletions src/rust/lqos_config/src/etc/v15/ip_ranges.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
pub allow_subnets: Vec<String>,
pub unknown_ip_honors_ignore: Option<bool>,
pub unknown_ip_honors_allow: Option<bool>,
}

impl Default for IpRanges {
Expand All @@ -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<bool> {
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<bool> {
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
}
}
25 changes: 24 additions & 1 deletion src/rust/lqosd/src/node_manager/local_api/unknown_ips.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -17,19 +19,39 @@ pub struct UnknownIp {
pub fn get_unknown_ips() -> Vec<UnknownIp> {
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
.raw_data
.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(),
Expand All @@ -38,6 +60,7 @@ pub fn get_unknown_ips() -> Vec<UnknownIp> {
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 <FIVE_MINUTES_IN_NANOS )
.sorted_by(|a, b| a.last_seen_nanos.cmp(&b.last_seen_nanos))
.collect()
Expand Down

0 comments on commit f6d00c6

Please sign in to comment.