Complete port scan protect

This commit is contained in:
DaLaw2 2024-12-02 15:46:28 +08:00
parent e3d2ed077d
commit 3620e5c146
8 changed files with 214 additions and 63 deletions

1
Cargo.lock generated
View File

@ -1200,6 +1200,7 @@ name = "net-guardia-common"
version = "0.1.0"
dependencies = [
"aya",
"network-types",
]
[[package]]

View File

@ -9,6 +9,7 @@ user = ["aya"]
[dependencies]
aya = { workspace = true, optional = true }
network-types = "0.0.7"
[lib]
path = "src/lib.rs"

View File

@ -17,3 +17,5 @@ pub const MAX_RULES: u32 = 1000;
/// Defines the upper limit for port-based filtering rules to maintain
/// efficient rule processing.
pub const MAX_RULES_PORT: usize = 32;
pub const MAX_PORT_ACCESS: usize = 36;

View File

@ -1,7 +1,8 @@
use crate::model::ip_address::{EbpfAddrPortV4, EbpfAddrPortV6};
use network_types::ip::IpProto;
pub struct IPv4Event {
pub protocol: u8,
pub protocol: IpProto,
pub source_ip: u32,
pub destination_ip: u32,
pub source_port: u16,
@ -23,7 +24,7 @@ impl IPv4Event {
}
pub struct IPv6Event {
pub protocol: u8,
pub protocol: IpProto,
pub source_ip: u128,
pub destination_ip: u128,
pub source_port: u16,

View File

@ -0,0 +1,91 @@
use aya_ebpf::macros::map;
use aya_ebpf::maps::{HashMap, LruHashMap};
use net_guardia_common::model::ip_address::{EbpfAddrPortV4, EbpfAddrPortV6, IPv4, IPv6};
use net_guardia_common::{MAX_PORT_ACCESS, MAX_RULES};
use net_guardia_common::model::event::{IPv4Event, IPv6Event};
use net_guardia_common::model::placeholder::PlaceHolder;
#[map]
static PORT_SCAN_IPV4: LruHashMap<IPv4, [u16; MAX_PORT_ACCESS]> =
LruHashMap::with_max_entries(1000, 0);
#[map]
static PORT_SCAN_IPV6: LruHashMap<IPv6, [u16; MAX_PORT_ACCESS]> =
LruHashMap::with_max_entries(1000, 0);
#[map]
static LAST_UPDATE_IPV4: LruHashMap<IPv4, u64> = LruHashMap::with_max_entries(1000, 0);
#[map]
static LAST_UPDATE_IPV6: LruHashMap<IPv6, u64> = LruHashMap::with_max_entries(1000, 0);
#[map]
static SCANNER_IPV4: HashMap<IPv4, PlaceHolder> = HashMap::with_max_entries(MAX_RULES, 0);
#[map]
static SCANNER_IPV6: HashMap<IPv6, PlaceHolder> = HashMap::with_max_entries(MAX_RULES, 0);
pub fn is_attack_ipv4(event: &IPv4Event) -> bool {
port_scan_ipv4(event)
}
pub fn is_attack_ipv6(event: &IPv6Event) -> bool {
port_scan_ipv6(event)
}
fn port_scan_ipv4(event: &IPv4Event) -> bool {
unsafe {
let ip = &event.source_ip;
let port = &event.source_port;
if SCANNER_IPV4.get(ip).is_some() {
return true;
}
if let Some(last_update) = LAST_UPDATE_IPV4.get(ip) {
if event.timestamp - *last_update > 60_000_000_000 {
let _ = PORT_SCAN_IPV4.remove(ip);
}
}
if let Some(ports) = PORT_SCAN_IPV4.get_ptr_mut(ip) {
for i in 0..MAX_PORT_ACCESS {
let ports = &mut *ports;
if ports[i] == *port {
return false;
}
if ports[i] == 0 {
ports[i] = *port;
return false;
}
}
let _ = SCANNER_IPV4.insert(&ip, &0_u8, 0);
true
} else {
true
}
}
}
fn port_scan_ipv6(event: &IPv6Event) -> bool {
unsafe {
let ip = &event.source_ip;
let port = &event.source_port;
if SCANNER_IPV6.get(ip).is_some() {
return true;
}
if let Some(last_update) = LAST_UPDATE_IPV6.get(ip) {
if event.timestamp - *last_update > 60_000_000_000 {
let _ = PORT_SCAN_IPV6.remove(ip);
}
}
if let Some(ports) = PORT_SCAN_IPV6.get_ptr_mut(ip) {
for i in 0..MAX_PORT_ACCESS {
let ports = &mut *ports;
if ports[i] == *port {
return false;
}
if ports[i] == 0 {
ports[i] = *port;
return false;
}
}
let _ = SCANNER_IPV6.insert(&ip, &0_u8, 0);
true
} else {
true
}
}
}

View File

@ -1,87 +1,140 @@
use aya_ebpf::macros::map;
use aya_ebpf::maps::{Array, HashMap};
use aya_ebpf::programs::XdpContext;
use net_guardia_common::model::event::{IPv4Event, IPv6Event};
use net_guardia_common::model::http_method::EbpfHttpMethod;
use net_guardia_common::model::ip_address::{EbpfAddrPortV4, EbpfAddrPortV6, IPv4, IPv6};
use net_guardia_common::model::placeholder::PlaceHolder;
use net_guardia_common::MAX_RULES;
use network_types::eth::EthHdr;
use network_types::ip::{IpProto, Ipv4Hdr, Ipv6Hdr};
use network_types::tcp::TcpHdr;
#[map]
static HTTP_SERVICE_V4: HashMap<EbpfAddrPortV4, EbpfHttpMethod> =
HashMap::with_max_entries(MAX_RULES, 0);
#[map]
static HTTP_SERVICE_V6: HashMap<EbpfAddrPortV6, EbpfHttpMethod> =
HashMap::with_max_entries(MAX_RULES, 0);
#[map]
static SSH_WHITE_LIST_ONLY: Array<PlaceHolder> = Array::with_max_entries(1, 0);
#[map]
static IPV4_SSH_SERVICE: HashMap<EbpfAddrPortV4, PlaceHolder> =
HashMap::with_max_entries(MAX_RULES, 0);
#[map]
static IPV6_SSH_SERVICE: HashMap<EbpfAddrPortV6, PlaceHolder> =
HashMap::with_max_entries(MAX_RULES, 0);
#[map]
static IPV4_SSH_WHITE_LIST: HashMap<IPv4, PlaceHolder> = HashMap::with_max_entries(MAX_RULES, 0);
#[map]
static IPV6_SSH_WHITE_LIST: HashMap<IPv6, PlaceHolder> = HashMap::with_max_entries(MAX_RULES, 0);
#[map]
static IPV4_SSH_BLACK_LIST: HashMap<IPv4, PlaceHolder> = HashMap::with_max_entries(MAX_RULES, 0);
#[map]
static IPV6_SSH_BLACK_LIST: HashMap<IPv6, PlaceHolder> = HashMap::with_max_entries(MAX_RULES, 0);
pub fn ipv4_service_rule_violation(event: &IPv4Event, start: usize, end: usize, offset: usize) -> bool {
pub fn ipv4_service_rule_violation(start: usize, end: usize, event: &IPv4Event) -> bool {
let protocol = event.protocol;
let source = event.get_source();
let destination = event.get_destination();
ipv4_http_service_violation(&destination, start, end, offset) || ipv4_ssh_service_violation(&source, &destination)
ipv4_http_service_violation(start, end, &protocol, &destination)
|| ipv4_ssh_service_violation(&source, &destination)
}
pub fn ipv6_service_rule_violation(event: &IPv6Event, start: usize, end: usize, offset: usize) -> bool {
pub fn ipv6_service_rule_violation(start: usize, end: usize, event: &IPv6Event) -> bool {
let protocol = event.protocol;
let source = event.get_source();
let destination = event.get_destination();
ipv6_http_service_violation(&destination, start, end, offset) || ipv6_ssh_service_violation(&source, &destination)
ipv6_http_service_violation(start, end, &protocol, &destination)
|| ipv6_ssh_service_violation(&source, &destination)
}
#[inline(always)]
fn ipv4_http_service_violation(addr_port: &EbpfAddrPortV4, start: usize, end: usize, offset: usize) -> bool {
match HTTP_SERVICE_V4.get_ptr_mut(addr_port) {
Some(allow_method) => match get_http_request_method(start, end, offset) {
Some(http_method) => unsafe { *allow_method & http_method == 0 },
None => true,
fn ipv4_http_service_violation(
start: usize,
end: usize,
protocol: &IpProto,
destination: &EbpfAddrPortV4,
) -> bool {
match protocol {
IpProto::Tcp => unsafe {
let offset = size_of::<EthHdr>() + size_of::<Ipv4Hdr>();
let tcp_header = &*((start + offset) as *const TcpHdr);
if tcp_header.syn() != 0 || tcp_header.rst() != 0 || tcp_header.fin() != 0 {
return false;
}
if tcp_header.psh() == 0 || tcp_header.ack() == 0 {
return false;
}
match HTTP_SERVICE_V4.get_ptr_mut(destination) {
Some(allow_method) => match get_http_request_method(start, end, offset) {
Some(http_method) => unsafe { *allow_method & http_method == 0 },
None => true,
},
None => false,
}
},
None => false,
_ => false,
}
}
#[inline(always)]
fn ipv6_http_service_violation(addr_port: &EbpfAddrPortV6, start: usize, end: usize, offset: usize) -> bool {
match HTTP_SERVICE_V6.get_ptr_mut(addr_port) {
Some(allow_method) => match get_http_request_method(start, end, offset) {
Some(http_method) => unsafe { *allow_method & http_method == 0 },
None => true,
fn ipv6_http_service_violation(
start: usize,
end: usize,
protocol: &IpProto,
destination: &EbpfAddrPortV6,
) -> bool {
match protocol {
IpProto::Tcp => unsafe {
let offset = size_of::<EthHdr>() + size_of::<Ipv6Hdr>();
let tcp_header = &*((start + offset) as *const TcpHdr);
if tcp_header.syn() != 0 || tcp_header.rst() != 0 || tcp_header.fin() != 0 {
return false;
}
if tcp_header.psh() == 0 || tcp_header.ack() == 0 {
return false;
}
match HTTP_SERVICE_V6.get_ptr_mut(destination) {
Some(allow_method) => match get_http_request_method(start, end, offset) {
Some(http_method) => unsafe { *allow_method & http_method == 0 },
None => true,
},
None => false,
}
},
None => false,
_ => false,
}
}
#[inline(always)]
fn get_http_request_method(start: usize, end: usize, offset: usize) -> Option<EbpfHttpMethod> {
if start + offset + 3 > end {
if start + offset + 8 > end {
return None;
}
let data = unsafe { core::slice::from_raw_parts((start + offset) as *const u8, 3) };
match data {
b"GET" => Some(1 << 0),
b"POS" => Some(1 << 1),
b"PUT" => Some(1 << 2),
b"DEL" => Some(1 << 3),
b"HEA" => Some(1 << 4),
b"OPT" => Some(1 << 5),
b"PAT" => Some(1 << 6),
b"TRA" => Some(1 << 7),
b"CON" => Some(1 << 8),
let data = unsafe { core::slice::from_raw_parts((start + offset) as *const u8, 8) };
match &data[..4] {
b"GET " => Some(1 << 0),
b"POST" if &data[4..5] == b" " => Some(1 << 1),
b"PUT " => Some(1 << 2),
b"DELE" if &data[4..7] == b"TE " => Some(1 << 3),
b"HEAD" if &data[4..5] == b" " => Some(1 << 4),
b"OPTI" if &data[4..8] == b"ONS " => Some(1 << 5),
b"PATC" if &data[4..6] == b"H " => Some(1 << 6),
b"TRAC" if &data[4..6] == b"E " => Some(1 << 7),
b"CONN" if &data[4..8] == b"ECT " => Some(1 << 8),
_ => None,
}
}
#[inline(always)]
fn ipv4_ssh_service_violation(source_ip: &EbpfAddrPortV4, destination: &EbpfAddrPortV4) -> bool {
fn ipv4_ssh_service_violation(source: &EbpfAddrPortV4, destination: &EbpfAddrPortV4) -> bool {
unsafe {
if IPV4_SSH_SERVICE.get(destination).is_some() {
if SSH_WHITE_LIST_ONLY.get(0).is_some() {
IPV4_SSH_WHITE_LIST.get(&source_ip[0]).is_none()
IPV4_SSH_WHITE_LIST.get(&source[0]).is_none()
} else {
IPV4_SSH_BLACK_LIST.get(&source_ip[0]).is_some()
IPV4_SSH_BLACK_LIST.get(&source[0]).is_some()
}
} else {
false

View File

@ -10,7 +10,7 @@ use aya_ebpf::{bindings::xdp_action, macros::xdp, programs::XdpContext};
#[allow(unused_imports)]
use aya_log_ebpf::info;
use network_types::eth::EtherType;
use crate::action::service;
use crate::action::{defence, service};
#[xdp]
pub fn net_guardia(ctx: XdpContext) -> u32 {
@ -23,24 +23,29 @@ pub fn net_guardia(ctx: XdpContext) -> u32 {
fn try_net_guardia(ctx: XdpContext) -> Result<u32, ()> {
let start = ctx.data();
let end = ctx.data_end();
let mut offset = 0_usize;
match parsing::parse_ether_type(start, end, &mut offset)? {
match parsing::parse_ether_type(start, end)? {
EtherType::Ipv4 => {
let event = parsing::parse_ipv4_packet(start, end, &mut offset)?;
let event = parsing::parse_ipv4_packet(start, end)?;
if blocking::should_block_ipv4(&event) {
return Ok(xdp_action::XDP_DROP);
}
if service::ipv4_service_rule_violation(&event, start, end, offset) {
if service::ipv4_service_rule_violation(start, end, &event) {
return Ok(xdp_action::XDP_DROP);
}
if defence::is_attack_ipv4(&event) {
return Ok(xdp_action::XDP_DROP);
}
monitor::update_stats_ipv4(&event);
}
EtherType::Ipv6 => {
let event = parsing::parse_ipv6_packet(start, end, &mut offset)?;
let event = parsing::parse_ipv6_packet(start, end)?;
if blocking::should_block_ipv6(&event) {
return Ok(xdp_action::XDP_DROP);
}
if service::ipv6_service_rule_violation(&event, start, end, offset) {
if service::ipv6_service_rule_violation(start, end, &event) {
return Ok(xdp_action::XDP_DROP);
}
if defence::is_attack_ipv6(&event) {
return Ok(xdp_action::XDP_DROP);
}
monitor::update_stats_ipv6(&event);

View File

@ -9,23 +9,23 @@ use network_types::{
};
#[inline(always)]
pub fn parse_ether_type(start: usize, end: usize, offset: &mut usize) -> Result<EtherType, ()> {
pub fn parse_ether_type(start: usize, end: usize) -> Result<EtherType, ()> {
if start + size_of::<EthHdr>() > end {
return Err(());
}
let eth = unsafe { &*(start as *const EthHdr) };
*offset += size_of::<EthHdr>();
Ok(eth.ether_type)
}
#[inline(always)]
pub fn parse_ipv4_packet(start: usize, end: usize, offset: &mut usize) -> Result<IPv4Event, ()> {
if start + *offset + size_of::<Ipv4Hdr>() > end {
pub fn parse_ipv4_packet(start: usize, end: usize) -> Result<IPv4Event, ()> {
let mut offset = size_of::<EthHdr>();
if start + offset + size_of::<Ipv4Hdr>() > end {
return Err(());
}
let ipv4 = unsafe { &*((start + *offset) as *const Ipv4Hdr) };
*offset += size_of::<Ipv4Hdr>();
let ipv4 = unsafe { &*((start + offset) as *const Ipv4Hdr) };
offset += size_of::<Ipv4Hdr>();
let protocol = ipv4.proto;
let source_ip = u32::from_be(ipv4.src_addr);
@ -38,7 +38,7 @@ pub fn parse_ipv4_packet(start: usize, end: usize, offset: &mut usize) -> Result
};
Ok(IPv4Event {
protocol: protocol as u8,
protocol,
source_ip,
destination_ip,
source_port,
@ -49,12 +49,13 @@ pub fn parse_ipv4_packet(start: usize, end: usize, offset: &mut usize) -> Result
}
#[inline(always)]
pub fn parse_ipv6_packet(start: usize, end: usize, offset: &mut usize) -> Result<IPv6Event, ()> {
if start + *offset + size_of::<Ipv6Hdr>() > end {
pub fn parse_ipv6_packet(start: usize, end: usize) -> Result<IPv6Event, ()> {
let mut offset = size_of::<EthHdr>();
if start + offset + size_of::<Ipv6Hdr>() > end {
return Err(());
}
let ipv6 = unsafe { &*((start + *offset) as *const Ipv6Hdr) };
*offset += size_of::<Ipv6Hdr>();
let ipv6 = unsafe { &*((start + offset) as *const Ipv6Hdr) };
offset += size_of::<Ipv6Hdr>();
let protocol = ipv6.next_hdr;
let source_ip = u128::from_be_bytes(unsafe { ipv6.src_addr.in6_u.u6_addr8 });
@ -67,7 +68,7 @@ pub fn parse_ipv6_packet(start: usize, end: usize, offset: &mut usize) -> Result
};
Ok(IPv6Event {
protocol: protocol as u8,
protocol,
source_ip,
destination_ip,
source_port,
@ -78,13 +79,11 @@ pub fn parse_ipv6_packet(start: usize, end: usize, offset: &mut usize) -> Result
}
#[inline(always)]
fn parse_tcp_port(start: usize, end: usize, offset: &mut usize) -> Result<(u16, u16), ()> {
let tcp: *const TcpHdr = (start + *offset) as *const TcpHdr;
if start + *offset + size_of::<TcpHdr>() > end {
fn parse_tcp_port(start: usize, end: usize, offset: usize) -> Result<(u16, u16), ()> {
let tcp: *const TcpHdr = (start + offset) as *const TcpHdr;
if start + offset + size_of::<TcpHdr>() > end {
return Err(());
}
*offset += size_of::<TcpHdr>();
Ok((
u16::from_be(unsafe { (*tcp).source }),
u16::from_be(unsafe { (*tcp).dest }),
@ -92,13 +91,11 @@ fn parse_tcp_port(start: usize, end: usize, offset: &mut usize) -> Result<(u16,
}
#[inline(always)]
fn parse_udp_port(start: usize, end: usize, offset: &mut usize) -> Result<(u16, u16), ()> {
let udp: *const UdpHdr = (start + *offset) as *const UdpHdr;
if start + *offset + size_of::<UdpHdr>() > end {
fn parse_udp_port(start: usize, end: usize, offset: usize) -> Result<(u16, u16), ()> {
let udp: *const UdpHdr = (start + offset) as *const UdpHdr;
if start + offset + size_of::<UdpHdr>() > end {
return Err(());
}
*offset += size_of::<UdpHdr>();
Ok((
u16::from_be(unsafe { (*udp).source }),
u16::from_be(unsafe { (*udp).dest }),