通过Linux内核协议栈netfilter拦截数据报文

本文介绍了如何通过Linux内核的netfilter框架注册钩子函数,实现在数据包经过Linux协议栈时进行拦截和调试。netfilter是一个用于数据报文过滤的子系统,支持数据包过滤、地址伪装、NAT等功能。文章详细讲解了网卡转发包的流程、netfilter框架的工作原理,以及如何注册和使用钩子函数,通过示例展示了如何打印数据包信息来监控其走向。
摘要由CSDN通过智能技术生成

通过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;
};

结构体参数说明

参数类型说明
hooknf_hookfn钩子函数
pfu_int8_t协议,默认采用PF_INET即可
hooknumunsigned int钩子点, NF_INET_FORWARD、NF_IP_LOCAL_IN 等
priorityint优先级,值越小,优先级越高,可为负数

hooknum可配置的值

hooknum说明
NF_IP_PRE_ROUTING0在路由之前的数据,无论是转发还是到本地的数据都会经过
NF_IP_LOCAL_IN1访问设备的数据
NF_INET_FORWARD2转发的数据
NF_IP_LOCAL_OUT3本地进程发出的数据
NF_IP_POST_ROUTING4通过设备出去的数据,包括转发和从本地进程发出的数据,可以理解为离开设备的数据都要经过POST_ROUTING

priority可配置的值

priority可以配置任意整数的,因为系统有默认的钩子,定义了一些参考值,我们开发过程中一般在系统priority的基础上进行加减操作,用于标记自己加入钩子的位置 以下为系统定义的优先级

priority说明
NF_IP_PRI_FIRSTINT_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_FILTER0-
NF_IP_PRI_SECURITY50-
NF_IP_PRI_NAT_SRC100-
NF_IP_PRI_SELINUX_LAST225-
NF_IP_PRI_CONNTRACK_HELPER300-
NF_IP_PRI_CONNTRACK_CONFIRMINT_MAX-
NF_IP_PRI_LASTINT_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

转载

通过Linux内核协议栈netfilter拦截数据报文 - 知乎 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值