Skip to content

Commit f6d00c6

Browse files
committed
FIXES #635
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.
1 parent 0f1ff6e commit f6d00c6

File tree

2 files changed

+73
-1
lines changed

2 files changed

+73
-1
lines changed

src/rust/lqos_config/src/etc/v15/ip_ranges.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
use std::net::{Ipv4Addr, Ipv6Addr};
2+
use ip_network::IpNetwork;
3+
use ip_network_table::IpNetworkTable;
14
use serde::{Serialize, Deserialize};
25

36
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
47
pub struct IpRanges {
58
pub ignore_subnets: Vec<String>,
69
pub allow_subnets: Vec<String>,
10+
pub unknown_ip_honors_ignore: Option<bool>,
11+
pub unknown_ip_honors_allow: Option<bool>,
712
}
813

914
impl Default for IpRanges {
@@ -16,6 +21,50 @@ impl Default for IpRanges {
1621
"100.64.0.0/10".to_string(),
1722
"192.168.0.0/16".to_string(),
1823
],
24+
unknown_ip_honors_ignore: Some(true),
25+
unknown_ip_honors_allow: Some(true),
1926
}
2027
}
28+
}
29+
30+
impl IpRanges {
31+
/// Maps the ignored IP ranges to an LPM table.
32+
pub fn ignored_network_table(&self) -> IpNetworkTable<bool> {
33+
let mut ignored = IpNetworkTable::new();
34+
for excluded_ip in self.ignore_subnets.iter() {
35+
let split: Vec<_> = excluded_ip.split('/').collect();
36+
if split[0].contains(':') {
37+
// It's IPv6
38+
let ip_network: Ipv6Addr = split[0].parse().unwrap();
39+
let ip = IpNetwork::new(ip_network, split[1].parse().unwrap()).unwrap();
40+
ignored.insert(ip, true);
41+
} else {
42+
// It's IPv4
43+
let ip_network: Ipv4Addr = split[0].parse().unwrap();
44+
let ip = IpNetwork::new(ip_network, split[1].parse().unwrap()).unwrap();
45+
ignored.insert(ip, true);
46+
}
47+
}
48+
ignored
49+
}
50+
51+
/// Maps the allowed IP ranges to an LPM table.
52+
pub fn allowed_network_table(&self) -> IpNetworkTable<bool> {
53+
let mut allowed = IpNetworkTable::new();
54+
for allowed_ip in self.allow_subnets.iter() {
55+
let split: Vec<_> = allowed_ip.split('/').collect();
56+
if split[0].contains(':') {
57+
// It's IPv6
58+
let ip_network: Ipv6Addr = split[0].parse().unwrap();
59+
let ip = IpNetwork::new(ip_network, split[1].parse().unwrap()).unwrap();
60+
allowed.insert(ip, true);
61+
} else {
62+
// It's IPv4
63+
let ip_network: Ipv4Addr = split[0].parse().unwrap();
64+
let ip = IpNetwork::new(ip_network, split[1].parse().unwrap()).unwrap();
65+
allowed.insert(ip, true);
66+
}
67+
}
68+
allowed
69+
}
2170
}

src/rust/lqosd/src/node_manager/local_api/unknown_ips.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::time::Duration;
22
use itertools::Itertools;
33
use serde::Serialize;
4+
use tracing::warn;
5+
use lqos_config::load_config;
46
use lqos_utils::units::DownUpOrder;
57
use lqos_utils::unix_time::time_since_boot;
68
use crate::shaped_devices_tracker::SHAPED_DEVICES;
@@ -17,19 +19,39 @@ pub struct UnknownIp {
1719
pub fn get_unknown_ips() -> Vec<UnknownIp> {
1820
const FIVE_MINUTES_IN_NANOS: u64 = 5 * 60 * 1_000_000_000;
1921

22+
let Ok(config) = load_config() else {
23+
warn!("Failed to load config");
24+
return vec![];
25+
};
26+
let allowed_ips = config.ip_ranges.allowed_network_table();
27+
let ignored_ips = config.ip_ranges.ignored_network_table();
28+
2029
let now = Duration::from(time_since_boot().unwrap()).as_nanos() as u64;
2130
let sd_reader = SHAPED_DEVICES.load();
2231
THROUGHPUT_TRACKER
2332
.raw_data
2433
.lock()
2534
.unwrap()
2635
.iter()
36+
// Remove all loopback devices
2737
.filter(|(k,_v)| !k.as_ip().is_loopback())
38+
// Remove any items that have a tc_handle of 0
2839
.filter(|(_k,d)| d.tc_handle.as_u32() == 0)
40+
// Remove any items that are matched by the shaped devices file
2941
.filter(|(k,_d)| {
3042
let ip = k.as_ip();
31-
!sd_reader.trie.longest_match(ip).is_some()
43+
// If the IP is in the ignored list, ignore it
44+
if config.ip_ranges.unknown_ip_honors_ignore.unwrap_or(true) && ignored_ips.longest_match(ip).is_some() {
45+
return false;
46+
}
47+
// If the IP is not in the allowed list, ignore it
48+
if config.ip_ranges.unknown_ip_honors_allow.unwrap_or(true) && allowed_ips.longest_match(ip).is_none() {
49+
return false;
50+
}
51+
// If the IP is in shaped devices, ignore it
52+
sd_reader.trie.longest_match(ip).is_none()
3253
})
54+
// Convert to UnknownIp
3355
.map(|(k,d)| {
3456
UnknownIp {
3557
ip: k.as_ip().to_string(),
@@ -38,6 +60,7 @@ pub fn get_unknown_ips() -> Vec<UnknownIp> {
3860
current_bytes: d.bytes_per_second,
3961
}
4062
})
63+
// Remove any items that have not been seen in the last 5 minutes
4164
.filter(|u| u.last_seen_nanos <FIVE_MINUTES_IN_NANOS )
4265
.sorted_by(|a, b| a.last_seen_nanos.cmp(&b.last_seen_nanos))
4366
.collect()

0 commit comments

Comments
 (0)