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.
332 lines
9.5 KiB
C
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 */
|