Skip to content

Commit 9b9dcfc

Browse files
feat: support CIDR ranges in imported IP blacklists
1 parent 8dad721 commit 9b9dcfc

9 files changed

Lines changed: 184 additions & 9 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ All Sniffnet releases with the relative changes are documented in this file.
1313
- Greek ([#1175](https://github.com/GyulyVGC/sniffnet/pull/1175))
1414
- Fix unseen hosts and services not appearing dimmed in thumbnail mode (fixes [#1142](https://github.com/GyulyVGC/sniffnet/issues/1142))
1515
- Fix `X-AppImage-Version` field in Linux AppImage metadata to correctly reflect the app version ([#1174](https://github.com/GyulyVGC/sniffnet/pull/1174) — fixes [#1003](https://github.com/GyulyVGC/sniffnet/issues/1003))
16+
- Support CIDR ranges in imported IP blacklists.
1617

1718
## [1.5.0] - 2026-04-14
1819
- Show which apps and programs are generating network traffic ([#1056](https://github.com/GyulyVGC/sniffnet/pull/1056) — fixes [#170](https://github.com/GyulyVGC/sniffnet/issues/170))

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ ctrlc = { version = "3.5.2", features = ["termination"] }
4646
dns-lookup = "3.0.1"
4747
etherparse = "0.20.1"
4848
iced = { version = "0.14.0", features = ["tokio", "svg", "advanced", "lazy", "image"] }
49+
ipnet = "2.12.0"
4950
listeners = "0.5.1"
5051
maxminddb = "0.28.1"
5152
pcap = "2.4.0"

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ Not what you're looking for? Check out [alternative installation methods](https:
116116
- 📌 keep an eye on your network even when the application is **minimized**
117117
- ️🔎 search and **inspect** each of your network connections in real time
118118
- 🔉 set custom **notifications** to inform you when defined network events occur
119-
- 🚫 import custom **IP blacklists** to highlight potentially dangerous connections
119+
- 🚫 import custom **IP blacklists** with exact IPs or CIDR ranges to highlight potentially dangerous connections
120120
- 🎨 choose the **style** that fits you the most, including custom themes support
121121
- ...and more!
122122

@@ -195,4 +195,3 @@ Follow the <a href="https://sniffnet.net/news"><b>news</b></a> and Sniffnet soci
195195
</td>
196196
</tr>
197197
</table>
198-
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
209.186.20.0/22
2+
209.186.24.0/21
3+
209.186.224.0/21
4+
209.186.232.0/22
5+
209.186.236.0/24
6+
209.233.156.0/22
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1.2.3.0/24
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
8.8.8.8
2+
2001:db8::1
3+
1.2.3.0/24
4+
2001:db9::/32 # IPv6 test network
5+
5.6.7.0/24 ; SBL123456
6+
9.9.9.0/24 # inline hash comment
7+
# full-line hash comment
8+
; full-line semicolon comment
9+
invalid text that should be ignored

src/networking/manage_packets.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,9 +534,14 @@ mod tests {
534534
use crate::Service;
535535
use crate::networking::manage_packets::{
536536
get_service, get_traffic_direction, get_traffic_type, is_local_connection,
537-
mac_from_dec_to_hex,
537+
mac_from_dec_to_hex, modify_or_insert_in_map,
538538
};
539539
use crate::networking::types::address_port_pair::AddressPortPair;
540+
use crate::networking::types::arp_type::ArpType;
541+
use crate::networking::types::capture_context::{CaptureSource, MyPcapImport};
542+
use crate::networking::types::icmp_type::IcmpType;
543+
use crate::networking::types::info_traffic::InfoTraffic;
544+
use crate::networking::types::ip_blacklist::IpBlacklist;
540545
use crate::networking::types::service_query::ServiceQuery;
541546
use crate::networking::types::traffic_direction::TrafficDirection;
542547
use crate::networking::types::traffic_type::TrafficType;
@@ -1534,6 +1539,56 @@ mod tests {
15341539
}
15351540
}
15361541

1542+
#[tokio::test]
1543+
async fn test_cidr_blacklist_marks_remote_address_blacklisted() {
1544+
let blacklist =
1545+
IpBlacklist::from_file("resources/test/ip_blacklist_real_cidr_ranges.txt".to_string())
1546+
.await;
1547+
let mut info_traffic = InfoTraffic::default();
1548+
let capture_source = CaptureSource::File(MyPcapImport::new(String::new()));
1549+
let key = AddressPortPair::new(
1550+
IpAddr::from_str("192.168.1.10").unwrap(),
1551+
Some(50000),
1552+
IpAddr::from_str("209.186.21.7").unwrap(),
1553+
Some(443),
1554+
Protocol::TCP,
1555+
);
1556+
1557+
modify_or_insert_in_map(
1558+
&mut info_traffic,
1559+
&key,
1560+
&capture_source,
1561+
(None, None),
1562+
IcmpType::default(),
1563+
ArpType::default(),
1564+
100,
1565+
&blacklist,
1566+
);
1567+
1568+
assert!(info_traffic.map.get(&key).unwrap().is_blacklisted);
1569+
1570+
let key = AddressPortPair::new(
1571+
IpAddr::from_str("192.168.1.10").unwrap(),
1572+
Some(50001),
1573+
IpAddr::from_str("209.186.237.1").unwrap(),
1574+
Some(443),
1575+
Protocol::TCP,
1576+
);
1577+
1578+
modify_or_insert_in_map(
1579+
&mut info_traffic,
1580+
&key,
1581+
&capture_source,
1582+
(None, None),
1583+
IcmpType::default(),
1584+
ArpType::default(),
1585+
100,
1586+
&blacklist,
1587+
);
1588+
1589+
assert!(!info_traffic.map.get(&key).unwrap().is_blacklisted);
1590+
}
1591+
15371592
#[test]
15381593
fn test_get_service_unknown() {
15391594
let unknown_port_1 = Some(39332);

src/networking/types/ip_blacklist.rs

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ use std::collections::HashSet;
22
use std::net::IpAddr;
33
use std::sync::Arc;
44

5+
use ipnet::IpNet;
6+
57
#[derive(Clone, Default, Debug)]
68
pub struct IpBlacklist {
79
ips: Arc<HashSet<IpAddr>>,
10+
networks: Arc<Vec<IpNet>>,
811
is_loading: bool,
912
}
1013

@@ -13,24 +16,32 @@ impl IpBlacklist {
1316
let Ok(buf) = tokio::fs::read_to_string(&path).await else {
1417
return IpBlacklist::default();
1518
};
16-
let mut set = HashSet::new();
19+
let mut ips = HashSet::new();
20+
let mut networks = Vec::new();
1721
for line in buf.lines() {
18-
if let Ok(ip) = line.trim().parse::<IpAddr>() {
19-
set.insert(ip);
22+
let Some(line) = clean_blacklist_line(line) else {
23+
continue;
24+
};
25+
26+
if let Ok(ip) = line.parse::<IpAddr>() {
27+
ips.insert(ip);
28+
} else if let Ok(network) = line.parse::<IpNet>() {
29+
networks.push(network);
2030
}
2131
}
2232
IpBlacklist {
23-
ips: Arc::new(set),
33+
ips: Arc::new(ips),
34+
networks: Arc::new(networks),
2435
is_loading: false,
2536
}
2637
}
2738

2839
pub fn contains(&self, ip: &IpAddr) -> bool {
29-
self.ips.contains(ip)
40+
self.ips.contains(ip) || self.networks.iter().any(|network| network.contains(ip))
3041
}
3142

3243
pub fn is_invalid(&self) -> bool {
33-
self.ips.is_empty() && !self.is_loading
44+
self.ips.is_empty() && self.networks.is_empty() && !self.is_loading
3445
}
3546

3647
pub fn is_loading(&self) -> bool {
@@ -42,6 +53,20 @@ impl IpBlacklist {
4253
}
4354
}
4455

56+
fn clean_blacklist_line(line: &str) -> Option<&str> {
57+
let line = line.trim();
58+
59+
if line.is_empty() || line.starts_with('#') || line.starts_with(';') {
60+
return None;
61+
}
62+
63+
let line = line.split(';').next().unwrap_or(line);
64+
let line = line.split('#').next().unwrap_or(line);
65+
let line = line.trim();
66+
67+
if line.is_empty() { None } else { Some(line) }
68+
}
69+
4570
#[cfg(test)]
4671
mod tests {
4772
use super::*;
@@ -55,6 +80,7 @@ mod tests {
5580
assert!(!blacklist.is_invalid());
5681
assert!(!blacklist.is_loading());
5782
assert_eq!(blacklist.ips.len(), 4);
83+
assert_eq!(blacklist.networks.len(), 0);
5884

5985
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8))));
6086
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(1, 2, 3, 255))));
@@ -75,10 +101,86 @@ mod tests {
75101
assert!(blacklist.is_invalid());
76102
assert!(!blacklist.is_loading());
77103
assert_eq!(blacklist.ips.len(), 0);
104+
assert_eq!(blacklist.networks.len(), 0);
78105

79106
assert!(!blacklist.contains(&IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8))));
80107
assert!(!blacklist.contains(&IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))));
81108
assert!(!blacklist.contains(&"::123".parse::<IpAddr>().unwrap()));
82109
assert!(!blacklist.contains(&"::".parse::<IpAddr>().unwrap()));
83110
}
111+
112+
#[tokio::test]
113+
async fn test_ip_blacklist_valid_with_cidr() {
114+
let blacklist =
115+
IpBlacklist::from_file("resources/test/ip_blacklist_valid_with_cidr.txt".to_string())
116+
.await;
117+
118+
assert!(!blacklist.is_invalid());
119+
assert!(!blacklist.is_loading());
120+
assert_eq!(blacklist.ips.len(), 2);
121+
assert_eq!(blacklist.networks.len(), 4);
122+
123+
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8))));
124+
assert!(blacklist.contains(&"2001:db8::1".parse::<IpAddr>().unwrap()));
125+
assert!(!blacklist.contains(&IpAddr::V4(Ipv4Addr::new(8, 8, 8, 9))));
126+
assert!(!blacklist.contains(&"2001:db8::2".parse::<IpAddr>().unwrap()));
127+
128+
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(1, 2, 3, 1))));
129+
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(1, 2, 3, 255))));
130+
assert!(!blacklist.contains(&IpAddr::V4(Ipv4Addr::new(1, 2, 5, 1))));
131+
132+
assert!(blacklist.contains(&"2001:db9::1".parse::<IpAddr>().unwrap()));
133+
assert!(blacklist.contains(&"2001:db9:ffff::1".parse::<IpAddr>().unwrap()));
134+
assert!(!blacklist.contains(&"2001:dba::1".parse::<IpAddr>().unwrap()));
135+
136+
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(5, 6, 7, 10))));
137+
assert!(!blacklist.contains(&IpAddr::V4(Ipv4Addr::new(5, 6, 8, 10))));
138+
139+
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(9, 9, 9, 9))));
140+
assert!(!blacklist.contains(&IpAddr::V4(Ipv4Addr::new(9, 9, 10, 9))));
141+
}
142+
143+
#[tokio::test]
144+
async fn test_ip_blacklist_valid_with_cidr_only() {
145+
let blacklist =
146+
IpBlacklist::from_file("resources/test/ip_blacklist_valid_cidr_only.txt".to_string())
147+
.await;
148+
149+
assert!(!blacklist.is_invalid());
150+
assert!(!blacklist.is_loading());
151+
assert_eq!(blacklist.ips.len(), 0);
152+
assert_eq!(blacklist.networks.len(), 1);
153+
154+
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(1, 2, 3, 1))));
155+
assert!(!blacklist.contains(&IpAddr::V4(Ipv4Addr::new(1, 2, 4, 1))));
156+
}
157+
158+
#[tokio::test]
159+
async fn test_ip_blacklist_real_cidr_ranges() {
160+
let blacklist =
161+
IpBlacklist::from_file("resources/test/ip_blacklist_real_cidr_ranges.txt".to_string())
162+
.await;
163+
164+
assert!(!blacklist.is_invalid());
165+
assert_eq!(blacklist.ips.len(), 0);
166+
assert_eq!(blacklist.networks.len(), 6);
167+
168+
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(209, 186, 20, 0))));
169+
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(209, 186, 23, 255))));
170+
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(209, 186, 24, 0))));
171+
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(209, 186, 31, 255))));
172+
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(209, 186, 224, 0))));
173+
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(209, 186, 231, 255))));
174+
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(209, 186, 232, 0))));
175+
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(209, 186, 235, 255))));
176+
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(209, 186, 236, 0))));
177+
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(209, 186, 236, 255))));
178+
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(209, 233, 156, 0))));
179+
assert!(blacklist.contains(&IpAddr::V4(Ipv4Addr::new(209, 233, 159, 255))));
180+
181+
assert!(!blacklist.contains(&IpAddr::V4(Ipv4Addr::new(209, 186, 19, 255))));
182+
assert!(!blacklist.contains(&IpAddr::V4(Ipv4Addr::new(209, 186, 32, 0))));
183+
assert!(!blacklist.contains(&IpAddr::V4(Ipv4Addr::new(209, 186, 237, 0))));
184+
assert!(!blacklist.contains(&IpAddr::V4(Ipv4Addr::new(209, 233, 160, 0))));
185+
}
84186
}

0 commit comments

Comments
 (0)