通过Linux内核协议栈netfilter拦截数据报文 - 知乎
通过Linux内核协议栈netfilter拦截数据报文
功能简介
通过内核模块的方式向netfilter框架注册钩子函数,实现简单的数据包转发调试,当有数据包经过Linux协议栈时,打印相关信息。目的在于熟悉netfilter框架的基础api,为后续开发防火墙等功能做准备。
netfilter简介
netfilter是由Rusty Russell提出的Linux 2.4内核防火墙框架,该框架既简洁又灵活,可实现安全策略应用中的许多功能,如数据包过滤、数据包处理、地址伪装、透明代理、动态网络地址转换(Network Address Translation,NAT),以及基于用户及媒体访问控制(Media Access Control,MAC)地址的过滤和基于状态的过滤、包速率限制等。
网卡转发包流程
以下为网口收包到转发的流程,NF_HOOK位置为netfilter钩子
pkt-flow-dxt1
netfilter框架流程
netfilter是Linux协议栈的一个子系统,用于数据报文的过滤。在路由器设备中,数据报文的来源和去向有很多场景。比如转发数据、本地发出的数据、访问设备本身的数据等,可以通过下图简单了解基本流程。
netfilter
netfilter钩子注册
netfilter为通用的框架,Linux系统已经内建了常用的钩子。netfilter和iptables命令时密切相关的,iptables命令的最终实现是基于netfilter,比如filter表、nat表、mangle表的各个链的实现,都在netfilter源码中找到具体实现,这里就不一一讲解。
netfilter外部接口
我们可以通过以下接口注册和卸载钩子函数,在钩子函数中可以将数据报文解析出来进行处理,比如过滤。
接口列表
注册一个钩子函数
int nf_register_net_hook(struct net *net, const struct nf_hook_ops ops);
void nf_unregister_net_hook(struct net *net, const struct nf_hook_ops ops);
注册多个钩子函数
int nf_register_net_hooks(struct net *net, const struct nf_hook_ops *reg,unsigned int n);
void nf_unregister_net_hooks(struct net *net, const struct nf_hook_ops *reg,unsigned int n);
注册结构体定义
struct nf_hook_ops {
/* User fills in from here down. */
nf_hookfn *hook;
struct net_device *dev;
void *priv;
u_int8_t pf;
unsigned int hooknum;
/* Hooks are ordered in ascending priority. */
int priority;
};
结构体参数说明
参数 | 类型 | 说明 |
---|---|---|
hook | nf_hookfn | 钩子函数 |
pf | u_int8_t | 协议,默认采用PF_INET即可 |
hooknum | unsigned int | 钩子点, NF_INET_FORWARD、NF_IP_LOCAL_IN 等 |
priority | int | 优先级,值越小,优先级越高,可为负数 |
hooknum可配置的值
hooknum | 值 | 说明 |
---|---|---|
NF_IP_PRE_ROUTING | 0 | 在路由之前的数据,无论是转发还是到本地的数据都会经过 |
NF_IP_LOCAL_IN | 1 | 访问设备的数据 |
NF_INET_FORWARD | 2 | 转发的数据 |
NF_IP_LOCAL_OUT | 3 | 本地进程发出的数据 |
NF_IP_POST_ROUTING | 4 | 通过设备出去的数据,包括转发和从本地进程发出的数据,可以理解为离开设备的数据都要经过POST_ROUTING |
priority可配置的值
priority可以配置任意整数的,因为系统有默认的钩子,定义了一些参考值,我们开发过程中一般在系统priority的基础上进行加减操作,用于标记自己加入钩子的位置 以下为系统定义的优先级
priority | 值 | 说明 |
---|---|---|
NF_IP_PRI_FIRST | INT_MIN | - |
NF_IP_PRI_RAW_BEFORE_DEFRAG | -450 | - |
NF_IP_PRI_CONNTRACK_DEFRAG | -400 | - |
NF_IP_PRI_RAW | -300 | - |
NF_IP_PRI_SELINUX_FIRST | -225 | - |
NF_IP_PRI_CONNTRACK | -200 | - |
NF_IP_PRI_MANGLE | -150 | - |
NF_IP_PRI_NAT_DST | -100 | - |
NF_IP_PRI_FILTER | 0 | - |
NF_IP_PRI_SECURITY | 50 | - |
NF_IP_PRI_NAT_SRC | 100 | - |
NF_IP_PRI_SELINUX_LAST | 225 | - |
NF_IP_PRI_CONNTRACK_HELPER | 300 | - |
NF_IP_PRI_CONNTRACK_CONFIRM | INT_MAX | - |
NF_IP_PRI_LAST | INT_MAX | - |
## 注册实例 |
下面我们写一个实例,通过打印数据包信息,看看数据包的走向 代码中,我们定义hook_ops数组,包含3个钩子实体 forward_hook、forward_hook2、local_in_hook,其中前两个为forward钩子,但挂载的优先级不一样,我们可以通过打印看到,优先级情况,而local_in_hook为访问设备本身的数据,通过ip地址我们可以很方便查看是否是我们想要的结果。
钩子实例
static u_int32_t forward_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
// ct为连接跟踪指针,标记一条数据流,非常重要的数据结构,贯穿整个netfilter框架,这里有些变量没有用到,后面的具体开发中会用
struct nf_conn *ct = (struct nf_conn *)skb->_nfct;
struct udphdr *udph = NULL; // udp头部指针,用于解析udp包头部
struct tcphdr *tcph = NULL; // tcp头部指针,用于解析tcp包头部
struct iphdr *iph = NULL; // ip头部,包含源ip、目的ip等三层报文信息
u_int16_t sport = 0;
u_int16_t dport = 0;
if (ct == NULL || !nf_ct_is_confirmed(ct))
{
return NF_ACCEPT;
}
iph = ip_hdr(skb); // skb为数据报文指针,在转发过程中skb用于记录一个数据报文,转发过程都是操作skb指针
if (!iph)
return NF_ACCEPT;
// 这里将数据包的源ip和目的ip打印出来
printk("1-----------forward %pI4--->%pI4\n", &iph->saddr, &iph->daddr);
// 返回NF_ACCEPT,让netfilter框架继续处理,该返回值决定了数据报文的命运,如果返回NF_DROP,那么该数据报文会丢弃,当然还有其他几个返回值,用的比较少
return NF_ACCEPT;
}
ops注册实例
static struct nf_hook_ops hook_ops[] __read_mostly = {
{
.hook = forward_hook, // 转发的数据会经过该函数处理
.pf = PF_INET,
.hooknum = NF_INET_FORWARD,
.priority = NF_IP_PRI_FIRST + 1,
},
{
.hook = forward_hook2,// 转发的数据会经过该函数处理,但在forward_hook之后处理
.pf = PF_INET,
.hooknum = NF_INET_FORWARD,
.priority = NF_IP_PRI_FIRST + 2, // 优先级比第一个低一点
},
{
.hook = local_in_hook, // 访问设备的数据经过该函数处理
.pf = PF_INET,
.hooknum = NF_INET_LOCAL_IN,
.priority = NF_IP_PRI_FIRST + 1,
}};
static int __init hook_init(void)
{
nf_register_net_hooks(&init_net, hook_ops, ARRAY_SIZE(hook_ops));
printk("#init netfilter hook....ok\n");
return 0;
}
编译运行
环境 代码:OpenWrt 19.07 或其他版本OpenWrt分支版本,linux内核5.4.xx版本 编译环境: ubuntu18 运行环境: OpenWrt系统(内核版本和编译模块版本一致) 编译 将源码包拷贝到OpenWrt package目录,make menuconfig选择kmod-netfilter_hook并保存
menu-netfilter
通过以下命令编译
make package/netfilter_hook/compile V=s
编译完成后会在build_dir子目录生成netfilter_hook.ko文件,将该文件通过winscp工具传输到OpenWrt开发板(路由器)中 然后执行以下命令:
insmod netfilter_hook.ko
cat /proc/kmsg
保证开发板能够通过wan口连接外网,并接入一台pc到lan口,pc分别访问设备和外网
测试结果
<4>[ 4343.926739] 1-----------forward 192.168.66.167--->14.215.177.38
<4>[ 4343.928167] 2-----------forward 192.168.66.167--->14.215.177.38
<4>[ 4343.930772] local in 192.168.10.189--->192.168.10.199
<4>[ 4343.978416] local in 192.168.10.189--->192.168.10.199
<4>[ 4344.022984] local in 192.168.10.189--->192.168.10.199
<4>[ 4344.070944] local in 192.168.10.189--->192.168.10.199
<4>[ 4344.120639] local in 192.168.10.189--->192.168.10.199
<4>[ 4344.168305] local in 192.168.10.189--->192.168.10.199
<4>[ 4344.218524] local in 192.168.10.189--->192.168.10.199
<4>[ 4344.266218] local in 192.168.10.189--->192.168.10.199
<4>[ 4344.280309] 1-----------forward 192.168.66.167--->14.215.177.38
<4>[ 4344.281698] 2-----------forward 192.168.66.167--->14.215.177.38
<4>[ 4344.283171] 1-----------forward 192.168.66.167--->14.215.177.38
<4>[ 4344.284758] 2-----------forward 192.168.66.167--->14.215.177.38
<4>[ 4344.286710] local in 192.168.10.189--->192.168.10.199
<4>[ 4344.310932] 1-----------forward 192.168.66.167--->14.215.177.38
<4>[ 4344.313649] 2-----------forward 192.168.66.167--->14.215.177.38
<4>[ 4344.319432] local in 192.168.10.189--->192.168.10.199
<4>[ 4344.352559] 1-----------forward 192.168.66.167--->14.215.177.38
<4>[ 4344.354507] 2-----------forward 192.168.66.167--->14.215.177.38
<4>[ 4344.356741] local in 192.168.10.189--->192.168.10.199
<4>[ 4344.372923] 1-----------forward 192.168.66.167--->14.215.177.38
<4>[ 4344.374703] 2-----------forward 192.168.66.167--->14.215.177.38
<4>[ 4344.380287] local in 192.168.10.189--->192.168.10.199
<4>[ 4344.430356] local in 192.168.10.189--->192.168.10.199
<4>[ 4344.478390] local in 192.168.10.189--->192.168.10.199
<4>[ 4344.522968] local in 192.168.10.189--->192.168.10.199
<4>[ 4344.568144] local in 192.168.10.189--->192.168.10.199
<4>[ 4344.618331] local in 192.168.10.189--->192.168.10.199
转载