- 論壇徽章:
- 0
|
注:此文為轉(zhuǎn)載
本文試圖從一個(gè)原始數(shù)據(jù)包處理流程的角度,結(jié)合源代碼(相應(yīng)的函數(shù))簡(jiǎn)單扼要地分析FreeBSD的內(nèi)核網(wǎng)絡(luò)處理.
主機(jī)對(duì)主機(jī)的方式是比較簡(jiǎn)單的,數(shù)據(jù)包從鏈路層上來,一路上行,達(dá)到用戶空間的應(yīng)用程序,一個(gè)數(shù)據(jù)包的生命期就結(jié)束了.對(duì)于像網(wǎng)關(guān)或防火墻之類包轉(zhuǎn)發(fā)的方式,處理起來就相對(duì)復(fù)雜了一些,這也是許多人迷惑不解之處.
上面是開場(chǎng)白,接下來就轉(zhuǎn)入正題.
老規(guī)矩,先建立場(chǎng)景,場(chǎng)景總是要假設(shè)并建立起來的.設(shè):
hostA -- GW -- hostB
主機(jī)A通過GW互訪hostB
談到數(shù)據(jù)的通訊,總是雙向的,如同2人談話,如果僅僅是一個(gè)人說,那就成了演講--廣播.GW就是扮演了一個(gè)傳遞員的角色,2人的話傳來傳去,粗俗的話,優(yōu)化的GW或防火墻十有八九是不傳的,免得制造矛盾.
對(duì)于主機(jī)如何產(chǎn)生包,本文不作詳細(xì)討論.關(guān)心此項(xiàng)內(nèi)容的,可以參見TCP/UDP處理以及內(nèi)核中的socket等系統(tǒng)調(diào)用.本文的重點(diǎn)放在GW上,分析GW是如何處理轉(zhuǎn)發(fā)數(shù)據(jù)包的.
hostA想要訪問hostB的FTP(21端口):
0. 先廣播詢問并獲得網(wǎng)關(guān)的MAC地址.誰是網(wǎng)關(guān),速速報(bào)來!!!
1. 連接hostB的FTP端口
2. 成功后,發(fā)送數(shù)據(jù)包
....
hostA找到網(wǎng)關(guān)的MAC地址后,發(fā)往非本網(wǎng)段的數(shù)據(jù)包的目標(biāo)MAC地址都是網(wǎng)關(guān)的MAC地址,但目標(biāo)IP地址不是網(wǎng)關(guān)的.
下面就看看GW都作了哪些工作
1. GW聽到一個(gè)包
NIC <-- 硬中斷發(fā)生了
| 調(diào)用驅(qū)動(dòng)的rxeof函數(shù).包處理開始.對(duì)于polling
| 方式,是CPU主動(dòng)去網(wǎng)卡讀包,這樣硬中斷數(shù)會(huì)少,
| 但是如果處理不及時(shí),數(shù)據(jù)包就丟了.對(duì)于小包,而
| 且網(wǎng)卡芯片上的buf很大時(shí),polling方式的好處就很
| 大了.反過來,在遭受小包攻擊時(shí),系統(tǒng)的中斷數(shù)就
| 會(huì)異常高,這是因?yàn)樾枰煌5仨憫?yīng)處理.
|
if_xxx.c <-- rxeof
| m_devget申請(qǐng)mbuf,從網(wǎng)卡的buf拷貝數(shù)據(jù)到mbuf,
| 一個(gè)數(shù)據(jù)包出現(xiàn).剝離ether_header后,調(diào)用
| ether_input(ifp, eh, m)
|
if_ethersubr.c <-- ether_input:
a. 一定要獲取ether_header,拿不到就釋放mbuf
丟掉這個(gè)包.
后續(xù)的處理中,該數(shù)據(jù)包隨時(shí)面臨著被丟棄的危險(xiǎn)
b. bpf想要看看這個(gè)包,那就給他看看,反正他不會(huì)
更改這個(gè)包,tcpdump可以通過bpf看到這個(gè)包
c. netgraph也要處理嗎,嗚,處理就處理,不怕.
netgraph是FreeBSD獨(dú)特的網(wǎng)絡(luò)處理進(jìn)程,并移
植到了其他BSD,這里是一個(gè)鉤子,掛接在驅(qū)動(dòng)
層可以處理最原始的數(shù)據(jù)包.
正常的鉤子入口在 ng_ether中.
d. 是網(wǎng)橋模式嗎?如果是的話,數(shù)據(jù)包就從這里轉(zhuǎn)
到另一網(wǎng)卡的發(fā)送隊(duì)列中了.參見bridge.c
預(yù)處理作完了,該ether_demux(ifp, eh, m)出場(chǎng)了
<-- ether_demux:
開始為IP預(yù)處理
a. 這個(gè)包需要流量控制嗎?先轉(zhuǎn)到ipfw再處理它
b. 這個(gè)包是我的嗎?上層準(zhǔn)備接收了嗎,否那就丟
棄這個(gè)包
c. 如果是多播,就置位多播,告訴上層是多播
預(yù)處理就要結(jié)束了,根據(jù)包類型,分揀到不同的上層隊(duì)列中
----------------------------------------------------------------------
上面就是在驅(qū)動(dòng)一層的包處理過程,在這個(gè)過程中,插接了bpf, netgraph, ipfw, ipfilter,vlan等處理.bpf 是只讀的,其他都可以更改原始包(包括包頭,包內(nèi)容).FreeBSD之所以可以在橋模式過濾IP包,是因?yàn)樵赽ridge.c中有ipfilter等filter的鉤子,通過抽取包內(nèi)的IP信息就可以完成各種規(guī)則作用.對(duì)于軟vlan,ether_demux通過調(diào)用相應(yīng)的鉤子,剝離標(biāo)簽后,重新調(diào)用ether_input,相對(duì)netgraph中的vlan,個(gè)人覺得效率低,雖然實(shí)現(xiàn)起來相對(duì)簡(jiǎn)單.netgraph處理完的包后,不再預(yù)處理了,直接調(diào)用ether_demux繼續(xù)IP的處理或ether_output_frame將包發(fā)出網(wǎng)關(guān).在這一層上,包處理的效率是非常高的,而且也要求必須高效率.
說完了2層的處理,下面就是3層的了.文件的目錄也就從dev(pci),net轉(zhuǎn)到netinet.
2. 三層--arp處理
if_ether.c <-- arp的處理
首先出場(chǎng)的是arpintr,看名字知道是處理中斷的.
從隊(duì)列中取出一個(gè)包,不管三七二十一,看看包頭,
注意這時(shí)的包已經(jīng)沒有ether_header了.如果是arp
類型的包,并符合處理要求,轉(zhuǎn)到in_arpinput(m).
當(dāng)然如果不合規(guī)矩照丟不誤.
<-- in_arpinput(m)
針對(duì)各種情況判斷處理,其中會(huì)調(diào)用arplookup
判斷處理后,發(fā)送reply.將路由指針rt置NULL,
調(diào)用ether_output,雖然調(diào)用的是if_output,但大
多數(shù)網(wǎng)卡驅(qū)動(dòng)都將此函數(shù)指針設(shè)為ether_output.
這時(shí),數(shù)據(jù)包就回到了2層,發(fā)送回去了.之所以,
用"回到",因?yàn)楸砻嫔峡磥硎沁@樣的,還是相同的
mbuf,只是內(nèi)容不同了.arp的請(qǐng)求應(yīng)答包是對(duì)稱的.
<-- arplookup(addr, create, proxy)
完成arp的緩沖,將此MAC地址放到rt路由表中,以備
將來發(fā)送包時(shí)查詢使用.
這個(gè)文件中還有一個(gè)重要的函數(shù) - arpresolve,用于通過IP地址獲取MAC地址,如果在rt樹中沒有找到(或超時(shí)了),就調(diào)用arprequest,廣播獲取與此IP對(duì)應(yīng)的MAC地址.
系統(tǒng)命令arp就是通過ioctl和這個(gè)文件打交道.
3. 三層--IP處理
ip_input.c <-- 流入網(wǎng)關(guān)的IP處理
ipintr,自然就是IP隊(duì)列的中斷處理了,它的任務(wù)很
簡(jiǎn)單,從隊(duì)列中取出一個(gè)mbuf,也就是一個(gè)數(shù)據(jù)包.
將其交給ip_input處理.
<-- ip_input
a. 先判斷要不要進(jìn)行ipfw等的處理,是的話,跳轉(zhuǎn)c
處理
b. 接下來,拿到IP頭,針對(duì)IP頭判斷處理
c. ipfw和ipfilter開始處理
在ipfw和ipfilter中,這個(gè)包可能會(huì)被丟棄,
轉(zhuǎn)發(fā),這時(shí)流入包的處理就會(huì)到此結(jié)束
d. 經(jīng)過了包過濾的開包流檢,開始處理IP選項(xiàng),
當(dāng)然了多播也不要忘了處理一下
e. 判斷一下,是送給自己IP的嗎?如果不是,要不
要調(diào)用ip_forward,傳出網(wǎng)關(guān)?
f. 看來需要傳遞給上層處理了,根據(jù)不同的協(xié)議
TCP/UDP,調(diào)用位于4層的協(xié)議處理函數(shù),該他們
干活了.
<-- ip_forward
這是該文件中另一個(gè)重要的函數(shù)
該函數(shù),會(huì)根據(jù)目標(biāo)地址,查找路由,如果找到路由了,
就調(diào)用ip_output,將數(shù)據(jù)包轉(zhuǎn)發(fā)走,否則回應(yīng)一個(gè)
ICMP,告訴發(fā)送方出錯(cuò)了.
真不容易,這個(gè)數(shù)據(jù)包經(jīng)過了重重關(guān)卡,終于要繼續(xù)前進(jìn),準(zhǔn)備出城了.且慢,出城也不是那么容易了,這比乘火車坐飛機(jī)的**嚴(yán)多了.真是寧可錯(cuò)殺一千不漏一個(gè).
ip_output.c <-- 流出網(wǎng)關(guān)的IP處理
ip_output,IP流出的處理主體函數(shù),處理的方式類似
包流入的處理,先是
a. 先判斷要不要進(jìn)行ipfw的處理,是的話,跳轉(zhuǎn)d
處理
b. 嗯,要判斷是不是來自4層,看看是否要處理一下
IP頭
c. 看看路由表,這個(gè)包該何去何從?不要忘了多播喲!
當(dāng)然了,如果是IP的廣播包,也要處理的.
例如PPPOE會(huì)發(fā)送IP的廣播包
d. 又開始ipfw和ipfilter的處理了
e. 對(duì)于loopback的包,怎么能放出去呢,丟掉它
f. ip包DF了嗎,包太大又不讓分拆的話,只好對(duì)不
起了,丟棄它.否則拆分它,形成mbuf簇,每個(gè)
簇由多個(gè)鏈構(gòu)成.ip_fragment做的就是這件事
包轉(zhuǎn)發(fā)幾乎涉及不到包重組.
g. 到此,終于可以通過if_ouput -- ether_output
將包傳送到了二層
----------------------------------------------------------------------
在三層上,是各種安全處理的最佳地點(diǎn),這時(shí)候,原始的包該處理的都處理,剩下的就是怎么根據(jù)IP完成各種各樣的規(guī)則處理了.在這一層,數(shù)據(jù)包可以被還原為一個(gè)發(fā)送方的IP包,并能夠進(jìn)一步解包成TCP/UDP,形成會(huì)話甚至應(yīng)用.由于分層的結(jié)構(gòu),采用SMP對(duì)包作進(jìn)一步處理時(shí),并不會(huì)對(duì)下層造成很大的影響(mbuf處理不及時(shí),造成mbuf耗盡等等)
4. 二層--ether_output
if_ethersubr.c <-- ether_output:
a. 需要判斷路由?那就看看,不合適的話就丟棄這個(gè)包
b. 看看arp表,有目的地址的MAC?沒有就去要一個(gè)回
來,沒要來?那就返回吧,出不去了
c. 添加ether_header
d. 什么,目標(biāo)地址是自己,if_simloop這個(gè)包
e. 看看netgraph要處理嗎?
f. 將包轉(zhuǎn)給ether_output_frame繼續(xù)處理
<-- ether_output_frame
a. 網(wǎng)橋要處理嗎?
b. ipfw還要處理一下?
c. 都處理完了吧,那就把包送到網(wǎng)卡的輸出隊(duì)列中吧,
等候網(wǎng)卡驅(qū)動(dòng)處理了
if_xxx.c <-- xxx_intr
網(wǎng)卡設(shè)備的中斷處理,負(fù)責(zé)發(fā)送接受等工作
<-- if_start
從隊(duì)列中取出包,調(diào)用xxx_encap,將包轉(zhuǎn)換為frame
最后再看一眼bpf.
----------------------------------------------------------------------
### if_simloop在if_loop.c文件中
千辛萬苦,數(shù)據(jù)包終于走出了網(wǎng)關(guān).
網(wǎng)絡(luò)處理程序的分支非常多,但是只要抓住主線,就會(huì)非常清晰其處理流程.其中涉及
到的處理函數(shù)也就那么幾個(gè).
其中涉及到的數(shù)據(jù)結(jié)構(gòu)也非常得多,隊(duì)列,mbuf鏈(簇),ifp,rt等是非常重要的數(shù)據(jù)體,很多時(shí)候如果不清楚這些結(jié)構(gòu),讀懂這些程序是非常困難的.同時(shí)針對(duì)某協(xié)議的封裝格式也要了解清楚,TCP/UDP->IP->mbuf,層層封裝的,不要僅僅是停留在書本上.
|
|