espilon-source/espilon_bot/components/mod_redteam/rt_mesh.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

297 lines
8.9 KiB
C

/*
* rt_mesh.c
* ESP-NOW mesh relay between Espilon agents.
*
* Protocol:
* Agent A (no internet) → ESP-NOW broadcast "ESPNOW_PROBE"
* Agent B (connected) → ESP-NOW unicast "ESPNOW_ACK:<device_id>"
* Agent A sends → "RELAY:<device_id>:<base64_encrypted_msg>"
* Agent B receives → forwards via TCP to C2
* Agent B receives resp → "REPLY:<device_id>:<base64_encrypted_resp>"
*
* ESP-NOW works WITHOUT WiFi association — pure 802.11 P2P.
*/
#include "sdkconfig.h"
#include "rt_mesh.h"
#include <string.h>
#ifdef CONFIG_MODULE_REDTEAM
#ifdef CONFIG_RT_MESH
#include <stdio.h>
#include "esp_log.h"
#include "esp_now.h"
#include "esp_wifi.h"
#include "lwip/sockets.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "utils.h"
static const char *TAG = "RT_MESH";
#define ESPNOW_PMK "espilon_mesh_pmk" /* 16 bytes primary master key */
#define ESPNOW_CHANNEL 1
#define PROBE_MAGIC "ESPNOW_PROBE"
#define ACK_MAGIC "ESPNOW_ACK:"
#define RELAY_MAGIC "RELAY:"
#define REPLY_MAGIC "REPLY:"
#define PROBE_INTERVAL_MS 5000
#define MAX_PEERS 4
static volatile bool s_running = false;
static volatile bool s_initialized = false;
static TaskHandle_t s_probe_task = NULL;
/* Best relay peer */
static rt_mesh_peer_t s_best_relay = {0};
static SemaphoreHandle_t s_relay_mutex = NULL;
/* ============================================================
* ESP-NOW receive callback
* ============================================================ */
static void espnow_recv_cb(const esp_now_recv_info_t *info,
const uint8_t *data, int len)
{
if (!s_running || !data || len <= 0) return;
/* ACK from a connected agent: "ESPNOW_ACK:<device_id>" */
if (len > (int)strlen(ACK_MAGIC) &&
memcmp(data, ACK_MAGIC, strlen(ACK_MAGIC)) == 0) {
const char *dev_id = (const char *)data + strlen(ACK_MAGIC);
int id_len = len - (int)strlen(ACK_MAGIC);
if (id_len <= 0 || id_len > 15) id_len = (id_len <= 0) ? 0 : 15;
if (id_len == 0) return;
if (s_relay_mutex && xSemaphoreTake(s_relay_mutex, 0) == pdTRUE) {
/* Use RSSI to pick the best relay */
int8_t rssi = info->rx_ctrl->rssi;
if (!s_best_relay.available || rssi > s_best_relay.rssi) {
memcpy(s_best_relay.mac, info->src_addr, 6);
memcpy(s_best_relay.device_id, dev_id, id_len);
s_best_relay.device_id[id_len] = '\0';
s_best_relay.rssi = rssi;
s_best_relay.available = true;
ESP_LOGI(TAG, "Relay found: %s (RSSI=%d)", s_best_relay.device_id, rssi);
}
xSemaphoreGive(s_relay_mutex);
}
return;
}
/* PROBE from another agent looking for a relay */
if (len == (int)strlen(PROBE_MAGIC) &&
memcmp(data, PROBE_MAGIC, strlen(PROBE_MAGIC)) == 0) {
/* We answer only if we have internet (sock >= 0) */
extern int sock;
if (sock >= 0) {
/* Send ACK with our device_id */
char ack[64];
int ack_len = snprintf(ack, sizeof(ack), "%s%s", ACK_MAGIC, CONFIG_DEVICE_ID);
/* Add peer if not already added */
esp_now_peer_info_t peer = {0};
memcpy(peer.peer_addr, info->src_addr, 6);
peer.channel = 0; /* current channel */
peer.encrypt = false;
esp_now_add_peer(&peer); /* ignore error if already exists */
esp_now_send(info->src_addr, (uint8_t *)ack, ack_len);
ESP_LOGI(TAG, "Answered PROBE from %02X:%02X:%02X:%02X:%02X:%02X",
info->src_addr[0], info->src_addr[1], info->src_addr[2],
info->src_addr[3], info->src_addr[4], info->src_addr[5]);
}
return;
}
/* RELAY request from another agent: forward to C2 via TCP */
if (len > (int)strlen(RELAY_MAGIC) &&
memcmp(data, RELAY_MAGIC, strlen(RELAY_MAGIC)) == 0) {
extern int sock;
extern SemaphoreHandle_t sock_mutex;
if (sock >= 0 && sock_mutex) {
const uint8_t *payload = data + strlen(RELAY_MAGIC);
int payload_len = len - strlen(RELAY_MAGIC);
xSemaphoreTake(sock_mutex, portMAX_DELAY);
int s = sock;
xSemaphoreGive(sock_mutex);
if (s >= 0) {
/* Forward as-is — the payload is already encrypted E2E */
lwip_write(s, payload, payload_len);
lwip_write(s, "\n", 1);
ESP_LOGI(TAG, "Relayed %d bytes to C2", payload_len);
}
}
return;
}
}
/* ============================================================
* ESP-NOW send callback
* ============================================================ */
static void espnow_send_cb(const uint8_t *mac, esp_now_send_status_t status)
{
/* Minimal — just log failures */
if (status != ESP_NOW_SEND_SUCCESS) {
ESP_LOGW(TAG, "ESP-NOW send failed to %02X:%02X:%02X:%02X:%02X:%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
}
/* ============================================================
* Probe task — periodically broadcast to find relays
* ============================================================ */
static void probe_task(void *arg)
{
(void)arg;
/* Broadcast peer */
esp_now_peer_info_t bcast = {0};
memset(bcast.peer_addr, 0xFF, 6);
bcast.channel = 0;
bcast.encrypt = false;
esp_now_add_peer(&bcast);
while (s_running) {
/* Broadcast probe */
esp_now_send(bcast.peer_addr,
(uint8_t *)PROBE_MAGIC,
strlen(PROBE_MAGIC));
vTaskDelay(pdMS_TO_TICKS(PROBE_INTERVAL_MS));
}
s_probe_task = NULL;
vTaskDelete(NULL);
}
/* ============================================================
* Public API
* ============================================================ */
bool rt_mesh_start(void)
{
if (s_running) return true;
if (!s_relay_mutex) {
s_relay_mutex = xSemaphoreCreateMutex();
}
if (!s_initialized) {
esp_err_t ret = esp_now_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "esp_now_init failed: %s", esp_err_to_name(ret));
return false;
}
esp_now_register_recv_cb(espnow_recv_cb);
esp_now_register_send_cb(espnow_send_cb);
s_initialized = true;
}
s_running = true;
memset(&s_best_relay, 0, sizeof(s_best_relay));
xTaskCreatePinnedToCore(probe_task, "rt_mesh", 3072, NULL, 4, &s_probe_task, 0);
ESP_LOGI(TAG, "ESP-NOW mesh relay started");
return true;
}
void rt_mesh_stop(void)
{
s_running = false;
/* Wait for probe task to stop */
for (int i = 0; i < 30 && s_probe_task != NULL; i++) {
vTaskDelay(pdMS_TO_TICKS(100));
}
if (s_initialized) {
esp_now_deinit();
s_initialized = false;
}
memset(&s_best_relay, 0, sizeof(s_best_relay));
ESP_LOGI(TAG, "ESP-NOW mesh relay stopped");
}
bool rt_mesh_is_running(void)
{
return s_running;
}
bool rt_mesh_send(const uint8_t *data, size_t len)
{
if (!s_running || !s_best_relay.available) return false;
if (len > 240) { /* ESP-NOW max payload = 250, minus RELAY: prefix */
ESP_LOGW(TAG, "Payload too large for ESP-NOW (%d bytes)", (int)len);
return false;
}
/* Build "RELAY:<payload>" */
uint8_t buf[250];
int prefix_len = strlen(RELAY_MAGIC);
memcpy(buf, RELAY_MAGIC, prefix_len);
memcpy(buf + prefix_len, data, len);
esp_err_t ret = esp_now_send(s_best_relay.mac, buf, prefix_len + len);
return (ret == ESP_OK);
}
void rt_mesh_probe(void)
{
if (!s_running) return;
/* Reset best relay */
if (s_relay_mutex && xSemaphoreTake(s_relay_mutex, portMAX_DELAY) == pdTRUE) {
memset(&s_best_relay, 0, sizeof(s_best_relay));
xSemaphoreGive(s_relay_mutex);
}
/* Broadcast probe immediately */
uint8_t bcast[6];
memset(bcast, 0xFF, 6);
esp_now_send(bcast, (uint8_t *)PROBE_MAGIC, strlen(PROBE_MAGIC));
}
bool rt_mesh_get_relay(rt_mesh_peer_t *out)
{
if (!out) return false;
if (!s_relay_mutex) {
memset(out, 0, sizeof(*out));
return false;
}
xSemaphoreTake(s_relay_mutex, portMAX_DELAY);
memcpy(out, &s_best_relay, sizeof(rt_mesh_peer_t));
xSemaphoreGive(s_relay_mutex);
return out->available;
}
#else /* !CONFIG_RT_MESH */
bool rt_mesh_start(void) { return false; }
void rt_mesh_stop(void) { }
bool rt_mesh_is_running(void) { return false; }
bool rt_mesh_send(const uint8_t *data, size_t len) { (void)data; (void)len; return false; }
void rt_mesh_probe(void) { }
bool rt_mesh_get_relay(rt_mesh_peer_t *out) {
if (out) { memset(out, 0, sizeof(*out)); out->available = false; }
return false;
}
#endif /* CONFIG_RT_MESH */
#endif /* CONFIG_MODULE_REDTEAM */