espilon-source/espilon_bot/components/mod_honeypot/hp_net_monitor.c
Eun0us 6d45770d98 epsilon: merge command system into core + add 5 new modules
Move command registry from components/command/ into components/core/.
New modules: mod_canbus, mod_honeypot, mod_fallback, mod_redteam, mod_ota.
Replace mod_proxy with tun_core (multiplexed SOCKS5 tunnel).
Kconfig extended with per-module settings and async worker config.
2026-02-28 20:07:59 +01:00

332 lines
9.5 KiB
C

/*
* hp_net_monitor.c
* Network anomaly detector: port scan, SYN flood.
*
* Uses a raw TCP socket (LWIP) to inspect incoming SYN packets.
* Maintains a per-IP tracking table (max 32 entries) with sliding
* window counters. Sends HP| events when thresholds are exceeded.
*
* Note: ARP monitoring requires LWIP netif hooks (layer 2) and is
* not possible via raw sockets. May be added via etharp callback later.
*/
#include "sdkconfig.h"
#ifdef CONFIG_MODULE_HONEYPOT
#include <stdio.h>
#include <string.h>
#include <stdatomic.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "lwip/sockets.h"
#include "utils.h"
#include "event_format.h"
#include "hp_config.h"
#include "hp_net_monitor.h"
#define TAG "HP_NET"
#define NET_MON_STACK 4096
#define NET_MON_PRIO 4
#define NET_MON_CORE 1
/* Tracking table */
#define MAX_TRACKED_IPS 32
#define WINDOW_SEC 10 /* Sliding window for detections */
/* ============================================================
* IP tracker entry
* ============================================================ */
typedef struct {
uint32_t ip; /* Network byte order */
uint32_t first_seen; /* Tick count (ms) */
uint32_t last_seen;
uint16_t unique_ports[32]; /* Ring buffer of destination ports */
uint8_t port_idx;
uint8_t port_count;
uint32_t syn_count; /* SYN packets in window */
bool portscan_alerted;
bool synflood_alerted;
} ip_tracker_t;
/* ============================================================
* State
* ============================================================ */
static atomic_bool net_running = false;
static atomic_bool net_stop_req = false;
static TaskHandle_t net_task = NULL;
static SemaphoreHandle_t tracker_mutex = NULL;
static ip_tracker_t trackers[MAX_TRACKED_IPS];
static int tracker_count = 0;
static uint32_t total_port_scans = 0;
static uint32_t total_syn_floods = 0;
/* ============================================================
* Tracker helpers
* ============================================================ */
static uint32_t now_ms(void)
{
return (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
}
static void ip_to_str(uint32_t ip_nbo, char *buf, size_t len)
{
uint8_t *b = (uint8_t *)&ip_nbo;
snprintf(buf, len, "%d.%d.%d.%d", b[0], b[1], b[2], b[3]);
}
static ip_tracker_t *find_or_create_tracker(uint32_t ip)
{
uint32_t now = now_ms();
/* Search existing */
for (int i = 0; i < tracker_count; i++) {
if (trackers[i].ip == ip)
return &trackers[i];
}
/* Evict oldest if full */
if (tracker_count >= MAX_TRACKED_IPS) {
int oldest_idx = 0;
uint32_t oldest_time = trackers[0].last_seen;
for (int i = 1; i < tracker_count; i++) {
if (trackers[i].last_seen < oldest_time) {
oldest_time = trackers[i].last_seen;
oldest_idx = i;
}
}
if (oldest_idx < tracker_count - 1)
trackers[oldest_idx] = trackers[tracker_count - 1];
tracker_count--;
}
ip_tracker_t *t = &trackers[tracker_count++];
memset(t, 0, sizeof(*t));
t->ip = ip;
t->first_seen = now;
t->last_seen = now;
return t;
}
static void expire_trackers(void)
{
uint32_t now = now_ms();
uint32_t window = WINDOW_SEC * 1000;
for (int i = 0; i < tracker_count; ) {
if ((now - trackers[i].last_seen) > window * 3) {
if (i < tracker_count - 1)
trackers[i] = trackers[tracker_count - 1];
tracker_count--;
} else {
if ((now - trackers[i].first_seen) > window) {
trackers[i].syn_count = 0;
trackers[i].port_count = 0;
trackers[i].port_idx = 0;
trackers[i].first_seen = now;
trackers[i].portscan_alerted = false;
trackers[i].synflood_alerted = false;
}
i++;
}
}
}
static bool port_already_seen(ip_tracker_t *t, uint16_t port)
{
for (int i = 0; i < t->port_count && i < 32; i++) {
if (t->unique_ports[i] == port)
return true;
}
return false;
}
/* ============================================================
* Event recording
* ============================================================ */
static void record_syn(uint32_t src_ip, uint16_t dst_port)
{
if (!tracker_mutex ||
xSemaphoreTake(tracker_mutex, pdMS_TO_TICKS(50)) != pdTRUE)
return;
ip_tracker_t *t = find_or_create_tracker(src_ip);
t->last_seen = now_ms();
t->syn_count++;
/* Track unique ports for portscan detection */
if (!port_already_seen(t, dst_port)) {
t->unique_ports[t->port_idx % 32] = dst_port;
t->port_idx++;
t->port_count++;
}
/* Snapshot values before releasing mutex */
uint8_t port_count = t->port_count;
uint32_t syn_count = t->syn_count;
bool ps_alerted = t->portscan_alerted;
bool sf_alerted = t->synflood_alerted;
int ps_thresh = hp_config_get_threshold("portscan");
if (port_count >= (uint8_t)ps_thresh && !ps_alerted) {
t->portscan_alerted = true;
total_port_scans++;
}
int sf_thresh = hp_config_get_threshold("synflood");
if (syn_count >= (uint32_t)sf_thresh && !sf_alerted) {
t->synflood_alerted = true;
total_syn_floods++;
}
xSemaphoreGive(tracker_mutex);
/* Send events outside mutex to avoid blocking */
if (port_count >= (uint8_t)ps_thresh && !ps_alerted) {
char ip_str[16];
ip_to_str(src_ip, ip_str, sizeof(ip_str));
char detail[64];
snprintf(detail, sizeof(detail), "unique_ports=%d window=%ds",
port_count, WINDOW_SEC);
event_send("PORT_SCAN", "HIGH", "00:00:00:00:00:00",
ip_str, 0, 0, detail, NULL);
}
if (syn_count >= (uint32_t)sf_thresh && !sf_alerted) {
char ip_str[16];
ip_to_str(src_ip, ip_str, sizeof(ip_str));
char detail[64];
snprintf(detail, sizeof(detail), "syn_count=%lu window=%ds",
(unsigned long)syn_count, WINDOW_SEC);
event_send("SYN_FLOOD", "CRITICAL", "00:00:00:00:00:00",
ip_str, 0, 0, detail, NULL);
}
}
/* ============================================================
* Raw socket listener task
* ============================================================ */
static void net_monitor_task(void *arg)
{
(void)arg;
int raw_fd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
if (raw_fd < 0) {
ESP_LOGE(TAG, "raw socket failed: %d", errno);
goto done;
}
struct timeval tv = { .tv_sec = 1, .tv_usec = 0 };
setsockopt(raw_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
ESP_LOGI(TAG, "Network monitor started");
net_running = true;
uint8_t pkt_buf[128];
while (!net_stop_req) {
struct sockaddr_in src_addr;
socklen_t addr_len = sizeof(src_addr);
int n = recvfrom(raw_fd, pkt_buf, sizeof(pkt_buf), 0,
(struct sockaddr *)&src_addr, &addr_len);
if (n > 0) {
uint8_t ihl = (pkt_buf[0] & 0x0F) * 4;
if (ihl < 20 || n < ihl + 20)
goto next;
uint8_t *tcp = pkt_buf + ihl;
uint16_t dst_port = (tcp[2] << 8) | tcp[3];
uint8_t flags = tcp[13];
/* SYN set, ACK not set → connection initiation */
if ((flags & 0x02) && !(flags & 0x10)) {
record_syn(src_addr.sin_addr.s_addr, dst_port);
}
}
next:
if (tracker_mutex &&
xSemaphoreTake(tracker_mutex, pdMS_TO_TICKS(50)) == pdTRUE) {
expire_trackers();
xSemaphoreGive(tracker_mutex);
}
}
close(raw_fd);
done:
net_running = false;
net_stop_req = false;
ESP_LOGI(TAG, "Network monitor stopped");
net_task = NULL;
vTaskDelete(NULL);
}
/* ============================================================
* Public API
* ============================================================ */
void hp_net_monitor_start(void)
{
if (net_running || net_task) {
ESP_LOGW(TAG, "Network monitor already running");
return;
}
if (!tracker_mutex)
tracker_mutex = xSemaphoreCreateMutex();
tracker_count = 0;
total_port_scans = total_syn_floods = 0;
memset(trackers, 0, sizeof(trackers));
net_stop_req = false;
BaseType_t ret = xTaskCreatePinnedToCore(net_monitor_task, "hp_net",
NET_MON_STACK, NULL, NET_MON_PRIO, &net_task, NET_MON_CORE);
if (ret != pdPASS) {
ESP_LOGE(TAG, "Failed to create net monitor task");
net_task = NULL;
}
}
void hp_net_monitor_stop(void)
{
if (!net_running && !net_task) {
ESP_LOGW(TAG, "Network monitor not running");
return;
}
net_stop_req = true;
ESP_LOGI(TAG, "Network monitor stop requested");
}
bool hp_net_monitor_running(void)
{
return net_running;
}
int hp_net_monitor_status(char *buf, size_t len)
{
int count = 0;
if (tracker_mutex &&
xSemaphoreTake(tracker_mutex, pdMS_TO_TICKS(50)) == pdTRUE) {
count = tracker_count;
xSemaphoreGive(tracker_mutex);
}
return snprintf(buf, len,
"running=%s tracked_ips=%d port_scans=%lu syn_floods=%lu",
net_running ? "yes" : "no",
count,
(unsigned long)total_port_scans,
(unsigned long)total_syn_floods);
}
#endif /* CONFIG_MODULE_HONEYPOT */