first commit
This commit is contained in:
commit
be335a4346
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
8
Cargo.toml
Normal file
8
Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "blackwall"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pnet = "0.35.0"
|
||||||
|
colored = "2.1.0"
|
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# BlackWall
|
||||||
|
A WIP next gen firewall for Linux.
|
||||||
|
![blackwall demo](/docs/demo.png)
|
BIN
docs/demo.png
Normal file
BIN
docs/demo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 117 KiB |
203
src/main.rs
Normal file
203
src/main.rs
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
use pnet::datalink;
|
||||||
|
use std::fs::{File, read_dir};
|
||||||
|
use std::io::{BufRead, BufReader};
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||||
|
use std::path::Path;
|
||||||
|
use colored::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
enum PortType {
|
||||||
|
TCP,
|
||||||
|
UDP,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
enum ConnectionType {
|
||||||
|
Client,
|
||||||
|
Server,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
struct PortInfo {
|
||||||
|
number: u16,
|
||||||
|
port_type: PortType,
|
||||||
|
process_name: String,
|
||||||
|
connection_type: ConnectionType,
|
||||||
|
state: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let interfaces = datalink::interfaces();
|
||||||
|
let open_ports = get_open_ports()?;
|
||||||
|
|
||||||
|
for interface in interfaces {
|
||||||
|
println!("{}", format!("Interface: {}", interface.name).green().bold());
|
||||||
|
|
||||||
|
let mut interface_ports: HashSet<PortInfo> = HashSet::new();
|
||||||
|
|
||||||
|
for ip in &interface.ips {
|
||||||
|
println!(" {}", format!("IP: {}", ip).cyan());
|
||||||
|
|
||||||
|
if let Some(ports) = open_ports.get(&ip.ip()) {
|
||||||
|
interface_ports.extend(ports.iter().cloned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !interface_ports.is_empty() {
|
||||||
|
println!(" {}", "Open ports:".yellow());
|
||||||
|
for port in interface_ports {
|
||||||
|
let port_type_color = if port.port_type == PortType::TCP { "TCP".blue() } else { "UDP".magenta() };
|
||||||
|
let conn_type_color = match port.connection_type {
|
||||||
|
ConnectionType::Server => "Server".green(),
|
||||||
|
ConnectionType::Client => "Client".yellow(),
|
||||||
|
ConnectionType::Unknown => "Unknown".red(),
|
||||||
|
};
|
||||||
|
println!(" {} ({}) - {} - {} ({})",
|
||||||
|
port.number.to_string().white().bold(),
|
||||||
|
port_type_color,
|
||||||
|
port.process_name.cyan(),
|
||||||
|
conn_type_color,
|
||||||
|
port.state.white());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!(" {}", "No open ports found".red());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_open_ports() -> Result<HashMap<IpAddr, HashSet<PortInfo>>, Box<dyn std::error::Error>> {
|
||||||
|
let mut inode_to_pid = HashMap::new();
|
||||||
|
let mut open_ports = HashMap::new();
|
||||||
|
|
||||||
|
// Map inodes to PIDs
|
||||||
|
for entry in read_dir("/proc")? {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
if path.is_dir() {
|
||||||
|
if let Some(pid) = path.file_name().and_then(|n| n.to_str()).and_then(|s| s.parse::<u32>().ok()) {
|
||||||
|
let fd_dir = path.join("fd");
|
||||||
|
if fd_dir.is_dir() {
|
||||||
|
for fd_entry in read_dir(fd_dir)? {
|
||||||
|
let fd_entry = fd_entry?;
|
||||||
|
let fd_path = fd_entry.path();
|
||||||
|
if let Ok(target) = std::fs::read_link(&fd_path) {
|
||||||
|
let target_str = target.to_string_lossy();
|
||||||
|
if target_str.starts_with("socket:[") {
|
||||||
|
let inode = target_str.trim_start_matches("socket:[").trim_end_matches(']');
|
||||||
|
inode_to_pid.insert(inode.to_string(), pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse TCP and UDP connections
|
||||||
|
parse_connections("/proc/net/tcp", &mut open_ports, &inode_to_pid, 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_connections(
|
||||||
|
file_path: &str,
|
||||||
|
open_ports: &mut HashMap<IpAddr, HashSet<PortInfo>>,
|
||||||
|
inode_to_pid: &HashMap<String, u32>,
|
||||||
|
port_type: PortType,
|
||||||
|
is_ipv6: bool
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let file = File::open(file_path)?;
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
|
||||||
|
for line in reader.lines().skip(1) {
|
||||||
|
let line = line?;
|
||||||
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||||
|
if parts.len() >= 10 {
|
||||||
|
let local_address = parts[1];
|
||||||
|
let remote_address = parts[2];
|
||||||
|
let state_hex = parts[3];
|
||||||
|
let inode = parts[9];
|
||||||
|
let address_parts: Vec<&str> = local_address.split(':').collect();
|
||||||
|
if address_parts.len() == 2 {
|
||||||
|
let ip = if is_ipv6 {
|
||||||
|
IpAddr::V6(parse_ipv6(address_parts[0])?)
|
||||||
|
} else {
|
||||||
|
IpAddr::V4(parse_ipv4(address_parts[0])?)
|
||||||
|
};
|
||||||
|
let port = u16::from_str_radix(address_parts[1], 16)?;
|
||||||
|
let process_name = get_process_name(inode_to_pid.get(inode))?;
|
||||||
|
let (connection_type, state) = determine_connection_type(&port_type, state_hex, remote_address);
|
||||||
|
let port_info = PortInfo {
|
||||||
|
number: port,
|
||||||
|
port_type: port_type.clone(),
|
||||||
|
process_name,
|
||||||
|
connection_type,
|
||||||
|
state,
|
||||||
|
};
|
||||||
|
open_ports.entry(ip).or_insert_with(HashSet::new).insert(port_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn determine_connection_type(port_type: &PortType, state_hex: &str, remote_address: &str) -> (ConnectionType, String) {
|
||||||
|
let state = u8::from_str_radix(state_hex, 16).unwrap_or(0);
|
||||||
|
match port_type {
|
||||||
|
PortType::TCP => match state {
|
||||||
|
1 => (ConnectionType::Client, "ESTABLISHED".to_string()),
|
||||||
|
2 => (ConnectionType::Client, "SYN_SENT".to_string()),
|
||||||
|
3 => (ConnectionType::Server, "SYN_RECV".to_string()),
|
||||||
|
4 => (ConnectionType::Server, "FIN_WAIT1".to_string()),
|
||||||
|
5 => (ConnectionType::Server, "FIN_WAIT2".to_string()),
|
||||||
|
6 => (ConnectionType::Client, "TIME_WAIT".to_string()),
|
||||||
|
7 => (ConnectionType::Server, "CLOSE".to_string()),
|
||||||
|
8 => (ConnectionType::Server, "CLOSE_WAIT".to_string()),
|
||||||
|
9 => (ConnectionType::Client, "LAST_ACK".to_string()),
|
||||||
|
10 => (ConnectionType::Server, "LISTEN".to_string()),
|
||||||
|
11 => (ConnectionType::Server, "CLOSING".to_string()),
|
||||||
|
_ => (ConnectionType::Unknown, format!("UNKNOWN ({})", state)),
|
||||||
|
},
|
||||||
|
PortType::UDP => {
|
||||||
|
if remote_address == "00000000:0000" {
|
||||||
|
(ConnectionType::Server, "UNCONN".to_string())
|
||||||
|
} else {
|
||||||
|
(ConnectionType::Client, "ESTABLISHED".to_string())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_process_name(pid: Option<&u32>) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
match pid {
|
||||||
|
Some(&pid) => {
|
||||||
|
let comm_path = Path::new("/proc").join(pid.to_string()).join("comm");
|
||||||
|
let mut name = std::fs::read_to_string(comm_path)?;
|
||||||
|
name.truncate(name.trim_end().len());
|
||||||
|
Ok(name)
|
||||||
|
},
|
||||||
|
None => Ok("Unknown".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_ipv4(hex: &str) -> Result<Ipv4Addr, Box<dyn std::error::Error>> {
|
||||||
|
let addr = u32::from_str_radix(hex, 16)?;
|
||||||
|
Ok(Ipv4Addr::from(addr.to_be()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_ipv6(hex: &str) -> Result<Ipv6Addr, Box<dyn std::error::Error>> {
|
||||||
|
let mut groups = [0u16; 8];
|
||||||
|
for (i, chunk) in hex.as_bytes().chunks(4).enumerate() {
|
||||||
|
groups[i] = u16::from_str_radix(std::str::from_utf8(chunk)?, 16)?;
|
||||||
|
}
|
||||||
|
Ok(Ipv6Addr::from(groups))
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user