use pnet::ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FilterMode {
#[default]
Allow,
Deny,
}
#[derive(Debug, Clone, Default)]
pub struct PortFilter {
pub source_ports: Vec<u16>,
pub destination_ports: Vec<u16>,
pub source_ranges: Vec<(u16, u16)>,
pub destination_ranges: Vec<(u16, u16)>,
pub match_any: bool,
}
impl PortFilter {
pub fn new() -> Self {
Self::default()
}
pub fn destination(mut self, port: u16) -> Self {
self.destination_ports.push(port);
self
}
pub fn source(mut self, port: u16) -> Self {
self.source_ports.push(port);
self
}
pub fn destination_range(mut self, range: std::ops::Range<u16>) -> Self {
self.destination_ranges
.push((range.start, range.end.saturating_sub(1)));
self
}
pub fn source_range(mut self, range: std::ops::Range<u16>) -> Self {
self.source_ranges
.push((range.start, range.end.saturating_sub(1)));
self
}
pub fn destination_list(mut self, ports: Vec<u16>) -> Self {
self.destination_ports.extend(ports);
self
}
pub fn source_list(mut self, ports: Vec<u16>) -> Self {
self.source_ports.extend(ports);
self
}
pub fn any_port(mut self) -> Self {
self.match_any = true;
self
}
pub fn matches(&self, src_port: u16, dst_port: u16) -> bool {
if self.match_any {
let all_ports: Vec<u16> = self
.source_ports
.iter()
.chain(self.destination_ports.iter())
.copied()
.collect();
let all_ranges: Vec<(u16, u16)> = self
.source_ranges
.iter()
.chain(self.destination_ranges.iter())
.copied()
.collect();
let port_match = all_ports.contains(&src_port)
|| all_ports.contains(&dst_port)
|| all_ranges
.iter()
.any(|(start, end)| src_port >= *start && src_port <= *end)
|| all_ranges
.iter()
.any(|(start, end)| dst_port >= *start && dst_port <= *end);
port_match
} else {
let src_match = self.source_ports.contains(&src_port)
|| self
.source_ranges
.iter()
.any(|(start, end)| src_port >= *start && src_port <= *end);
let dst_match = self.destination_ports.contains(&dst_port)
|| self
.destination_ranges
.iter()
.any(|(start, end)| dst_port >= *start && dst_port <= *end);
let src_ok = self.source_ports.is_empty() && self.source_ranges.is_empty() || src_match;
let dst_ok = self.destination_ports.is_empty() && self.destination_ranges.is_empty()
|| dst_match;
src_ok && dst_ok
}
}
}
#[derive(Debug, Clone, Default)]
pub struct IpFilter {
pub ipv4_addresses: Vec<Ipv4Addr>,
pub ipv6_addresses: Vec<Ipv6Addr>,
pub check_source: bool,
pub check_destination: bool,
}
impl IpFilter {
pub fn new() -> Self {
Self { check_source: true, check_destination: true, ..Default::default() }
}
pub fn allow(mut self, ip: &str) -> Result<Self, String> {
let addr: IpAddr = ip.parse().map_err(|e| format!("Invalid IP: {e}"))?;
match addr {
IpAddr::V4(v4) => self.ipv4_addresses.push(v4),
IpAddr::V6(v6) => self.ipv6_addresses.push(v6),
}
Ok(self)
}
pub fn allow_list(mut self, ips: Vec<&str>) -> Result<Self, String> {
for ip in ips {
self = self.allow(ip)?;
}
Ok(self)
}
pub fn source_only(mut self) -> Self {
self.check_source = true;
self.check_destination = false;
self
}
pub fn destination_only(mut self) -> Self {
self.check_source = false;
self.check_destination = true;
self
}
pub fn matches(&self, src_ip: &IpAddr, dst_ip: &IpAddr) -> bool {
let src_match = if self.check_source {
match src_ip {
IpAddr::V4(v4) => self.ipv4_addresses.contains(v4),
IpAddr::V6(v6) => self.ipv6_addresses.contains(v6),
}
} else {
false
};
let dst_match = if self.check_destination {
match dst_ip {
IpAddr::V4(v4) => self.ipv4_addresses.contains(v4),
IpAddr::V6(v6) => self.ipv6_addresses.contains(v6),
}
} else {
false
};
src_match || dst_match
}
}
#[derive(Debug, Clone, Default)]
pub struct SubnetFilter {
pub ipv4_subnets: Vec<Ipv4Network>,
pub ipv6_subnets: Vec<Ipv6Network>,
pub check_source: bool,
pub check_destination: bool,
}
impl SubnetFilter {
pub fn new() -> Self {
Self { check_source: true, check_destination: true, ..Default::default() }
}
pub fn allow(mut self, cidr: &str) -> Result<Self, String> {
let network: IpNetwork = cidr.parse().map_err(|e| format!("Invalid CIDR: {e}"))?;
match network {
IpNetwork::V4(v4) => self.ipv4_subnets.push(v4),
IpNetwork::V6(v6) => self.ipv6_subnets.push(v6),
}
Ok(self)
}
pub fn allow_list(mut self, cidrs: Vec<&str>) -> Result<Self, String> {
for cidr in cidrs {
self = self.allow(cidr)?;
}
Ok(self)
}
pub fn source_only(mut self) -> Self {
self.check_source = true;
self.check_destination = false;
self
}
pub fn destination_only(mut self) -> Self {
self.check_source = false;
self.check_destination = true;
self
}
pub fn matches(&self, src_ip: &IpAddr, dst_ip: &IpAddr) -> bool {
let src_match = if self.check_source {
match src_ip {
IpAddr::V4(v4) => self.ipv4_subnets.iter().any(|net| net.contains(*v4)),
IpAddr::V6(v6) => self.ipv6_subnets.iter().any(|net| net.contains(*v6)),
}
} else {
false
};
let dst_match = if self.check_destination {
match dst_ip {
IpAddr::V4(v4) => self.ipv4_subnets.iter().any(|net| net.contains(*v4)),
IpAddr::V6(v6) => self.ipv6_subnets.iter().any(|net| net.contains(*v6)),
}
} else {
false
};
src_match || dst_match
}
}
#[derive(Debug, Clone, Default)]
pub struct FilterConfig {
pub port_filter: Option<PortFilter>,
pub ip_filter: Option<IpFilter>,
pub subnet_filter: Option<SubnetFilter>,
pub mode: FilterMode,
}
impl FilterConfig {
pub fn new() -> Self {
Self::default()
}
pub fn mode(mut self, mode: FilterMode) -> Self {
self.mode = mode;
self
}
pub fn with_port_filter(mut self, filter: PortFilter) -> Self {
self.port_filter = Some(filter);
self
}
pub fn with_ip_filter(mut self, filter: IpFilter) -> Self {
self.ip_filter = Some(filter);
self
}
pub fn with_subnet_filter(mut self, filter: SubnetFilter) -> Self {
self.subnet_filter = Some(filter);
self
}
pub fn should_process(
&self,
src_ip: &IpAddr,
dst_ip: &IpAddr,
src_port: u16,
dst_port: u16,
) -> bool {
if self.port_filter.is_none() && self.ip_filter.is_none() && self.subnet_filter.is_none() {
return true;
}
match self.mode {
FilterMode::Allow => {
if let Some(ref filter) = self.port_filter {
if !filter.matches(src_port, dst_port) {
return false;
}
}
if let Some(ref filter) = self.ip_filter {
if !filter.matches(src_ip, dst_ip) {
return false;
}
}
if let Some(ref filter) = self.subnet_filter {
if !filter.matches(src_ip, dst_ip) {
return false;
}
}
true
}
FilterMode::Deny => {
let mut all_match = true;
if let Some(ref filter) = self.port_filter {
all_match = all_match && filter.matches(src_port, dst_port);
}
if let Some(ref filter) = self.ip_filter {
all_match = all_match && filter.matches(src_ip, dst_ip);
}
if let Some(ref filter) = self.subnet_filter {
all_match = all_match && filter.matches(src_ip, dst_ip);
}
!all_match
}
}
}
}