137 std::vector<BlockEntry> entries;
138 std::ifstream file(path);
143 while (std::getline(file, line)) {
144 if (line.empty() || line[0] ==
'#')
146 auto comment_pos = line.find(
'#');
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"));
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"));
158 entries.push_back({ip, comment});
210 const char *timestamp) {
212 for (
const auto &d : drops)
215 std::cout <<
"[" << timestamp <<
"] === " << label <<
" ===" << std::endl;
216 std::cout <<
" drops: " << total <<
" packets" << std::endl;
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; });
224 std::cout <<
" top offenders:" << std::endl;
226 char buf[INET_ADDRSTRLEN];
227 for (
const auto &d : sorted) {
230 inet_ntop(AF_INET, &d.first, buf,
sizeof(buf));
231 std::cout <<
" " << buf <<
" " << d.second << std::endl;
234 std::cout << std::endl;
246int main(
int argc,
char *argv[]) {
264 if (!whitelist_conf.empty()) {
265 std::cout <<
"loaded " << whitelist_conf.size() <<
" entries from "
270 whitelist_conf.insert(whitelist_conf.end(), whitelist_db.begin(),
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++)
286 std::cout <<
"blocker started on " << cfg.
iface <<
" (poll every "
290 auto metrics = std::make_shared<MetricsStore>();
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();
299 std::thread http_thread([metrics, &cfg]() {
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 "
307 <<
"# TYPE ebpf_blocker_whitelist_entries gauge\n"
308 <<
"ebpf_blocker_whitelist_entries " << metrics->whitelist_size
310 <<
"# HELP ebpf_blocker_blacklist_entries Current blacklist "
312 <<
"# TYPE ebpf_blocker_blacklist_entries gauge\n"
313 <<
"ebpf_blocker_blacklist_entries " << metrics->blacklist_size
315 <<
"# HELP ebpf_blocker_xdp_attached XDP attached flag (1=attached, "
317 <<
"# TYPE ebpf_blocker_xdp_attached gauge\n"
318 <<
"ebpf_blocker_xdp_attached " << (metrics->xdp_attached ? 1 : 0)
320 <<
"# HELP ebpf_blocker_drops_total Total dropped packets since "
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
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";
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 <<
"\"} "
340 ss <<
"# HELP ebpf_blocker_drops_hourly Drops since start of current "
342 <<
"# TYPE ebpf_blocker_drops_hourly gauge\n";
343 if (metrics->hourly_drops.empty()) {
344 ss <<
"ebpf_blocker_drops_hourly 0\n";
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
352 ss <<
"# HELP ebpf_blocker_drops_daily Drops since start of current "
354 <<
"# TYPE ebpf_blocker_drops_daily gauge\n";
355 if (metrics->daily_drops.empty()) {
356 ss <<
"ebpf_blocker_drops_daily 0\n";
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
364 res.set_content(ss.str(),
"text/plain; charset=utf-8");
366 std::cout <<
"metrics HTTP server listening on " << cfg.
metrics_listen
371 if (!svr.listen(host, port))
372 std::cerr <<
"failed to start metrics HTTP server on "
375 http_thread.detach();
391 std::cout <<
"SIGUSR1: XDP detached, traffic passes through"
395 time_t now = time(
nullptr);
397 localtime_r(&now, &tm_now);
399 int cur_hour = tm_now.tm_hour;
400 int cur_min = tm_now.tm_min;
401 int cur_day = tm_now.tm_yday;
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;
417 whitelist_conf.insert(whitelist_conf.end(), whitelist_db.begin(),
427 for (
const auto &d : drops)
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;
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();
449 strftime(ts,
sizeof(ts),
"%Y-%m-%d %H:%M", &tm_now);
451 if (cur_min == 0 && cur_hour != prev_hour) {
454 std::lock_guard<std::shared_mutex> lock(metrics->mtx);
455 snapshot = metrics->hourly_drops;
456 metrics->hourly_drops.clear();
458 if (!snapshot.empty())
460 prev_hour = cur_hour;
463 if (cur_hour == 0 && cur_min == 0 && cur_day != prev_day) {
466 std::lock_guard<std::shared_mutex> lock(metrics->mtx);
467 snapshot = metrics->daily_drops;
468 metrics->daily_drops.clear();
470 if (!snapshot.empty())
474 }
catch (
const std::exception &e) {
475 std::cerr <<
"update error: " << e.what() << std::endl;
479 std::cout <<
"shutting down..." << std::endl;
483 }
catch (
const std::exception &e) {
484 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.