Files
DenyIP/cap.c
2025-04-23 11:41:06 +08:00

412 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "cap.h"
#include "common.h"
#include "libipset.h"
#include "libcurl.h"
#include "cache.h"
pcap_if_t *alldevs, *device;
pcap_t *handle; // 会话句柄
struct bpf_program fp; // 编译后的过滤器
pid_t pid = -1; // 子进程全局PID
#define SHM_SIZE 1024 // 共享内存大小
#define SHM_KEY 0124 // 共享内存键值
int shmid = -1;
int RULE_NAME_NUMBER = 0; // ipset 集合集合数
char *RULE_NAME = NULL; // 共享内存
char *ip2region_area = NULL; // ip2region 解析结果
char *command_result = NULL; // 执行命令的结果
void Processing_IP_addresses(char *src_ip)
{
// 地域白名单
char _region_list[WHITELIST_IP_NUM][WHITELIST_IP_NUM] = { { 0 }, { 0 } };
char _REGION_LIST[BUFFER] = { 0 };
const char *REGION_ENV = NULL;
char ipset_query_command[256] = { 0 };
// 定义 Response 结构体
Response response;
// 如果 IP 地址已在缓存中且未过期,则跳过查询
if (is_ip_in_cache(src_ip)) {
_printf(RED "IP:%s 已在缓存中,跳过查询\n" REDEND, src_ip);
return;
}
// 如果ipset规则已经存在则跳过查询
snprintf(ipset_query_command, sizeof(ipset_query_command), "ipset test %s %s 2>/dev/null", RULE_NAME, src_ip);
if (system(ipset_query_command) == 0) {
// _printf(RED "Ipset 规则内已经存在 %s\n" REDEND, src_ip);
return;
}
if (1 == is_ip_in_set(cn_ip, src_ip)) {
_printf(RED "IP:%s 已经标记为国内,跳过!!!\n" REDEND, src_ip);
for (int i = 0; i < MAXIPSET_; i++) {
if (cn_ip[i][0] != '\0') {
printf("%s ", cn_ip[i]);
}
}
if (cn_ip_len(cn_ip) >= 10240) { // 清理集合
clear_ip_set(cn_ip);
truncate_file("cn.txt"); // 清空文件
}
printf("cn_ip_len(cn_ip): %d\n", cn_ip_len(cn_ip));
return;
}
// 执行查询并添加到缓存
ip2region_area = ip2region("ip2region/ip2region.xdb", src_ip);
if (ip2region_area == NULL) {
_printf(RED "ip2region 解析地域错误\n" REDEND);
return;
}
// 取环境变量
REGION_ENV = getenv("REGION");
if (REGION_ENV != NULL) {
_printf("REGION: %s\n", REGION_ENV);
strcpy(_REGION_LIST, REGION_ENV);
} else {
strcpy(_REGION_LIST, "局域网 内网 中国 ");
}
split_string(_REGION_LIST, " ", _region_list);
if (isregion(ip2region_area, _region_list) == 1) { // 返回1表示在白名单列表
;
} else {
if (cache_size < MAX_CACHE_SIZE) // 缓存IP数不够预备设定值
{
sleep(1);
_printf("缓存 IP 数 %d\n", cache_size);
}
add_ip_to_cache(src_ip); // 添加到缓存
char *p = CurlGetIpArea(src_ip);
if (parse_json_to_struct(p, &response) == 0) { // 解析 JSON 到结构体
if (NULL == strstr(response.continent_country, "中国")) { // 这时是国外IP
_printf(RED "CurlGetIpArea(): %s %s\n" REDEND, src_ip, response.continent_country);
add_ip_to_ipset(RULE_NAME, src_ip);
} else { // 这时是国内IP
if (-1 == add_cn_ip(cn_ip, src_ip)) { // 添加国内IP到缓存
_printf(RED "add_cn_ip() Error!!! 错误:集合已满\n" REDEND);
}
_printf("IP: %s 离线库为国外, API 判断为国内, 标记为已处理!!!\n", src_ip);
if (append_string_to_file("cn.txt", src_ip) != 0) {
_printf("append_string_to_file() Error!!!\n");
}
}
} else {
fprintf(stderr, "Failed to parse JSON.\n");
}
if (p != NULL) {
free(p);
}
}
if (ip2region_area != NULL) {
free(ip2region_area);
ip2region_area = NULL;
}
return;
}
// 回调函数,在捕获到每个数据包时调用
void packet_handler(u_char *args, const struct pcap_pkthdr *header, const u_char *packet)
{
int ethernet_header_len = 14;
//struct ip *ip_header = (struct ip *)(packet + ethernet_header_len);
struct ip *ip_header = (struct ip *)(packet + ethernet_header_len);
if (ip_header->ip_v != 4)
return; // 只处理 IPv4
char src_ip[INET_ADDRSTRLEN] = { 0 };
inet_ntop(AF_INET, &(ip_header->ip_src), src_ip, INET_ADDRSTRLEN);
Processing_IP_addresses(src_ip);
return;
}
void usage()
{
printf("DenyIP version %s\n", _VERSION);
puts("拒绝Linux服务器非大陆IP工具");
puts("MAIL: aixiao@aixiao.me");
puts("Date: 20241024");
puts(" Usage: denyip [-d] [-i <interface>] [-s <start|stop>] [-h|-?]");
puts(" -d --daemon Daemon mode");
puts(" -i --interface interface (default eth0)");
puts(" -f --protocol 过滤器 [\"tcp\" | \"udp\" | \"tcp or udp\"] (default \"tcp\")");
puts(" -l print iptables rule");
puts(" -s --signal regular signal (default start|stop) ");
puts(" start Enable Iptables rule");
puts(" stop Disable Iptables rule");
puts(" -h|-? Help Information");
puts("");
exit(0);
}
void cleanup_(int signum)
{
_printf("Received signal %d, cleaning up...\n", signum);
// 终止子进程
if (pid > 0) {
kill(pid, SIGTERM);
}
// 释放共享内存
if (RULE_NAME != NULL) {
shmdt(RULE_NAME);
shmctl(shmid, IPC_RMID, NULL);
}
// 在程序结束时,清理缓存链表
free_ip_cache();
if (ip2region_area != NULL) {
free(ip2region_area);
ip2region_area = NULL;
}
if (command_result != NULL) {
free(command_result);
command_result = NULL;
}
// 清理
pcap_freecode(&fp);
pcap_freealldevs(alldevs); // 释放设备列表
//pcap_close(handle); // 关闭会话句柄
// 退出主进程
exit(0);
return;
}
int main(int argc, char **argv)
{
// 注册 SIGTERM 信号处理函数
signal(SIGTERM, cleanup_);
int opt;
char errbuf[PCAP_ERRBUF_SIZE] = { 0 }; // 错误缓冲区
char protocol[256] = "tcp";
char interface[256] = "{ 0 }";
char Ipset_Command[BUFFER] = { 0 };
strcpy(interface, "eth0");
memset(&alldevs, 0, sizeof(alldevs));
memset(&device, 0, sizeof(device));
memset(errbuf, 0, PCAP_ERRBUF_SIZE);
int longindex = 0;
char optstring[] = "di:f:s:lh?";
static struct option longopts[] = {
{ "interface", required_argument, 0, 'i' },
{ "protocol", required_argument, 0, 'f' },
{ "signal", required_argument, 0, 's' },
{ "daemon", no_argument, 0, 'd' },
{ "l", no_argument, 0, 'l' },
{ "help", no_argument, 0, 'h' },
{ "?", no_argument, 0, '?' },
{ 0, 0, 0, 0 }
};
while (-1 != (opt = getopt_long(argc, argv, optstring, longopts, &longindex))) {
switch (opt) {
case 'd':
if (daemon(1, 1)) {
perror("daemon");
}
break;
case 'i':
strcpy(interface, optarg);
break;
case 'f':
strcpy(protocol, optarg);
break;
case 'l':
system("iptables -L -v -n --line-numbers");
exit(0);
break;
case 's':
if (strcmp(optarg, "start") == 0) {
memset(Ipset_Command, 0, BUFFER);
// 将 MAXIPSET_RULT_NAME_NUM 替换为实际值
snprintf(Ipset_Command, sizeof(Ipset_Command), "for n in $(seq 0 %d); do iptables -A INPUT -p tcp -m set --match-set root$n src -j DROP 2> /dev/null; done", MAXIPSET_RULT_NAME_NUM);
system(Ipset_Command);
exit(0);
} else if (strcmp(optarg, "stop") == 0) {
memset(Ipset_Command, 0, BUFFER);
// 将 MAXIPSET_RULT_NAME_NUM 替换为实际值
snprintf(Ipset_Command, sizeof(Ipset_Command), "for n in $(seq 0 %d); do iptables -D INPUT -p tcp -m set --match-set root$n src -j DROP 2> /dev/null; done", MAXIPSET_RULT_NAME_NUM);
system(Ipset_Command);
exit(0);
} else {
usage();
exit(0);
}
break;
case 'h':
case '?':
usage();
exit(0);
break;
default:
usage();
exit(0);
break;
}
}
// 创建共享内存
shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0666);
if (shmid < 0) {
perror("shmget");
exit(1);
}
// 连接共享内存到进程地址空间
RULE_NAME = (char *)shmat(shmid, NULL, 0);
if (RULE_NAME == (char *)-1) {
perror("shmat");
exit(1);
}
// 读取缓存
if (1 == file_exists_access("cn.txt")) {
int line_count = 0;
int result = read_file_to_array("cn.txt", cn_ip, &line_count);
if (result != 0) {
fprintf(stderr, "Failed to read file with error code: %d\n", result);
return 1;
}
char *local_addr = GetLocalAddr("https://inet-ip.aixiao.me/");
if (local_addr == NULL) {
perror("GetLocalAddr()");
return 1;
}
remove_char(local_addr, '\n');
printf("Local Address: %s\n", local_addr);
printf("Read %d lines from file:\n", line_count);
for (int i = 0; i < line_count; i++) {
printf("Line %d: %s\n", i + 1, cn_ip[i]);
}
free(local_addr);
}
pid = fork(); // 创建子进程
if (pid == 0) // 子进程
{
int count = 0;
snprintf(RULE_NAME, BUFFER, "root%d", RULE_NAME_NUMBER);
if (create_ipset(RULE_NAME) != 0) {
_printf("创建 IPSet %s 失败\n", RULE_NAME);
}
while (1) {
//_printf("子进程当前 Ipset Rule 名 %s\n", RULE_NAME);
count = get_ip_count_in_ipset(RULE_NAME);
if (count >= 0) {
_printf("子进程当前 Ipset Rule 名 %s, 数量: %d \n", RULE_NAME, count);
if (count >= MAXIPSET && RULE_NAME_NUMBER <= MAXIPSET_RULT_NAME_NUM) // RULE_中的IP数量不超过MAXIPSET并且集合不能超过 MAXIPSET_RULT_NAME_NUM 个
{
RULE_NAME_NUMBER++;
snprintf(RULE_NAME, BUFFER, "root%d", RULE_NAME_NUMBER); // 更新规则名称
// 创建新的 IPSet
if (create_ipset(RULE_NAME) != 0) {
_printf("创建 IPSet %s 失败\n", RULE_NAME);
} else {
char iptables_command[256];
sprintf(iptables_command, "iptables -I INPUT -m set --match-set %s src -j DROP", RULE_NAME);
system(iptables_command);
}
}
if (RULE_NAME_NUMBER == MAXIPSET_RULT_NAME_NUM) {
_printf("已达到最大规则数限制,停止创建新规则。\n");
_printf("请手动清理Ipset规则\n");
}
}
sleep(3); // 每 3 秒检查一次
}
}
// 查找可用的网络设备
if (pcap_findalldevs(&alldevs, errbuf) == -1) {
fprintf(stderr, "无法找到设备: %s\n", errbuf);
return 1;
}
// 打印可用设备列表
_printf("可用的设备:\n");
for (device = alldevs; device != NULL; device = device->next) {
_printf("设备: %s\n", device->name);
}
// 打开设备以进行数据包捕获
handle = pcap_open_live(interface, BUFSIZ, 1, 1000, errbuf);
if (handle == NULL) {
fprintf(stderr, "无法打开设备 %s: %s\n", interface, errbuf);
pcap_freealldevs(alldevs);
return 1;
}
// 编译过滤器
if (pcap_compile(handle, &fp, protocol, 0, PCAP_NETMASK_UNKNOWN) == -1) {
fprintf(stderr, "无法编译过滤器 %s: %s\n", protocol, pcap_geterr(handle));
pcap_close(handle);
pcap_freealldevs(alldevs);
return 1;
}
// 设置过滤器
if (pcap_setfilter(handle, &fp) == -1) {
fprintf(stderr, "无法设置过滤器 %s: %s\n", protocol, pcap_geterr(handle));
pcap_freecode(&fp);
pcap_close(handle);
pcap_freealldevs(alldevs);
return 1;
}
// 开始捕获数据包
if (pcap_loop(handle, 0, packet_handler, NULL) < 0) {
fprintf(stderr, "捕获数据包时出错: %s\n", pcap_geterr(handle));
pcap_freecode(&fp);
pcap_close(handle);
pcap_freealldevs(alldevs);
return 1;
}
// 在程序结束时,清理缓存链表
free_ip_cache();
// 清理
pcap_freecode(&fp);
pcap_freealldevs(alldevs); // 释放设备列表
pcap_close(handle); // 关闭会话句柄
return 0;
}