1、 流量控制的实现要求:路由器执行流量控制后,对每一个非特权IP都加以流量控制策略。实现对每一个IP地址的流量限制。
2、 实现思路:基于HTB方法。 通过c程序来执行的shell程序,来配置策略,创建类和分类器。然后在Netfilter中注册钩子函数,对数据包打标记,以便以分类器根据该标记来对数据包进行匹配。
3、 具体实现,分为两步:
(1)用C语言来执行HTB的shell命令(写入文件,最后执行该文件),文件中部分信息如下:
#!/bin/sh
tc qdisc del dev eth1 root 2>/dev/null #删除以前的eth1的qdisc
tc qdisc add dev eth1 root handle 1: htb #为eth1创建qdisc的根
tc class add dev eth1 parent 1:0 classid 1:1 htb rate 1024kbit ceil 1024kbit
#为创建子类1:1, 并限制该类的速率不超过1024
tc filter add dev eth1 protocol ip parent 1:0 prio 1 handle 1 fw flowid 1:1
#为该类创建分类器,标记为“1”的数据包会被送往该类(handle 1:匹配标记为1的数据包)
tc qdisc add dev eth1 parent 1:1 handle 1 tbf rate 1024kbit burst 20k latency 10ms
在该类下创建tbf(令牌桶过滤器)队列队则
tc class add dev eth1 parent 1:0 classid 1:2 htb rate 1024kbit ceil 1024kbit
tc filter add dev eth1 protocol ip parent 1:0 prio 1 handle 2 fw flowid 1:2
tc qdisc add dev eth1 parent 1:2 handle 2 tbf rate 1024kbit burst 20k latency 10ms
tc class add dev eth1 parent 1:0 classid 1:89 htb rate 1024kbit ceil 1024kbit
tc filter add dev eth1 protocol ip parent 1:0 prio 1 handle 89 fw flowid 1:89
tc qdisc add dev eth1 parent 1:89 handle 89 tbf rate 1024kbit burst 20k latency 10ms
……
(2)在内核Netfilter中利用对数据包打标记,标记就对应以该数据包源或目的地址的C网段部分的数字,也就是分类器filter中handle对应的标记。这样,内核会根据打标记来匹配数据包,并根据标记来判断应有哪个分类器filter来处理。
以控制上行速率为例,在/linux/net/ipv4/netfilter/rate.c中编写针对上行数据包的HOOK(钩子)函数upload_hook,实现片段为:
unsigned int upload_rate_hook(unsigned int hooknum,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
struct sk_buff *skb = *pskb;
struct iphdr *iph;
unsigned int n;
iph = skb->nh.iph;
if (skb->dev && skb->dev->name && strcmp(skb->dev->name, LAN_IFNAME) == 0) //判断数据是否来自LAN口eth0
{
if (ip_counters_enable == 1)
{
unsigned int subnet = iph->saddr & lan_ip_netmask;
if (subnet == lan_ip_subnet) //判断是都否和LAN口在一个网段
{
struct tcphdr *tcph = (void *)iph + iph->ihl*4;
if (iph->daddr == lan_ip_addr && ntohs(tcph->dest) == 80)
return NF_ACCEPT;
add_uprate(iph->saddr, ntohs(iph->tot_len)); //用于流量统计(与这里的QoS实现没关系)
n = iph->saddr & (~lan_ip_netmask); //得到IP地址的C网段部分
skb->nfmark = n; //给该IP包打上标记
skb->nfcache |= NFC_ALTERED; //告知内核(打了标记这个)变化,
}
}
}
return NF_ACCEPT;
}
其中n = iph->saddr & (~lan_ip_netmask)的作用是提取IP地址和子网掩码做相与,得到IP地址的C网段部分的数字,再利用skb->nfmark = n;给来自该IP地址的数据包打上标记“n”, 这个“n”就与分类器filter中handle对应的标记项对应,filter会将匹配的数据包送到相应的tc的class中,从而实现了对IP数据包的流量控制。skb_buff中的nfcache原来打算是用来做netfilter的cache机制的。就是说,如果一个数据包被netfilter中的某个module动过了的话,动这个数据包的那个模块就应该给这个skb的nfcache字段打个标记。
设计好HOOK函数后,还需要向内核Netfilter注册该HOOK函数,注册的实现方式如下:
在static int __init sfitc_init(void)函数中,添加:
static struct nf_hook_ops upload_hook_ops; //定义一个nf_hook_ops数据结构
upload_hook_ops.hook = upload_rate_hook; //指定对应的HOOK函数
upload_hook_ops.hooknum = NF_IP_PRE_ROUTING; //挂在PREROUTING链
macfilter_ops.pf = PF_INET; //网络协议指定
macfilter_ops.priority = (NF_IP_PRI_FIRST + 3); //设置优先级
nf_register_hook(&upload_hook_ops); //向内核注册nf_hook_ops数据结构
(4)在net/ipv4/netfilter下的Makefile中,加上obj-y += rate.o,编译内核的时候会将该模块编译进内核。