基于netfilter抓包
背景
本文介绍一种抓取网络数据包的方法,基本思想是通过netfilter/iptables在TCP /IP网络层过滤出网络报文,然后将数据压缩、加密后,上传至云端服务器分析。
过滤出的数据为上行的原始的TCP报文,包括MAC首部、PPP首部(如果是PPPOE连接), IP首部、TCP首部以及部分或全部用户数据。
代码基于linux3.14.10验证通过,文中提供部分代码,主要探讨其中几个模块。
一、框架概览
上网设备负责过滤出数据包并上传到云端服务器,云端服务器将收到的数据进行解析处理。
设备端需要将过滤到的数据包通过网络上传到服务器,为确保数据安全,需采用aes128对原始数据加密。
云端服务器的功能如下:
1)分析各网络设备上传的大数据,从中提炼有价值的数据,这是最主要的工作。
2)管理秘钥,不定期地通知各网络设备更新public key公钥。
3)控制网络设备系统的逻辑行为,是否开启该功能、多长时间运行一次等。
4)查询网络设备的状态,其中获取设备侧的通讯协议比较重要。设备侧软件可能随时升级,导致不同的设备上传的数据格式有差异,通过查询设备侧的通讯协议,云端服务器可以灵活兼容各种网络设备上的软件。
上网设备端的功能如下:
1)通过netfilter/iptables扩展target的方式,过滤处理网络报文。
2)数据压缩加密后上传。
3)系统性能监控。若网络数据流量很大,不停的过滤处理数据包,可能影响系统性能,所以需要控制这种影响性能的情况出现。
服务器与网络设备之间的通讯:
采用http方式。根据自己的需求,为通讯双方定义一份数据包格式,通过post、put请求交互即可。网络传输的报文需加密处理。
数据加密采用openssl库完成。
上传的文件格式为:
字节序: high byte -----low byte
pub key num: 云端公钥序列号
AES128 len: 本地随机生成AES128密钥,用云端公钥对这个AES128密钥加密,加密后的密文长度定义为AES128 len
AES128:本地随机生成的AES128密钥,用云端公钥对这个AES128密钥加密,加密后的密文内容定义为AES128
AES128报文数据:获取的所有数据包经过本地随机生成的AES128密钥加密后的密文内容。里面时各个packet信息
二、内核态过滤处理网络数据包
linux中有很多过滤处理网络报文的方法,比如libpcap就是一个功能强大的工具,但如果自己想动手做个工具,或者开源的有一些限制无法满足你的特殊需求,那么就可以自己搞一个。
网络数据包涉及的OSI七层模型如下,我们可以在相关的层进行过滤处理。
从软件的角度考虑,可以在数据链路层或者网络层进行过滤处理,都能达到过滤处理的目的。
如果在数据链路层过滤处理,可以在网卡驱动发送数据包的接口函数hard_start_xmit中添加hook,所有的上行数据都会通过该接口发送出去。这种方法更直接,也更灵活,但是对系统的性能、稳定性影响比较大。网卡中断可能非常多,在hard_start_xmit引入额外的工作量会引起网络性能下降,甚至会占用过多的cpu影响系统整体性能,为平衡性能考虑,需要小心的设计代码逻辑,如果在这层过滤处理网络数据包,需要对代码做大负载压测,以便发现可能存在的隐患。若前期测试不充分,后期可能出现各种各样的系统问题,排查成本较大。
如果在网络层过滤处理,可借助于网络层已有的netfilter框架,扩展iptables target去过滤处理数据包。该层在内核网络代码逻辑架构上位于更上层,footprint开销比底层的数据链路层小,对系统性能的影响就少一些。另外只需在linux netfilter的框架上扩展一个iptables模块去过滤处理数据包,新增代码与内核代码没有耦合,独立性强,易于维护。相比于数据链路层实现方式,在网络层过滤处理数据包是个更好的选择。这篇文章采取了该方法。
在网络层过滤处理数据包,首先需要了解网络报文的流经路径。数据包会在netfilter框架中转一圈,在这个框架中有各种规则,若数据包符合某个规则(也叫匹配,iptables match),netfilter就会将该数据包交给该规则对应的hook(也叫iptables target)处理。netfilter中有链、表概念,在每个链中有很多规则一条一天串起来,netfilter按规则的先后顺序判断数据包是否match。不同的链中会有功能相似的规则,这些规则组成表。表、链是逻辑上对hook的划分。在linux3.14.10版本的内核代码中,netfilter实现了4表5链,主要功能描述如下:
4张表:
filter表:一般用于过滤数据包,有INPUT、FORWARD、OUTPUT三个链。
nat表:用于nat功能(端口映射,地址映射等),有PREROUTING、POSTROUTING两个链。
mangle表:用于对特定数据包的修改,有INPUT、FORWARD、OUTPUT、PREROUTING、POSTROUTING五个链。
raw表:有限级最高,设置raw时一般是为了不再让iptables做数据包的链接跟踪处理,提高性能,有OUTPUT、PREROUTING两个链。
5个链:
INPUT链——进来的数据包应用此规则链中的策略
OUTPUT链——外出的数据包应用此规则链中的策略
FORWARD链——转发数据包时应用此规则链中的策略
PREROUTING链——对数据包作路由选择前应用此链中的规则(所有的数据包进来的时侯都先由这个链处理)
POSTROUTING链——对数据包作路由选择后应用此链中的规则(所有的数据包出来的时侯都先由这个链处理)
简化的网络数据的路径如下:
假如代码是运行在路由器设备中,那么我们可以选择filter表中的FORWARD链,在此添加我们的hook,就能过滤处理出通过该路由器上网的数据报文。
加入代码时运行在手机中,那么我们可以选择filter表中的OUTPUT链,在此添加我们的hook,就能过滤处理出手机上网的数据报文。
netfilter/iptables有默认的一些iptables target,当数据包匹配规则后,可以用这些target处理。不过为过滤处理网络报文,需要自己扩展实现target,假设我们添加的过滤处理网络报文的hook称为PCAP,那么我们可以通过下面的命令来过滤处理数据报文,将符合规则的tcp报文送入PCAP中处理:
iptables -t filter -w -I FORWARD -i br-lan -o eth0.2 -p tcp -j PCAP (具体参数可以根据网络设备实际情况修改)
所以为了过滤处理数据包,我们只需要在内核中实现一个PCAP模块,然后在用户态执行上面的iptables命令即可。
三、内核态与用户态的数据交互
内核里面过滤处理到数据包以后需要发给用户态,由用户态做压缩加密等处理后上传服务器。内核态与用户态数据的交互有普通磁盘文件、proc文件、sys文件、mmap文件映射、netlink通讯、信号等方式,由于我们需要传输大量的数据,且需要双向通讯,所以用netlink通讯比较好。
网上有很多关于netlink的介绍,用起来也比较简单,这篇文章就不再介绍netlink了,直接上代码。
自定义的一些数据结构:
static struct sockaddr_nl src_addr, dst_addr;
static struct iovec iov_recv;
static int sockfd = -1;
static struct nlmsghdr *nlh_recv = NULL;
static struct msghdr msg_recv;
/* 内核空间发送给用户空间的netlink payload消息长度。
* 因为netlink消息额外有nlmsghdr消息头(16字节),
* 所以用户空间recvmsg收到的消息长度为NL_BUFFER_SIZE+sizeof(nlmsghdr)。
*/
#define NL_BUFFER_SIZE (1024*4)
/* netlink消息类型 */
#define NLMSG_U2K_CAP_UNSPEC (1 << 0) /* 未定义 */
#define NLMSG_U2K_CAP_START (1 << 1) /* 用户空间-->内核空间 开始过滤处理 */
#define NLMSG_U2K_CAP_STOP (1 << 2) /* 用户空间-->内核空间 停止过滤处理 */
#define NLMSG_K2U_BUFFER_FULL (1 << 3) /* 内核空间-->用户空间 缓冲区满 */
#define NLMSG_U2K_DEBUG_ON (1 << 4) /* 用户空间-->内核空间 打开测试功能 */
#define NLMSG_U2K_DEBUG_OFF (1 << 5) /* 用户空间-->内核空间 关闭测试功能 */
/* 内核与用户空间netlink通信用到的一些数据结构 */
static struct sockaddr_nl src_addr, dst_addr;
static struct iovec iov_recv;
static int sockfd = -1;
static struct nlmsghdr *nlh_recv = NULL;
static struct msghdr msg_recv;
用户态创建netlink套接字:
/* 初始化netlink,用户空间与内核双向通信
* 1)用户空间-->内核空间 设置过滤处理开关
* 2)内核空间-->用户空间 发送过滤处理到的数据数据报文给用户空间
*
* 这个函数的资源需要通过release_netlink释放
*/
static int create_netlink(void)
{
/* 源地址 */
sockfd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CAPTURE_PACKET);
if (-1 == sockfd) {
LOG_MSG_E("%s %s %d can't create netlink socket!\n", __FILE__,
__func__, __LINE__);
return -1;
}
bzero(&src_addr, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid();
src_addr.nl_groups = 0;
bind(sockfd, (struct sockaddr *)&src_addr, sizeof(src_addr));
/* 目的地址 */
bzero(&dst_addr, sizeof(dst_addr));
dst_addr.nl_family = AF_NETLINK;
dst_addr.nl_pid = 0;
dst_addr.nl_groups = 0;
/* 构造接收消息缓冲区 */
nlh_recv = malloc(NL_BUFFER_SIZE);
if (!nlh_recv) {
syslog(LOG_ERR, "%s %s %d can't create netlink socket!\n",
__FILE__, __func__, __LINE__);
close(sockfd);
sockfd = -1;
return -1;
}
nlh_recv->nlmsg_len = NL_BUFFER_SIZE;
nlh_recv->nlmsg_pid = getpid();
nlh_recv->nlmsg_flags = 0;
nlh_recv->nlmsg_type = NLMSG_U2K_CAP_UNSPEC;
iov_recv.iov_base = (void *)nlh_recv;
iov_recv.iov_len = nlh_recv->nlmsg_len;
msg_recv.msg_name = (void *)&dst_addr;
msg_recv.msg_namelen = sizeof(dst_addr);
msg_recv.msg_iov = &iov_recv;
msg_recv.msg_iovlen = 1;
return 0;
}
static inline void release_netlink(void)
{
if (-1 != sockfd) {
close(sockfd);
sockfd = -1;
}
if (nlh_recv) {
free(nlh_recv);
nlh_recv = NULL;
}
}
用户态发送数据给内核态:
/* 发送消息到内核空间
* state取值:
* NLMSG_U2K_CAP_START 开始过滤处理
* NLMSG_U2K_CAP_STOP 停止过滤处理
* NLMSG_K2U_BUFFER_FULL 缓冲区满
* NLMSG_U2K_DEBUG_ON 打开内核测试功能
* NLMSG_U2K_DEBUG_OFF 关闭内核测试功能
*/
int send_msg_to_kernel(int state)
{
struct nlmsghdr *nlh_send = NULL;
struct iovec iov_send;
struct msghdr msg_send;
memset(&iov_send, 0, sizeof(struct iovec));
memset(&msg_send, 0, sizeof(struct msghdr));
/* 构造发送消息缓冲区 */
nlh_send = malloc(NLMSG_SPACE(0));
if (!nlh_send) {
syslog(LOG_ERR, "%s %s %d can't get mem fail!\n", __FILE__,
__func__, __LINE__);
return -1;
}
nlh_send->nlmsg_len = NLMSG_SPACE(0);
nlh_send->nlmsg_pid = getpid();
nlh_send->nlmsg_flags = 0;
iov_send.iov_base = (void *)nlh_send;
iov_send.iov_len = nlh_send->nlmsg_len;
msg_send.msg_name = (void *)&dst_addr;
msg_send.msg_namelen = sizeof(dst_addr);
msg_send.msg_iov = &iov_send;
msg_send.msg_iovlen = 1;
nlh_send->nlmsg_type = state;
sendmsg(sockfd, &msg_send, 0);
free(nlh_send);
LOG_MSG_D("send_msg_to_kernel send msg:%d\n", state);
return 0;
}
内核态初始化netlink:
struct sock *sock;
struct netlink_kernel_cfg nlcfg = {
.input = nl_custom_data_ready,
};
/* 新增一个netlink协议类型,协议类型最多32个。也可以复用内核已有的通用协议 NETLINK_GENERIC */
#define NETLINK_CAPTURE_PACKET 30 /* capture packet */
sock = netlink_kernel_create(&init_net, NETLINK_CAPTURE_PACKET, &nlcfg);
if (!sock) {
err = -1;
printk("pcap create netlink fail\n");
}
五、附上内核空间的源码(iptables target、netlink通讯)
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/netlink.h>
#include <net/netlink.h>
#include <net/net_namespace.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/kthread.h>
#include <linux/netfilter/x_tables.h>
#include <linux/ip.h>
MODULE_DESCRIPTION("pcap");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("geshifei@126.com");
#define dprintk(fmt, args...) do {\
if (debug_mode) {\
printk(fmt, ## args);\
}\
} while(0)
/* netlink消息类型 */
#define NLMSG_U2K_CAP_UNSPEC (1 << 0) /* 未定义 */
#define NLMSG_U2K_CAP_START (1 << 1) /* 用户空间-->内核空间 开始过滤处理*/
#define NLMSG_U2K_CAP_STOP (1 << 2) /* 用户空间-->内核空间 停止过滤处理 */
#define NLMSG_K2U_BUFFER_FULL (1 << 3) /* 内核空间-->用户空间 缓冲区满 */
#define NLMSG_U2K_CAP_DEBUG_ON (1 << 4) /* 内核空间-->用户空间 打开调试模式 */
#define NLMSG_U2K_CAP_DEBUG_OFF (1 << 5) /* 内核空间-->用户空间 关闭调试模式 */
/* netlink消息长度,3k。
* nlmsg_new还会加上nlmsghdr、skb_shared_info等额外信息,定义3k目的是为了nlmsg_new只申请一个page */
#define NL_BUFFER_SIZE (1024*3)
/* pcap_buffer最前面的2个字节用于表示pcap_buffer中实际的数据长度,不包括前面保留的2个字节 */
#define PCAP_BUFFER_HEAD_RESERVE 2
/* 每个抓到的包的第三个字节开始为6个字节的设备MAC*/
#define PCAP_MAC_HEAD_RESERVE 6
/* 这个宏表示单个tcp数据包存储的最长长度 ,超出部分忽略。pcap_buffer尾部少于1024字节时,就认为满了 。*/
#define PCAP_BUFFER_TAIL_RESERVE 1024
/* 尾部没有空间存放(PCAP_BUFFER_TAIL_RESERVE+2+2+6)个字节数据 */
#define NL_TX_SIZE (NL_BUFFER_SIZE - 4 - PCAP_BUFFER_TAIL_RESERVE - PCAP_MAC_HEAD_RESERVE)
static bool debug_mode = false;
static char *pcap_buffer = NULL;
static char *pcap_curr_ptr = NULL;
/* pcap_buffer中已经写入的数据包字节数 */
static __u32 pcap_buffer_len = 0;
static bool pcap_buffer_full = true;
static struct sk_buff *out_skb = NULL;
static struct task_struct *pcap_kthread = NULL;
static __u32 user_nlmsg_pid = 0;
static bool pcap_mod_exit = false;
/* iptables target 用这个完成量通知pcap_thread_fn线程缓冲区满了 */
static DECLARE_COMPLETION(pcap_buffer_completion);
static DEFINE_SPINLOCK(pcap_buffer_lock);
static struct sock *sock;
/*
* 测试线程,用来模拟iptables的target,往pcap_buffer写数据 。
* 周期1分钟写满缓冲区
*/
#ifdef CAPTURE_PACKET_DEBUG_THREAD
static struct task_struct *test_kthread = NULL;
static int pcap_test_thread_fn(void *dummy)
{
int i = 0;
unsigned long flags;
while (!kthread_should_stop()) {
if (pcap_buffer_full) {
ssleep(1);
continue;
}
spin_lock_irqsave(&pcap_buffer_lock, flags);
/* must check again */
if (pcap_buffer_full) {
spin_unlock_irqrestore(&pcap_buffer_lock, flags);
continue;
}
if (pcap_buffer) {
for (i = PCAP_BUFFER_HEAD_RESERVE;
i < NL_BUFFER_SIZE - 1; i++) {
pcap_buffer[i] = 'a' + i % 26;
}
pcap_buffer[i] = '\0';
pcap_buffer[0] =
(unsigned
char)((NL_BUFFER_SIZE -
PCAP_BUFFER_HEAD_RESERVE) >> 8);
pcap_buffer[1] =
(unsigned char)(NL_BUFFER_SIZE -
PCAP_BUFFER_HEAD_RESERVE);
pcap_buffer_full = true;
printk("pcap_test_thread_fn buffer is full\n");
complete(&pcap_buffer_completion);
}
spin_unlock_irqrestore(&pcap_buffer_lock, flags);
ssleep(60);
}
return 0;
}
static int debug_create_thread(void)
{
test_kthread =
kthread_run(pcap_test_thread_fn, NULL, "pcap_test_thread");
if (IS_ERR(test_kthread)) {
//printk(KERN_ERR "%s %d create thread error %ld!\n", __FILE__, __LINE__, PTR_ERR(test_kthread));
return PTR_ERR(test_kthread);
}
return 0;
}
#endif
/* iptables target
* 过滤出的数据包写入缓冲区pcap_buffer中。
* 缓冲区写满后通知pcap_thread_fn线程把数据发送到用户空间处理。
* 此时target不会再去写pcap_buffer,只有当用户程序处理完收到的数
* 据并发送NLMSG_U2K_CAP_PACKET通知内核后,target才会继续用采集
* 到的数据包填充pcap_buffer。
*
* pcap_buffer缓冲区数据包格式如下:
* [总长度高字节 总长度低字节][长度高字节 长度低字节 MAC tcp数据包1][长度高字节 长度低字节 MAC tcp数据包2]
* 总长度不包括两个字节的“长度”本身
* 各个数据包内的长度不包括两个字节的"长度"本身和6个字节的设备MAC
*/
static unsigned int
capture_packet_tg(struct sk_buff *skb, const struct xt_action_param *par)
{
int ret = -1;
unsigned int data_len = 0;
unsigned char s_mac[6];
struct iphdr *iph; /* debug */
struct ethhdr *eth;
if (pcap_buffer_full) {
return XT_CONTINUE;
}
iph = ip_hdr(skb);
eth = eth_hdr(skb);
memcpy(s_mac, eth->h_source, 6);
dprintk(" pcap: %x ----> %x, len=%d\n", iph->saddr,
iph->daddr, skb->len);
dprintk("source_mac:%02x:%02x:%02x:%02x:%02x:%02x\n", s_mac[0],s_mac[1],s_mac[2],s_mac[3],s_mac[4],s_mac[5]);
/* 在eth层判断data_len < 82 ,在ip层减去以太网头长度 */
data_len = skb->len;
if (data_len <= 68) {
return XT_CONTINUE;
}
if (data_len > PCAP_BUFFER_TAIL_RESERVE) {
data_len = PCAP_BUFFER_TAIL_RESERVE;
}
ret = spin_trylock(&pcap_buffer_lock);
if (!ret) {
return XT_CONTINUE;
}
/* 必须再次检查 */
if (pcap_buffer_full) {
goto unlock;
}
if (pcap_buffer && pcap_curr_ptr) {
/* 写缓冲区 */
*(pcap_curr_ptr++) = (unsigned char)(data_len >> 8);
*(pcap_curr_ptr++) = (unsigned char)(data_len);
*(pcap_curr_ptr++) = (unsigned char)(s_mac[0]);
*(pcap_curr_ptr++) = (unsigned char)(s_mac[1]);
*(pcap_curr_ptr++) = (unsigned char)(s_mac[2]);
*(pcap_curr_ptr++) = (unsigned char)(s_mac[3]);
*(pcap_curr_ptr++) = (unsigned char)(s_mac[4]);
*(pcap_curr_ptr++) = (unsigned char)(s_mac[5]);
memcpy(pcap_curr_ptr, skb->data, data_len);
pcap_curr_ptr += data_len;
pcap_buffer_len += (data_len + PCAP_BUFFER_HEAD_RESERVE + PCAP_MAC_HEAD_RESERVE);
dprintk("pcap:record data_len:%d\n",
data_len);
if (pcap_buffer_len > NL_TX_SIZE) {
pcap_buffer_full = true;
pcap_buffer[0] =
(unsigned char)(pcap_buffer_len >> 8);
pcap_buffer[1] =
(unsigned char)(pcap_buffer_len);
dprintk(" pcap: buf is full, len=%d\n",
pcap_buffer_len);
complete(&pcap_buffer_completion);
}
}
unlock:
spin_unlock(&pcap_buffer_lock);
return XT_CONTINUE;
}
/* 负责用户空间、内核空间通信的线程。
* 这个线程大多数时候都是睡眠状态,只有当target写满pcap_buffer后才会被唤醒。
*
* 这个线程还负责接收用户空间线程退出的信息,以后扩展用。
*/
static int pcap_thread_fn(void *dummy)
{
struct nlmsghdr *nlh;
while (1) {
/* payload 大小NL_BUFFER_SIZE,用户程序收到的数据大小还需要加上sizeof(nlmsghdr) */
out_skb = nlmsg_new(NL_BUFFER_SIZE, GFP_KERNEL);
if (!out_skb) {
ssleep(60 * 5); /* sleep 5 min */
dprintk(KERN_ERR "%s %s %d err\n", __FILE__, __func__,
__LINE__);
continue;
}
nlh =
nlmsg_put(out_skb, 0, 0, NLMSG_K2U_BUFFER_FULL,
NL_BUFFER_SIZE, 0);
if (!nlh) {
dprintk(KERN_ERR "%s %s %d err\n", __FILE__, __func__,
__LINE__);
nlmsg_free(out_skb);
ssleep(60 * 5);
continue;
}
pcap_buffer = nlmsg_data(nlh);
/* 保留最前面两个字节单元, [总长度高字节 总长度低字节][长度高字节 长度低字节 MAC tcp数据包1][长度高字节 长度低字节 MAC tcp数据包2] */
pcap_curr_ptr = pcap_buffer + PCAP_BUFFER_HEAD_RESERVE;
dprintk("pcap_thread_fn wait completion\n");
wait_for_completion(&pcap_buffer_completion);
dprintk("pcap_thread_fn after completion\n");
while (pcap_mod_exit) {
/* 这个时候回收内存时安全的,因为capture_packet_exit中已经把pcap_buffer_full置为true了 */
if (out_skb) {
nlmsg_free(out_skb);
out_skb = NULL;
}
if (kthread_should_stop()) {
return 0;
}
ssleep(1);
}
if (out_skb) {
/* 必须注意,nlmsg_unicast内部会释放out_skb,所以不能再次操作out_skb */
dprintk("nlmsg_unicast to user\n");
nlmsg_unicast(sock, out_skb, user_nlmsg_pid);
out_skb = NULL;
pcap_buffer = NULL;
pcap_curr_ptr = NULL;
pcap_buffer_len = 0;
}
}
return 0;
}
static void nl_custom_data_ready(struct sk_buff *skb)
{
struct nlmsghdr *nlh;
nlh = nlmsg_hdr(skb);
switch (nlh->nlmsg_type) {
case NLMSG_U2K_CAP_START:
dprintk
("nl_custom_data_ready receive start msg, nlh->nlmsg_pid:%d\n",
nlh->nlmsg_pid);
spin_lock(&pcap_buffer_lock);
user_nlmsg_pid = nlh->nlmsg_pid;
pcap_buffer_full = false;
pcap_buffer_len = 0;
spin_unlock(&pcap_buffer_lock);
break;
case NLMSG_U2K_CAP_STOP:
dprintk("nl_custom_data_ready receive stop msg\n");
/* 禁止iptables写数据 */
spin_lock(&pcap_buffer_lock);
pcap_buffer_full = true;
spin_unlock(&pcap_buffer_lock);
break;
case NLMSG_U2K_CAP_DEBUG_ON:
printk("nl_custom_data_ready debug mode enable\n");
debug_mode = true;
break;
case NLMSG_U2K_CAP_DEBUG_OFF:
dprintk("nl_custom_data_ready debug mode disable\n");
debug_mode = false;
break;
default:
dprintk(KERN_INFO "%s %s %d unknow msg type:%u recieved!\n",
__FILE__, __func__, __LINE__, nlh->nlmsg_type);
}
return;
}
static void nl_custom_data_ready(struct sk_buff *skb);
static struct xt_target capture_packet_tg_reg __read_mostly = {
.name = "pcap",
.revision = 0,
.family = NFPROTO_UNSPEC,
.target = capture_packet_tg,
.me = THIS_MODULE,
};
/* 初始化 */
static int __init capture_packet_init(void)
{
int err = 0;
struct netlink_kernel_cfg nlcfg = {
.input = nl_custom_data_ready,
};
err = xt_register_target(&capture_packet_tg_reg);
if (err) {
printk("pcap register target fail\n");
return err;
}
sock = netlink_kernel_create(&init_net, NETLINK_CAPTURE_PACKET, &nlcfg);
if (!sock) {
err = -1;
printk("pcap create netlink fail\n");
goto unregister_target;
}
pcap_kthread = kthread_run(pcap_thread_fn, NULL, "pcap_thread");
if (IS_ERR(pcap_kthread)) {
err = PTR_ERR(pcap_kthread);
printk("pcap create pcap thread fail\n");
goto fail_free_netlink;
}
#ifdef CAPTURE_PACKET_DEBUG_THREAD
err = debug_create_thread();
if (err) {
printk("pcap create test thread fail\n");
goto fail_stop_thread;
}
#endif
printk(KERN_INFO "pcap ok\n");
return 0;
#ifdef CAPTURE_PACKET_DEBUG_THREAD
fail_stop_thread:
spin_lock(&pcap_buffer_lock);
pcap_buffer_full = true;
spin_unlock(&pcap_buffer_lock);
kthread_stop(test_kthread);
pcap_mod_exit = true;
complete(&pcap_buffer_completion);
#endif
fail_free_netlink:
netlink_kernel_release(sock);
unregister_target:
xt_unregister_target(&capture_packet_tg_reg);
printk("pcap init fail, err:%d\n", err);
return err;
}
void __exit capture_packet_exit(void)
{
xt_unregister_target(&capture_packet_tg_reg);
netlink_kernel_release(sock);
/* 必须确保iptables target不会去操作 pcap_buffer了,因为线程pcap_kthread
* 退出,会回收pcap_buffer内存。
*/
spin_lock(&pcap_buffer_lock);
pcap_buffer_full = true;
spin_unlock(&pcap_buffer_lock);
#ifdef CAPTURE_PACKET_DEBUG_THREAD
kthread_stop(test_kthread);
#endif
pcap_mod_exit = true;
complete(&pcap_buffer_completion);
kthread_stop(pcap_kthread);
printk(KERN_INFO "pcap ok\n");
}
module_init(capture_packet_init);
module_exit(capture_packet_exit);
openssl加密代码、网络设备与云端交互、压缩等,都是现成的技术,网上资料很多。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话