#include "cap.h"
#include "common.h"
#include "libipset.h"
#include "libcurl.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; // 执行命令的结果

#define CACHE_TTL 180           // 设定缓存的存活时间为 600 秒 (10 分钟)
#define MAX_CACHE_SIZE 100      // 缓存最多存储 100 个 IP 地址
struct ip_cache_node *ip_cache_head = NULL; //  缓存链表的头节点
int cache_size = 0;             // 当前缓存中的 IP 数量

// 定义链表结构，用于缓存 IP 地址
struct ip_cache_node {
    char ip[INET_ADDRSTRLEN];   // 存储 IP 地址
    time_t timestamp;           // 记录缓存时间
    struct ip_cache_node *next; // 指向下一个节点
};

#define MAXIPSET_ 100
#define MAXIPLEN 32
char cn_ip[MAXIPSET_][MAXIPLEN] = { 0 };

// 添加一个 IP 到集合（如果已存在则不添加）
int add_cn_ip(char cn_ip[MAXIPSET_][MAXIPLEN], char *ip)
{
    if (ip == NULL || strlen(ip) >= MAXIPLEN) {
        return -1;              // 错误：无效的 IP 地址或过长
    }
    // 检查是否已存在
    for (int i = 0; i < MAXIPSET_; i++) {
        if (cn_ip[i][0] != '\0' && strcmp(cn_ip[i], ip) == 0) {
            return 1;           // IP 已存在，返回特殊代码
        }
    }
    // 查找空位并添加
    for (int i = 0; i < MAXIPSET_; i++) {
        if (cn_ip[i][0] == '\0') { // 检查是否为空
            strcpy(cn_ip[i], ip);
            return 0;           // 成功添加
        }
    }
    return -1;                  // 错误：集合已满
}

// 判断给定的IP地址是否在集合中
int is_ip_in_set(char cn_ip[MAXIPSET_][MAXIPLEN], const char *ip)
{
    if (ip == NULL) {
        return 0;               // 错误：无效的IP地址指针
    }
    for (int i = 0; i < MAXIPSET_; i++) {
        if (cn_ip[i][0] != '\0' && strcmp(cn_ip[i], ip) == 0) {
            return 1;           // 找到匹配的IP地址
        }
    }
    return 0;                   // 没有找到匹配的IP地址
}

// 计算集合中非空IP的数量
int cn_ip_len(char cn_ip[MAXIPSET_][MAXIPLEN])
{
    int count = 0;              // 用于计数非空IP
    for (int i = 0; i < MAXIPSET_; i++) {
        if (cn_ip[i][0] != '\0') {
            count++;            // 非空IP计数
        }
    }
    return count;
}

// 安全的清理IP地址集合，将所有位置置为空字符串
void clear_ip_set(char cn_ip[MAXIPSET_][MAXIPLEN])
{
    if (cn_ip == NULL) {
        return;                 // 如果指针无效，则直接返回
    }
    for (int i = 0; i < MAXIPSET_; i++) {
        memset(cn_ip[i], '\0', MAXIPLEN);
    }
}

// 检查 IP 是否已在缓存中并是否过期
int is_ip_in_cache(const char *ip)
{
    time_t now = time(NULL);    // 获取当前时间
    struct ip_cache_node *current = ip_cache_head;
    struct ip_cache_node *prev = NULL;

    while (current != NULL) {
        // 如果 IP 匹配并且未过期
        if (strcmp(current->ip, ip) == 0) {
            if (now - current->timestamp <= CACHE_TTL) {
                return 1;       // IP 在缓存中，且未过期
            } else {
                // 如果过期，从链表中移除这个节点
                if (prev == NULL) {
                    ip_cache_head = current->next;
                } else {
                    prev->next = current->next;
                }

                free(current);
                cache_size--;
                return 0;       // IP 过期，不再缓存
            }
        }

        prev = current;
        current = current->next;
    }
    return 0;                   // IP 不在缓存中
}

// 将新 IP 添加到缓存，若缓存过大则移除最早的 IP
void add_ip_to_cache(const char *ip)
{
    // 如果缓存大小超过限制，移除最早的 IP
    if (cache_size >= MAX_CACHE_SIZE) {
        struct ip_cache_node *current = ip_cache_head;
        struct ip_cache_node *prev = NULL;

        // 找到链表的最后一个节点
        while (current->next != NULL) {
            prev = current;
            current = current->next;
        }

        // 移除最后一个节点（最早的 IP）
        if (prev != NULL) {
            prev->next = NULL;
        } else {
            ip_cache_head = NULL;
        }
        free(current);
        cache_size--;
    }

    // 创建新的缓存节点并添加到链表头部
    struct ip_cache_node *new_node = (struct ip_cache_node *)malloc(sizeof(struct ip_cache_node));
    if (new_node == NULL) {
        perror("malloc");
        return;
    }
    strncpy(new_node->ip, ip, INET_ADDRSTRLEN);
    new_node->timestamp = time(NULL); // 记录当前时间
    new_node->next = ip_cache_head;
    ip_cache_head = new_node;
    cache_size++;
}

// 清理缓存链表，释放所有节点的内存
void free_ip_cache()
{
    struct ip_cache_node *current = ip_cache_head;
    while (current != NULL) {
        struct ip_cache_node *next = current->next;
        free(current);
        current = next;
    }

    ip_cache_head = NULL;
    cache_size = 0;
}

// 回调函数，在捕获到每个数据包时调用
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);
    char src_ip[INET_ADDRSTRLEN] = { 0 };

    // 地域白名单
    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;

    inet_ntop(AF_INET, &(ip_header->ip_src), src_ip, INET_ADDRSTRLEN);

    // 如果 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) >= 100) {      // 清理集合
            clear_ip_set(cn_ip);
        }

        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 = curl_get_area(src_ip);
        if (parse_json_to_struct(p, &response) == 0) { // 解析 JSON 到结构体            

            if (NULL == strstr(response.continent_country, "中国")) { // 这时是国外IP
                _printf(RED "%s %s\n" REDEND, src_ip, response.continent_country);
                add_ip_to_ipset(RULE_NAME, src_ip);
            } else {            // 这时是国内IP
                add_cn_ip(cn_ip, src_ip); // 添加国内IP到缓存
                _printf("IP: %s 离线库为国外, API 判断为国内, 标记为已处理!!!\n", src_ip);
            }

        } 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 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("      -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[] = "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:s:lh?";
    static struct option longopts[] = {
        { "interface", required_argument, 0, 'i' },
        { "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 '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);
    }

    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(9);           // 每 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;
}
