用Netfilter模块实现基于令牌桶的每IP地址流量控制

简介:
在《 修改netfilter的limit模块实现基于单个ip的流量监控 》的最后,写了一个修订版的match回调函数,但是修订的版本还只是能监控单包或者两包的流量,粒度还是过于粗糙,因此使用传统的令牌桶的方式更好。在数据结构上,没有必要真的实现一个令牌桶,而是基于时间的流逝生成受控制数量的令牌即可-以时间的流逝来洗涤旧迹,也就是将两次发包或者收包的间隔和令牌数量联系起来。在Linux内核的标准流控实现(qdisc)以及鲁迅的散文中,这么做很常见。
     以下是所有可编译运行的代码,整个流控代码分为两大部分。第一部分是内核模块,实现了netfilter的一个match;第二部分是用户态iptables扩展库,实现了一个match配置。
以下是内核模块,基于内核版本2.6.32-5-amd64,位于/usr/src/linux-source-2.6.32/net/netfilter/xt_limit.c:
/**  *     2011/11/06 by marywangran  *    这个版本实现只能使所有规则共享一个上限,作为全局管理局部特例  *    很方便,不多的几条iptables规则即可  */ #include <linux/module.h> #include <linux/skbuff.h> #include <linux/spinlock.h> #include <linux/interrupt.h> #include <net/ip.h>  #include <linux/netfilter/x_tables.h> #include <linux/types.h>  MODULE_LICENSE("GPL"); MODULE_AUTHOR("marywangran <marywangran@126.com>"); MODULE_DESCRIPTION("Xtables: rate-limit match"); MODULE_ALIAS("ipt_limit"); MODULE_ALIAS("ip6t_limit");  struct xt_rateinfo {         __u32 type;    //定义类型,1为源地址限速,2为目标地址限速         __u32 burst;    //定义最大流量,统计值 };  struct src_controler {         struct list_head src_list;         int curr;             //当前的IP连接数         int max;            //最大的IP连接数         spinlock_t lock; };  struct src_entry {         struct list_head list;         int type;        //同xt_rateinfo结构体的type         __u32   addr;            unsigned long prev_hit; //上次包到来的时间         unsigned long toks;    //当前拥有的令牌数         spinlock_t lock; };  struct src_controler *src_ctl;  static bool limit_mt(const struct sk_buff *skb, const struct xt_match_param *par) {         const struct xt_rateinfo *r = par->matchinfo;         unsigned long now = jiffies;          struct list_head *lh;         struct src_entry *entry = NULL;         struct src_entry *find_entry;         long tokens;         struct iphdr *iph = ip_hdr(skb);         __u32 this_addr = 0;          if (r->type == 1) {                 this_addr = iph->saddr;         } else {                 this_addr = iph->daddr;         }          spin_lock (&src_ctl->lock);  //操作链表一定加锁,多CPU下防止并发修改,访问         list_for_each(lh, &src_ctl->src_list) {                 find_entry = list_entry(lh, struct src_entry, list);                 if ((this_addr == find_entry->addr) &&                     (find_entry->type == r->type)) {                         entry = find_entry;                         break;                 }         }         spin_unlock (&src_ctl->lock);         if (entry) {                 spin_lock (&src_ctl->lock);                 list_del(&entry->list);                 list_add(&entry->list, &src_ctl->src_list);                 spin_unlock (&src_ctl->lock);         } else {                    if (src_ctl->curr+1 < src_ctl->max) { add_entry:                         entry = kmalloc(sizeof(struct src_entry), GFP_ATOMIC); //必须使用ATOMIC标志,因为有可能在(软)中断中运行,不能睡眠。                         memset(entry, 0, sizeof(struct src_entry));                         entry->addr = this_addr;                         entry->toks = r->burst; //第一次分配令牌时不加倍(加倍理由为防止使用浮点运算),以防TCP的慢启动增加突发流量,TCP的慢启动实际上很快,指数级的。                         entry->prev_hit = now;                         entry->type = r->type;                         spin_lock_init(&entry->lock);                         spin_lock (&src_ctl->lock);                         list_add(&entry->list, &src_ctl->src_list);                         src_ctl->curr++;     //应该使用atomic_inc进行递增                         spin_unlock (&src_ctl->lock);                 } else {                         entry = list_entry(src_ctl->src_list.prev, struct src_entry, list);                         if (now-entry->now > 1000) {                                 spin_lock (&src_ctl->lock);                                 list_del(&entry->list);                                 src_ctl->curr--;                                 spin_unlock (&src_ctl->lock);                                 vfree(entry);   //解锁后vfree                                 goto add_entry;                         }                         return 1;                 }         }     //以下根据流逝的时间来确定令牌的数量         tokens = min_t(long, (now-entry->prev_hit)*r->burst, r->burst*1000);         tokens += entry->toks;         if (tokens > (long)r->burst*1000)                         tokens = r->burst*1000;                 tokens -= skb->len*1000; //统一增加HZ倍,避免在内核使用浮点数和除法。          if (tokens >= 0) {                 spin_lock (&entry->lock);                 entry->prev_hit = now;                 entry->toks = tokens;    //令牌积累                 spin_unlock (&entry->lock);                 return 0;         }         return 1; }  static bool limit_mt_check(const struct xt_mtchk_param *par) {         struct xt_rateinfo *r = par->matchinfo;         if (r->burst == 0 || r->type == 0) {                 return false;         }         if (r->type != 1 && r->type != 2)                 return false;         return true; }  static void limit_mt_destroy(const struct xt_mtdtor_param *par) {     //TODO }   static struct xt_match limit_mt_reg __read_mostly = {         .name             = "limit",         .revision         = 0,         .family           = NFPROTO_UNSPEC,         .match            = limit_mt,         .checkentry       = limit_mt_check,         .destroy          = limit_mt_destroy,         .matchsize        = sizeof(struct xt_rateinfo),         .me               = THIS_MODULE, };  static int __init limit_mt_init(void) {         src_ctl = kmalloc(sizeof(struct src_controler), GFP_KERNEL); //初始化全局变量,insmod上下文,可以使用KERNEL标志         memset(src_ctl, 0, sizeof(struct src_controler));         INIT_LIST_HEAD(&src_ctl->src_list);    //初始化全局变量的链表         src_ctl->curr = 0;         src_ctl->max = 2000;         spin_lock_init(&src_ctl->lock);         return xt_register_match(&limit_mt_reg); }  static void __exit limit_mt_exit(void) {         struct src_entry *entry = NULL;         struct list_head *lh = NULL, *lh2 = NULL;         xt_unregister_match(&limit_mt_reg);         spin_lock(&src_ctl->lock);         list_for_each_safe(lh, lh2, &src_ctl->src_list) { //一定要用safe宏,因为这是个外部迭代器                 entry = list_entry(lh, struct src_entry, list);                 list_del(&entry->list);                 kfree(entry);         }         spin_unlock(&src_ctl->lock);         kfree(src_ctl);  }  module_init(limit_mt_init); module_exit(limit_mt_exit);

为了方便编译和安装,以下是Makefile:
LINUXPATH = /lib/modules/`uname -r`/build CURDIR = $(shell pwd) KBUILD_OUTPUT = $(CURDIR) CROSS_COMPILE = ARCH =  obj-m                           += xt_limit.o  all: limit  limit:         $(MAKE) -C $(LINUXPATH) M=$(CURDIR) modules         @echo "*********************************************"         @echo "*  The MODULE is OK!!"         @echo "*********************************************" .PHONY: clean clean:         rm -rf *.o *.ko *.mod.c *.symvers *.mod.o .*.cmd  ../common/*.o .tmp_versions 

安装:
直接make后insmod或者将ko文件拷贝到/lib/modules/`uname -r`/kernel/net/netfilter/下面,然后modprobe xt_limit


****************************************************************************************************************************
以下是用户态iptables模块的代码,基于iptables版本1.4.12,位于iptables-1.4.12/extensions/libxt_limit.c:
/**  *    2011/11/06 by marywangran  *    修改自iptables-1.4.12/extensions/libxt_limit.c  */  #include <stdio.h> #include <string.h> #include <stdlib.h> #include <xtables.h> #include <linux/netfilter/x_tables.h> #include <linux/netfilter/xt_limit.h>  #define XT_LIMIT_BURST  500000  /**  *    新增轻量级rateinfo结构,对应于内核的等价结构  */ struct xt_rateinfo_new {         __u32 type;         __u32 burst; };  enum {         O_TYPE = 0,         O_BURST, };  static void limit_help(void) {         printf( "limit match options:\n" "--type source[1]|destination[2]        define source limit or destination limit\n" "--limit-burst rate                     rate to match in a burst, default %u\n", XT_LIMIT_BURST); }  static const struct xt_option_entry limit_opts[] = {         {.name = "type", .id = O_TYPE, .type = XTTYPE_STRING},         {.name = "limit-burst", .id = O_BURST, .type = XTTYPE_UINT32,          .flags = XTOPT_PUT, XTOPT_POINTER(struct xt_rateinfo_new, burst),          .min = 0, .max = 1073741824}, //1024*1024*1024         XTOPT_TABLEEND, };  static int parse_rate(const char *rate, uint32_t *val) {         uint32_t r;         r = atoi(rate);         if (!r)                 return 0;         if (r != 1 && r != 2) //1为限制源地址,2为限制目的地址                 return 0;         *val = r;         return 1; }  static void limit_init(struct xt_entry_match *m) {         struct xt_rateinfo_new *r = (struct xt_rateinfo_new *)m->data;         parse_rate("1", &r->type);         r->burst = XT_LIMIT_BURST; }  static void limit_parse(struct xt_option_call *cb) {         struct xt_rateinfo_new *r = cb->data;         xtables_option_parse(cb);         switch (cb->entry->id) {         case O_TYPE:                 if (!parse_rate(cb->arg, &r->type))                         xtables_error(PARAMETER_PROBLEM,                                    "bad rate \"%s\"'", cb->arg);                 break;         }         if (cb->invert)                 xtables_error(PARAMETER_PROBLEM,                            "limit does not support invert"); }  static void print_rate(uint32_t period) {         printf(" %u", period); }  static void limit_print(const void *ip, const struct xt_entry_match *match, int numeric) {         const struct xt_rateinfo_new *r = (const void *)match->data;         printf(" type: avg"); print_rate(r->type);         printf(" burst %u", r->burst); }  static void limit_save(const void *ip, const struct xt_entry_match *match) {         const struct xt_rateinfo_new *r = (const void *)match->data;          printf(" --type"); print_rate(r->type);         if (r->burst != XT_LIMIT_BURST)                 printf(" --limit-burst %u", r->burst); }  static struct xtables_match limit_match = {         .family         = NFPROTO_UNSPEC,         .name           = "limit",         .version        = XTABLES_VERSION,         .size           = XT_ALIGN(sizeof(struct xt_rateinfo_new)),         .help           = limit_help,         .init           = limit_init,         .x6_parse       = limit_parse,         .print          = limit_print,         .save           = limit_save,         .x6_options     = limit_opts, };  void _init(void) {         xtables_register_match(&limit_match); }


安装:

进入$IPTABLES_SOURCE/extensions目录,执行make install,    
测试:
此时添加下列的规则:
iptables -A INPUT -m limit --type 1 --limit-burst 500000 -j DROP
iptables -A OUTPUT -m limit --type 2 --limit-burst 100000 -j DROP
即可对本机发出的和接收的基于IP地址的数据流进行限速,如果在本机往同一台机器scp多个文件,那么这多个数据传输流将共享配置的限速额带宽。如果想限制某一个而不是完全限制,则在规则中添加其它的match进行筛选。

说明:以上的代码都是基于现有的Linux内核以及iptables源码修改的,框架虽在,然逻辑已全非,如果需保留原来的limit功能,请自行实现新的文件。



 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1270919


相关文章
|
2月前
|
网络协议 网络安全 网络性能优化
使用到UDP协议的情况下该如何防护
使用到UDP协议的情况下该如何防护
|
4月前
|
算法 网络协议 网络架构
【网络层】动态路由算法、自治系统AS、IP数据报格式
【网络层】动态路由算法、自治系统AS、IP数据报格式
33 0
|
算法
对于IPv4协议,寻址和路由有什么区别呢?
IPv4协议是IP协议的第4个版本,IPv4为传输层提供Host-To-Host,同时IPv4需要底层的数据链路层的支持。 IP协议不负责数据的可靠性,传输数据时,数据被切分为一个个数据封包,IP协议上层的传输层协议会对数据进行一层拆分,然后再IP协议会在一次拆分,两次拆分是为了适合底层的设备。
116 0
|
网络协议 中间件 Linux
SOME/IP概述2【SOME/IP的主要中间件功能+SOME/IP报文PDU的封装】
SOME/IP概述2【SOME/IP的主要中间件功能+SOME/IP报文PDU的封装】
SOME/IP概述2【SOME/IP的主要中间件功能+SOME/IP报文PDU的封装】
|
网络协议 网络架构
|
网络虚拟化
路由与交换系列之GVRP协议的配置
路由与交换系列GVRP协议的配置
465 0
|
安全 物联网 网络虚拟化
路由与交换系列之基本IP ACL特性与配置
• 掌握基本IP ACL的原理 • 掌握ACL在企业网络中的应用 • 掌握基本IP ACL的配置方式 • 掌握基本IP ACL的验证效果
4182 8
  路由与交换系列之基本IP ACL特性与配置
|
网络协议 网络架构
路由与交换系列之简单的路由策略与默认路由汇总路由的运用
路由策略使用不同的匹配条件和匹配模式选择路由和改变路由属性。在特定的场景 中,路由策略的6种过滤器也能单独使用,实现路由过滤。
3647 1
路由与交换系列之简单的路由策略与默认路由汇总路由的运用
|
网络协议 物联网
DFP 数据转发协议应用实例
稳控科技编写的一套数据转发规则, 取自“自由转发协议 FFP(Free Forward Protocol)” ,或者 DFP(DoubleF Protocol), DF 也可以理解为 Datas Forward(数据转发)的缩写。DF 协议是与硬件接口无关的数据链路层协议,规定了数据流如何在不同设备之间、不同接口之间的传输方向。 DF 协议一般用于延长数字接口的传输距离(数据中继),它与硬件接口类型无关,可以基于 UART、 LoRA、TCP 等异步数据传输介质。
DFP 数据转发协议应用实例
|
缓存 网络协议 物联网
DFP 数据转发协议 规则说明(二)
DFP 是什么? 稳控科技编写的一套数据转发规则, 取自“自由转发协议 FFP(Free Forward Protocol)” ,或者 DFP(DoubleF Protocol), DF 也可以理解为 Datas Forward(数据转发)的缩写。DF 协议是与硬件接口无关的数据链路层协议,规定了数据流如何在不同设备之间、不同接口之间的传输方向。 DF 协议一般用于延长数字接口的传输距离(数据中继),它与硬件接口类型无关,可以基于 UART、 LoRA、TCP 等异步数据传输介质。
DFP 数据转发协议 规则说明(二)

热门文章

最新文章