140 std::vector<BlockEntry> entries;
141 std::ifstream file(path);
146 while (std::getline(file, line)) {
147 if (line.empty() || line[0] ==
'#')
149 auto comment_pos = line.find(
'#');
151 (comment_pos != std::string::npos) ? line.substr(0, comment_pos) : line;
152 ip.erase(ip.find_last_not_of(
" \t\r\n") + 1);
153 ip.erase(0, ip.find_first_not_of(
" \t\r\n"));
156 std::string comment =
"whitelist.conf";
157 if (comment_pos != std::string::npos) {
158 comment = line.substr(comment_pos + 1);
159 comment.erase(0, comment.find_first_not_of(
" \t\r\n"));
161 entries.push_back({ip, comment});
249int main(
int argc,
char *argv[]) {
267 if (!whitelist_conf.empty()) {
268 std::cout <<
"loaded " << whitelist_conf.size() <<
" entries from "
273 whitelist_conf.insert(whitelist_conf.end(), whitelist_db.begin(),
278 if (whitelist_conf.empty()) {
279 std::cerr <<
"WARNING: whitelist is empty! "
280 <<
"Your SSH connection may be at risk. "
281 <<
"Sleeping 30 seconds before applying rules..." << std::endl;
282 for (
int i = 0; i < 30 &&
g_running; i++)
289 std::cout <<
"blocker started on " << cfg.
iface <<
" (poll every "
295 const char *env_version = std::getenv(
"PRODUCTS_VERSION");
296 std::string products_version =
297 (env_version !=
nullptr && *env_version !=
'\0') ? env_version :
"dev";
298 auto products_log = std::make_unique<products_log::ProductsLog>(
299 "ebpf-blocker", products_version);
300 products_log->PostStart();
302 auto metrics = std::make_shared<MetricsStore>();
304 std::lock_guard<std::shared_mutex> lock(metrics->mtx);
305 metrics->whitelist_size = whitelist_conf.size();
306 metrics->blacklist_size = blacklist.size();
307 metrics->xdp_attached =
true;
308 metrics->total_drops.clear();
311 std::thread http_thread([metrics, &cfg]() {
313 svr.Get(
"/metrics", [metrics](
const httplib::Request &,
314 httplib::Response &res) {
315 std::shared_lock<std::shared_mutex> lock(metrics->mtx);
316 std::ostringstream ss;
317 ss <<
"# HELP ebpf_blocker_whitelist_entries Current whitelist "
319 <<
"# TYPE ebpf_blocker_whitelist_entries gauge\n"
320 <<
"ebpf_blocker_whitelist_entries " << metrics->whitelist_size
322 <<
"# HELP ebpf_blocker_blacklist_entries Current blacklist "
324 <<
"# TYPE ebpf_blocker_blacklist_entries gauge\n"
325 <<
"ebpf_blocker_blacklist_entries " << metrics->blacklist_size
327 <<
"# HELP ebpf_blocker_xdp_attached XDP attached flag (1=attached, "
329 <<
"# TYPE ebpf_blocker_xdp_attached gauge\n"
330 <<
"ebpf_blocker_xdp_attached " << (metrics->xdp_attached ? 1 : 0)
332 <<
"# HELP ebpf_blocker_drops_total Total dropped packets since "
334 <<
"# TYPE ebpf_blocker_drops_total counter\n";
335 char ip_buf[INET_ADDRSTRLEN];
336 for (
const auto &[ip, count] : metrics->total_drops) {
337 inet_ntop(AF_INET, &ip, ip_buf,
sizeof(ip_buf));
338 ss <<
"ebpf_blocker_drops_total{ip=\"" << ip_buf <<
"\"} " << count
341 ss <<
"# HELP ebpf_blocker_drops_interval Drops in last poll interval\n"
342 <<
"# TYPE ebpf_blocker_drops_interval gauge\n";
343 if (metrics->interval_drops.empty()) {
344 ss <<
"ebpf_blocker_drops_interval 0\n";
346 for (
const auto &[ip, count] : metrics->interval_drops) {
347 inet_ntop(AF_INET, &ip, ip_buf,
sizeof(ip_buf));
348 ss <<
"ebpf_blocker_drops_interval{ip=\"" << ip_buf <<
"\"} "
352 ss <<
"# HELP ebpf_blocker_drops_hourly Drops since start of current "
354 <<
"# TYPE ebpf_blocker_drops_hourly gauge\n";
355 if (metrics->hourly_drops.empty()) {
356 ss <<
"ebpf_blocker_drops_hourly 0\n";
358 for (
const auto &[ip, count] : metrics->hourly_drops) {
359 inet_ntop(AF_INET, &ip, ip_buf,
sizeof(ip_buf));
360 ss <<
"ebpf_blocker_drops_hourly{ip=\"" << ip_buf <<
"\"} " << count
364 ss <<
"# HELP ebpf_blocker_drops_daily Drops since start of current "
366 <<
"# TYPE ebpf_blocker_drops_daily gauge\n";
367 if (metrics->daily_drops.empty()) {
368 ss <<
"ebpf_blocker_drops_daily 0\n";
370 for (
const auto &[ip, count] : metrics->daily_drops) {
371 inet_ntop(AF_INET, &ip, ip_buf,
sizeof(ip_buf));
372 ss <<
"ebpf_blocker_drops_daily{ip=\"" << ip_buf <<
"\"} " << count
376 res.set_content(ss.str(),
"text/plain; charset=utf-8");
378 std::cout <<
"metrics HTTP server listening on " << cfg.
metrics_listen
383 if (!svr.listen(host, port))
384 std::cerr <<
"failed to start metrics HTTP server on "
387 http_thread.detach();
403 std::cout <<
"SIGUSR1: XDP detached, traffic passes through"
407 time_t now = time(
nullptr);
409 localtime_r(&now, &tm_now);
411 int cur_hour = tm_now.tm_hour;
412 int cur_min = tm_now.tm_min;
413 int cur_day = tm_now.tm_yday;
419 std::cout <<
"XDP re-attached" << std::endl;
420 }
catch (
const std::exception &e) {
421 std::cerr <<
"failed to re-attach XDP: " << e.what() << std::endl;
429 whitelist_conf.insert(whitelist_conf.end(), whitelist_db.begin(),
439 for (
const auto &d : drops)
446 std::lock_guard<std::shared_mutex> lock(metrics->mtx);
447 for (
const auto &d : drops) {
448 metrics->total_drops[d.first] += d.second;
449 metrics->hourly_drops[d.first] += d.second;
450 metrics->daily_drops[d.first] += d.second;
452 metrics->interval_drops.clear();
453 for (
const auto &d : drops)
454 metrics->interval_drops[d.first] += d.second;
455 metrics->whitelist_size = whitelist_conf.size();
456 metrics->blacklist_size = blacklist.size();
461 strftime(ts,
sizeof(ts),
"%Y-%m-%d %H:%M", &tm_now);
463 if (cur_min == 0 && cur_hour != prev_hour) {
466 std::lock_guard<std::shared_mutex> lock(metrics->mtx);
467 snapshot = metrics->hourly_drops;
468 metrics->hourly_drops.clear();
470 if (!snapshot.empty())
472 prev_hour = cur_hour;
475 if (cur_hour == 0 && cur_min == 0 && cur_day != prev_day) {
478 std::lock_guard<std::shared_mutex> lock(metrics->mtx);
479 snapshot = metrics->daily_drops;
480 metrics->daily_drops.clear();
482 if (!snapshot.empty())
486 }
catch (
const std::exception &e) {
487 std::cerr <<
"update error: " << e.what() << std::endl;
491 std::cout <<
"shutting down..." << std::endl;
492 products_log->PostShutdown();
496 }
catch (
const std::exception &e) {
497 std::cerr <<
"fatal error: " << e.what() << std::endl;
std::atomic< bool > g_xdp_attached
Глобальный флаг: true когда XDP-программа прикреплена к интерфейсу.
int main(int argc, char *argv[])
static std::vector< BlockEntry > ReadWhitelistFile(const std::string &path)
static XdpBlocker * g_blocker
static void PrintIntervalDrops(const std::vector< std::pair< uint32_t, uint64_t > > &drops)
std::atomic< bool > g_detach_requested
Глобальный флаг: true когда SIGUSR1 запросил detach XDP.
static void PrintTopDrops(const std::string &label, const DropMap &drops, const char *timestamp)
static void PrintStats(const std::vector< BlockEntry > &whitelist, const std::vector< BlockEntry > &blacklist)
std::map< uint32_t, uint64_t > DropMap
Удобный алиас для карты соответствия IP (uint32) количеству дропов.
static Config ParseArgs(int argc, char *argv[])
std::atomic< bool > g_running
Глобальный флаг: false запускает graceful shutdown.
static void HandleSignal(int sig)
Конфигурация и глобальное состояние eBPF Blocker.