Hook--拦截IP地址访问

Hook–拦截IP地址访问:

1、需求

  1. 不允许任何进程(真实存在的进程)访问黑名单里的网站;
  2. 用日志(进程名+时间+访问对象+结果 的形式)记录被拦截的访问记录及其他访问成功的记录;
  3. 定期检查黑名单是否发生变化。

2、需求分析

1. 目标:
- 实现一个网络访问过滤器,允许或拒绝网络连接和接受请求,基于一个黑名单中的IP地址列表。

2. 主要功能:

- 黑名单管理:程序维护一个IP地址的黑名单,用于存储禁止访问的IP地址。
    - 连接过滤:拦截与黑名单中的IP地址的连接尝试。
    - 访问记录:记录成功和被拦截的网络访问事件,包括时间戳、进程ID、操作类型、IP地址和端口信息。
    - 动态黑名单:支持动态更新黑名单,从外部文件(blacklist.txt)读取IP地址,以及定期检查是否有更新。
    - 基于动态链接:使用`dlopen`和`dlsym`从libc.so.6动态链接系统库中的`connect`和`accept`函数,以便拦截网络连接。

3. 代码结构:

  • main函数中,程序初始化了黑名单、获取系统库的句柄并动态链接connectaccept函数。
  • connect函数用于拦截网络连接,检查连接请求的IP地址是否在黑名单中,记录访问事件,然后根据结果决定是否允许连接。(暂时只支持ipv4地址)
  • accept函数类似地用于拦截接受请求,并进行相同的操作。

4. 黑名单管理:

  • 黑名单的IP地址列表在程序中以blacklist数组表示,可以在程序运行时从外部文件blacklist.txt加载新的IP地址。
  • 黑名单文件每隔一小时进行检查,如果有新的IP地址,程序将加载它们并更新黑名单。

5. 访问记录:

- 成功连接和被拦截的访问事件都会被记录到`ip_access_log.txt`日志文件,包括时间戳、进程ID、操作类型、IP地址和端口信息。

6. 动态链接:

- 程序使用`dlopen`和`dlsym`来动态链接系统库中的`connect`和`accept`函数,以便能够拦截这些函数的调用并添加自定义的过滤逻辑。

7. 异常处理:

  • 如果无法读取黑名单文件,程序会输出错误信息。

8. 测试地址:

  • 提供了一些测试用的IP地址,包括本机地址、百度、Google的公共DNS服务器和Cloudflare的公共DNS服务器。

3、代码部署

1、确保当前目录下有黑名单文件:blacklist.txt
2、确保当前目录下有日志文件:ip_access_log.txt
3、查询几个可供测试的ip地址进行命令行的测试:telnet x.x.x.x
   这里提供: 本机地址:127.0.0.1
   			百度:220.181.38.149
   			Google的公共DNS服务器:8.8.8.8
   			Cloudflare的公共DNS服务器:1.1.1.1
4、使用以下命令 将ip.c文件编译成动态链接库
   gcc ip.c -fpic -shared -ldl -o ip.so
5、使用以下命令将ip.so动态库注入当前目录
   export LD_PRELOAD=./ip.so
6、测试
   telnet 127.0.0.1
   telnet 220.181.38.149
   telnet 8.8.8.8
   telnet 1.1.1.1

4、代码

#define _GNU_SOURCE // 启用GNU扩展特性
#include <stdio.h>
#include <sys/types.h>
#include <dlfcn.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

/*
可供测试的IP地址:
    本机地址:127.0.0.1
    百度:220.181.38.149
    Google的公共DNS服务器:8.8.8.8
    Cloudflare的公共DNS服务器:1.1.1.1
*/

// 定义黑名单,包含禁止访问的IP地址
const char *blacklist[] = {
    "127.0.0.1"};

// 黑名单中IP地址的数量
static int numBlacklist = sizeof(blacklist) / sizeof(blacklist[0]);

// 记录上次黑名单检查的时间
static time_t lastBlacklistCheckTime = 0;

// 定义黑名单文件的路径
static const char *blacklistFile = "blacklist.txt";

// 记录访问记录到日志文件
void logAccess(const char *action, int sockfd, const char *ip, int port);

// 检查黑名单是否发生变化
void checkBlacklistChanges();

// 判断接入的网络是否为过滤IP,如果是则提前返回(针对客户端发起连接的IP地址)
typedef int (*new_socket)(int, const struct sockaddr *, socklen_t);
typedef int (*new_accept)(int, struct sockaddr *, socklen_t *);

// (针对客户端发起连接的IP地址)拦截连接请求
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
    void *handle = NULL;
    new_socket old_connect = NULL; // 保存成old_connect以备调用

    // 获得libc.so.6的句柄
    handle = dlopen("libc.so.6", RTLD_LAZY);

    // 返回open函数在libc.so.6中的加载时的地址
    old_connect = (new_socket)dlsym(handle, "connect");

    char ip[128];
    memset(ip, 0, sizeof(ip)); // ip地址
    int port = -1;             // 端口号

    // 如果传入的地址是IPv4地址
    if (AF_INET == addr->sa_family)
    {
        // 将传入的地址强制转换为IPv4地址结构
        struct sockaddr_in *sa4 = (struct sockaddr_in *)addr;
        // 使用inet_ntop函数将IPv4地址转换为人类可读的字符串形式(IP地址)
        inet_ntop(AF_INET, (void *)(struct sockaddr *)&sa4->sin_addr, ip, sizeof(ip));
        // 提取端口号,并将其从网络字节序转换为主机字节序
        port = ntohs(sa4->sin_port);
        // 打印IP地址和端口号
        printf("\nAF_INET IP===%s:%d\n", ip, port);
    }
    checkBlacklistChanges();
    // 记录访问信息到日志文件
    logAccess("connect", sockfd, ip, port);
    for (int i = 0; i < numBlacklist; ++i)
    {
        if (0 == strcmp(ip, blacklist[i]))
        {
            printf("\n===%s netfilter...connect failed!\n", ip);
            // 记录被拦截的访问信息到日志文件
            logAccess("connect (Blocked)", sockfd, ip, port);
            return -1;
        }
    }

    printf("\nPID:%d, socket:%d, %s Successfully connected!\n", getpid(), sockfd, ip);
    // 记录成功的访问信息到日志文件
    logAccess("connect (Success)", sockfd, ip, port);
    return old_connect(sockfd, addr, addrlen);
}

// 监控和控制通过套接字发起的连接请求(针对服务端发起连接的IP地址)拦截接受请求
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
{
    void *handle = NULL;
    new_accept old_accept = NULL; // 保存old_accept以备调用

    // 获得libc.so.6的句柄
    handle = dlopen("libc.so.6", RTLD_LAZY);

    // 返回accept函数在libc.so.6中的加载时的地址
    old_accept = (new_accept)dlsym(handle, "accept");

    char ip[128];
    memset(ip, 0, sizeof(ip)); // Ip地址
    int clientfd = -1;         // 客户端socketfd

    if (AF_INET == addr->sa_family)
    {
        // 将传入的地址强制转换为IPv4地址结构
        struct sockaddr_in *sa4 = (struct sockaddr_in *)addr;
        // 使用 inet_ntoa 函数将 IPv4 地址转换为 IP 地址字符串,并将其复制到 ip 变量中
        strcpy(ip, inet_ntoa(sa4->sin_addr));
    }
    checkBlacklistChanges();
    // 记录访问信息到日志文件
    logAccess("accept", sockfd, ip, -1);

    for (int i = 0; i < numBlacklist; ++i)
    {
        if (0 == strcmp(ip, blacklist[i]))
        {
            printf("\nPID:%d  ===%s=== netfilter...accept failed!\n", getpid(), ip);
            // 记录被拦截的访问信息到日志文件
            logAccess("accept (Blocked)", sockfd, ip, -1);
            return -1;
        }
    }
    // 放行  调用原始的 accept 函数以接受客户端的连接请求
    clientfd = old_accept(sockfd, addr, addrlen);
    printf("\nPID:%d, clientfd:%d, %s Successfully accepted!\n", getpid(), clientfd, ip);
    // 记录成功的访问信息到日志文件
    logAccess("accept (Success)", sockfd, ip, -1);
    return clientfd;
}
// 记录访问结果到日志文件
void logAccess(const char *action, int sockfd, const char *ip, int port)
{
    time_t now;
    struct tm *tm_info;
    char timestamp[20];

    time(&now);                                            // 获取当前时间
    tm_info = localtime(&now);                             // 将当前时间转换为本地时间结构
    strftime(timestamp, 20, "%Y-%m-%d %H:%M:%S", tm_info); // 格式化时间为字符串(年-月-日 时:分:秒)

    char logEntry[256]; // 存储日志条目
    snprintf(logEntry, sizeof(logEntry), "[%s] [PID:%d] [%s] [IP:%s] [Port:%d]\n", timestamp, getpid(), action, ip, port);

    // 打开日志文件,将信息追加到文件末尾
    FILE *logFile = fopen("ip_access_log.txt", "a");
    if (logFile != NULL)
    {
        fputs(logEntry, logFile);
        fclose(logFile);
    }
}

// 检查黑名单是否发生变化
void checkBlacklistChanges()
{
    time_t current_time;
    time(&current_time);
    // 检查黑名单变化的时间间隔,这里设置为1小时
    if (current_time - lastBlacklistCheckTime >= 3600)
    {
        FILE *file = fopen(blacklistFile, "r");
        if (file)
        {
            // 读取黑名单文件中的内容
            char line[256];
            int numBlacklistCount = 0; // 跟踪黑名单中的ip地址数量
            while (fgets(line, sizeof(line), file))
            {
                // 去掉行末的换行符
                line[strcspn(line, "\n")] = '\0';
                // 将新的ip地址加入黑名单
                blacklist[numBlacklistCount] = strdup(line);
                numBlacklistCount++;
            }
            numBlacklist = numBlacklistCount;
            fclose(file);
            // 更新黑名单中的IP地址数量
            for (int i = numBlacklistCount; i < sizeof(blacklist) / sizeof(blacklist[0]); ++i)
            {
                blacklist[i] = NULL;
            }
            lastBlacklistCheckTime = current_time; // 更新上次检查时间
        }
    }
    else
    {
        perror("Error reading blacklist file");
    }
}
<think> 我们正在讨论ZLM Hook。由于用户的问题是关于ZLM Hook的使用方法、原理和应用场景,我们需要先明确ZLM Hook是什么。根据上下文,ZLM通常指ZooKeeper、Logstash、Mesos等,但结合“Hook”一词,更可能是某个特定系统的钩子机制。然而,在已知信息中,ZLM可能指的是ZLMediaKit,一个开源的流媒体服务器框架。因此,我们假设用户询问的是ZLMediaKit中的Hook机制。 ZLMediaKit是一个基于C++11的高性能运营级流媒体服务框架,它提供了RTSP、RTMP、HTTP等多种协议的支持。在ZLMediaKit中,Hook机制允许开发者通过HTTP回调的方式,在媒体流的生命周期中插入自定义逻辑,例如鉴权、事件通知等。 下面我将从使用方法、原理和应用场景三个方面介绍ZLM Hook。 ### 1. 使用方法 在ZLMediaKit中,Hook机制通过配置文件和HTTP回调实现。具体步骤如下: **步骤1:配置Hook** 在ZLMediaKit的配置文件(通常为`config.ini`)中,可以设置各个事件的Hook回调地址。例如: ```ini [hook] enable=1 on_publish=http://127.0.0.1:8080/hook/on_publish on_play=http://127.0.0.1:8080/hook/on_play on_flow_report=http://127.0.0.1:8080/hook/on_flow_report ... ``` 这里,我们为`on_publish`(推流事件)、`on_play`(播放事件)等设置了回调地址。当这些事件发生时,ZLMediaKit会向配置的URL发送HTTP POST请求。 **步骤2:实现Hook服务器** 我们需要实现一个HTTP服务器来接收这些回调请求,并返回处理结果。例如,使用Python Flask实现一个简单的Hook服务器: ```python from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/hook/on_publish', methods=['POST']) def on_publish(): data = request.json # 进行推流鉴权等逻辑 if data['app'] == 'live' and data['stream'] == 'test': return jsonify(code=0, msg="success") else: return jsonify(code=-1, msg="authentication failed") if __name__ == '__main__': app.run(host='0.0.0.0', port=8080) ``` **步骤3:处理回调并响应** ZLMediaKit要求Hook服务器返回特定的JSON格式。例如,对于推流事件(`on_publish`),如果鉴权通过,需要返回`{"code":0}`;拒绝则返回非0。其他事件也有类似的返回值要求。 ### 2. 原理 ZLMediaKit的Hook机制是基于事件的回调系统。其工作原理如下: - **事件触发**:当媒体流相关事件(如推流、播放、停止、录制等)发生时,ZLMediaKit会触发对应的Hook事件。 - **HTTP回调**:ZLMediaKit将事件信息(如流应用名、流ID、客户端IP等)以JSON格式通过HTTP POST请求发送到配置的Hook URL。 - **业务处理**:Hook服务器接收到请求后,执行自定义逻辑(如鉴权、记录日志、通知等)。 - **响应处理**:Hook服务器返回处理结果(JSON格式),ZLMediaKit根据返回结果决定是否继续执行后续操作(如允许推流/播放,或拒绝)。 这种机制将媒体服务器的核心流程与业务逻辑分离,使得开发者可以灵活扩展功能而无需修改ZLMediaKit源码。 ### 3. 应用场景 ZLM Hook广泛应用于以下场景: - **推流/播放鉴权**:在推流或播放前进行权限验证,例如验证token或用户身份。 - **流量统计**:通过`on_flow_report`事件统计流量使用情况。 - **事件通知**:如录制完成通知、流状态变化通知等。 - **在线状态管理**:通过`on_stream_changed`事件(流新增或断开)维护在线流列表。 - **动态控制**:例如根据业务需求动态调整转推、录制等行为。 ### 总结 ZLM Hook是ZLMediaKit提供的一种回调机制,通过HTTP回调将媒体流生命周期中的关键事件暴露给外部系统,使得开发者可以方便地实现自定义业务逻辑。其使用方法包括配置Hook回调地址、实现Hook服务器并处理回调返回响应;原理是基于事件触发的HTTP回调;应用场景主要包括鉴权、流量统计、事件通知等。 §§相关问题§§ 1. ZLM Hook支持哪些事件类型? 2. 如何保证Hook服务器的性能和高可用性? 3. ZLM Hook回调失败时,ZLMediaKit会如何处理? 4. 除了HTTP Hook,ZLMediaKit还支持其他扩展方式吗? </think> ### ZLM Hook详解 ZLM Hook是ZLMediaKit(开源流媒体服务器框架)的核心扩展机制,通过HTTP回调实现业务逻辑的注入。以下是具体分析: #### 一、使用方法 1. **配置Hook地址** ```ini ; config.ini配置文件 [hook] enable=1 ; 启用Hook admin_params=secret=123456 ; 鉴权密钥 on_flow_report=http://127.0.0.1:8080/hook/flow ; 流量统计回调 on_publish=http://127.0.0.1:8080/hook/publish ; 推流鉴权回调 ``` 支持30+事件类型,包括`on_play`(播放鉴权)、`on_record_mp4`(录制完成通知)等。 2. **实现回调接口** ```python from flask import Flask, request app = Flask(__name__) @app.route('/hook/publish', methods=['POST']) def on_publish(): data = request.json if data["app"] != "live" or data["stream"] != "test": return {"code": -1, "msg": "鉴权失败"} # 拒绝推流 return {"code": 0} # 允许推流 ``` 3. **响应规则** - 必须返回JSON:`{"code":0}`表示成功,非0值触发错误 - 超时时间默认10秒(可配置) - 支持修改媒体参数(如`enable_audio`: false关闭音频) #### 二、工作原理 1. **事件驱动架构** ```mermaid graph LR A[媒体事件] --> Z[ZLMediaKit] Z -->|HTTP POST| H[Hook服务器] H -->|JSON响应| Z Z -->|执行动作| C[客户端] ``` 2. **数据处理流程** - 序列化:ZLMediaKit将事件数据转为JSON(含`app`、`stream_id`、`ip`等字段) - 回调传输:通过HTTP/1.1发送POST请求 - 业务决策:外部服务返回控制指令(如拒绝推流、修改转码参数) - 超时处理:未响应时默认放行请求(可配置为拒绝) #### 三、应用场景 1. **安全控制** - 推流/播放鉴权(动态校验Token) - IP黑白名单过滤 - 防盗链(Referer校验) 2. **业务集成** - 直播状态监控(自动下线无观众直播) - 录制文件云端存储(回调触发OSS上传) - 流量统计分析(通过`on_flow_report`统计带宽) 3. **自动化运维** - 自动转码(回调触发FFmpeg处理) - 故障切换(流中断时触发CDN切换) - 负载均衡(根据机器负载动态分配推流节点) #### 四、最佳实践 1. **性能优化** - 使用高性能Web框架(如Golang的Gin) - 异步处理耗时操作(日志写入/数据库查询) - 设置合理超时(建议≤3秒) 2. **高可用方案** ```mermaid graph TB Z[ZLM] -->|回调| L[本地缓存队列] L --> S1[Server1] L --> S2[Server2] L --> S3[Server3] ``` 3. **安全建议** - HTTPS传输敏感数据 - 请求签名验证(防重放攻击) - 频率限制(防DDoS) > 典型案例:某直播平台通过`on_publish`实现动态计费,允许VIP用户推流4K内容,免费用户仅限720P[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

霜晨月c

谢谢老板地打赏~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值