通过前面的学习,我们窥探了整个Netfilter框架,下面我们就通过一些编程实例来进一步学习。

一. 基于网络设备接口进行数据包过滤
根据hook函数接收的参数中的 struct net_device 结构,net_device 结构体用于描述网络接口设备,其中name这个成员表示对应设备的名字,我们可以通过比对来判断数据包的源接口或目的接口。

/*安装一个丢弃所有进入我们指定接口的数据包的 netfilter hook 函数*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>

MODULE_LICENSE("Dual BSD/GPL");

static struct nf_hook_ops nfho;

static char *drop_if = "wlan0";//以太网(无线)

//钩子函数,注意参数格式与开发环境源码树保持一致
unsigned int hook_func(const struct nf_hook_ops *ops, 
        struct sk_buff *skb,
        const struct net_device *in,
        const struct net_device *out,
        int (*okfn)(struct sk_buff *))
{
    if(0 == strcmp(in->name, drop_if))//如果数据包是来源这个接口
    {
        printk("Dropped packet on %s ...\n", drop_if);
        return NF_DROP;//丢弃数据包
    }
    else
    {
        printk("Allow packet pass...\n");
        return NF_ACCEPT;//允许数据包通过
    }
}

static int __init hook_init(void)
{
    nfho.hook = hook_func;//关联对应处理函数
    nfho.hooknum = NF_INET_PRE_ROUTING;//ipv4的第一个hook
    nfho.pf = PF_INET;//ipv4,所以用这个
    nfho.priority = NF_IP_PRI_FIRST;//优先级,第一顺位

    nf_register_hook(&nfho);//注册

    return 0;
}
static void __exit hook_exit(void)
{
    nf_unregister_hook(&nfho);//注销
}

module_init(hook_init);
module_exit(hook_exit);

由于我是用的wifi,所以这里测试用的源接口设备是无线网卡wlan0。
要怎么知道网卡名字呢?ifconfig 即可。

加载该模块后,dmesg,当你打开网页的时候,就会打印出
[114623.911168] Dropped packet on wlan0 …
[114623.911832] Dropped packet on wlan0 …
[114623.913117] Dropped packet on wlan0 …
[114623.942018] Dropped packet on wlan0 …
[114623.943559] Dropped packet on wlan0 …
[114623.943580] Dropped packet on wlan0 …

二. 基于地址进行数据包过滤
与基于接口进行过滤的原理类似,不过我们利用的信息是 sk_buff 中的ip地址信息。如何通过 sk_buff 访问到数据包传输对应的ip地址,在前面剖析linux内核网络协议栈的时候,我们经过了深度剖析。

//开发环境 linux kernel 3.13.0-43
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>//for ip header

MODULE_LICENSE("Dual BSD/GPL");

static struct nf_hook_ops nfho;

static unsigned char *drop_if = "\x77\x4b\xd9\x6d";//baidu的ip地址

//钩子函数,注意参数格式与开发环境源码树保持一致
unsigned int hook_func(const struct nf_hook_ops *ops, 
        struct sk_buff *skb,
        const struct net_device *in,
        const struct net_device *out,
        int (*okfn)(struct sk_buff *))
{
    struct iphdr *ip = ip_hdr(skb);//获取数据包的ip首部
    if(ip->saddr == *(unsigned int *)drop_if)//ip首部中的源端ip地址比对
    {
        //打印网址
        printk("Dropped packet from %d.%d.%d.%d\n",*drop_if,
                *(drop_if+1), *(drop_if+2),*(drop_if+3));
        return NF_DROP;
    }
    else
    {
        //打印网址,这里把长整型转换成点十格式
        unsigned char *p = (unsigned char *)&(ip->saddr);
        printk("Allowed packet from %d.%d.%d.%d\n",p[3]&0xff,
                p[2]&0xff, p[1]&0xff, p[0]&0xff);
        return NF_ACCEPT;
    }
}

static int __init hook_init(void)
{
    nfho.hook = hook_func;//关联对应处理函数
    nfho.hooknum = NF_INET_PRE_ROUTING;//ipv4的第一个hook
    nfho.pf = PF_INET;//ipv4,所以用这个
    nfho.priority = NF_IP_PRI_FIRST;//优先级,第一顺位

    nf_register_hook(&nfho);//注册

    return 0;
}
static void __exit hook_exit(void)
{
    nf_unregister_hook(&nfho);//注销
}

module_init(hook_init);
module_exit(hook_exit);

基于sk_buff,找到数据包的ip首部,虽然前面已经在分析协议栈的时候,有了剖析,但那是基于低版本的,这里开发的内核源码树版本是3.13,这里的 sk_buff 较低版本有非常大的改动。当然在数据包中找ip首部也好,还是tcp首部也好,原理都是一样的,都是通过首部的长度,根据首地址偏移得来。

有时候,linux版本升级的时候,有些内核结构和接口会发生轻微变化,所以我们编写内核驱动的时候,一定要根据你的开发版本的内核接口编写。

上面这个例子,我们是通过丢弃从 www.baidu.com 传来的数据包,但允许其余网站的数据传输,所以你可以访问其余网站,但是不能访问百度(实则是它们根据你的请求(http请求,前面我们有剖析过tinyhttpd 的源码,大致了解了http),返回的数据你(应用层)接不接收的到)。

linux 终端:ping www.baidu.com 你就知道百度的ip地址了。

三. 基于TCP目的端口进行数据包过滤
同上面的基于ip地址,这里我们要获得数据包 sk_buff 中的TCP首部。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>//for ip header
#include <linux/tcp.h>//for tcp header

MODULE_LICENSE("Dual BSD/GPL");

static struct nf_hook_ops nfho;

static unsigned char *drop_port = "\x00\x5a";//port 90

//钩子函数,注意参数格式与开发环境源码树保持一致
unsigned int hook_func(const struct nf_hook_ops *ops, 
        struct sk_buff *skb,
        const struct net_device *in,
        const struct net_device *out,
        int (*okfn)(struct sk_buff *))
{
    struct iphdr *ip = ip_hdr(skb);//获取ip首部
    struct tcphdr *tcp = tcp_hdr(skb);//获取tcp首部

    if(!skb)
        return NF_ACCEPT;
    if(!tcp)
        return NF_ACCEPT;
    if(ip->protocol != IPPROTO_TCP)//不是tcp协议的放行
        return NF_ACCEPT;

    //检查目的端口,也就是本机上的某个应用程序
    if(tcp->dest == *(unsigned short *)drop_port)
    {
        printk("Dropped packet from %d\n", tcp->dest);
        return NF_DROP;
    }
    else
    {
        printk("Allowed packet from %d\n", tcp->dest);
        return NF_ACCEPT;
    }
}

static int __init hook_init(void)
{
    nfho.hook = hook_func;//关联对应处理函数
    nfho.hooknum = NF_INET_PRE_ROUTING;//ipv4的第一个hook
    nfho.pf = PF_INET;//ipv4,所以用这个
    nfho.priority = NF_IP_PRI_FIRST;//优先级,第一顺位

    nf_register_hook(&nfho);//注册

    return 0;
}

static void __exit hook_exit(void)
{
    nf_unregister_hook(&nfho);//注销
}

module_init(hook_init);
module_exit(hook_exit);

测试:这里我们丢弃的是发往tcp端口为90的数据包。为了测试,我们额外在用户态编写了一个简单的 tcp socket 服务器/客户端的程序。
用户态 tcp socket 程序
在上面程序中,我们设定的端口是 90,正常情况下,该服务器/客户端可以进行正常通信。一旦加载模块后,该服务器模型便无法进行通信了,出现阻塞,一旦卸载模块,又可以正常通信了。

需要说明的是,上面一些简单的hook函数并没有什么存在价值,这里只是通过小小实例来深化一下Netfilter Hook机制。在后续篇章,我们会进一步的探讨Netfilter Hook。

develop environment:linux kernel 3.13.0-43-generic

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐