linux实现流量监控的几种方法

linux实现流量监控的⼏种⽅法
blog.sina/s/blog_6a1837e90100v9ye.html
⼀、使⽤iptables命令;
URL: /post/340/
⼆、修改Netfilter的limit模块
URL: blog.csdn/dog250/article/details/6940578
三、iptables的五个HOOK点;
四、使⽤libpcap:需移植,暂未深⼊研究;
五、修改内核:暂⽆⽅案;
附:whyxx.blog.51cto/2227948/560914
--------------------------------------------------------------------------------------------------
⼀、使⽤iptables命令:
相信不少朋友都知道,使⽤Linux搭建路由⽹关,提供nat上⽹服务是⾮常简单的事情,⽽且性能也不错。但现在p2p的⼯具很多,有时候带宽会被这些⼯具在⽆意中就占满了(例如:使⽤迅雷、BT下载等)。这时候,总希望看看到底是谁在占⽤带宽。这样的⼯具有很多,如ntop、bandwidthd、iftop、IPTraf、MRTG等等,它们也提供了⾮常⽅便的图形监控界⾯,操作也⾮常简单。可惜,它们都有⼀些缺点,像实时性不够、IP流量分散、需要使⽤Web来查看等,恰好这些就是好我需要的。
为此,我利⽤iptables的统计功能,编写了⼀个⼩脚本来实现要求。(原想⽤Perl的Net::Pcap模块的对数据包解码统计的,但既然有现成的,为什么不⽤呢?)O(∩_∩)O哈哈~
⼀、查看⽹卡流量
⾸先,可能我们需要查看的是服务器上总的⽹卡流量。这个Linux提供了很好的数据:
引⽤
# cat /proc/net/dev
Inter-|  Receive                                                |  Transmit
face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed    lo:10020933  79976    0    0    0    0          0        0 10020933  79976    0    0    0    0      0          0
eth0:3274190272 226746109 438150 858758 369237    0          0        0 2496830239 218418052    0    0    0    0      0          0
sit0:      0      0    0    0    0    0          0        0        0      0    0    0    0    0      0          0
tun0:      0      0    0    0    0    0          0        0        0      0    0    0    0    0      0          0
吕侃近况tun1:    4675      51    0    0    0    0          0        0    8116      48    0    0    0    0      0          0
tun2:  51960    562    0    0    0    0          0        0  249612    3077    0    0    0    0      0          0
ppp0:4163571679 12086605    0    0    0    0          0        0 3089285665 15934370    0    0    0    0      0          0
这是⽹络启动后,通过服务器上各⽹卡的总流量,但不是很直观。(受排版影响,不太好看)
这⾥,提供⼀个⼩⼯具:
下载⽂件
点击这⾥下载⽂件
这⼯具不是我写的,作者是他。使⽤⾮常简单:
引⽤
# sh flow.sh
Usage: flow.sh <ethernet device> <sleep time>
< flow.sh eth0 2
# sh flow.sh ppp0 2
IN: 232 KByte/s  OUT: 30 KByte/s
IN: 230 KByte/s  OUT: 38 KByte/s
IN: 241 KByte/s  OUT: 30 KByte/s
给出您要监控的⽹卡设备,然后是间隔时间,即会告诉您该设备的流量。
⼆、查看客户端IP实际流量的原理
接下来,进⼊我们的正题。除了通过上述脚本可以查看到⽹卡的实际流量外,我们该如何查看每个客户端的单独流量呢?先说说原理吧。
1、iptables设置
该过程最主要的就是利⽤了iptables的统计功能。
当我们⽤iptables实现nat转发后,所有的数据包要出去,必须要通过这台⽹关服务器,也就是说,我们只要在上⾯监控即可。并且,这些数据包都会经过iptables的FORWARD chain。这时,我们只要给iptables加上下述语句:
# iptables -I FORWARD -s 192.168.228.200 -j ACCEPT
# iptables -I FORWARD -d 192.168.228.200 -j ACCEPT
那么,通过192.168.228.200(客户端)经该服务器路由⽹关转发出去的数据包就会记录在iptables FORWARD chain 中。
如:
引⽤
# iptables -v -n -x -L FORWARD
Chain FORWARD (policy DROP 5 packets, 351 bytes)
pkts      bytes target    prot opt in    out    source              destination
2834533 360907743 ACCEPT    all  --  *      *      192.168.228.200      0.0.0.0/0
3509528 3253144061 ACCEPT    all  --  *      *      0.0.0.0/0            192.168.228.200
这样,我们通过⼀些简单的运算,就可以得到实际的流量:
引⽤
# iptables -L -v -n -x|grep '192.168.228.200';sleep 3;iptables -L -v -n -x|grep '192.168.228.200'
2872143 365711591 ACCEPT    all  --  *      *      192.168.228.200      0.0.0.0/0
3555831 3297100630 ACCEPT    all  --  *      *      0.0.0.0/0            192.168.228.200
2872750 365777302 ACCEPT    all  --  *      *      192.168.228.200      0.0.0.0/0
3556591 3297814562 ACCEPT    all  --  *      *      0.0.0.0/0            192.168.228.200
# echo '(3297814562-3297100630)/1024/3'|bc
232
# echo '(365777302-365711591)/1024/3'|bc
21
原理就是这么简单。
※注意,FORWARD chain记录的流量中,不经过该⽹关转发的流量不会记录。也就是说,若你从该服务器上直接下载,流量是记录在INPUT和OUTPUT chain,⽽不是FORWARD中的。要统计那些数据,⽅法是相同的。
--------------------------------------------------------------------------------------------------
⼆、修改Netfilter的limit模块
1.问题和思路
linux内核的netfilter框架中有⼀个叫做limit的模块,⽤于匹配单位时间内过往的包的数量,注意,这个模块实现了⼀个
match,⽽不能直接⽤于流控的⽬的,因此你不能直接使⽤下列的命令实现流控:
iptables –A FORWARD –s xxx –d yyy –m limit ...  –j DROP
因为这样的话,所有匹配到的数据包就都被drop掉了。你应该这么做:
iptables –A FORWARD –s xxx –d yyy –m limit ... –j ACCEPT
iptables –A FORWARD –s xxx –d yyy –j DROP
然⽽仍然需要注意的是,这个match是基于包的数量的,⽽不是基于数据字节流量的,因此这种流控⽅式很不准确,如上,限制单个基于ip地址的流在每秒发送20个数据包,⽽这20个数据包可能是20个mtu⼤⼩的数据包,也可能是20个1字节ip载荷⼤⼩的数据包,也可能仅仅是20个tcp的ack包,这样的流控显然不是真正的流控。
我现在需要做的是基于单个源ip进⾏秒级别的⼊⼝流量的字节限速,怎么做呢?当然可以通过tc来做,那就是使⽤tc的police策略来进⾏配置,可是那样的话有问题,第⼀个问题就是police没有队列,这就意味着所有超额的流量将被丢弃⽽不是被缓存,这也许就是tc社区为何说linux⼊⼝限速做的不甚好的原因之所在吧;第⼆个问题就是你需要把所有需要被限速的ip地址作为filter的匹配规则显式的配置出来,⽽这会导致策略表的快速膨胀,⼤⼤增加了内存的占⽤。因此不到万不得已,我不会再考虑使⽤tc来完成这个流控。
接下来要考虑的就是使⽤iptables统计来完成流控。因为netfilter会纪录所有rule的统计信息,因此周期的调⽤iptables–L –x –n …然后将统计信息相减后除以调⽤周期,使⽤外部脚本来完成这个流控实际上也是可以的。然⽽这⼜会⾯对和tc 同样的问题,既然需要iptables来统计信息,那么统计哪些流量的信息你同样需要显式配置出来,这同样会导致filter表的膨胀,最终导致内存占⽤以及遍历filter的转发效率的降低。
于是乎,办法还要想别的,最直接的办法就是⾃⼰实现。简单点考虑,我也不要什么队列,既然tc都没有⼊⼝整形队列,那我也不要,超过限额的全部丢弃即可。最直接的⽅案就是修改netfilter的limit模块,因为它⾜够简单,扩展它时阻⼒最⼩,于是乎,改了它!修改动作很少,基本分为四点:
第⼀:维护⼀个list_head,保存所有的到达本机的ip数据报的源ip地址;
第⼆:修改match函数,在源ip链表中寻该数据包的源ip,若到,取出统计信息,看看⼀秒内流量是否超限,若是,则匹配,若没有则不匹配;如果在链表中没有到,则创建⼀个entry,记录下当前时间和当前数据包长度,返回不匹配;将到的entry取出,重新插⼊到head位置,或者将新创建的entry插⼊到head位置,这样可以模拟lru,为第四步创造好处;
第三:如果链表长度满了,则匹配所有的数据包;
第四:需要新增加entry且链表已经满了时,根据entry的上次更新时间以及最短不惑跃时间看是否能删除某⼀个entry。
上述四个步骤⼤体上分两个阶段实现,第⼀阶段暂时不实现第四点,这也符合我的⼀贯风格,第四点以及模块释放时的善后⼯作暂时没有测试,⾸先要把功能先跑通。现在假设已经实现了上述所有,我只需要配置以下的规则就可以实现针对每⼀个源ip进⾏限速了:
iptables –A –FORWARD/INPUT –m –limit 20/sec –j MY_CHAIN
注意,上述的20/sec已经不再是基于包数量的了,⽽是基于字节的,并且,我没有直接drop掉这些包,⽽是交给了⼀个⾃定义的chain来处理,这样可以⽅便的将机制和策略进⾏分离,或许管理员并不是想丢弃这些超限包,⽽只是纪录下⽇志,或许管理员会永远封死这些ip地址,也许仅仅封死⼀段时间,待收到罚⾦之后再给予开放…
2.实现华大博雅
⾸先定义数据结构。以下的数据结构是⼀个包装,定义了⼀个全局的链表,以及⼀些控制参数,由于这个只是个测试版,因此没有考虑多处理器的并发处理,因此也就没有定义spin_lock,在正式的实现中,⼀定要定义⼀个lock的。
view plaincopy to clipboardprint?
1. struct src_controler {
2.        struct list_head src_list;
3.        int curr;    //当前⼀共有多少了entry
4.        int max;    //最多能有多少个entry
5. };
下⾯的⼀个结构体定义了⼀个源地址entry中包含哪些东西,⽆⾮就是⼀秒内已经过去了多长的数据包以及时间戳等信息。
view plaincopy to clipboardprint?
1. struct src_entry {
2.        struct list_head list;
3.        __u32  src_addr;    //源地址武夷菌素
4.        unsigned long prev;    //上次的时间戳
5.        unsigned long passed;    //⼀秒内已经过去了多少数据
6. };
struct src_controler *src_ctl;    //全局变量
接下来就是修改模块的初始化和卸载函数
view plaincopy to clipboardprint?
1. static int __init xt_limit_init(void)
2. {
3.        int ret;
4.        src_ctl = kmalloc(sizeof(struct src_controler), GFP_KERNEL); //初始化全局变量
5.        memset(src_ctl, 0, sizeof(struct src_controler));
6.        INIT_LIST_HEAD(&src_ctl->src_list);    //初始化全局变量的链表
7.        src_ctl->curr = 0;
8.        src_ctl->max = 1000;    //本应该通过模块参数传进来的,这⾥写死,毕竟是个测试版
9.
10.        ret = xt_register_match(&ipt_limit_reg);
11.        if (ret)
12.                return ret;
13.
14.        ret = xt_register_match(&limit6_reg);
15.        if (ret)
16.                xt_unregister_match(&ipt_limit_reg);
17.
18.        return ret;
19. }
20. static void __exit xt_limit_fini(void)资源优化配置
21. {
22.        xt_unregister_match(&ipt_limit_reg);
23.        xt_unregister_match(&limit6_reg);
24.        //这⾥应该有⼀个清理链表的操作,测试版没有实现
25. }
最后,编写match回调函数,删掉原来的,⾃⼰写新的逻辑
view plaincopy to clipboardprint?
1. static int
2. ipt_limit_match(const struct sk_buff *skb,
3.                const struct net_device *in,
4.                const struct net_device *out,
5.                const struct xt_match *match,
6.                const void *matchinfo,
7.                int offset,
8.                unsigned int protoff,
9.                int *hotdrop)
10. {
11.        struct xt_rateinfo *r = ((struct xt_rateinfo *)matchinfo)->master;
12.        unsigned long now = jiffies, prev = 0;
13.        struct list_head *lh;
14.        struct src_entry *entry = NULL;
15.        struct src_entry *find_entry;
16.        unsigned long nowa;
17.        struct iphdr *iph = skb->nh.iph;
18.        __u32 this_addr = iph->saddr;
19.
20.        list_for_each(lh, &src_ctl->src_list) { //遍历链表,到这个ip地址对应的entry
舆情控制21.                find_entry = list_entry(lh, struct src_entry, list);
22.                if (this_addr == find_entry->src_addr) {
23.                        entry = find_entry;
24.                        break;
25.                }
26.        }
27.        if (entry) { //如果到,将其加在头,这样实现了⼀个简单的lru
28.                prev = entry->prev;
29.                list_del(&entry->list);
30.                list_add(&entry->list, &src_ctl->src_list);
31.        } else {    //如果没有到,看看能否添加
32.                if (src_ctl->curr+1 < src_ctl->max) {
33. add_entry:
34.                        entry = kmalloc(sizeof(struct src_entry), GFP_KERNEL);
35.                        memset(entry, 0, sizeof(struct src_entry));
36.                        entry->src_addr = this_addr;
37.                        prev = entry->prev = now - 1000;
38.                        list_add(&entry->list, &src_ctl->src_list);
39.            src_ctl->curr++;    //正确做法是atomic_inc
40.                } else { //如果已经满了,那么看看能否删除最后的那个不活动的entry
41.                        entry = list_entry(src_ctl->src_list.prev, struct src_entry, list);
42.                        if (now-entry->prev > 1000)
43.                                goto add_entry;
44.                        return 1;
45.                }
46.        }
47.        nowa = entry->passed + skb->len;
48.        if (now-prev < 1000) {    //这⾥的1000其实应该是HZ变量的值,由于懒得引头⽂件了,直接写死了。如果距上
次统计还没有到1秒,则累加数据,不匹配
49.                entry->passed = nowa;
50.                return 0;
51.        } else {光伏并网发电模拟装置
52.                entry->prev = now;
53.                entry->passed = 0;
54.                if (r->burst >= nowa) {    //如果到达了1秒,则判断是否超限,如果超限,则匹配,没有超限则重置字段,
不匹配
55.                        return 0;
56.                } else {
57.                        return 1;
58.                }
59.        }
60.        return -1;    //不会到达这⾥
61. }
编译之:
make -C /usr/src/kernels/2.6.18-92.el5-i686 SUBDIRS=`pwd` modules
使⽤之:
#!/bin/bash
iptables -A INPUT -d 192.168.1.247/32 -m limit --limit 1/sec --limit-burst $1 -j $2
运⾏上述脚本:test.sh 1000 DROP
然后下载⼤⽂件,看看是否被限速了!...
注意,上述的实现中,数据单位是字节,其实正常起码应该是100字节,做成可配置的会更好。
3.优化和反思
优化⼀:上述实现中,使⽤list_head在⼤量源ip的情况下,遍历链表的开销⽐较⼤,虽然lru原则可以
最⼤的减⼩这种开销,但是还是很⼤,特别是⽤户并不想超限,反⽽间隔相对久的时间访问⼀次,⼤量这样的⽤户和⼤量频繁访问的⽤户混杂在⼀起,频繁访问的⽤户的entry会⼀直在前⾯,遍历时开销较⼩,⽽⼤量间隔相对久访问的⽤户的entry会在后⾯,遍历开销⽐较⼤,这会不会导致dos攻击,我由于没有环境还真的没有测试。事实上使⽤hash表来组织它们是更好的选

本文发布于:2024-09-24 18:25:22,感谢您对本站的认可!

本文链接:https://www.17tex.com/xueshu/104288.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:流量   实现   没有   统计   模块   数据包
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议