国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
linux實(shí)現(xiàn)流量監(jiān)控的幾種方法

http://blog.sina.com.cn/s/blog_6a1837e90100v9ye.html


一、使用iptables命令;

    URL:http://www.linuxfly.org/post/340/

二、修改Netfilter的limit模塊;

    URL:http://blog.csdn.net/dog250/article/details/6940578

三、iptables的五個(gè)HOOK點(diǎn);

四、使用libpcap:需移植,暫未深入研究;

五、修改內(nèi)核:暫無(wú)方案;

 

附:http://whyxx.blog.51cto.com/2227948/560914 

 

--------------------------------------------------------------------------------------------------

一、使用iptables命令:

 

 相信不少朋友都知道,使用Linux搭建路由網(wǎng)關(guān),提供nat上網(wǎng)服務(wù)是非常簡(jiǎn)單的事情,而且性能也不錯(cuò)。但現(xiàn)在p2p的工具很多,有時(shí)候帶寬會(huì)被這些工具在無(wú)意中就占滿了(例如:使用迅雷、BT下載等)。這時(shí)候,總希望看看到底是誰(shuí)在占用帶寬。這樣的工具有很多,如ntop、bandwidthd、iftop、IPTraf、MRTG等等,它們也提供了非常方便的圖形監(jiān)控界面,操作也非常簡(jiǎn)單。可惜,它們都有一些缺點(diǎn),像實(shí)時(shí)性不夠、IP流量分散、需要使用Web來(lái)查看等,恰好這些就是好我需要的。
    為此,我利用iptables的統(tǒng)計(jì)功能,編寫了一個(gè)小腳本來(lái)實(shí)現(xiàn)要求。(原想用Perl的Net::Pcap模塊的對(duì)數(shù)據(jù)包解碼統(tǒng)計(jì)的,但既然有現(xiàn)成的,為什么不用呢?)O(∩_∩)O哈哈~

一、查看網(wǎng)卡流量
首先,可能我們需要查看的是服務(wù)器上總的網(wǎng)卡流量。這個(gè)Linux提供了很好的數(shù)據(jù):

引用
# cat /proc/net/dev
Inter-|  Receive                                                 Transmit
face|bytes    packetserrs drop fifo frame compressedmulticast|bytes    packetserrs drop fifo colls carrier compressed
    lo:10020933  79976                             0 10020933  79976                           0
  eth0:3274190272 226746109438150 858758369237                    0 2496830239218418052                           0
  sit0:                                                                             0
  tun0:                                                                             0
  tun1:    4675      51                                8116      48                           0
  tun2:  51960    562                              249612    3077                           0
  ppp0:416357167912086605                             0 308928566515934370                           0


這是網(wǎng)絡(luò)啟動(dòng)后,通過(guò)服務(wù)器上各網(wǎng)卡的總流量,但不是很直觀。(受排版影響,不太好看)
這里,提供一個(gè)小工具:


這工具不是我寫的,作者是。使用非常簡(jiǎn)單:

引用
# sh flow.sh
Usage: flow.sh <ethernet device><sleep time>
    e.g.flow.sh eth0 2
# sh flow.sh ppp0 2
IN: 232 KByte/s   OUT: 30KByte/s
IN: 230 KByte/s   OUT: 38KByte/s
IN: 241 KByte/s   OUT: 30KByte/s


給出您要監(jiān)控的網(wǎng)卡設(shè)備,然后是間隔時(shí)間,即會(huì)告訴您該設(shè)備的流量。

二、查看客戶端IP實(shí)際流量的原理
接下來(lái),進(jìn)入我們的正題。除了通過(guò)上述腳本可以查看到網(wǎng)卡的實(shí)際流量外,我們?cè)撊绾尾榭疵總€(gè)客戶端的單獨(dú)流量呢?先說(shuō)說(shuō)原理吧。
1、iptables設(shè)置
該過(guò)程最主要的就是利用了iptables的統(tǒng)計(jì)功能。
當(dāng)我們用iptables實(shí)現(xiàn)nat轉(zhuǎn)發(fā)后,所有的數(shù)據(jù)包要出去,必須要通過(guò)這臺(tái)網(wǎng)關(guān)服務(wù)器,也就是說(shuō),我們只要在上面監(jiān)控即可。并且,這些數(shù)據(jù)包都會(huì)經(jīng)過(guò)iptables的FORWARDchain。這時(shí),我們只要給iptables加上下述語(yǔ)句:

# iptables -I FORWARD -s 192.168.228.200 -jACCEPT
# iptables -I FORWARD -d 192.168.228.200 -j ACCEPT


那么,通過(guò)192.168.228.200(客戶端)經(jīng)該服務(wù)器路由網(wǎng)關(guān)轉(zhuǎn)發(fā)出去的數(shù)據(jù)包就會(huì)記錄在iptables FORWARDchain中。
如:

引用
# iptables -v -n -x -L FORWARD
Chain FORWARD (policy DROP 5 packets, 351 bytes)
    pkts      bytestarget    prot optin    out    source              destination
2834533 360907743ACCEPT    all  --            192.168.228.200      0.0.0.0/0
3509528 3253144061ACCEPT    all  --            0.0.0.0/0            192.168.228.200


這樣,我們通過(guò)一些簡(jiǎn)單的運(yùn)算,就可以得到實(shí)際的流量:

引用
# iptables -L -v -n -x|grep'192.168.228.200';sleep 3;iptables -L -v -n -x|grep'192.168.228.200'
2872143 365711591ACCEPT    all  --            192.168.228.200      0.0.0.0/0
3555831 3297100630ACCEPT    all  --            0.0.0.0/0            192.168.228.200
2872750 365777302ACCEPT    all  --            192.168.228.200      0.0.0.0/0
3556591 3297814562ACCEPT    all  --            0.0.0.0/0            192.168.228.200
# echo '(3297814562-3297100630)/1024/3'|bc
232
# echo '(365777302-365711591)/1024/3'|bc
21


原理就是這么簡(jiǎn)單。
※ 注意,F(xiàn)ORWARDchain記錄的流量中,不經(jīng)過(guò)該網(wǎng)關(guān)轉(zhuǎn)發(fā)的流量不會(huì)記錄。也就是說(shuō),若你從該服務(wù)器上直接下載,流量是記錄在INPUT和OUTPUTchain,而不是FORWARD中的。要統(tǒng)計(jì)那些數(shù)據(jù),方法是相同的。

--------------------------------------------------------------------------------------------------

二、修改Netfilter的limit模塊

1.問(wèn)題和思路

linux內(nèi)核的netfilter框架中有一個(gè)叫做limit的模塊,用于匹配單位時(shí)間內(nèi)過(guò)往的包的數(shù)量,注意,這個(gè)模塊實(shí)現(xiàn)了一個(gè)match,而不能直接用于流控的目的,因此你不能直接使用下列的命令實(shí)現(xiàn)流控:
iptables –A FORWARD –s xxx –d yyy –m limit ...  –jDROP
因?yàn)檫@樣的話,所有匹配到的數(shù)據(jù)包就都被drop掉了。你應(yīng)該這么做:
iptables –A FORWARD –s xxx –d yyy –m limit ... –j ACCEPT
iptables –A FORWARD –s xxx –d yyy –j DROP
然而仍然需要注意的是,這個(gè)match是基于包的數(shù)量的,而不是基于數(shù)據(jù)字節(jié)流量的,因此這種流控方式很不準(zhǔn)確,如上,限制單個(gè)基于ip地址的流在每秒發(fā)送20個(gè)數(shù)據(jù)包,而這20個(gè)數(shù)據(jù)包可能是20個(gè)mtu大小的數(shù)據(jù)包,也可能是20個(gè)1字節(jié)ip載荷大小的數(shù)據(jù)包,也可能僅僅是20個(gè)tcp的ack包,這樣的流控顯然不是真正的流控。
   我現(xiàn)在需要做的是基于單個(gè)源ip進(jìn)行秒級(jí)別的入口流量的字節(jié)限速,怎么做呢?當(dāng)然可以通過(guò)tc來(lái)做,那就是使用tc的police策略來(lái)進(jìn)行配置,可是那樣的話有問(wèn)題,第一個(gè)問(wèn)題就是police沒(méi)有隊(duì)列,這就意味著所有超額的流量將被丟棄而不是被緩存,這也許就是tc社區(qū)為何說(shuō)linux入口限速做的不甚好的原因之所在吧;第二個(gè)問(wèn)題就是你需要把所有需要被限速的ip地址作為filter的匹配規(guī)則顯式的配置出來(lái),而這會(huì)導(dǎo)致策略表的快速膨脹,大大增加了內(nèi)存的占用。因此不到萬(wàn)不得已,我不會(huì)再考慮使用tc來(lái)完成這個(gè)流控。
   接下來(lái)要考慮的就是使用iptables統(tǒng)計(jì)來(lái)完成流控。因?yàn)閚etfilter會(huì)紀(jì)錄所有rule的統(tǒng)計(jì)信息,因此周期的調(diào)用iptables–L–x –n…然后將統(tǒng)計(jì)信息相減后除以調(diào)用周期,使用外部腳本來(lái)完成這個(gè)流控實(shí)際上也是可以的。然而這又會(huì)面對(duì)和tc同樣的問(wèn)題,既然需要iptables來(lái)統(tǒng)計(jì)信息,那么統(tǒng)計(jì)哪些流量的信息你同樣需要顯式配置出來(lái),這同樣會(huì)導(dǎo)致filter表的膨脹,最終導(dǎo)致內(nèi)存占用以及遍歷filter的轉(zhuǎn)發(fā)效率的降低。
   于是乎,辦法還要想別的,最直接的辦法就是自己實(shí)現(xiàn)。簡(jiǎn)單點(diǎn)考慮,我也不要什么隊(duì)列,既然tc都沒(méi)有入口整形隊(duì)列,那我也不要,超過(guò)限額的全部丟棄即可。最直接的方案就是修改netfilter的limit模塊,因?yàn)樗銐蚝?jiǎn)單,擴(kuò)展它時(shí)阻力最小,于是乎,改了它!修改動(dòng)作很少,基本分為四點(diǎn):
第一:維護(hù)一個(gè)list_head,保存所有的到達(dá)本機(jī)的ip數(shù)據(jù)報(bào)的源ip地址;
第二:修改match函數(shù),在源ip鏈表中尋找該數(shù)據(jù)包的源ip,若找到,取出統(tǒng)計(jì)信息,看看一秒內(nèi)流量是否超限,若是,則匹配,若沒(méi)有則不匹配;如果在鏈表中沒(méi)有找到,則創(chuàng)建一個(gè)entry,記錄下當(dāng)前時(shí)間和當(dāng)前數(shù)據(jù)包長(zhǎng)度,返回不匹配;將找到的entry取出,重新插入到head位置,或者將新創(chuàng)建的entry插入到head位置,這樣可以模擬lru,為第四步創(chuàng)造好處;
第三:如果鏈表長(zhǎng)度滿了,則匹配所有的數(shù)據(jù)包;
第四:需要新增加entry且鏈表已經(jīng)滿了時(shí),根據(jù)entry的上次更新時(shí)間以及最短不惑躍時(shí)間看是否能刪除某一個(gè)entry。
上述四個(gè)步驟大體上分兩個(gè)階段實(shí)現(xiàn),第一階段暫時(shí)不實(shí)現(xiàn)第四點(diǎn),這也符合我的一貫風(fēng)格,第四點(diǎn)以及模塊釋放時(shí)的善后工作暫時(shí)沒(méi)有測(cè)試,首先要把功能先跑通?,F(xiàn)在假設(shè)已經(jīng)實(shí)現(xiàn)了上述所有,我只需要配置以下的規(guī)則就可以實(shí)現(xiàn)針對(duì)每一個(gè)源ip進(jìn)行限速了:
iptables –A –FORWARD/INPUT –m –limit 20/sec –j MY_CHAIN
注意,上述的20/sec已經(jīng)不再是基于包數(shù)量的了,而是基于字節(jié)的,并且,我沒(méi)有直接drop掉這些包,而是交給了一個(gè)自定義的chain來(lái)處理,這樣可以方便的將機(jī)制和策略進(jìn)行分離,或許管理員并不是想丟棄這些超限包,而只是紀(jì)錄下日志,或許管理員會(huì)永遠(yuǎn)封死這些ip地址,也許僅僅封死一段時(shí)間,待收到罰金之后再給予開放…

2.實(shí)現(xiàn)

首先定義數(shù)據(jù)結(jié)構(gòu)。以下的數(shù)據(jù)結(jié)構(gòu)是一個(gè)包裝,定義了一個(gè)全局的鏈表,以及一些控制參數(shù),由于這個(gè)只是個(gè)測(cè)試版,因此沒(méi)有考慮多處理器的并發(fā)處理,因此也就沒(méi)有定義spin_lock,在正式的實(shí)現(xiàn)中,一定要定義一個(gè)lock的。

  1. struct src_controler  
  2.         struct list_head src_list;  
  3.         int curr;     //當(dāng)前一共有多少了entry  
  4.         int max;    //最多能有多少個(gè)entry  
  5. };  


下面的一個(gè)結(jié)構(gòu)體定義了一個(gè)源地址entry中包含哪些東西,無(wú)非就是一秒內(nèi)已經(jīng)過(guò)去了多長(zhǎng)的數(shù)據(jù)包以及時(shí)間戳等信息。

  1. struct src_entry  
  2.         struct list_head list;  
  3.         __u32   src_addr;    //源地址  
  4.         unsigned long prev;    //上次的時(shí)間戳  
  5.         unsigned long passed;    //一秒內(nèi)已經(jīng)過(guò)去了多少數(shù)據(jù)  
  6. };  


struct src_controler *src_ctl;   //全局變量
接下來(lái)就是修改模塊的初始化和卸載函數(shù)

  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;    //本應(yīng)該通過(guò)模塊參數(shù)傳進(jìn)來(lái)的,這里寫死,畢竟是個(gè)測(cè)試版  
  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.         //這里應(yīng)該有一個(gè)清理鏈表的操作,測(cè)試版沒(méi)有實(shí)現(xiàn)  
  25.  


最后,編寫match回調(diào)函數(shù),刪掉原來(lái)的,自己寫新的邏輯

  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) //遍歷鏈表,找到這個(gè)ip地址對(duì)應(yīng)的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) //如果找到,將其加在頭,這樣實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的lru  
  28.                 prev entry->prev;  
  29.                 list_del(&entry->list);  
  30.                 list_add(&entry->list, &src_ctl->src_list);  
  31.         else    //如果沒(méi)有找到,看看能否添加  
  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 //如果已經(jīng)滿了,那么看看能否刪除最后的那個(gè)不活動(dòng)的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其實(shí)應(yīng)該是HZ變量的值,由于懶得引頭文件了,直接寫死了。如果距上次統(tǒng)計(jì)還沒(méi)有到1秒,則累加數(shù)據(jù),不匹配  
  49.                 entry->passed nowa;  
  50.                 return 0;  
  51.         else  
  52.                 entry->prev now;      
  53.                 entry->passed 0;  
  54.                 if (r->burst >= nowa)    //如果到達(dá)了1秒,則判斷是否超限,如果超限,則匹配,沒(méi)有超限則重置字段,不匹配  
  55.                         return 0;  
  56.                 else  
  57.                         return 1;  
  58.                  
  59.          
  60.         return -1;    //不會(huì)到達(dá)這里  
  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
運(yùn)行上述腳本:test.sh 1000DROP
然后下載大文件,看看是否被限速了!...
注意,上述的實(shí)現(xiàn)中,數(shù)據(jù)單位是字節(jié),其實(shí)正常起碼應(yīng)該是100字節(jié),做成可配置的會(huì)更好。

3.優(yōu)化和反思

優(yōu)化一:上述實(shí)現(xiàn)中,使用list_head在大量源ip的情況下,遍歷鏈表的開銷比較大,雖然lru原則可以最大的減小這種開銷,但是還是很大,特別是用戶并不想超限,反而間隔相對(duì)久的時(shí)間訪問(wèn)一次,大量這樣的用戶和大量頻繁訪問(wèn)的用戶混雜在一起,頻繁訪問(wèn)的用戶的entry會(huì)一直在前面,遍歷時(shí)開銷較小,而大量間隔相對(duì)久訪問(wèn)的用戶的entry會(huì)在后面,遍歷開銷比較大,這會(huì)不會(huì)導(dǎo)致dos攻擊,我由于沒(méi)有環(huán)境還真的沒(méi)有測(cè)試。事實(shí)上使用hash表來(lái)組織它們是更好的選擇,linux的ip_conntrack中的紀(jì)錄就是使用hash表來(lái)組織的,軟件嘛,就這幾種數(shù)據(jù)結(jié)構(gòu)。
優(yōu)化二:上述實(shí)現(xiàn)中,僅僅是針對(duì)源地址進(jìn)行流量匹配,而沒(méi)有管目的地址,因?yàn)殚_始說(shuō)了,針對(duì)目的地址的流控可以用tc實(shí)現(xiàn),然而那樣的話,需要顯式配置filter,很不方便,因此這個(gè)實(shí)現(xiàn)應(yīng)該加個(gè)配置,用于針對(duì)任意目的地址進(jìn)行流量控制,比tc方便多了。

優(yōu)化三:上述實(shí)現(xiàn)中,數(shù)據(jù)單位是字節(jié),這樣很不合理,應(yīng)該是可以配置的才對(duì),比如默認(rèn)是字節(jié),還可以是k,m,g等等。

優(yōu)化四:應(yīng)該實(shí)現(xiàn)一個(gè)機(jī)制,定期清理不活躍的entry,以防止內(nèi)存占用率過(guò)高。

反思:為何在入口位置的流控不實(shí)現(xiàn)隊(duì)列呢?我們還是要想想流控的目的是什么,其一就是避免擁塞-網(wǎng)絡(luò)的擁塞以及主機(jī)上層緩沖區(qū)的擁塞,對(duì)于接收數(shù)據(jù)而言,無(wú)論如何,流量對(duì)到達(dá)此地之前的網(wǎng)絡(luò)的影響已經(jīng)發(fā)生了,對(duì)往后的網(wǎng)絡(luò)的影響還沒(méi)有發(fā)生,因此對(duì)于已經(jīng)發(fā)生的影響,沒(méi)有必要再去進(jìn)行速率適配了,直接執(zhí)行動(dòng)作即可。

    如果你真的還需要limit模塊完成它本來(lái)的功能,那么就別改limit模塊了,還是直接寫一個(gè)為好,這樣也更靈活,畢竟我們也就不需要再配置--limit1/sec去迎合limit的語(yǔ)法了,具體方法參見《編寫iptables模塊實(shí)現(xiàn)不連續(xù)IP地址的DNAT-POOL

修正:
如果同時(shí)下載多個(gè)局域網(wǎng)內(nèi)的大文件,會(huì)發(fā)現(xiàn)上述的match回調(diào)函數(shù)工作的不是很好,速度并沒(méi)有被限制住,這是因?yàn)槲矣?jì)時(shí)統(tǒng)計(jì)統(tǒng)計(jì)的粒度太粗,一秒統(tǒng)計(jì)一次,這一秒中,很多大包將溜過(guò)去,因此需要更細(xì)粒度的統(tǒng)計(jì),那就是實(shí)時(shí)的統(tǒng)計(jì),使用數(shù)據(jù)量/時(shí)間間隔這個(gè)除式來(lái)統(tǒng)計(jì),代碼如下:

  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.     unsigned long rate;  
  18.         struct iphdr *iph skb->nh.iph;  
  19.         __u32 this_addr iph->saddr;  
  20.   
  21.         list_for_each(lh, &src_ctl->src_list)  
  22.                 find_entry list_entry(lh, struct src_entry, list);  
  23.                 if (this_addr == find_entry->src_addr)  
  24.                         entry find_entry;  
  25.                         break;  
  26.                  
  27.          
  28.         if (entry)  
  29.                 prev entry->prev;  
  30.                 list_del(&entry->list);  
  31.                 list_add(&entry->list, &src_ctl->src_list);  
  32.         else  
  33.                 if (src_ctl->curr+1 src_ctl->max)  
  34. add_entry:  
  35.                         entry kmalloc(sizeof(struct src_entry), GFP_KERNEL);  
  36.                         memset(entry, 0, sizeof(struct src_entry));  
  37.                         entry->src_addr this_addr;  
  38.                         prev entry->prev now 1000;  
  39.                         list_add(&entry->list, &src_ctl->src_list);  
  40.                         src_ctl->curr++;  
  41.                 else  
  42.                         entry list_entry(src_ctl->src_list.prev, struct src_entry, list);  
  43.                         if (now-entry->prev 1000)  
  44.                                 goto add_entry;  
  45.                         return 1;  
  46.                  
  47.          
  48.         nowa entry->passed skb->len;  
  49.     entry->passed nowa;  
  50.     if (now-prev 0)  
  51.         rate entry->passed/(now-prev);  
  52.     else  
  53.         rate nowa;  
  54.     entry->prev now;  
  55.     entry->passed 0;  
  56.     if (rate r->burst)  
  57.         return 1;  
  58.      
  59.         return 0;  
  60.  

 

--------------------------------------------------------------------------------------------------

三、使用iptables的HOOK掛載點(diǎn):

 

1.模塊分析

針對(duì)ipv4協(xié)議,內(nèi)核的防火墻框架NetfilterIP層精心挑選了五個(gè)插入點(diǎn):NF_IP_PRE_ROUTING,NF_IP_LOCAL_IN,NF_IP_FORWARD,NF_IP_LOCAL_OUT,NF_IP_POST_ROUTING,分別對(duì)應(yīng)IP層的五個(gè)不同位置。這樣,用戶可以選擇合適的切入點(diǎn)添加自己的內(nèi)核模塊。

在這里,我們針對(duì)從網(wǎng)卡接收進(jìn)來(lái)的包進(jìn)行過(guò)濾處理,基于源地址+端口號(hào)進(jìn)行過(guò)濾,該程序的功能可以丟棄來(lái)自指定IP地址+端口號(hào)的數(shù)據(jù)包。需要針對(duì)所有進(jìn)入系統(tǒng)的包進(jìn)行過(guò)濾,不屬于發(fā)往本機(jī)的數(shù)據(jù)包我們不予理會(huì),因此我們選擇模塊的切入點(diǎn)在NF_IP_LOCAL_IN,該切入點(diǎn)的位置在IP層向上層協(xié)議棧傳遞數(shù)據(jù)包的函數(shù)ip_local_deliver()  

NF_HOOK(PF_INET,NF_IP_LOCAL_IN,skb,skb->dev,NULL,ip_local_deliver_finish)

IP packet NF_HOOK() IPv4協(xié)議棧上鉤出來(lái)以后,就進(jìn)入 linux-2.4.19/net/core/netfilter.c 中的 nf_hook_slow()函數(shù)進(jìn)行處理。這個(gè)函數(shù)干的主要事情,就是根據(jù) nf_hooks[][] 數(shù)組開始處理 packet。而這個(gè)nf_hooks[][]鏈表二維數(shù)組的原型為:  

struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS];

該二維數(shù)組就是根據(jù)【協(xié)議類型】【鉤子點(diǎn)】存儲(chǔ)的該協(xié)議類型在這個(gè)鉤子點(diǎn)上的處理函數(shù),任何一個(gè)希望使用Netfilter鉤子的模塊都需要將模塊處理函數(shù)在nf_hooks數(shù)組的相應(yīng)鏈表上進(jìn)行注冊(cè)。我們編寫的防火墻函數(shù)模塊,實(shí)際上就是生成一個(gè)struct nf_hook_ops結(jié)構(gòu)的實(shí)例,并用nf_register_hook將其登記在nf_hooks[][]上。該結(jié)構(gòu)的原型為:

struct nf_hook_ops
{
      struct list_head list;  //
鏈表
      nf_hookfn *hook;  //
處理函數(shù)指針
      int pf;            //
協(xié)議好
      int hooknum;      //HOOK
號(hào)
      int priority;        //
優(yōu)先級(jí)
};

   該結(jié)構(gòu)的本質(zhì),是一個(gè)nf_hookfn 函數(shù)。這個(gè)函數(shù)將對(duì)被釣上來(lái)的 IP packet 進(jìn)行初步的處理?,F(xiàn)在來(lái)看看nf_hookfn函數(shù)的原型:

           typedef unsigned int nf_hookfn(unsigned int hooknum,
                                       struct sk_buff **skb,
                                       conststruct net_device *in,
                                       const struct net_device *out,
                                       int (*okfn)(struct sk_buff *));

  nf_hookfn函數(shù)的第一個(gè)參數(shù)用于指定hook類型。第二個(gè)參數(shù)一個(gè)指向指針的指針,該指針指向的指針指向一個(gè)sk_buff數(shù)據(jù)結(jié)構(gòu),網(wǎng)絡(luò)堆棧用sk_buff數(shù)據(jù)結(jié)構(gòu)來(lái)描述數(shù)據(jù)包。緊跟在skb之后的兩個(gè)參數(shù)是指向net_device數(shù)據(jù)結(jié)構(gòu)的指針,net_device數(shù)據(jù)結(jié)構(gòu)被Linux內(nèi)核用于描述所有類型的網(wǎng)絡(luò)接口。這兩個(gè)參數(shù)中的第一個(gè)——in,用于描述數(shù)據(jù)包到達(dá)的接口,毫無(wú)疑問(wèn),參數(shù)out用于描述數(shù)據(jù)包離開的接口。必須明白,在通常情況下,這兩個(gè)參數(shù)中將只有一個(gè)被提供。例如:參數(shù)in只用于NF_IP_PRE_ROUTINGNF_IP_LOCAL_INhook,參數(shù)out只用于NF_IP_LOCAL_OUTNF_IP_POST_ROUTINGhook。最后,傳遞給hook函數(shù)的最后一個(gè)參數(shù)是一個(gè)命名為okfn函數(shù)指針,該函數(shù)以一個(gè)sk_buff數(shù)據(jù)結(jié)構(gòu)作為它唯一的參數(shù),并且返回一個(gè)整型的值。

 

2.模塊設(shè)計(jì)                  

了解了HOOK函數(shù)接收到的信息中最有趣和最有用的部分后,看看我們?nèi)绾我愿鞣N各樣的方式來(lái)利用這些信息來(lái)過(guò)濾數(shù)據(jù)包。我們的模塊實(shí)現(xiàn)的基于源地址+端口號(hào)進(jìn)行過(guò)濾的功能其實(shí)可以劃分為兩個(gè)規(guī)則:

首先,根據(jù)數(shù)據(jù)包的源地址進(jìn)行過(guò)濾,我們從sk_buff數(shù)據(jù)結(jié)構(gòu)中提取感興趣的信息。skb參數(shù)是一個(gè)指向sk_buff數(shù)據(jù)結(jié)構(gòu)的指針的指針嗎。為了避免犯錯(cuò)誤,聲明一個(gè)另外的指向skb_buff數(shù)據(jù)結(jié)構(gòu)的指針并且將skb指針指向的指針賦值給這個(gè)新的指針是一個(gè)好習(xí)慣,就像這樣:
  
    struct sk_buff *sb = *skb;

   
這樣,你訪問(wèn)這個(gè)數(shù)據(jù)結(jié)構(gòu)的元素時(shí)只需要反引用一次就可以了。獲取一個(gè)數(shù)據(jù)包的IP頭通過(guò)使用sk_buff數(shù)據(jù)結(jié)構(gòu)中的網(wǎng)絡(luò)層包頭來(lái)完成。這個(gè)頭位于一個(gè)聯(lián)合中,可以通過(guò)sk_buff->nh.iph這樣的方式來(lái)訪問(wèn)。示例代碼1中的函數(shù)演示了當(dāng)?shù)玫揭粋€(gè)數(shù)據(jù)包的sk_buff數(shù)據(jù)結(jié)構(gòu)時(shí),如何利用它來(lái)檢查收到的數(shù)據(jù)包的源IP地址與被禁止的地址是否相同。

示例代碼1 : 檢查收到的數(shù)據(jù)包的源IP
  
    unsigned char *deny_ip = "\x7f\x00\x00\x01"; 
    
    ...

        static int check_ip_packet(struct sk_buff *skb)
        {
            
            if (!skb )return NF_ACCEPT;
            if (!(skb->nh.iph)) return NF_ACCEPT;
        
            if (skb->nh.iph->saddr == *(unsignedint *)deny_ip) {
            return NF_DROP;
            }

            return NF_ACCEPT;
        }
  
  
這樣,如果數(shù)據(jù)包的源地址與我們?cè)O(shè)定的丟棄數(shù)據(jù)包的地址匹配,那么該數(shù)據(jù)包將被丟棄。為了使這個(gè)函數(shù)能按預(yù)期的方式工作,deny_ip的值應(yīng)當(dāng)以網(wǎng)絡(luò)字節(jié)序(Big-endian,與Intel相反)存放。雖然這個(gè)函數(shù)不太可能以一個(gè)空的指針作為參數(shù)來(lái)調(diào)用,帶一點(diǎn)點(diǎn)偏執(zhí)狂從來(lái)不會(huì)有什么壞處。當(dāng)然,如果錯(cuò)誤確實(shí)發(fā)生了,那么該函數(shù)將會(huì)返回NF_ACCEPT。這樣Netfilter可以繼續(xù)處理這個(gè)數(shù)據(jù)包。示例代碼2展現(xiàn)了用于演丟棄匹配給定IP地址的數(shù)據(jù)包的簡(jiǎn)單模塊。
  
  
示例代碼2: 基于數(shù)據(jù)包源地址的過(guò)濾


#define __KERNEL__
#define MODULE

#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/skbuff.h>
#include <linux/ip.h>                 
#include<linux/netfilter.h>
#include<linux/netfilter_ipv4.h>


static struct nf_hook_ops nfho;


static unsigned char *drop_ip = "\x7f\x00\x00\x01";


unsigned int hook_func(unsigned int hooknum,
                     struct sk_buff **skb,
                     const struct net_device *in,
                     const struct net_device *out,
                     int (*okfn)(struct sk_buff *))
{
  struct sk_buff *sb = *skb;
  
   if(sb->nh.iph->saddr == *(unsigned int*)drop_ip) {
   //if (sb->nh.iph->saddr == drop_ip){
      printk("Dropped packet from... %d.%d.%d.%d\n",
    *drop_ip, *(drop_ip + 1),
 *(drop_ip+ 2), *(drop_ip + 3));
      return NF_DROP;
   }else {
      return NF_ACCEPT;
  }
}


int init_module()
{
  
  nfho.hook      = hook_func;        
  nfho.hooknum  = NF_IP_LOCAL_IN;
  nfho.pf      = PF_INET;
  nfho.priority = NF_IP_PRI_FIRST;  

  nf_register_hook(&nfho);

  return 0;
}


void cleanup_module()
{
  nf_unregister_hook(&nfho);
}

其次,另一個(gè)要實(shí)現(xiàn)的簡(jiǎn)單規(guī)則是基于數(shù)據(jù)包的TCP目的端口進(jìn)行過(guò)濾。這只比檢查IP地址的要求要高一點(diǎn)點(diǎn),因?yàn)槲覀冃枰约簞?chuàng)建一個(gè)TCP頭的指針。獲取一個(gè)TCP頭的指針是一件簡(jiǎn)單的事情——分配一個(gè)tcphdr數(shù)據(jù)結(jié)構(gòu)(linux/tcp.h 中定義)的指針,并將它指向我們的數(shù)據(jù)包中IP頭之后的數(shù)據(jù)。示例代碼3給出了檢查數(shù)據(jù)包的TCP目的端口是否與某個(gè)我們要丟棄數(shù)據(jù)包的端口匹配的代碼。

  
  
示例代碼3: 檢查收到的數(shù)據(jù)包的TCP目的端口
        unsigned char *deny_port = "\x00\x19";  

    ...

        static int check_tcp_packet(struct sk_buff *skb)
        {
            struct tcphdr *thead;

            
            if (!skb ) return NF_ACCEPT;
            if (!(skb->nh.iph)) return NF_ACCEPT;

            
            if (skb->nh.iph->protocol !=IPPROTO_TCP) {
                return NF_ACCEPT;
            }

            thead = (struct tcphdr *)(skb->data +
                                     (skb->nh.iph->ihl * 4));

            
            if ((thead->dest) == *(unsigned short *)deny_port){
                return NF_DROP;
            }
        
        return NF_ACCEPT;
        }

3.行動(dòng)

 

4.總結(jié)

--------------------------------------------------------------------------------------------------

 

附:

linux iptables是由兩個(gè)組件組成:NetfilterIptables組成:

Netfilter組件稱之為內(nèi)核空間,是linux內(nèi)核的一部分,是在Linux內(nèi)核中為攔截額操作數(shù)據(jù)包提供的一套框架。其框架包含以下三部分:

1:為每種網(wǎng)絡(luò)協(xié)議(IPV4,IPv6等)定義一套HOOK函數(shù),這些HOOK函數(shù)在數(shù)據(jù)包流過(guò)IP協(xié)議棧的幾個(gè)關(guān)鍵點(diǎn)被調(diào)用。在這幾個(gè)點(diǎn)鐘,協(xié)議棧將把數(shù)據(jù)包及HOOK函數(shù)標(biāo)號(hào)作為參考調(diào)用Netfilter框架。

2:內(nèi)核的任何模塊可以對(duì)每種協(xié)議的一個(gè)或多個(gè)HOOK函數(shù)進(jìn)行注冊(cè)以實(shí)現(xiàn)掛接,這樣當(dāng)某個(gè)數(shù)據(jù)包被傳遞給Netfilter框架時(shí),內(nèi)核能檢測(cè)是否有哪個(gè)模塊對(duì)該協(xié)議和HOOK函數(shù)進(jìn)行了注冊(cè)。若注冊(cè)了,則調(diào)用該模塊,這樣這些模塊就有機(jī)會(huì)檢查該數(shù)據(jù)包丟棄/修改/傳入用戶空間的隊(duì)列。

3:那些在用戶控件隊(duì)列中排隊(duì)的數(shù)據(jù)包是被傳遞給用戶空間異步的處理。

IPV4中定義了5個(gè)HOOK,如圖  



Netfilter根據(jù)網(wǎng)絡(luò)報(bào)文的流向,分為三部分:流入,流經(jīng),流出。

在以下幾個(gè)點(diǎn)插入處理過(guò)程:

NF_IP_PRE_ROUTING,在報(bào)文作路由以前執(zhí)行;

NF_IP_FORWARD,在報(bào)文轉(zhuǎn)向另一個(gè)NIC以前執(zhí)行;

NF_IP_POST_ROUTING,在報(bào)文流出以前執(zhí)行;

NF_IP_LOCAL_IN,在流入本地的報(bào)文作路由以后執(zhí)行;

NF_IP_LOCAL_OUT,在本地報(bào)文做流出路由前執(zhí)行。

分析:當(dāng)網(wǎng)絡(luò)上的數(shù)據(jù)包從左邊進(jìn)入系統(tǒng),數(shù)據(jù)包經(jīng)過(guò)第一個(gè)點(diǎn)調(diào)用HOOK函數(shù)NF_IP_PRE_ROUTING進(jìn)行處理;然后就進(jìn)入本地路由表,查看該數(shù)據(jù)包是需要轉(zhuǎn)發(fā)還是發(fā)給本地;若該數(shù)據(jù)包時(shí)發(fā)往本地的,則該數(shù)據(jù)包經(jīng)過(guò)HOOK函數(shù)NF_IP_LOCAL_IN處理以后傳遞給上層協(xié)議;若該數(shù)據(jù)包是需要轉(zhuǎn)發(fā),則會(huì)被NF_IP_FORWARD處理;經(jīng)過(guò)轉(zhuǎn)發(fā)的數(shù)據(jù)包最后由NF_IP_POST_ROUTING處理后發(fā)送到網(wǎng)絡(luò)上;本地產(chǎn)生的數(shù)據(jù)經(jīng)過(guò)路由選擇后,調(diào)用NF_IP_LOCAL_OUT進(jìn)行處理,然后經(jīng)過(guò)NF_IP_POST_ROUTING處理后發(fā)送到網(wǎng)絡(luò)上。

 

當(dāng)這些HOOK函數(shù)被經(jīng)過(guò)的數(shù)據(jù)包調(diào)用時(shí)將返回下列值之一,告知Netfilter如果對(duì)數(shù)據(jù)包采取相應(yīng)動(dòng)作。

NF_ACCEPT   繼續(xù)正常的報(bào)文處理

NF_DROP     丟棄報(bào)文

NF_STOLEN   HOOK函數(shù)處理了該報(bào)文,不要再繼續(xù)傳送

NF_QUEUE    將報(bào)文入列,通常交由用戶程序處理

NF_REPEAT   再次調(diào)用該HOOK函數(shù)

 

Iptables用戶空間工具

iptables 組件是一種工具,也稱為用戶空間。它使插入、修改和除去信息包過(guò)濾表中的規(guī)則變得容易。這些規(guī)則存儲(chǔ)在表的對(duì)象中。

Table

1filter

該表的作用主要用于包過(guò)濾。它在LOCAL_INFORWARD,LOCAL_OUT三處HOOK函數(shù)進(jìn)行了注冊(cè)。

2Nat

該表的作用主要用于地址轉(zhuǎn)換。它在PRE_ROUTING,POST_ROUTING兩處HOOK函數(shù)進(jìn)行了注冊(cè)。

3Mangle

該表的作用是進(jìn)行數(shù)據(jù)包內(nèi)容的修改。它在Netfilter的所有5個(gè)HOOK函數(shù)進(jìn)行了注冊(cè)。

Chain

 在每張表中,內(nèi)核使用鏈管理數(shù)據(jù)包,每個(gè)鏈中包含了一組規(guī)則表。

表與鏈的關(guān)系:

Table

Chain

Filter

INPUT

Filter

FORWARD

Filter

OUTPUT

Nat

PREROUTING

Nat

OUTPUT

Nat

POSTOUTING

Mangle

PREROUTING

Mangle

INPUT

Mangle

OUTPUT

Mangle

FORWARD

Mangle

POSTROUTING

 INPUT=HOOK函數(shù)LOCALIN

 OUTPUT=HOOK函數(shù)LOCALOUT

 PREROUTING=HOOK函數(shù)PREROUTING

 POSTROUTING=HOOK函數(shù)POSTROUTING

 FORWARD=HOOK函數(shù)FORWARD

 鏈的名字與HOOK函數(shù)名相似,鏈的作用和位置也與HOOK函數(shù)相吻合,可以說(shuō)鏈就是實(shí)現(xiàn)HOOK函數(shù)調(diào)用的方式。

Target 目標(biāo)

鏈的每個(gè)規(guī)則都會(huì)有一個(gè)目標(biāo),目標(biāo)決定了該規(guī)則對(duì)數(shù)據(jù)包如何處理。

常用目標(biāo):

ACCEPT:允許

DROP:拒絕

REJECT:同DROP一樣,不過(guò)它會(huì)向發(fā)送方返回個(gè)錯(cuò)誤信息

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
LINUX2.4.x網(wǎng)絡(luò)安全框架
Netfilter框架
netfilter: Linux 防火墻在內(nèi)核中的實(shí)現(xiàn)
iptables ipt_do_table
netfilter中iptables表的實(shí)現(xiàn)
linux中鉤子函數(shù)的理解
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服