目录
Linux的一大优点是其网络功能。路由器,交换机等许多网络产品均基于嵌入式Linux操作系统。
网络过滤是Linux内核中很好的基础结构,它使我们能够过滤和操作网络堆栈中的数据包。您可以构建用于防火墙过滤的网络过滤器,记录数据包,加密/解密等等。
我们可以将网络过滤器构建为内核模块,并动态地加载/卸载它的功能。
这篇文章假定您知道如何编写和加载一个简单的内核模块,如果不是从本文开始的话
简单的例子
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/init.h>
#include <net/sock.h>
#include <linux/inet.h>
#include <linux/ip.h>
#include <linux/kernel.h>
#include <linux/netfilter.h>
#include <uapi/linux/netfilter_ipv4.h>
unsigned int main_hook(unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff*))
{
struct iphdr *iph;
iph = ip_hdr(skb);
if(iph->saddr == in_aton("192.168.0.2"))
{
return NF_DROP;
}
return NF_ACCEPT;
}
static struct nf_hook_ops netfops;
int __init my_module_init(void)
{
netfops.hook = main_hook;
netfops.pf = PF_INET;
netfops.hooknum = 0;
netfops.priority = NF_IP_PRI_FIRST;
nf_register_hook(&netfops);
return 0;
}
void my_module_exit(void)
{
nf_unregister_hook(&netfops);
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
这是一个简单的过滤器示例,它将丢弃来自IP 192.168.0.2的所有数据包
模块初始化功能在路由之前在输入路径中注册过滤器。要注册过滤器,我们需要指定:
- 挂钩函数– main_hook
- 套接字系列(IPv4)– PF_INET
- 挂钩类型–路由之前的输入路径– 0
- 挂钩优先级–如果我们在同一位置注册多个挂钩– NP_IP_PRI_FIRST
参数取决于系列–在IPv4中,您可以注册5点过滤器:
- 输入路径–路由之前
- 输入路径–路由之后
- 输出路径–布线之前
- 输出路径–路由之后
- 从一个网络适配器转发到另一个
要构建该模块,我们需要为内核配置网络过滤器支持:

构建模块并对其进行测试:
- 从IP运行ping命令:192.168.0.2 –应该得到正确的答复–保持运行
- 将模块插入本地内核(insmod ./filter.ko)
- 您现在不应该看到回复
- 删除模块-再次看到正确的回复
每个数据包都通过挂钩函数main_hook传递。让我们详细了解一下:
unsigned int main_hook(unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff*))
{
struct iphdr *iph;
iph = ip_hdr(skb);
if(iph->saddr == in_aton("192.168.0.2"))
{
return NF_DROP;
}
return NF_ACCEPT;
}
第一个参数是钩子编号–例如,如果我们想在路由之前将所有消息记录在输入和输出路径中,则可以对多个钩子使用相同的函数
第二个参数是套接字缓冲区–表示数据包的数据结构
接下来,我们有两个网络适配器–输入用于输入,输出用于输出,以及在转发的情况下两者
最后一个参数是指向最后一个过滤器的函数指针-调用它会取消该过滤器旁边的所有其他过滤器(这就是为什么我们有优先级的原因)
实施
我们首先设置一个指向数据包IP标头的指针。现在,我们可以访问源IP和目标IP以及所有其他IP标头字段。
然后我们检查源地址,并丢弃来自192.168.0.2的数据包。在其他情况下,我们接受该数据包,则将调用下一个过滤器
挂钩函数必须返回以下值之一:
- NF_DROP –丢弃数据包–释放资源
- NF_ACCEPT –接受数据包–继续下一个过滤器
- NF_STOLEN –不要继续处理数据包但不要释放它–我的责任是释放它
- NF_QUEUE –将数据包排队以进行用户空间处理
- NF_REPEAT –再次调用此过滤器
用户空间处理
如果返回NF_QUEUE,则控件将交付给用户空间。要在用户空间中实现它,我们应该使用库:nfnetlink,netfilter_queue
我将在以后的文章中介绍这些库,但是对于这篇文章,这是一个非常简单的示例,仅用于理解概念(没有错误检查和复杂的处理):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <linux/types.h>
#include <linux/netfilter.h>
#include <libnetfilter_queue/libnetfilter_queue.h>
static int filter_fn(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data)
{
int id;
struct nfqnl_msg_packet_hdr *ph;
ph = nfq_get_msg_packet_hdr(nfa);
id = ntohl(ph->packet_id);
printf("filter function id=%d\n",id);
if ( id % 3 == 0)
return nfq_set_verdict(qh, id, NF_DROP, 0, NULL);
return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL);
}
int main(int argc, char **argv)
{
struct nfq_handle *h;
struct nfq_q_handle *qh;
int fd;
int rv;
char buf[4096];
h = nfq_open();
nfq_bind_pf(h, AF_INET);
qh = nfq_create_queue(h, 0, filter_fn, NULL);
nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff);
fd = nfq_fd(h);
while ((rv = recv(fd, buf, sizeof(buf), 0)))
{
printf("received\n");
nfq_handle_packet(h, buf, rv);
}
nfq_destroy_queue(qh);
nfq_close(h);
exit(0);
}
在主函数中,我们打开连接,创建队列,然后将函数filter_fn注册为我们的过滤器,然后启动接收循环。
在过滤功能中,我们可以丢弃或接受数据包。
更改内核代码以返回NF_QUEUE:
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/init.h>
#include <net/sock.h>
#include <linux/inet.h>
#include <linux/ip.h>
#include <linux/kernel.h>
#include <linux/netfilter.h>
#include <uapi/linux/netfilter_ipv4.h>
unsigned int main_hook(unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff*))
{
struct iphdr *iph;
iph = ip_hdr(skb);
if(iph->saddr == in_aton("192.168.0.2"))
{
return NF_QUEUE;
}
return NF_ACCEPT;
}
static struct nf_hook_ops netfops;
int __init my_module_init(void)
{
netfops.hook = main_hook;
netfops.pf = PF_INET;
netfops.hooknum = 0;
netfops.priority = NF_IP_PRI_FIRST;
nf_register_hook(&netfops);
return 0;
}
void my_module_exit(void)
{
nf_unregister_hook(&netfops);
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
构建用户空间应用程序:
# gcc -o app ./simple.c -lnfnetlink -lnetfilter_queue
要测试代码,请构建并插入模块,然后运行应用程序
$ sudo insmod ./netfildrv.ko
$ sudo ./app
received
filter function id=1
received
filter function id=2
received
filter function id=3
received
...
本文介绍了如何在Linux内核中构建网络过滤器,用于丢弃特定源IP的数据包。通过示例代码展示了如何编写内核模块,动态加载和卸载,并解释了内核模块的网络过滤功能。此外,还提到了用户空间处理,通过libnetfilter_queue库将数据包传递给用户空间进行进一步处理。
2669

被折叠的 条评论
为什么被折叠?



