今年二月上旬,Yahoo、eBay、CNN.com、Amazon、Buy.com和E*Trade等著名商業網站連續遭到黑客攻擊,造成了數以十億美元的損失,向世人再一次敲響了網絡並不安全的警鐘。防火牆作為一種網絡或系統之間強制實行訪問控制的機制,是確保網絡安全的重要手段。目前社會上各種商業產品的防火牆非常多,功能也大都很強。我們暫且不管這些防火牆產品的價格如何,由於它們在開發設計過程中注重的是產品的通用性、兼容性,考慮更多的是市場和利潤,因此對於某些特殊的應用就不一定很合適。如果用戶能根據自己的實際需要,將防火牆設計的一般理論和方法與自己系統的具體實踐相結合,設計一些小而精、精而強的防火牆程序,則往往可以發揮出比花大價錢買來的通用型防火牆更好的作用。
由於篇幅所限,本文不可能對防火牆的一般理論和結構進行深入的討論,因此僅以Linux系統為例,具體說明防火牆程序的設計方法。
一、 從程序設計角度看Linux網絡
編寫防火牆程序並不一定要求對Linux網絡內核有多麼深刻的理解,只是需要明白在網絡內核中有這樣一種機制,那就是內核可以自動調用用戶編寫的防火牆程序,並根據這個防火牆程序返回的結果來決定對網絡收發數據報的處理策略。這一點可以從圖1中看出。
二、 怎樣將自己編寫的防火牆程序登記到內核中
我們已經知道內核在網絡層中自動調用用戶編寫的防火牆程序。但有一個前提條件就是用戶必須正確地將自己編寫的防火牆程序登記到內核中。關於Linux內核驅動程序的編寫方法,可參見本刊第四期中《Linux設備驅動程序設計實例》一文。
內核中提供了防火牆的登記和卸載函數,分別是register_firewall和unregister_firewall,參見firewall.c。
1、 register_firewall
函數原型如下:
int register_firewall(int pf,struct firewall_ops *fw)
返回值:0代表成功,小於0表示不成功。
參數:
* 協議標志pf,主要的取值及其代表的協議如下:
2代表Ipv4協議,4代表IPX協議,10代表Ipv6協議等。
* 參數結構fw定義如下:
struct firewall_ops{
struct firewall_ops *next;
int (*fw_forward)(struct firewall_ops *this, int pf,
struct device *dev, void *phdr, void *arg, struct sk_buff **pskb);
int (*fw_input)(struct firewall_ops *this, int pf,
struct device *dev, void *phdr, void *arg, struct sk_buff **pskb);
int (*fw_output)(struct firewall_ops *this, int pf,
struct device *dev, void *phdr, void *arg, struct sk_buff **pskb);
int fw_pf;
int fw_priority;
};
結構中next的域將由內核來修改,使其指向下一個防火牆模塊。
fw_pf域為協議標志,含義同上。
fw_priority指定優先級,一般應大於0。
fw_input、fw_output、fw_forward是用戶編寫的防火牆函數模塊,在接收到網絡報和發送網絡報時內核將調用這些模塊,後面將詳細討論。
2、 unregister_firewall
unregister_firewall的原型說明與調用方法同register_firewall。
三、 防火牆函數模塊的設計
1、 防火牆函數模塊的返回值
返回值是至關重要的,內核將根據它來決定對網絡數據報采取的處理策略。主要返回值及意義如下:
0和1 通知內核忽略該網絡報。
-1 通知內核忽略該網絡報,並發送不可達到的網絡控制報(ICMP報文)。
2 通知內核認可該網絡報。
2、 各模塊函數的入口參數
* 參數this
指向register_firewall中的fw參數結構。
* 參數pf
含義同register_firewall中的pf參數。
* 參數dev
dev是指向數據結構device的指針。在Linux系統中,每一個網絡設備都是用device數據結構來描述的。在系統引導期間,網絡設備驅動程序向Linux登記設備信息,如設備名、設備的I/O基地址、設備中斷號、網卡的48位硬件地址等,device數據結構中包括這些設備信息以及設備服務函數的地址。關於device結構的詳細信息可參見netdevice.h頭文件。
* 參數phdr
該參數指向鏈路層數據報報頭首址。
* 參數arg
利用這個參數可以向內核傳遞信息,如重定向時的端口號。
* 參數pskb
此參數是指向sk_buff結構指針的指針。在Linux中,所有網絡數據的發送和接收都用sk_buff數據結構表示。在sk_buff數據結構中包含有對應設備結構的device地址、傳輸層、網絡層、鏈路層協議頭地址等。關於sk_buff的定義可參見skbuff.h頭文件。
3、防火牆程序示例
下面給出一個簡單防火牆程序。在這裡假設讀者對以太協議、IP協議、TCP協議等常用協議有一定的了解。用命令行"gcc -Wall -O2 -c MyFirewall.c"進行編譯,再用insmod命令加載程序後,系統將只響應外部網絡用TCP協議的80端口所進行的訪問。要讓系統恢復原有功能,則可用rmmod命令卸載該程序,源代碼見網站www.pccomputing.com.cn上的同名文章。
// MyFirewall.c 2000年3月7日編寫 #ifndef __KERNEL__ # define __KERNEL__ //按內核模塊編譯 #endif #ifndef MODULE # define MODULE //按設備驅動程序模塊編譯 #endif #include <linux/module.h> //最基本的內核模塊頭文件 #include <linux/sched.h> #include <linux/kernel.h> //最基本的內核模塊頭文件 #include <linux/netdevice.h> #include <linux/ip.h> #include <linux/tcp.h> #include <linux/skbuff.h> #include <linux/proc_fs.h> #include <linux/if.h> #include <linux/in.h> #include <linux/firewall.h> #define SOL_ICMP 1 #define PERMIT_PORT 80 //只允許訪問TCP的80端口 int zzl_input(struct firewall_ops *this,int pf,struct device *dev, void *phdr,void *arg,struct sk_buff **pskb) {//每當收到一個網絡報時,此函數將被內核調用 struct tcphdr *tcph; //TCP的頭指針 struct iphdr *iph; //IP頭指針 struct sk_buff *skb=*pskb; if (skb->protocol==htons(ETH_P_ARP)){ printk("\nPermit a ARP Packet"); return FW_ACCEPT;//允許地址解析協議報 } if(skb->protocol==htons(ETH_P_RARP)){ printk("\nPermit a RARP Packet"); return FW_ACCEPT;//允許反向地址解析協議報 } if(skb->protocol==htons(ETH_P_IP)) { iph=skb->nh.iph; if (iph->protocol==SOL_ICMP) { printk("\nPermit a ICMP Packet"); return FW_ACCEPT;//允許網絡控制報 } if(iph->protocol==SOL_TCP){ tcph=skb->h.th; if(tcph->dest==PERMIT_PORT){ printk("\nPermit a valid access"); return FW_ACCEPT;//允許對TCP端口80的訪問 } } } return FW_REJECT;//禁止對本計算機的所有其它訪問 } int zzl_output(struct firewall_ops *this,int pf,struct device *dev, void *phdr,void *arg,struct sk_buff **pskb) {//程序編寫方法同zzl_input函數模塊 printk("\nzzl_output is called "); return FW_SKIP; } int zzl_foreward(struct firewall_ops *this,int pf,struct device *dev, void *phdr,void *arg,struct sk_buff **pskb) {//程序編寫方法同zzl_input函數模塊 printk("\nzzl_foreward is called "); return FW_SKIP; } struct firewall_ops zzl_ops= { NULL, zzl_foreward, zzl_input, zzl_output, PF_INET, 01 }; int init_module(void) { if(register_firewall(PF_INET,&zzl_ops)!=0) { printk("\nunable register firewall"); return -1; } printk("\nzzl_ops=%p",&zzl_ops); return 0; } void cleanup_module(void) { printk("unload\n"); unregister_firewall(PF_INET,&zzl_ops); } (作者地址:北京市北三環中路四號750房 100720 收稿日期:2000.3.13)
——摘自:游俠部落