hashlimit模块实现分析

来源:互联网 发布:2016淘宝店铺装修模板 编辑:程序博客网 时间:2024/05/30 05:24
免责声明:有些地方没有仔细看,如有错误,不负责任,欢迎指正好久不发贴子了,来骗点积分。1、引言一直用tc来做流控,偶然发现Netfilter有一个名为hashlimit的东东,是基于TBF(令牌桶算法)的简单的流量控制的。例如:QUOTE:iptables -A INPUT -p tcp --dport 22 -m hashlimit --hashlimit-name foo --hashlimit 100/sec --hashlimit-burst 10 --hashlimit-mode srcip -j ACCEPT iptables -A INPUT -p tcp --dport 22 -j DROPTBF算法还是比较简单的,著名的《计算机网络》一书中有算法介绍,这里就不重复了。注意的是,TBF可以是基于字节,也可以是基于数据包数量的。hashlimit使用了后者。--hashlimit-name foo :取个名,反映在proc中;--hashlimit 100/sec:每秒的数据包小于或等于XXX;--hashlimit-burst 10 :每秒允许的突发;都是“每秒”,究竟是谁的每秒呢?主要就是看hashlimit-mode srcip(hash模式),也就是限制的标的取哪一个,可以是四种:源地址、目的地址、源端口、目的端口;以源地址为例,如果规则中增加了-s 192.168.0.0/24,那么只有来源地址符合这个的进入-m hashlimit,hash模式为源地址,意味着将每个来源地址都做为一个限制标的!!--hashlimit 100/sec:每秒的数据包小于或等于XXX;准确点就是192.168.0.0/24上的每一个主机每秒……要实现TBF,有几个基本的要素:1、桶最大的令牌数;credit_cap2、可发放的令牌数,它是根据单位时间定时刷新的;credit3、令牌的消耗,每通过一个包,消耗多少个令牌。cost基本关系是:每过一个包,都要进行credit -= cost,直至credit <=0,即没有可供分配的令牌了——超限溢出;定时刷新credit,但是总量不得超过credit_cap。2、实现废话少说,来看代码。[Copy to clipboard] [ - ]CODE:static inthashlimit_match(const struct sk_buff *skb,                const struct net_device *in,                const struct net_device *out,                const void *matchinfo,                int offset,                int *hotdrop){        struct ipt_hashlimit_info *r =                 ((struct ipt_hashlimit_info *)matchinfo)->u.master;        struct ipt_hashlimit_htable *hinfo = r->hinfo;        unsigned long now = jiffies;        struct dsthash_ent *dh;        struct dsthash_dst dst;        /* 根据hash mode和当前包,构建dst结构 */        memset(&dst, 0, sizeof(dst));        if (hinfo->cfg.mode & IPT_HASHLIMIT_HASH_DIP)                //目的IP                dst.dst_ip = skb->nh.iph->daddr;        if (hinfo->cfg.mode & IPT_HASHLIMIT_HASH_SIP)                //源IP                dst.src_ip = skb->nh.iph->saddr;        if (hinfo->cfg.mode & IPT_HASHLIMIT_HASH_DPT                //端口            ||hinfo->cfg.mode & IPT_HASHLIMIT_HASH_SPT) {                u_int16_t ports[2];                if (get_ports(skb, offset, ports)) {                        /* We've been asked to examine this packet, and we                           can't.  Hence, no choice but to drop. */                        *hotdrop = 1;                        return 0;                }                if (hinfo->cfg.mode & IPT_HASHLIMIT_HASH_SPT)                        dst.src_port = ports[0];                if (hinfo->cfg.mode & IPT_HASHLIMIT_HASH_DPT)                        dst.dst_port = ports[1];        } 这段代码根据hash模块,取出数据包相应的元素。struct dsthash_dst是hash表使用的结构。[Copy to clipboard] [ - ]CODE:        spin_lock_bh(&hinfo->lock);                                //查找hash表        dh = __dsthash_find(hinfo, &dst);查看hash表中,是否有与dst一匹备的节点,之所以叫hashlimit,就是因为每一个元素(例如以来源地址每一个IP),都被存放在hash表中(一个来源地址占据一个hash节点)。[Copy to clipboard] [ - ]CODE:        if (!dh) {                dh = __dsthash_alloc_init(hinfo, &dst);                //未命中,分配新的接点,并增加至hash 表                if (!dh) {                        /* enomem... don't match == DROP */                        if (net_ratelimit())                                printk(KERN_ERR "%s: ENOMEM/n", __FUNCTION__);                        spin_unlock_bh(&hinfo->lock);                        return 0;                }                                //构建结点成员                dh->expires = jiffies + msecs_to_jiffies(hinfo->cfg.expire);        //定时器                dh->rateinfo.prev = jiffies;                                        //最后修改时间定为当前时间                dh->rateinfo.credit = user2credits(hinfo->cfg.avg *                 //初始化令牌                                                        hinfo->cfg.burst);        //初始化令牌桶大小,在令牌没有被消耗前,两者是相同的                dh->rateinfo.credit_cap = user2credits(hinfo->cfg.avg *                                                         hinfo->cfg.burst);                dh->rateinfo.cost = user2credits(hinfo->cfg.avg);                //初始化当前包的开销,每过一个包递减                spin_unlock_bh(&hinfo->lock);                return 1;        }如果不在hash表中,则分配新的节点,插入hash表,然后初始化节点成员dh。代码很简单,但是关键是对这些东东的计算,后文分析:[Copy to clipboard] [ - ]CODE:                dh->rateinfo.credit = user2credits(hinfo->cfg.avg *                 //初始化令牌                                                        hinfo->cfg.burst);        //初始化令牌桶大小,在令牌没有被消耗前,两者是相同的                dh->rateinfo.credit_cap = user2credits(hinfo->cfg.avg *                                                         hinfo->cfg.burst);                dh->rateinfo.cost = user2credits(hinfo->cfg.avg);                //初始化当前包的开销,每过一个包递因为前面已经return 1了,接下来是hash命中的情况了:[Copy to clipboard] [ - ]CODE:        /* 更新超时时间戳 */        dh->expires = now + msecs_to_jiffies(hinfo->cfg.expire);        /* 重新计算令牌 */        rateinfo_recalc(dh, now);        if (dh->rateinfo.credit >= dh->rateinfo.cost) {                                //如果还有令牌                /* We're underlimit. */                dh->rateinfo.credit -= dh->rateinfo.cost;                        //减去当前包消耗掉的令牌,通过                spin_unlock_bh(&hinfo->lock);                return 1;        }这段代码意思是,周期性地刷新令牌,并且判断是否还有可供分配的令牌。对于每经过一个包,都会递减令牌数。直接dh->rateinfo.credit < dh->rateinfo.cost这也就意味着没有可供分配令牌了。所以,最后return 0;代码结构非常简单,与TBF算法一模一样,关键是两个地方,一个是初始化时,对于令牌的分配。另一个是得新计算令牌数函数rateinfo_recalc()需要重点理解的是令牌刷新的时间周期,虽然用户态使用了秒、分、小时……不过内核是使用的jiffy。在初始计算的时候,user2credits() 函数被调用:[Copy to clipboard] [ - ]CODE:                dh->rateinfo.credit = user2credits(hinfo->cfg.avg *                                                         hinfo->cfg.burst);                dh->rateinfo.credit_cap = user2credits(hinfo->cfg.avg *                                                         hinfo->cfg.burst);                dh->rateinfo.cost = user2credits(hinfo->cfg.avg);注意到的,cost比前者差了一个burst倍。举个例子,如果初始允许突发包是10个。那么credit可能是3200,而cost 320,相差10倍。这也意味着,在credit不被更新的情况下(在一个jiffy周期之内),超过被允许的突发值后,令牌就用完了。来看user2credits的实现:[Copy to clipboard] [ - ]CODE:/* Precision saver. */static inline u_int32_tuser2credits(u_int32_t user){        /* If multiplying would overflow... */        if (user > 0xFFFFFFFF / (HZ*CREDITS_PER_JIFFY))                /* Divide first. */                return (user / IPT_HASHLIMIT_SCALE) * HZ * CREDITS_PER_JIFFY;        return (user * HZ * CREDITS_PER_JIFFY) / IPT_HASHLIMIT_SCALE;}CREDITS_PER_JIFFY宏表示的是每个jiffy要产生的令牌数,乘以HZ,则表示为每秒。之所以要除以IPT_HASHLIMIT_SCALE,是因为在iptables获取值的时候,有一个反向操作(难道不想让用户态传递的时候,这个值过大,需要转换一下???):[Copy to clipboard] [ - ]CODE:*val = IPT_DSTLIMIT_SCALE * mult / r;最重要的函数是rateinfo_recalc,它负责在每个jiffy周期,产生新的令牌,以使游戏能够继续下去:[Copy to clipboard] [ - ]CODE:static inline void rateinfo_recalc(struct dsthash_ent *dh, unsigned long now){        dh->rateinfo.credit += (now - xchg(&dh->rateinfo.prev, now))                                         * CREDITS_PER_JIFFY;        if (dh->rateinfo.credit > dh->rateinfo.credit_cap)                dh->rateinfo.credit = dh->rateinfo.credit_cap;}以前一个包至目前时间的jiffy的差值N,并乘上CREDITS_PER_JIFFY得到新的令牌数。并且,它最大不能超过令牌桶的大小。3、一个现实的例子假如设定以下参数:每秒平均值:100/sec允许突发:10;每jiffy产生的令牌为32,即CREDITS_PER_JIFFY = 32;那么可以计算出:credit = 3200,cost = 320;如果每秒是100个包,因为每个包消耗320,则每秒共需要的令牌数总计为320 * 100 + 320 * 10 = 32000 + 3200因为CREDITS_PER_JIFFY为32,则每秒产生的令牌是32 * 1000 ,并且,初始化时,credit = 3200,总计32000 + 3200嘿嘿,刚刚好!!