ebpf-blocker 1.0.0
XDP-based packet blocker using eBPF
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1/// \file main.cpp
2/// \brief Точка входа: event loop, обработка сигналов, метрики Prometheus.
3/// \details Содержит главный цикл приложения: загрузка конфигурации,
4/// инициализация БД и XDP, периодический опрос списков и
5/// обслуживание HTTP-эндпоинта /metrics.
6
7#include "main.h"
8#include "db.h"
9#include "xdp_loader.h"
10
11#include <arpa/inet.h>
12#include <csignal>
13#include <cstring>
14#include <ctime>
15#include <fstream>
16#include <getopt.h>
17#include <iostream>
18#include <map>
19#include <shared_mutex>
20#include <sstream>
21#include <thread>
22#include <unistd.h>
23
24#include "httplib.h"
25
26/**********************************************************************/
27/* Global state */
28/**********************************************************************/
29
30std::atomic<bool> g_running{true};
31std::atomic<bool> g_xdp_attached{false};
32std::atomic<bool> g_detach_requested{false};
33
34/// Потокобезопасный аккумулятор метрик Prometheus.
36 std::map<uint32_t, uint64_t>
37 total_drops; ///< Суммарное количество дропов с запуска процесса.
38 std::map<uint32_t, uint64_t>
39 interval_drops; ///< Дропов за последний интервал опроса.
40 std::map<uint32_t, uint64_t> hourly_drops; ///< Дропов с начала текущего часа.
41 std::map<uint32_t, uint64_t> daily_drops; ///< Дропов с начала текущего дня.
42 size_t whitelist_size = 0; ///< Текущее количество записей в whitelist.
43 size_t blacklist_size = 0; ///< Текущее количество записей в blacklist.
44 bool xdp_attached = false; ///< Прикреплён ли XDP в данный момент.
45 std::shared_mutex mtx; ///< Защищает все поля выше.
46};
47
48static XdpBlocker *g_blocker = nullptr;
49
50/**********************************************************************/
51/* Signal handler */
52/**********************************************************************/
53
54/// Обрабатывает сигналы ОС для управления жизненным циклом.
55///
56/// - SIGINT / SIGTERM: запрос плавного завершения.
57/// - SIGUSR1: запрос открепления XDP (аварийное отключение).
58/// @param[in] sig Номер сигнала.
59static void HandleSignal(int sig) {
60 switch (sig) {
61 case SIGINT:
62 case SIGTERM:
63 g_running = false;
64 break;
65 case SIGUSR1:
66 g_detach_requested = true;
67 break;
68 }
69}
70
71/**********************************************************************/
72/* Argument parsing */
73/**********************************************************************/
74
75/// Парсит аргументы командной строки через getopt_long.
76/// @param[in] argc Количество аргументов из main().
77/// @param[in] argv Массив аргументов из main().
78/// @return Заполненная структура Config (для пропущенных флагов —
79/// значения по умолчанию).
80static Config ParseArgs(int argc, char *argv[]) {
81 Config cfg;
82 static struct option long_opts[] = {
83 {"iface", required_argument, nullptr, 'i'},
84 {"db-path", required_argument, nullptr, 'd'},
85 {"whitelist", required_argument, nullptr, 'w'},
86 {"interval", required_argument, nullptr, 't'},
87 {"metrics-listen", required_argument, nullptr, 'm'},
88 {"help", no_argument, nullptr, 'h'},
89 {nullptr, 0, nullptr, 0}};
90
91 int opt;
92 while ((opt = getopt_long(argc, argv, "i:d:w:t:m:h", long_opts, nullptr)) !=
93 -1) {
94 switch (opt) {
95 case 'i':
96 cfg.iface = optarg;
97 break;
98 case 'd':
99 cfg.db_path = optarg;
100 break;
101 case 'w':
102 cfg.whitelist_conf = optarg;
103 break;
104 case 't':
105 cfg.interval_sec = std::stoi(optarg);
106 break;
107 case 'm':
108 cfg.metrics_listen = optarg;
109 break;
110 case 'h':
111 std::cout
112 << "Usage: ebpf-blocker [options]\n"
113 << " -i, --iface IFACE Network interface (default: eth0)\n"
114 << " -d, --db-path PATH SQLite database path\n"
115 << " -w, --whitelist PATH Whitelist config file\n"
116 << " -t, --interval SEC Poll interval (default: 60)\n"
117 << " -m, --metrics-listen ADDR Metrics HTTP listen address "
118 "(default: 0.0.0.0:9101)\n"
119 << " -h, --help Show this help\n";
120 exit(0);
121 }
122 }
123 return cfg;
124}
125
126/**********************************************************************/
127/* Whitelist file parser */
128/**********************************************************************/
129
130/// Читает записи белого списка из конфигурационного файла.
131///
132/// Формат: одна IP/CIDR на строку, опционально '# комментарий' в конце.
133/// Пустые строки и строки, начинающиеся с '#', игнорируются.
134/// @param[in] path Путь к файлу конфигурации белого списка.
135/// @return Список распарсенных записей.
136static std::vector<BlockEntry> ReadWhitelistFile(const std::string &path) {
137 std::vector<BlockEntry> entries;
138 std::ifstream file(path);
139 if (!file.is_open())
140 return entries;
141
142 std::string line;
143 while (std::getline(file, line)) {
144 if (line.empty() || line[0] == '#')
145 continue;
146 auto comment_pos = line.find('#');
147 std::string ip =
148 (comment_pos != std::string::npos) ? line.substr(0, comment_pos) : line;
149 ip.erase(ip.find_last_not_of(" \t\r\n") + 1);
150 ip.erase(0, ip.find_first_not_of(" \t\r\n"));
151 if (ip.empty())
152 continue;
153 std::string comment = "whitelist.conf";
154 if (comment_pos != std::string::npos) {
155 comment = line.substr(comment_pos + 1);
156 comment.erase(0, comment.find_first_not_of(" \t\r\n"));
157 }
158 entries.push_back({ip, comment});
159 }
160 return entries;
161}
162
163#include <algorithm>
164
165/**********************************************************************/
166/* Logging helpers */
167/**********************************************************************/
168
169/// Логирует текущее количество записей в whitelist и blacklist (heartbeat).
170/// @param[in] whitelist Список записей белого списка.
171/// @param[in] blacklist Список записей чёрного списка.
172static void PrintStats(const std::vector<BlockEntry> &whitelist,
173 const std::vector<BlockEntry> &blacklist) {
174 std::cout << "whitelist: " << whitelist.size() << " entries, "
175 << "blacklist: " << blacklist.size() << " entries" << std::endl;
176}
177
178/// Удобный алиас для карты соответствия IP (uint32) количеству дропов.
179using DropMap = std::map<uint32_t, uint64_t>;
180
181/// Логирует статистику дропов за последний интервал опроса (всего + топ).
182/// @param[in] drops Вектор пар (source_ip, количество_дропов).
183static void
184PrintIntervalDrops(const std::vector<std::pair<uint32_t, uint64_t>> &drops) {
185 uint64_t total = 0;
186 for (const auto &d : drops)
187 total += d.second;
188
189 std::cout << "drops: " << total << " packets";
190
191 if (!drops.empty()) {
192 auto top = std::max_element(
193 drops.begin(), drops.end(),
194 [](const auto &a, const auto &b) { return a.second < b.second; });
195
196 char buf[INET_ADDRSTRLEN];
197 inet_ntop(AF_INET, &top->first, buf, sizeof(buf));
198 std::cout << " | top: " << buf << " (" << top->second << ")";
199 }
200
201 std::cout << std::endl;
202}
203
204/// Логирует периодическую статистику дропов (почасовую/ежедневную) с топ-5
205/// нарушителями.
206/// @param[in] label Метка периода ("почасовая" или "ежедневная").
207/// @param[in] drops Карта дропов (IP -> количество).
208/// @param[in] timestamp Временная метка для лога.
209static void PrintTopDrops(const std::string &label, const DropMap &drops,
210 const char *timestamp) {
211 uint64_t total = 0;
212 for (const auto &d : drops)
213 total += d.second;
214
215 std::cout << "[" << timestamp << "] === " << label << " ===" << std::endl;
216 std::cout << " drops: " << total << " packets" << std::endl;
217
218 if (!drops.empty()) {
219 using Pair = std::pair<uint32_t, uint64_t>;
220 std::vector<Pair> sorted(drops.begin(), drops.end());
221 std::sort(sorted.begin(), sorted.end(),
222 [](const Pair &a, const Pair &b) { return a.second > b.second; });
223
224 std::cout << " top offenders:" << std::endl;
225 int count = 0;
226 char buf[INET_ADDRSTRLEN];
227 for (const auto &d : sorted) {
228 if (count++ >= 5)
229 break;
230 inet_ntop(AF_INET, &d.first, buf, sizeof(buf));
231 std::cout << " " << buf << " " << d.second << std::endl;
232 }
233 }
234 std::cout << std::endl;
235}
236
237/**********************************************************************/
238/* Main entry point */
239/**********************************************************************/
240
241/// Точка входа.
242///
243/// Инициализирует БД, загружает XDP-программу, запускает event loop
244/// с периодическим опросом списков и HTTP-сервером метрик Prometheus.
245/// @return 0 при успешном завершении.
246int main(int argc, char *argv[]) {
247 Config cfg = ParseArgs(argc, argv);
248
249 signal(SIGINT, HandleSignal);
250 signal(SIGTERM, HandleSignal);
251 signal(SIGUSR1, HandleSignal);
252
253 try {
254 BlockerDB db(cfg.db_path);
255 db.Migrate();
256
257 XdpBlocker blocker;
258 g_blocker = &blocker;
259
260 blocker.Load(cfg.iface);
261 g_xdp_attached = true;
262
263 auto whitelist_conf = ReadWhitelistFile(cfg.whitelist_conf);
264 if (!whitelist_conf.empty()) {
265 std::cout << "loaded " << whitelist_conf.size() << " entries from "
266 << cfg.whitelist_conf << std::endl;
267 }
268
269 auto whitelist_db = db.GetWhitelist();
270 whitelist_conf.insert(whitelist_conf.end(), whitelist_db.begin(),
271 whitelist_db.end());
272
273 auto blacklist = db.GetBlacklist();
274
275 if (whitelist_conf.empty()) {
276 std::cerr << "WARNING: whitelist is empty! "
277 << "Your SSH connection may be at risk. "
278 << "Sleeping 30 seconds before applying rules..." << std::endl;
279 for (int i = 0; i < 30 && g_running; i++)
280 sleep(1);
281 }
282
283 blocker.UpdateWhitelist(whitelist_conf);
284 blocker.UpdateBlacklist(blacklist);
285
286 std::cout << "blocker started on " << cfg.iface << " (poll every "
287 << cfg.interval_sec << "s)" << std::endl;
288 PrintStats(whitelist_conf, blacklist);
289
290 auto metrics = std::make_shared<MetricsStore>();
291 {
292 std::lock_guard<std::shared_mutex> lock(metrics->mtx);
293 metrics->whitelist_size = whitelist_conf.size();
294 metrics->blacklist_size = blacklist.size();
295 metrics->xdp_attached = true;
296 metrics->total_drops.clear();
297 }
298
299 std::thread http_thread([metrics, &cfg]() {
300 httplib::Server svr;
301 svr.Get("/metrics", [metrics](const httplib::Request &,
302 httplib::Response &res) {
303 std::shared_lock<std::shared_mutex> lock(metrics->mtx);
304 std::ostringstream ss;
305 ss << "# HELP ebpf_blocker_whitelist_entries Current whitelist "
306 "entries\n"
307 << "# TYPE ebpf_blocker_whitelist_entries gauge\n"
308 << "ebpf_blocker_whitelist_entries " << metrics->whitelist_size
309 << "\n"
310 << "# HELP ebpf_blocker_blacklist_entries Current blacklist "
311 "entries\n"
312 << "# TYPE ebpf_blocker_blacklist_entries gauge\n"
313 << "ebpf_blocker_blacklist_entries " << metrics->blacklist_size
314 << "\n"
315 << "# HELP ebpf_blocker_xdp_attached XDP attached flag (1=attached, "
316 "0=detached)\n"
317 << "# TYPE ebpf_blocker_xdp_attached gauge\n"
318 << "ebpf_blocker_xdp_attached " << (metrics->xdp_attached ? 1 : 0)
319 << "\n"
320 << "# HELP ebpf_blocker_drops_total Total dropped packets since "
321 "process start\n"
322 << "# TYPE ebpf_blocker_drops_total counter\n";
323 char ip_buf[INET_ADDRSTRLEN];
324 for (const auto &[ip, count] : metrics->total_drops) {
325 inet_ntop(AF_INET, &ip, ip_buf, sizeof(ip_buf));
326 ss << "ebpf_blocker_drops_total{ip=\"" << ip_buf << "\"} " << count
327 << "\n";
328 }
329 ss << "# HELP ebpf_blocker_drops_interval Drops in last poll interval\n"
330 << "# TYPE ebpf_blocker_drops_interval gauge\n";
331 if (metrics->interval_drops.empty()) {
332 ss << "ebpf_blocker_drops_interval 0\n";
333 } else {
334 for (const auto &[ip, count] : metrics->interval_drops) {
335 inet_ntop(AF_INET, &ip, ip_buf, sizeof(ip_buf));
336 ss << "ebpf_blocker_drops_interval{ip=\"" << ip_buf << "\"} "
337 << count << "\n";
338 }
339 }
340 ss << "# HELP ebpf_blocker_drops_hourly Drops since start of current "
341 "hour\n"
342 << "# TYPE ebpf_blocker_drops_hourly gauge\n";
343 if (metrics->hourly_drops.empty()) {
344 ss << "ebpf_blocker_drops_hourly 0\n";
345 } else {
346 for (const auto &[ip, count] : metrics->hourly_drops) {
347 inet_ntop(AF_INET, &ip, ip_buf, sizeof(ip_buf));
348 ss << "ebpf_blocker_drops_hourly{ip=\"" << ip_buf << "\"} " << count
349 << "\n";
350 }
351 }
352 ss << "# HELP ebpf_blocker_drops_daily Drops since start of current "
353 "day\n"
354 << "# TYPE ebpf_blocker_drops_daily gauge\n";
355 if (metrics->daily_drops.empty()) {
356 ss << "ebpf_blocker_drops_daily 0\n";
357 } else {
358 for (const auto &[ip, count] : metrics->daily_drops) {
359 inet_ntop(AF_INET, &ip, ip_buf, sizeof(ip_buf));
360 ss << "ebpf_blocker_drops_daily{ip=\"" << ip_buf << "\"} " << count
361 << "\n";
362 }
363 }
364 res.set_content(ss.str(), "text/plain; charset=utf-8");
365 });
366 std::cout << "metrics HTTP server listening on " << cfg.metrics_listen
367 << std::endl;
368 auto colon = cfg.metrics_listen.find_last_of(':');
369 auto host = cfg.metrics_listen.substr(0, colon);
370 auto port = std::stoi(cfg.metrics_listen.substr(colon + 1));
371 if (!svr.listen(host, port))
372 std::cerr << "failed to start metrics HTTP server on "
373 << cfg.metrics_listen << std::endl;
374 });
375 http_thread.detach();
376
377 int prev_hour = -1;
378 int prev_day = -1;
379
380 while (g_running) {
381 for (int i = 0; i < cfg.interval_sec && g_running; i++)
382 sleep(1);
383
384 if (!g_running)
385 break;
386
387 if (g_detach_requested) {
388 g_blocker->Unload();
389 g_xdp_attached = false;
390 g_detach_requested = false;
391 std::cout << "SIGUSR1: XDP detached, traffic passes through"
392 << std::endl;
393 }
394
395 time_t now = time(nullptr);
396 struct tm tm_now;
397 localtime_r(&now, &tm_now);
398
399 int cur_hour = tm_now.tm_hour;
400 int cur_min = tm_now.tm_min;
401 int cur_day = tm_now.tm_yday;
402
403 if (!g_xdp_attached) {
404 try {
405 blocker.Load(cfg.iface);
406 g_xdp_attached = true;
407 std::cout << "XDP re-attached" << std::endl;
408 } catch (const std::exception &e) {
409 std::cerr << "failed to re-attach XDP: " << e.what() << std::endl;
410 continue;
411 }
412 }
413
414 try {
415 whitelist_conf = ReadWhitelistFile(cfg.whitelist_conf);
416 whitelist_db = db.GetWhitelist();
417 whitelist_conf.insert(whitelist_conf.end(), whitelist_db.begin(),
418 whitelist_db.end());
419
420 blacklist = db.GetBlacklist();
421
422 blocker.UpdateWhitelist(whitelist_conf);
423 blocker.UpdateBlacklist(blacklist);
424
425 auto drops = blocker.ReadAndClearDropCounters();
426
427 for (const auto &d : drops)
428 db.UpdateLastBlockedAt(d.first);
429
430 PrintStats(whitelist_conf, blacklist);
431 PrintIntervalDrops(drops);
432
433 {
434 std::lock_guard<std::shared_mutex> lock(metrics->mtx);
435 for (const auto &d : drops) {
436 metrics->total_drops[d.first] += d.second;
437 metrics->hourly_drops[d.first] += d.second;
438 metrics->daily_drops[d.first] += d.second;
439 }
440 metrics->interval_drops.clear();
441 for (const auto &d : drops)
442 metrics->interval_drops[d.first] += d.second;
443 metrics->whitelist_size = whitelist_conf.size();
444 metrics->blacklist_size = blacklist.size();
445 metrics->xdp_attached = g_xdp_attached;
446 }
447
448 char ts[64];
449 strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M", &tm_now);
450
451 if (cur_min == 0 && cur_hour != prev_hour) {
452 DropMap snapshot;
453 {
454 std::lock_guard<std::shared_mutex> lock(metrics->mtx);
455 snapshot = metrics->hourly_drops;
456 metrics->hourly_drops.clear();
457 }
458 if (!snapshot.empty())
459 PrintTopDrops("Hourly stats (last 60 min)", snapshot, ts);
460 prev_hour = cur_hour;
461 }
462
463 if (cur_hour == 0 && cur_min == 0 && cur_day != prev_day) {
464 DropMap snapshot;
465 {
466 std::lock_guard<std::shared_mutex> lock(metrics->mtx);
467 snapshot = metrics->daily_drops;
468 metrics->daily_drops.clear();
469 }
470 if (!snapshot.empty())
471 PrintTopDrops("Daily stats (last 24h)", snapshot, ts);
472 prev_day = cur_day;
473 }
474 } catch (const std::exception &e) {
475 std::cerr << "update error: " << e.what() << std::endl;
476 }
477 }
478
479 std::cout << "shutting down..." << std::endl;
480 blocker.Unload();
481 g_blocker = nullptr;
482
483 } catch (const std::exception &e) {
484 std::cerr << "fatal error: " << e.what() << std::endl;
485 return 1;
486 }
487
488 return 0;
489}
Definition db.h:41
std::vector< BlockEntry > GetBlacklist()
Definition db.cpp:198
void Migrate()
Definition db.cpp:89
void UpdateLastBlockedAt(uint32_t ip)
Definition db.cpp:146
std::vector< BlockEntry > GetWhitelist()
Definition db.cpp:194
std::vector< std::pair< uint32_t, uint64_t > > ReadAndClearDropCounters()
Читает все записи из карты drop_counter и атомарно очищает их.
void Unload()
Открепляет XDP и закрывает BPF-объект. Безопасен для многократного вызова.
void UpdateWhitelist(const std::vector< BlockEntry > &entries)
void UpdateBlacklist(const std::vector< BlockEntry > &entries)
void Load(const std::string &iface)
Обёртка SQLite-базы данных и утилиты парсинга CIDR.
std::atomic< bool > g_xdp_attached
Глобальный флаг: true когда XDP-программа прикреплена к интерфейсу.
Definition main.cpp:31
int main(int argc, char *argv[])
Definition main.cpp:246
static std::vector< BlockEntry > ReadWhitelistFile(const std::string &path)
Definition main.cpp:136
static XdpBlocker * g_blocker
Definition main.cpp:48
static void PrintIntervalDrops(const std::vector< std::pair< uint32_t, uint64_t > > &drops)
Definition main.cpp:184
std::atomic< bool > g_detach_requested
Глобальный флаг: true когда SIGUSR1 запросил detach XDP.
Definition main.cpp:32
static void PrintTopDrops(const std::string &label, const DropMap &drops, const char *timestamp)
Definition main.cpp:209
static void PrintStats(const std::vector< BlockEntry > &whitelist, const std::vector< BlockEntry > &blacklist)
Definition main.cpp:172
std::map< uint32_t, uint64_t > DropMap
Удобный алиас для карты соответствия IP (uint32) количеству дропов.
Definition main.cpp:179
static Config ParseArgs(int argc, char *argv[])
Definition main.cpp:80
std::atomic< bool > g_running
Глобальный флаг: false запускает graceful shutdown.
Definition main.cpp:30
static void HandleSignal(int sig)
Definition main.cpp:59
Конфигурация и глобальное состояние eBPF Blocker.
Конфигурация приложения, полученная из аргументов командной строки.
Definition main.h:11
std::string metrics_listen
Адрес HTTP-сервера метрик.
Definition main.h:17
std::string iface
Сетевой интерфейс для прикрепления XDP.
Definition main.h:12
std::string whitelist_conf
Файл конфигурации белого списка.
Definition main.h:15
int interval_sec
Интервал опроса БД в секундах.
Definition main.h:18
std::string db_path
Путь к SQLite-базе данных.
Definition main.h:13
Потокобезопасный аккумулятор метрик Prometheus.
Definition main.cpp:35
std::map< uint32_t, uint64_t > daily_drops
Дропов с начала текущего дня.
Definition main.cpp:41
std::shared_mutex mtx
Защищает все поля выше.
Definition main.cpp:45
std::map< uint32_t, uint64_t > total_drops
Суммарное количество дропов с запуска процесса.
Definition main.cpp:37
std::map< uint32_t, uint64_t > hourly_drops
Дропов с начала текущего часа.
Definition main.cpp:40
bool xdp_attached
Прикреплён ли XDP в данный момент.
Definition main.cpp:44
std::map< uint32_t, uint64_t > interval_drops
Дропов за последний интервал опроса.
Definition main.cpp:39
size_t blacklist_size
Текущее количество записей в blacklist.
Definition main.cpp:43
size_t whitelist_size
Текущее количество записей в whitelist.
Definition main.cpp:42
Загрузчик BPF-объекта и менеджер XDP-программы.