【Linux 驱动】Netfilter/iptables (五) 数据包过滤
通过前面的学习,我们窥探了整个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
更多推荐
所有评论(0)