Compare commits

..

No commits in common. "ff3fc643c186846fb2a6251d9b644895dd00699d" and "1d7aef084623a60c1344dbb1e3ae55e5b0d7a258" have entirely different histories.

3 changed files with 38 additions and 438 deletions

332
Cargo.lock generated
View File

@ -1,332 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "blackwall"
version = "0.1.0"
dependencies = [
"colored",
"pnet",
]
[[package]]
name = "colored"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
dependencies = [
"lazy_static",
"windows-sys",
]
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "ipnetwork"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e"
dependencies = [
"serde",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.159"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "no-std-net"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65"
[[package]]
name = "pnet"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "682396b533413cc2e009fbb48aadf93619a149d3e57defba19ff50ce0201bd0d"
dependencies = [
"ipnetwork",
"pnet_base",
"pnet_datalink",
"pnet_packet",
"pnet_sys",
"pnet_transport",
]
[[package]]
name = "pnet_base"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7"
dependencies = [
"no-std-net",
]
[[package]]
name = "pnet_datalink"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e79e70ec0be163102a332e1d2d5586d362ad76b01cec86f830241f2b6452a7b7"
dependencies = [
"ipnetwork",
"libc",
"pnet_base",
"pnet_sys",
"winapi",
]
[[package]]
name = "pnet_macros"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13325ac86ee1a80a480b0bc8e3d30c25d133616112bb16e86f712dcf8a71c863"
dependencies = [
"proc-macro2",
"quote",
"regex",
"syn",
]
[[package]]
name = "pnet_macros_support"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eed67a952585d509dd0003049b1fc56b982ac665c8299b124b90ea2bdb3134ab"
dependencies = [
"pnet_base",
]
[[package]]
name = "pnet_packet"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c96ebadfab635fcc23036ba30a7d33a80c39e8461b8bd7dc7bb186acb96560f"
dependencies = [
"glob",
"pnet_base",
"pnet_macros",
"pnet_macros_support",
]
[[package]]
name = "pnet_sys"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d4643d3d4db6b08741050c2f3afa9a892c4244c085a72fcda93c9c2c9a00f4b"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "pnet_transport"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f604d98bc2a6591cf719b58d3203fd882bdd6bf1db696c4ac97978e9f4776bf"
dependencies = [
"libc",
"pnet_base",
"pnet_packet",
"pnet_sys",
]
[[package]]
name = "proc-macro2"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "serde"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "2.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"

View File

@ -1,4 +1,3 @@
# BlackWall # BlackWall
A WIP next gen firewall for Linux. A WIP next gen firewall for Linux.
![blackwall demo](/docs/demo.png) ![blackwall demo](/docs/demo.png)

View File

@ -1,10 +1,10 @@
use colored::*;
use pnet::datalink; use pnet::datalink;
use std::collections::{HashMap, HashSet}; use std::fs::{File, read_dir};
use std::fs::{read_dir, read_to_string, File};
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
use std::collections::{HashMap, HashSet};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::path::Path; use std::path::Path;
use colored::*;
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum PortType { enum PortType {
@ -33,16 +33,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let open_ports = get_open_ports()?; let open_ports = get_open_ports()?;
for interface in interfaces { for interface in interfaces {
println!( println!("{}", format!("Interface: {}", interface.name).green().bold());
"{}",
format!("Interface: {}", interface.name).green().bold()
);
let mut interface_ports: HashSet<PortInfo> = HashSet::new(); let mut interface_ports: HashSet<PortInfo> = HashSet::new();
for ip in &interface.ips { for ip in &interface.ips {
println!(" {}", format!("IP: {}", ip).cyan()); println!(" {}", format!("IP: {}", ip).cyan());
if let Some(ports) = open_ports.get(&ip.ip()) { if let Some(ports) = open_ports.get(&ip.ip()) {
interface_ports.extend(ports.iter().cloned()); interface_ports.extend(ports.iter().cloned());
} }
@ -51,29 +48,23 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
if !interface_ports.is_empty() { if !interface_ports.is_empty() {
println!(" {}", "Open ports:".yellow()); println!(" {}", "Open ports:".yellow());
for port in interface_ports { for port in interface_ports {
let port_type_color = if port.port_type == PortType::TCP { let port_type_color = if port.port_type == PortType::TCP { "TCP".blue() } else { "UDP".magenta() };
"TCP".blue()
} else {
"UDP".magenta()
};
let conn_type_color = match port.connection_type { let conn_type_color = match port.connection_type {
ConnectionType::Server => "Server".green(), ConnectionType::Server => "Server".green(),
ConnectionType::Client => "Client".yellow(), ConnectionType::Client => "Client".yellow(),
ConnectionType::Unknown => "Unknown".red(), ConnectionType::Unknown => "Unknown".red(),
}; };
println!( println!(" {} ({}) - {} - {} ({})",
" {} ({}) - {} - {} ({})", port.number.to_string().white().bold(),
port.number.to_string().white().bold(), port_type_color,
port_type_color, port.process_name.cyan(),
port.process_name.cyan(), conn_type_color,
conn_type_color, port.state.white());
port.state.white()
);
} }
} else { } else {
println!(" {}", "No open ports found".red()); println!(" {}", "No open ports found".red());
} }
println!(); println!();
} }
@ -89,11 +80,7 @@ fn get_open_ports() -> Result<HashMap<IpAddr, HashSet<PortInfo>>, Box<dyn std::e
let entry = entry?; let entry = entry?;
let path = entry.path(); let path = entry.path();
if path.is_dir() { if path.is_dir() {
if let Some(pid) = path if let Some(pid) = path.file_name().and_then(|n| n.to_str()).and_then(|s| s.parse::<u32>().ok()) {
.file_name()
.and_then(|n| n.to_str())
.and_then(|s| s.parse::<u32>().ok())
{
let fd_dir = path.join("fd"); let fd_dir = path.join("fd");
if fd_dir.is_dir() { if fd_dir.is_dir() {
for fd_entry in read_dir(fd_dir)? { for fd_entry in read_dir(fd_dir)? {
@ -102,9 +89,7 @@ fn get_open_ports() -> Result<HashMap<IpAddr, HashSet<PortInfo>>, Box<dyn std::e
if let Ok(target) = std::fs::read_link(&fd_path) { if let Ok(target) = std::fs::read_link(&fd_path) {
let target_str = target.to_string_lossy(); let target_str = target.to_string_lossy();
if target_str.starts_with("socket:[") { if target_str.starts_with("socket:[") {
let inode = target_str let inode = target_str.trim_start_matches("socket:[").trim_end_matches(']');
.trim_start_matches("socket:[")
.trim_end_matches(']');
inode_to_pid.insert(inode.to_string(), pid); inode_to_pid.insert(inode.to_string(), pid);
} }
} }
@ -115,34 +100,10 @@ fn get_open_ports() -> Result<HashMap<IpAddr, HashSet<PortInfo>>, Box<dyn std::e
} }
// Parse TCP and UDP connections // Parse TCP and UDP connections
parse_connections( parse_connections("/proc/net/tcp", &mut open_ports, &inode_to_pid, PortType::TCP, false)?;
"/proc/net/tcp", parse_connections("/proc/net/tcp6", &mut open_ports, &inode_to_pid, PortType::TCP, true)?;
&mut open_ports, parse_connections("/proc/net/udp", &mut open_ports, &inode_to_pid, PortType::UDP, false)?;
&inode_to_pid, parse_connections("/proc/net/udp6", &mut open_ports, &inode_to_pid, PortType::UDP, true)?;
PortType::TCP,
false,
)?;
parse_connections(
"/proc/net/tcp6",
&mut open_ports,
&inode_to_pid,
PortType::TCP,
true,
)?;
parse_connections(
"/proc/net/udp",
&mut open_ports,
&inode_to_pid,
PortType::UDP,
false,
)?;
parse_connections(
"/proc/net/udp6",
&mut open_ports,
&inode_to_pid,
PortType::UDP,
true,
)?;
Ok(open_ports) Ok(open_ports)
} }
@ -152,7 +113,7 @@ fn parse_connections(
open_ports: &mut HashMap<IpAddr, HashSet<PortInfo>>, open_ports: &mut HashMap<IpAddr, HashSet<PortInfo>>,
inode_to_pid: &HashMap<String, u32>, inode_to_pid: &HashMap<String, u32>,
port_type: PortType, port_type: PortType,
is_ipv6: bool, is_ipv6: bool
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
let file = File::open(file_path)?; let file = File::open(file_path)?;
let reader = BufReader::new(file); let reader = BufReader::new(file);
@ -168,37 +129,28 @@ fn parse_connections(
let address_parts: Vec<&str> = local_address.split(':').collect(); let address_parts: Vec<&str> = local_address.split(':').collect();
if address_parts.len() == 2 { if address_parts.len() == 2 {
let ip = if is_ipv6 { let ip = if is_ipv6 {
IpAddr::V6(parse_ipv6(address_parts[0])?) IpAddr::V6(parse_ipv6(&address_parts[0])?)
} else { } else {
IpAddr::V4(parse_ipv4(address_parts[0])?) IpAddr::V4(parse_ipv4(&address_parts[0])?)
}; };
let port = u16::from_str_radix(address_parts[1], 16)?; let port = u16::from_str_radix(address_parts[1], 16)?;
let process_name = get_process_name(inode_to_pid.get(inode))?; let process_name = get_process_name(inode_to_pid.get(inode))?;
let (connection_type, state) = let (connection_type, state) = determine_connection_type(&port_type, state_hex, local_address, remote_address);
determine_connection_type(&port_type, state_hex, local_address, remote_address); let port_info = PortInfo {
let port_info = PortInfo { number: port,
number: port, port_type: port_type.clone(),
port_type: port_type.clone(),
process_name, process_name,
connection_type, connection_type,
state, state,
}; };
open_ports open_ports.entry(ip).or_insert_with(HashSet::new).insert(port_info);
.entry(ip)
.or_default()
.insert(port_info);
} }
} }
} }
Ok(()) Ok(())
} }
fn determine_connection_type( fn determine_connection_type(port_type: &PortType, state_hex: &str, local_address: &str, remote_address: &str) -> (ConnectionType, String) {
port_type: &PortType,
state_hex: &str,
local_address: &str,
remote_address: &str,
) -> (ConnectionType, String) {
let state = u8::from_str_radix(state_hex, 16).unwrap_or(0); let state = u8::from_str_radix(state_hex, 16).unwrap_or(0);
match port_type { match port_type {
@ -206,15 +158,13 @@ fn determine_connection_type(
match state { match state {
1 => { 1 => {
// ESTABLISHED: Check if the remote port is 0 (unlikely for a client) // ESTABLISHED: Check if the remote port is 0 (unlikely for a client)
let remote_port = let remote_port = u16::from_str_radix(remote_address.split(':').last().unwrap_or("0"), 16).unwrap_or(0);
u16::from_str_radix(remote_address.split(':').last().unwrap_or("0"), 16)
.unwrap_or(0);
if remote_port == 0 { if remote_port == 0 {
(ConnectionType::Server, "ESTABLISHED".to_string()) (ConnectionType::Server, "ESTABLISHED".to_string())
} else { } else {
(ConnectionType::Client, "ESTABLISHED".to_string()) (ConnectionType::Client, "ESTABLISHED".to_string())
} }
} },
2 => (ConnectionType::Client, "SYN_SENT".to_string()), 2 => (ConnectionType::Client, "SYN_SENT".to_string()),
3 => (ConnectionType::Server, "SYN_RECV".to_string()), 3 => (ConnectionType::Server, "SYN_RECV".to_string()),
4 => (ConnectionType::Unknown, "FIN_WAIT1".to_string()), 4 => (ConnectionType::Unknown, "FIN_WAIT1".to_string()),
@ -227,43 +177,26 @@ fn determine_connection_type(
11 => (ConnectionType::Unknown, "CLOSING".to_string()), 11 => (ConnectionType::Unknown, "CLOSING".to_string()),
_ => (ConnectionType::Unknown, format!("UNKNOWN ({})", state)), _ => (ConnectionType::Unknown, format!("UNKNOWN ({})", state)),
} }
} },
PortType::UDP => { PortType::UDP => {
if remote_address == "00000000:0000" { if remote_address == "00000000:0000" {
(ConnectionType::Server, "UNCONN".to_string()) (ConnectionType::Server, "UNCONN".to_string())
} else { } else {
(ConnectionType::Client, "ESTABLISHED".to_string()) (ConnectionType::Client, "ESTABLISHED".to_string())
} }
} },
} }
} }
fn get_process_name(pid: Option<&u32>) -> Result<String, Box<dyn std::error::Error>> { fn get_process_name(pid: Option<&u32>) -> Result<String, Box<dyn std::error::Error>> {
match pid { match pid {
Some(&pid) => { Some(&pid) => {
let comm_path = Path::new("/proc").join(pid.to_string()).join("comm"); let comm_path = Path::new("/proc").join(pid.to_string()).join("comm");
let cmdline_path = Path::new("/proc").join(pid.to_string()).join("cmdline"); let mut name = std::fs::read_to_string(comm_path)?;
name.truncate(name.trim_end().len());
// Try to read from comm file first Ok(name)
if let Ok(mut name) = read_to_string(&comm_path) { },
name.truncate(name.trim_end().len());
if !name.is_empty() {
return Ok(name);
}
}
// If comm is empty or unreadable, try cmdline
if let Ok(cmdline) = read_to_string(&cmdline_path) {
let parts: Vec<&str> = cmdline.split('\0').collect();
if !parts.is_empty() {
if let Some(name) = Path::new(parts[0]).file_name() {
return Ok(name.to_string_lossy().into_owned());
}
}
}
Ok("Unknown".to_string())
}
None => Ok("Unknown".to_string()), None => Ok("Unknown".to_string()),
} }
} }