吳 華,鄧 波
(中國電子科技集團(tuán)公司第三十研究所,四川 成都 610041)
隨著互聯(lián)網(wǎng)應(yīng)用日益普及,越來越多的電子設(shè)備可以接入互聯(lián)網(wǎng),導(dǎo)致可用IPv4 地址資源愈發(fā)緊缺。為解決這一難題,本文引入了網(wǎng)絡(luò)地址轉(zhuǎn)換技術(shù)[1](Network Address Translation,NAT),讓原本無法上網(wǎng)并且使用內(nèi)部因特網(wǎng)互聯(lián)協(xié)議(Internet Protocol,IP)地址的電子設(shè)備可以成功連接互聯(lián)網(wǎng)。
Linux 操作系統(tǒng)是一個(gè)開源的操作系統(tǒng),具有高效性、靈活性和良好的網(wǎng)絡(luò)性能等特點(diǎn),大量應(yīng)用于各類電子設(shè)備。Netfilter 是Linux 3.10 內(nèi)核的一個(gè)子系統(tǒng),可以完成數(shù)據(jù)包過濾、連接跟蹤、地址轉(zhuǎn)換等重要功能,是一個(gè)結(jié)構(gòu)合理、功能強(qiáng)大、擴(kuò)展性強(qiáng)的網(wǎng)絡(luò)架構(gòu)[2-3]。本文將分析Linux 操作系統(tǒng)的Netfilter 框架和NAT 工作原理,并討論其具體的功能實(shí)現(xiàn)進(jìn)。此外,本文基于Netfilter 框架和Linux系統(tǒng),設(shè)計(jì)了一種新的進(jìn)程間通信方式ukcomm。
Netfilter 在傳輸控制協(xié)議/因特網(wǎng)互聯(lián)協(xié)議(Transmission Control protocol/Internet Protocol,TCP/IP)協(xié)議棧中定義了5 個(gè)檢查點(diǎn)(hook)和相應(yīng)的數(shù)據(jù)結(jié)構(gòu),規(guī)定了在檢查點(diǎn)上對(duì)這些數(shù)據(jù)結(jié)構(gòu)引用的過程。用戶只需要在檢查點(diǎn)注冊(cè)一些處理函數(shù)(鉤子函數(shù)),編寫符合自己需求的過濾規(guī)則,就可對(duì)從檢查點(diǎn)獲取的數(shù)據(jù)包進(jìn)行修改、丟棄或發(fā)送到用戶空間等處理[4]。
Linux 系統(tǒng)的Netfilter 部署于內(nèi)核態(tài),它在TCP/IP 協(xié)議棧的數(shù)據(jù)轉(zhuǎn)發(fā)/處理流程中提供了5 個(gè)鉤子函數(shù)掛接點(diǎn)[5],如圖1中5個(gè)虛線橢圓框圖所示。
圖1 Netfilter 框架流程
這5 個(gè)鉤子函數(shù)掛接點(diǎn)具體功能如下文所述。
(1)PRE_ROUTING:位于被路由代碼處理之前,因此所有收到的包在進(jìn)一步接受任何處理之前都必須經(jīng)過該鉤子函數(shù)進(jìn)行處理,可實(shí)現(xiàn)非法網(wǎng)絡(luò)包的快速丟棄。
(2)LOCAL_IN:所有地址指向本網(wǎng)卡的傳入包都需要經(jīng)過該鉤子函數(shù)的處理,在此,iptables模塊提供的INPUT 規(guī)則列表來篩選傳入的數(shù)據(jù)包。
(3)FORWARD:所有需要在內(nèi)外網(wǎng)接口、不同網(wǎng)卡間轉(zhuǎn)發(fā)的報(bào)文都需經(jīng)過該鉤子函數(shù)的處理。
(4)LOCAL_OUT:本網(wǎng)卡發(fā)送的報(bào)文首先經(jīng)過該鉤子函數(shù)掛接點(diǎn)進(jìn)行處理,在此,iptables 模塊提供OUTPUT 規(guī)則列表來篩選外發(fā)的數(shù)據(jù)包。
(5)POST_ROUTING:是在所有外發(fā)包通過網(wǎng)絡(luò)離開本網(wǎng)卡之前訪問它們的最后機(jī)會(huì),可以對(duì)本單元主動(dòng)發(fā)送或者轉(zhuǎn)發(fā)的報(bào)文進(jìn)行最后的過濾/NAT 等處理。
網(wǎng)絡(luò)接口驅(qū)動(dòng)收到數(shù)據(jù)包后,會(huì)先進(jìn)行循環(huán)冗余校核(Cyclic Redundancy Check,CRC)校驗(yàn)和報(bào)文合法性檢測(cè);通過檢測(cè)的數(shù)據(jù)包會(huì)先進(jìn)入PRE_ROUTING 檢查點(diǎn),由協(xié)議棧查詢路由決定數(shù)據(jù)包是發(fā)送到本地處理還是轉(zhuǎn)發(fā)給其他網(wǎng)絡(luò)設(shè)備;發(fā)送到本地處理的數(shù)據(jù)包將進(jìn)入LOCAL_IN 檢查點(diǎn);需要轉(zhuǎn)發(fā)的數(shù)據(jù)包將進(jìn)入FORWARD 檢查點(diǎn),然后通過POST_ROUTING 檢查點(diǎn)后由網(wǎng)絡(luò)接口驅(qū)動(dòng)輸出;而需要本地處理的數(shù)據(jù)包將繼續(xù)通過LOCAL_OUT檢查點(diǎn)和POST_ROUTING 檢查點(diǎn)后進(jìn)入本地網(wǎng)絡(luò)處理[6-8]。
每一個(gè)檢查點(diǎn)注冊(cè)的鉤子函數(shù),都必須返回一個(gè)值來通知內(nèi)核,由內(nèi)核進(jìn)行相應(yīng)的處理。鉤子函數(shù)的返回值定義如下文所述。
(1)NF_DROP:數(shù)據(jù)包丟棄,內(nèi)核釋放為他分配的資源;
(2)NF_ACCEPT:接收數(shù)據(jù)包,內(nèi)核繼續(xù)傳送報(bào)文;
(3)NF_STOLEN:數(shù)據(jù)包由鉤子函數(shù)處理,內(nèi)核不再繼續(xù)傳送;
(4)NF_QUEUE:數(shù)據(jù)包發(fā)送到用戶程序處理,內(nèi)核不再進(jìn)行操作;
(5)NF_REPEAT:再次調(diào)用該鉤子函數(shù)。
NAT 技術(shù)是指替換IP 報(bào)文頭部的地址信息。該技術(shù)通常部署在網(wǎng)絡(luò)出口位置,實(shí)現(xiàn)私有IP 地址與公網(wǎng)IP 地址間的轉(zhuǎn)換[9]。
根據(jù)開放式系統(tǒng)互聯(lián)參考模型(Open System Interconnect Reference Model,OSI)的分層結(jié)構(gòu),網(wǎng)絡(luò)層將數(shù)據(jù)封裝成數(shù)據(jù)包時(shí),包含兩個(gè)重要的信息:源IP 地址(發(fā)送數(shù)據(jù)包的網(wǎng)絡(luò)設(shè)備接口IP 地址)及目的IP 地址(接收數(shù)據(jù)包的網(wǎng)絡(luò)設(shè)備接口IP 地址)[10]。具有NAT 功能的網(wǎng)絡(luò)設(shè)備收到數(shù)據(jù)包后,按既定的規(guī)則對(duì)IP 數(shù)據(jù)包中的源IP 地址或目的IP地址進(jìn)行轉(zhuǎn)換,然后將數(shù)據(jù)包轉(zhuǎn)發(fā)至外網(wǎng)或內(nèi)網(wǎng)。
Linux 系統(tǒng)支持模塊化機(jī)制,因此可以利用可加載模塊對(duì)Netfilter 進(jìn)行擴(kuò)展[11]。
NAT 地址轉(zhuǎn)換模塊就是編寫為一個(gè)內(nèi)核態(tài)模塊程序,程序編譯成ko 文件格式,通過Linux 系統(tǒng)自帶的insmod 和rmmod 命令動(dòng)態(tài)地加載進(jìn)內(nèi)核或從內(nèi)核卸載。
NAT 地址轉(zhuǎn)換模塊向上接收應(yīng)用程序下發(fā)的NAT 地址轉(zhuǎn)換指示,向下依賴于Linux 操作系統(tǒng)提供的Netfilter 實(shí)現(xiàn)報(bào)文獲取。應(yīng)用程序與NAT 地址轉(zhuǎn)換模塊之間的通信通過新設(shè)計(jì)的ukComm 實(shí)現(xiàn)。如圖2 所示。
圖2 NAT 功能模塊工作位置
Linux 系統(tǒng)中用戶態(tài)和內(nèi)核態(tài)之間的通信方式主要包括系統(tǒng)調(diào)用、文件系統(tǒng)和NetLink 套接字等方式[12-13]。然而,在Linux 下要實(shí)現(xiàn)用戶態(tài)程序調(diào)用設(shè)備中自開發(fā)內(nèi)核模塊的某種功能,不可能直接采用系統(tǒng)調(diào)用方式;但如果基于Netlink 這種異步通信方式又比較復(fù)雜;此外,如果每個(gè)內(nèi)核模塊開發(fā)自己的為字符設(shè)備驅(qū)動(dòng),則代碼非常冗余。為此,特開發(fā)ukComm 模塊,為所有需要通過同步方式調(diào)用內(nèi)核態(tài)模塊功能的用戶態(tài)/內(nèi)核態(tài)程序開發(fā)一套公用的調(diào)用接口。
ukComm 模塊基于偽字符設(shè)備驅(qū)動(dòng),通過ioctl實(shí)現(xiàn)用戶態(tài)到內(nèi)核態(tài)的統(tǒng)一同步調(diào)用接口,由用戶態(tài)與內(nèi)核態(tài)兩個(gè)部分組成,軟件架構(gòu)如圖3 所示。
圖3 ukComm 軟件模塊架構(gòu)
ukComm 用戶態(tài):為用戶態(tài)程序提供統(tǒng)一調(diào)用接口,接口形式為int uk_ioctl(int cmd,char*buf),通過不同的cmd 編碼即可實(shí)現(xiàn)調(diào)用內(nèi)核態(tài)的不同“函數(shù)”,而buf 用于實(shí)現(xiàn)信息的讀取或者寫入。
ukComm 內(nèi)核態(tài):為內(nèi)核態(tài)程序提供統(tǒng)一調(diào)用接口,接口形式為int uk_ioctl_reg (int cmd,ioctl_func func),內(nèi)核態(tài)程序通過該接口注冊(cè)對(duì)應(yīng)某命令(cmd)的處理函數(shù)。
ukComm 通信處理流程如圖4 所示。
圖4 ukComm 通信處理流程
在系統(tǒng)初始化過程中,各個(gè)支持通過ukComm向用戶態(tài)提供“函數(shù)調(diào)用”接口的內(nèi)核模塊調(diào)用uk_ioctl_reg 函數(shù)到ukComm 內(nèi)核態(tài)模塊注冊(cè)自己所支持的ioctl 命令以及所對(duì)應(yīng)處理函數(shù)。隨后系統(tǒng)運(yùn)行中,用戶態(tài)的程序通過uk_ioctl 調(diào)用內(nèi)核態(tài)程序提供的“函數(shù)”,其總體處理流程如下:
(1)ukComm 用戶態(tài)程序打開ukComm 內(nèi)核態(tài)模塊提供的偽字符設(shè)備,如果未能成功打開該偽字符設(shè)備則返回失敗,否則獲取到文件句柄,繼續(xù)下一步處理;
(2)ukComm 用戶態(tài)程序調(diào)用ioctl 函數(shù),傳入基于上述步驟中得到的文件句柄、用戶程序給出的命令(cmd)和參數(shù)信息(buf);
(3)ukComm 內(nèi)核態(tài)程序基于cmd 查找對(duì)應(yīng)該處理函數(shù),如果查詢不到則返回失敗,否則調(diào)用該處理函數(shù),該處理函數(shù)的返回值作為本次ioctl 的返回值;
(4)ukComm 用戶態(tài)程序關(guān)閉本次打開偽字符設(shè)備的文件句柄,返回ioctl 函數(shù)的返回值。
NAT 地址轉(zhuǎn)換模塊通過ukComm 接收應(yīng)用程序的指示,將應(yīng)用程序下發(fā)的地址轉(zhuǎn)換映射關(guān)系保存在一個(gè)數(shù)組nat_addr_maps[]中,其結(jié)構(gòu)如下:
利 用Netfilter 可擴(kuò)展 的框架,在PRE_ROUTING 檢查點(diǎn)可以對(duì)除本地發(fā)出的數(shù)據(jù)包外的所有數(shù)據(jù)進(jìn)行處理;在POST_ROUTING 檢查點(diǎn)對(duì)本地發(fā)出的報(bào)文進(jìn)行處理。
本文對(duì)來自內(nèi)網(wǎng)的數(shù)據(jù),在POST_ROUTING檢查點(diǎn)創(chuàng)建鉤子函數(shù)對(duì)源地址進(jìn)行轉(zhuǎn)換;對(duì)來自外網(wǎng)的數(shù)據(jù),在PRE_ROUTING 檢查點(diǎn)創(chuàng)建鉤子函數(shù)對(duì)目的地址進(jìn)行轉(zhuǎn)換。
Netfilter 提供了非常簡(jiǎn)單的接口函數(shù),編程人員可以非常方便地將自己寫的鉤子函數(shù)添加到Netfilter 中而被其調(diào)用。Netfilter 提供了int nf_register_hook(struct nf_hook_ops *ops)接口函數(shù)來在某一個(gè)檢查點(diǎn)注冊(cè)一個(gè)鉤子函數(shù),ops 只是一個(gè)數(shù)據(jù)結(jié)構(gòu)。本文所用的兩個(gè)檢查點(diǎn)鉤子函數(shù)的注冊(cè)處理如下:
內(nèi)核模塊的退出處理可以調(diào)用nf_unregister_ hook(&nfInputHook) 和nf_unregister_hook(&nfOutput Hook)完成兩個(gè)鉤子函數(shù)的卸載。
以nf_hookfn 函數(shù)為模塊編寫鉤子函數(shù)input_hook_func 和output_hook_func,nf_hookfn 函數(shù)的原型為:
nf_hookfn 函數(shù)的5 個(gè)參數(shù)由NF_HOOK 宏進(jìn)行傳遞:第1 個(gè)參數(shù)用于指定注冊(cè)鉤子函數(shù)的檢查點(diǎn);第2 個(gè)參數(shù)用于指向一個(gè)sk_buff 數(shù)據(jù)結(jié)構(gòu)[14-16],即數(shù)據(jù)包的地址;第3 個(gè)參數(shù)和第4 個(gè)參數(shù)用于描述網(wǎng)絡(luò)接口,參數(shù)in 用來描述數(shù)據(jù)包到達(dá)的接口,參數(shù)out 用來描述數(shù)據(jù)包離開的接口。通常情況下,參數(shù)in 用于PRE_ROUTING 檢查點(diǎn)的鉤子函數(shù),參數(shù)out 用于POST_ROUTING 檢查點(diǎn)的鉤子函數(shù),兩個(gè)參數(shù)中只有一個(gè)被提供。第5 個(gè)參數(shù)函數(shù)okfn 是當(dāng)對(duì)應(yīng)檢查點(diǎn)的鉤子函數(shù)注冊(cè)為空時(shí),Netfilter 調(diào)用的處理函數(shù),也是鉤子函數(shù)返回NF_ACCEPT 時(shí)Netfilter 調(diào)用的處理函數(shù)。
鉤子函數(shù)的具體實(shí)現(xiàn)如下:
其中find_innet_addr 和find_outnet_addr 都是從地址轉(zhuǎn)換映射表nat_addr_maps 中查找對(duì)應(yīng)的轉(zhuǎn)換IP,若映射表中為查詢到對(duì)應(yīng)的轉(zhuǎn)換IP 則無需進(jìn)行轉(zhuǎn)換,直接將輸入的參數(shù)返回。
本文通過分析Netfilter 的框架和Linux 系統(tǒng)可動(dòng)態(tài)加載的內(nèi)核模塊機(jī)制,利用基于偽字符設(shè)備驅(qū)動(dòng)設(shè)計(jì)的ukComm 模塊,實(shí)現(xiàn)了一個(gè)NAT 地址轉(zhuǎn)換模塊。該模塊可根據(jù)用戶下發(fā)的策略,進(jìn)行源地址轉(zhuǎn)換和目的地址轉(zhuǎn)換,有助于緩解IP 地址不足的問題,還能有效避免來自網(wǎng)絡(luò)外部的攻擊,達(dá)到隱藏并保護(hù)網(wǎng)絡(luò)內(nèi)部IP 的目的。此外,NAT 功能模塊采用Netfilter 框架,具有良好的代碼結(jié)構(gòu),易于維護(hù)和擴(kuò)展,運(yùn)行在Linux 內(nèi)核態(tài),所以運(yùn)行非常高效。