上一篇文章中演示如何在conntrak中增加包数和字节数的简单统计功能。如果想实现比较完善的统计功能,比如区分内网数据包流动的两个方向(接收和发送)来单独统计,在此基础上增加流量控制,限制某个内网ip的连接数,包速和字节速,能够在客户端控制网关的流量控制功能,查看某个ip的统计信息以及属于该ip连接的更详细信息,并能增加,修改,删除流量控制规则,甚至增加验证上网功能等等,则显然不能使用前面的方法,因为这样会对原有代码修改很大,可能会引入错误,没有扩展性,结构不清晰。需要重新设计一套框架,姑且叫做tc table,
2.应用环境
外网<----->[eth0 linux网关 eth1]<----------->内网
linux 网关启用nat
3.为何不使用链路层tc
linux链路层tc功能已经很好很强大,但是不能满足该应用环境和应用需求。
4.框架
tc table显然需要依赖于netfilter框架.依赖于conntrack和nat,为了简化只考虑ipv4.
在v2.4.0的内核中,netfilter框架如下
现在考虑一下可能的钩子注册点,比较合理的有三个地方,forward,local in,local out.
因为在prerouting和postrouting处很难搞清数据的流向,是从内到外还是从外到内,从外到本机还是本机到外,本机到本机等等?经过rout后,数据的方向基本确定。在forward处控制内外之间的流量,在local in和local out处控制本机收发流量。
5.钩子优先级
钩子的优先级决定了钩子在处理数据的先后顺序。
在forward中,tctable钩子安排在filter.forward后
在local in中,tctable钩子安排在filter.input后
在local out中,tctable钩子安排在filter.output后
这样做好的好处是数据包有可能被filter过滤,这样的数据包就不需要被tctable处理.
filter的三个钩子优先级都是NF_IP_PRI_FILTER=0,因此可以定义tctable的钩子优先级为NF_IP_PRI_FILTER+10
6.基本代码框架
套用iptable_filter.c的代码,形成如下的最简单的框架
tctable_filter.c
#include
#include
#include
#include
static void dump(const char * msg, struct iphdr * iph)
{
printk("%s:%u.%u.%u.%u->%u.%u.%u.%u %u\n",
msg,
NIPQUAD(iph->saddr),
NIPQUAD(iph->daddr),
iph->protocol
);
}
static unsigned int
tct_forward_hook(unsigned int hook,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
dump("tct_forward_hook",(*pskb)->nh.iph);
return NF_ACCEPT;
}
static unsigned int
tct_local_in_hook(unsigned int hook,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
dump("tct_local_in_hook",(*pskb)->nh.iph);
return NF_ACCEPT;
}
static unsigned int
tct_local_out_hook(unsigned int hook,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
/* root is playing with raw sockets. */
if ((*pskb)->len < sizeof(struct iphdr)
|| (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr)) {
if (net_ratelimit())
printk("ipt_hook: happy cracking.\n");
return NF_ACCEPT;
}
dump("tct_local_out_hook",(*pskb)->nh.iph);
return NF_ACCEPT;
}
static struct nf_hook_ops tct_ops[]
= { { { NULL, NULL }, tct_local_in_hook, PF_INET, NF_IP_LOCAL_IN, NF_IP_PRI_FILTER+10 },
{ { NULL, NULL }, tct_forward_hook, PF_INET, NF_IP_FORWARD, NF_IP_PRI_FILTER+10 },
{ { NULL, NULL }, tct_local_out_hook, PF_INET, NF_IP_LOCAL_OUT, NF_IP_PRI_FILTER+10 }
};
static int __init init(void)
{
int ret;
/* Register hooks */
ret = nf_register_hook(&tct_ops[0]);
if (ret < 0)
return ret;
ret = nf_register_hook(&tct_ops[1]);
if (ret < 0)
goto cleanup_hook0;
ret = nf_register_hook(&tct_ops[2]);
if (ret < 0)
goto cleanup_hook1;
return ret;
cleanup_hook1:
nf_unregister_hook(&tct_ops[1]);
cleanup_hook0:
nf_unregister_hook(&tct_ops[0]);
return ret;
}
static void __exit fini(void)
{
unsigned int i;
for (i = 0; i < sizeof(tct_ops)/sizeof(struct nf_hook_ops); i++)
nf_unregister_hook(&tct_ops[i]);
}
module_init(init);
module_exit(fini);
7.编译,测试
在net/ipv4/netfilter/Makefile中,增加一行
obj-m += tctable_filter.o
然后make modules
make modules_install
tctable_filter是一个模块,不需要make这么复杂,在第一次make时,截取编译命令
gcc -D__KERNEL__ -I/usr/src/linux-2.4.7-10/include -Wall -Wstrict-prototypes -Wno-trigraphs -O2 -fomit-frame-pointer -fno-strict-aliasing -fno-common -Wno-unused -pipe -mpreferred-stack-boundary=2 -march=i686 -DMODULE -DMODVERSIONS -include /usr/src/linux-2.4.7-10/include/linux/modversions.h -c -o tctable_filter.o tctable_filter.c
以后有修改用这个编译就行了
然后手工执行
cp tctable_filter.o /lib/modules/2.4.7-10custom/kernel/net/ipv4/netfilter/
最后insmod tctable_filter
dmseg看看可有输出。
tct_local_out_hook:10.1.9.30->10.1.9.45 6
tct_local_in_hook:10.1.9.45->10.1.9.30 6
tct_local_out_hook:10.1.9.30->10.1.9.45 6
tct_local_in_hook:10.1.9.45->10.1.9.30 6
tct_local_out_hook:10.1.9.30->10.1.9.45 6
tct_local_in_hook:10.1.9.45->10.1.9.30 6
tct_local_out_hook:10.1.9.30->10.1.9.45 6