#include "stats.h" #include "nftables.h" #include "log.h" #include "geo.h" #include void show_active_bans(void) { msg(C_CYAN, "=== 🔥 活跃封禁列表 (最新 5 条) ==="); /* 解析并提取IP和过期时间 */ char command[MAX_COMMAND_LEN]; snprintf(command, sizeof(command), "{ nft list set %s %s 2>/dev/null; nft list set %s %s 2>/dev/null; } | " "grep -E 'expires [0-9]+(s|m|h|d|ms)' | " "awk '{ip=\"\"; time=\"\"; for(i=1;i<=NF;i++) { if($i==\"expires\") time=$(i+1); else if(index($i,\".\")>0 || index($i,\":\")>0) ip=$i } if(ip && time) print ip\" \"time}' | " "sort -t' ' -k2 | tail -n 5", NFT_TABLE, NFT_SET, NFT_TABLE, NFT_SET_V6); FILE *fp = popen(command, "r"); if (!fp) { printf("(无法获取数据)\n\n"); return; } char line[MAX_LINE_LEN]; int count = 0; printf("%s %-20s %-15s%s\n", C_YELLOW, "IP 地址", "剩余时间", C_RESET); printf("-------------------------------------\n"); while (fgets(line, sizeof(line), fp)) { line[strcspn(line, "\n")] = 0; if (strlen(line) > 0) { char ip[MAX_IP_LEN] = {0}; char time_raw[64] = {0}; if (sscanf(line, "%s %s", ip, time_raw) == 2) { // 解析nft时间格式:86394588ms, 23h59m54s等 long long total_s = 0; char *p = time_raw; long long num = 0; while (*p) { if (isdigit(*p)) { num = num * 10 + (*p - '0'); } else { if (*p == 'd') total_s += num * 86400; else if (*p == 'h') total_s += num * 3600; else if (*p == 'm' && *(p+1) == 's') { total_s += num / 1000; p++; } else if (*p == 'm') total_s += num * 60; else if (*p == 's') total_s += num; num = 0; } p++; } long long h = total_s / 3600; long long m = (total_s % 3600) / 60; long long s = total_s % 60; char time_str[64]; if (h > 0) { snprintf(time_str, sizeof(time_str), "%lldh%lldm%llds", h, m, s); } else if (m > 0) { snprintf(time_str, sizeof(time_str), "%lldm%llds", m, s); } else { snprintf(time_str, sizeof(time_str), "%llds", s); } printf(" - %-20s %s\n", ip, time_str); count++; } } } pclose(fp); if (count == 0) { printf("(目前没有被封禁的 IP)\n"); } printf("\n"); } /* 检查段idx是否会被更精确的段取代(相同count但更小mask) */ static inline bool is_agg_replaced(void *agg_array, int agg_count, int idx) { struct agg_entry { char subnet[64]; int count; int mask; }; struct agg_entry *agg = (struct agg_entry *)agg_array; for (int j = 0; j < agg_count; ++j) { if (agg[j].mask > agg[idx].mask && agg[j].count == agg[idx].count && strncmp(agg[j].subnet, agg[idx].subnet, strlen(agg[idx].subnet)) == 0) { return true; } } return false; } void show_subnet_aggregation(void) { msg(C_CYAN, "=== 📊 攻击源聚合统计 (IP 段归类) ==="); FILE *fp = fopen(PERSIST_FILE, "r"); if (!fp) { printf("(无数据)\n\n"); return; } /* 统计各级网段 */ struct { char subnet[64]; int count; int mask; } agg[256]; int agg_count = 0; int v6_count = 0; char line[MAX_LINE_LEN]; while (fgets(line, sizeof(line), fp)) { line[strcspn(line, "\n")] = 0; if (strlen(line) == 0) continue; /* 提取IP部分 */ char *pipe = strchr(line, '|'); if (pipe) *pipe = '\0'; if (strchr(line, ':')) { v6_count++; continue; } /* 解析IPv4,统计/8, /16, /24 */ unsigned int a, b, c, d; if (sscanf(line, "%u.%u.%u.%u", &a, &b, &c, &d) == 4) { char subnet_24[64], subnet_16[64], subnet_8[64]; snprintf(subnet_24, sizeof(subnet_24), "%u.%u.%u", a, b, c); snprintf(subnet_16, sizeof(subnet_16), "%u.%u", a, b); snprintf(subnet_8, sizeof(subnet_8), "%u", a); /* 统计/24 */ int found = 0; for (int i = 0; i < agg_count; ++i) { if (agg[i].mask == 24 && strcmp(agg[i].subnet, subnet_24) == 0) { agg[i].count++; found = 1; break; } } if (!found && agg_count < 256) { snprintf(agg[agg_count].subnet, sizeof(agg[agg_count].subnet), "%s", subnet_24); agg[agg_count].count = 1; agg[agg_count].mask = 24; agg_count++; } /* 统计/16 */ found = 0; for (int i = 0; i < agg_count; ++i) { if (agg[i].mask == 16 && strcmp(agg[i].subnet, subnet_16) == 0) { agg[i].count++; found = 1; break; } } if (!found && agg_count < 256) { snprintf(agg[agg_count].subnet, sizeof(agg[agg_count].subnet), "%s", subnet_16); agg[agg_count].count = 1; agg[agg_count].mask = 16; agg_count++; } /* 统计/8 */ found = 0; for (int i = 0; i < agg_count; ++i) { if (agg[i].mask == 8 && strcmp(agg[i].subnet, subnet_8) == 0) { agg[i].count++; found = 1; break; } } if (!found && agg_count < 256) { snprintf(agg[agg_count].subnet, sizeof(agg[agg_count].subnet), "%s", subnet_8); agg[agg_count].count = 1; agg[agg_count].mask = 8; agg_count++; } } } fclose(fp); /* 第一步:按count降序排序,同count时按IP第一段数字排序 */ for (int i = 0; i < agg_count - 1; ++i) { for (int j = i + 1; j < agg_count; ++j) { bool should_swap = false; if (agg[j].count > agg[i].count) { should_swap = true; } else if (agg[j].count == agg[i].count) { /* 同count时,按IP第一段数字排序 */ int ip_i = atoi(agg[i].subnet); int ip_j = atoi(agg[j].subnet); if (ip_j < ip_i) { should_swap = true; } } if (should_swap) { char tmp_subnet[64]; int tmp_count = agg[i].count, tmp_mask = agg[i].mask; strcpy(tmp_subnet, agg[i].subnet); agg[i].count = agg[j].count; agg[i].mask = agg[j].mask; strcpy(agg[i].subnet, agg[j].subnet); agg[j].count = tmp_count; agg[j].mask = tmp_mask; strcpy(agg[j].subnet, tmp_subnet); } } } /* 第二步:将子网段移到父网段后面形成层级 */ for (int i = 0; i < agg_count; ++i) { /* 查找i的所有直接子网段(下一级),移到i后面 */ int insert_pos = i + 1; /* 先跳过已经在正确位置的子网 */ while (insert_pos < agg_count && agg[insert_pos].mask > agg[i].mask && strncmp(agg[insert_pos].subnet, agg[i].subnet, strlen(agg[i].subnet)) == 0) { insert_pos++; } /* 从insert_pos后面查找其他子网 */ for (int j = insert_pos; j < agg_count; ++j) { /* 检查j是否是i的子网(前缀完全匹配且mask更大) */ size_t prefix_len = strlen(agg[i].subnet); if (agg[j].mask > agg[i].mask && strncmp(agg[j].subnet, agg[i].subnet, prefix_len) == 0 && (agg[j].subnet[prefix_len] == '.' || agg[j].subnet[prefix_len] == '\0')) { /* j是i的子网,移动到insert_pos */ char tmp_subnet[64]; int tmp_count = agg[j].count, tmp_mask = agg[j].mask; strcpy(tmp_subnet, agg[j].subnet); /* 将insert_pos到j-1的元素向后移动 */ for (int k = j; k > insert_pos; --k) { agg[k].count = agg[k-1].count; agg[k].mask = agg[k-1].mask; strcpy(agg[k].subnet, agg[k-1].subnet); } /* 插入到insert_pos */ agg[insert_pos].count = tmp_count; agg[insert_pos].mask = tmp_mask; strcpy(agg[insert_pos].subnet, tmp_subnet); insert_pos++; } } } /* 输出聚合结果,只显示count>=2的,并去重:如果大段和小段数量相同则只显示小段 */ bool has_output = false; int show_count = 0; int aggregated_count = 0; for (int i = 0; i < agg_count && show_count < 10; ++i) { if (agg[i].count < 2 || is_agg_replaced(agg, agg_count, i)) { continue; } has_output = true; /* 检查是否是子网(用于缩进显示和重复计数检测) */ bool is_child = false; for (int k = 0; k < i; ++k) { if (agg[k].count >= 2 && agg[k].mask < agg[i].mask && !is_agg_replaced(agg, agg_count, k)) { size_t prefix_len = strlen(agg[k].subnet); if (strncmp(agg[i].subnet, agg[k].subnet, prefix_len) == 0 && (agg[i].subnet[prefix_len] == '.' || agg[i].subnet[prefix_len] == '\0')) { is_child = true; break; } } } /* 非子网段才计入aggregated_count(避免重复计数) */ if (!is_child) { aggregated_count += agg[i].count; } /* 显示段信息,子网段增加缩进 */ char display_subnet[64]; const char *suffix = (agg[i].mask == 8) ? ".0.0.0/8" : (agg[i].mask == 16) ? ".0.0/16" : ".0/24"; snprintf(display_subnet, sizeof(display_subnet), "%s%s", agg[i].subnet, suffix); if (is_child) { printf(" └─ %-19s %s(%d 个)%s\n", display_subnet, C_RED, agg[i].count, C_RESET); } else { printf(" - %-22s %s(%d 个)%s\n", display_subnet, C_RED, agg[i].count, C_RESET); } show_count++; } /* 计算散乱IP数量 */ int total_ipv4 = 0; FILE *fp_count = fopen(PERSIST_FILE, "r"); if (fp_count) { char line_tmp[MAX_LINE_LEN]; while (fgets(line_tmp, sizeof(line_tmp), fp_count)) { line_tmp[strcspn(line_tmp, "\n")] = 0; if (strlen(line_tmp) > 0) { char *pipe = strchr(line_tmp, '|'); if (pipe) *pipe = '\0'; if (!strchr(line_tmp, ':')) total_ipv4++; } } fclose(fp_count); } int scattered_count = total_ipv4 - aggregated_count; /* 显示散乱IP */ if (scattered_count > 0 || (!has_output && total_ipv4 > 0)) { if (scattered_count > 0) { printf(" - (散乱 IPv4) (%d 个)\n", scattered_count); } else { printf(" - (散乱 IPv4)\n"); } } if (v6_count > 0) { printf(" - (IPv6 地址) (%d 个)\n", v6_count); } printf("\n"); } void show_country_stats(void) { msg(C_CYAN, "=== 🌍 攻击源国家/地区统计 ==="); FILE *fp = fopen(PERSIST_FILE, "r"); if (!fp) { printf("(暂无数据)\n\n"); return; } struct { char code[MAX_COUNTRY_CODE]; int count; } stats[128]; int stat_count = 0; char line[MAX_LINE_LEN]; bool has_data = false; while (fgets(line, sizeof(line), fp)) { line[strcspn(line, "\n")] = 0; char *pipe = strchr(line, '|'); if (pipe && strlen(pipe + 1) > 0) { has_data = true; char *code = pipe + 1; int found = 0; for (int i = 0; i < stat_count; ++i) { if (strcmp(stats[i].code, code) == 0) { stats[i].count++; found = 1; break; } } if (!found && stat_count < 128) { strncpy(stats[stat_count].code, code, MAX_COUNTRY_CODE-1); stats[stat_count].code[MAX_COUNTRY_CODE-1] = 0; stats[stat_count].count = 1; stat_count++; } } } fclose(fp); if (!has_data) { printf("(暂无国家信息)\n\n"); return; } // 排序 for (int i = 0; i < stat_count-1; ++i) { for (int j = i+1; j < stat_count; ++j) { if (stats[j].count > stats[i].count) { char tmp_code[MAX_COUNTRY_CODE]; int tmp_count = stats[i].count; strcpy(tmp_code, stats[i].code); stats[i].count = stats[j].count; strncpy(stats[i].code, stats[j].code, MAX_COUNTRY_CODE); stats[j].count = tmp_count; strncpy(stats[j].code, tmp_code, MAX_COUNTRY_CODE); } } } int show_n = stat_count < 9 ? stat_count : 9; for (int i = 0; i < show_n; ++i) { printf(" - %s %s(%d 个)%s\n", get_country_name(stats[i].code), C_RED, stats[i].count, C_RESET); } printf("\n"); } void show_statistics(void) { int nft_v4_count = nft_get_set_count(NFT_SET); int nft_v6_count = nft_get_set_count(NFT_SET_V6); int nft_count = nft_v4_count + nft_v6_count; int local_count = 0; FILE *fp = fopen(PERSIST_FILE, "r"); if (fp) { char line[MAX_LINE_LEN]; while (fgets(line, sizeof(line), fp)) { if (strlen(line) > 1) local_count++; } fclose(fp); } msg(C_CYAN, "=== 🛡️ BIP 防护概览 ==="); printf("当前生效: %s%d%s 条 | 本地记录: %s%d%s 条\n\n", C_GREEN, nft_count, C_RESET, C_YELLOW, local_count, C_RESET); show_active_bans(); show_subnet_aggregation(); show_country_stats(); msg(C_CYAN, "=== 📝 最新拦截日志 (Last 10) ==="); log_show_recent(10); printf("\n"); } void show_statistics_watch(bool watch_mode) { if (!watch_mode) { show_statistics(); return; } /* 动态监控模式 */ printf("\033[?25l"); /* 隐藏光标 */ while (1) { printf("\033[2J\033[H"); /* 清屏并移到顶部 */ /* 显示时间戳 */ time_t now = time(NULL); struct tm *t = localtime(&now); printf("%s[实时监控] 刷新时间: %04d-%02d-%02d %02d:%02d:%02d (按 Ctrl+C 退出)%s\n\n", C_YELLOW, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, C_RESET); show_statistics(); sleep(2); /* 每2秒刷新一次 */ } printf("\033[?25h"); /* 恢复光标 */ }