1. 目标:
- 实现一个网络访问过滤器,允许或拒绝网络连接和接受请求,基于一个黑名单中的IP地址列表。
2. 主要功能:
- 黑名单管理:程序维护一个IP地址的黑名单,用于存储禁止访问的IP地址。
- 连接过滤:拦截与黑名单中的IP地址的连接尝试。
- 访问记录:记录成功和被拦截的网络访问事件,包括时间戳、进程ID、操作类型、IP地址和端口信息。
- 动态黑名单:支持动态更新黑名单,从外部文件(blacklist.txt)读取IP地址,以及定期检查是否有更新。
- 基于动态链接:使用`dlopen`和`dlsym`从libc.so.6动态链接系统库中的`connect`和`accept`函数,以便拦截网络连接。
3. 代码结构:
main
函数中,程序初始化了黑名单、获取系统库的句柄并动态链接connect
和accept
函数。connect
函数用于拦截网络连接,检查连接请求的IP地址是否在黑名单中,记录访问事件,然后根据结果决定是否允许连接。(暂时只支持ipv4地址)accept
函数类似地用于拦截接受请求,并进行相同的操作。4. 黑名单管理:
blacklist
数组表示,可以在程序运行时从外部文件blacklist.txt
加载新的IP地址。5. 访问记录:
- 成功连接和被拦截的访问事件都会被记录到`ip_access_log.txt`日志文件,包括时间戳、进程ID、操作类型、IP地址和端口信息。
6. 动态链接:
- 程序使用`dlopen`和`dlsym`来动态链接系统库中的`connect`和`accept`函数,以便能够拦截这些函数的调用并添加自定义的过滤逻辑。
7. 异常处理:
8. 测试地址:
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
#define _GNU_SOURCE // 启用GNU扩展特性
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*
可供测试的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(¤t_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");
}
}