mirror of
https://github.com/DaLaw2/NetGuardia.git
synced 2026-08-24 14:10:28 +09:00
Complete port scan protect
This commit is contained in:
parent
e3d2ed077d
commit
3620e5c146
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1200,6 +1200,7 @@ name = "net-guardia-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aya",
|
||||
"network-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@ -9,6 +9,7 @@ user = ["aya"]
|
||||
|
||||
[dependencies]
|
||||
aya = { workspace = true, optional = true }
|
||||
network-types = "0.0.7"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 }),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user