簡介
ipfw是FreeBSD內建的防火牆指令,我們可以用它來管理進出的網路交通。如果防火牆伺服器是扮演著路由器(gateway例如上一篇中的NAT伺服器)的角色,則進出的封包會被ipfw處理二次,而如果防火牆扮演的是橋接器(bridge)的角色,則封包只會被處理一次。這個觀念關係著我們以下所要介紹的語法,有的語法並不適用於橋接器。
另外,我們在設定防火牆時有二種模式,一種模式是預設拒絕所有在線上,再一條一條加入允許的在線上;另一種是預設接受所有在線上,加入幾條拒絕的規則。如果是非常強調安全性,應該是使用預設拒絕所有在線上,再一條一條加入我們允許的規則。
我們會將firewall的設定寫在/etc/rc.firewall中,每一條設定都是以先入為主(firstmatchwins)的方式來呈現,也就是先符合的規則(rules)為優先。所有進出的封包都會被這些規則過濾,因此我們會儘量減少規則的數量,以加速處理的速度。
詳細信息
在kernel中,關於防火牆的設定有下列幾條:
防火牆
optionsIPFIREWALL
支援NAT
optionsIPdivert
下面這一行是預設允許所有封包通過,如果沒有這一行,
就必須在/etc/rc.firewall中設定封包的規則。
這條規則內定編號是65535,也就是所有規則的最後一條
如果沒有加這一條規則,內定就是拒絕所有封包,
只允許規則中允許的封包通過。
optionsIPFIREWALL_DEFAULT_TO_ACCEPT
這一行是讓你可以在ipfw中設定要記錄哪些封包,
如果沒有這一行,就算設定了要留下記錄也不會有作用。
optionsIPFIREWALL_VERBOSE
這一行是限制每一條規則所要記錄的封包數量,
因為同樣的規則可能有許多記錄,加上這一條可以使
同樣的記錄重複數減少,以避免記錄檔案爆增。
optionsIPFIREWALL_VERBOSE_LIMIT=10
下面這一行是用來支援封包轉向,
當你要使用fwd動作時必須要有這一項設定。
optionsIPFIREWALL_FORWARD
如果要使用pipe來限制頻寬,必須加入下列選項以支持dummynet。
optionsDUMMYNET
ipfw也支持狀態維持(keep-state)的功能,就是可以讓符合設定的規則以動態的方式來分配增加規則(地址或連線連線埠)來讓封包通過。也就是說防火牆可以記住一個外流的封包所使用的地址及連線連線埠,並在接下來的幾分鐘內允許外界回響。這種動態分配的規則有時間的限制,一段時間內會檢查在線上狀態,並清除記錄。
所有的規則都有計數器記錄封包的數量、位數、記錄的數量及時間等。而這些記錄可以用ipfw指令來顯示或清除。
在說明ipfw規則的語法之前,我們先來看這個指令的用法。ipfw可以使用參數:
指令說明
ipfwaddrule新增一條規則。規則(rule)的語法請參考下一節的說明。
ipfwdeletenumber刪除一條編號為number的規則。
ipfw-fflush清除所有的規則。
ipfwzero將計數統計歸零。
ipfwlist列出現在所有規則,可以配合下列參數使用。
-a使用list時,可以列出封包統計的數目。
-f不要提出確認的詢問。
-q當新增(add)、歸零(ZERO)、或清除(flush)時,不要列出任何回應。當使用遠程登入,以script(如sh/etc/rc.firewall)來修改防火牆規則時,內定會列出你修改的規則。但是當下了flush之後,會立即關掉所有在線上,這時候回響的訊息無法傳達終端機,而規則也將不被繼續執行。此時唯一的方法就是回到該計算機前重新執行了。在修改防火牆規則時,最好在計算機前修改,以免因為一個小錯誤而使網路在線上中斷。
-t當使用list時,列出最後一個符合的時間。
-N在輸出時嘗試解析IP位址及服務的名稱。
-sfield當列出規則時,依哪一個計數器(封包的數量、位數、記錄的數量及時間)來排序。
12.3.1ipfw規則
我們在過濾封包時,可以依據下列的幾個封包所包含的信息來處理該封包:
接收或傳送的接口,可以使用接口名稱或地址。
方向,流入或流出。
來源或目的地的IP位址,也可以加上子網掩碼。
通訊協定,TCP,UDP,ICMP等。
TCPflags。
IPfragmentflag。
IPoptions。
ICMP的類型。
和封包相關的socketUser/groupID。
使用IP位址或TCP/UDP的連線埠號來做為規則可能蠻危險的,因為這二種都有可能被以假的信息所矇騙(spoof)。但是這二種卻也是最常被使用的方法。
下列為ipfwrules的語法:
numberactionlogprotofromsrctodistinterface_specoption
使用包起來的表示可有可無,我們一一為大家說明它們的意義:
number:
number是一個數字,用來定義規則的順序,因為規則是以先入為主的方式處理,如果你將規則設定放在一個檔案中(如/etc/rc.firewall),規則會依每一行排列的順序自動分配編號。你也可以在規則中加上編號,這樣就不需要按順序排列了。如果是在命令列中下ipfw指令來新增規則的話,也要指定編號,這樣才能讓規則依我們的喜好排列,否則就會以指令的先後順序來排。這個編號不要重複,否則結果可能不是你想要的樣子。
action:
action表示我們這條規則所要做的事,可以用的action有下列幾個:
命令意義
allow允許的規則,符合則通過。也可以使用pass,permit,accept等別名。
deny拒絕通過的規則。
reject拒絕通過的規則,符合規則的封包將被丟棄並傳回一個hostunreachable的ICMP。
count更新所有符合規則的計數器。
check-state檢查封包是否符合動態規則,如果符合則停止比對。若沒有check-state這條規則,動態規則將被第一個keep-state的規則所檢查。
divertport將符合divertsock的封包轉向到指定的port。
fwdipaddr,port將符合規則封包的去向轉向到ipaddr,ipaddr可以是IP位址或是hostname。如果設定的ipaddr不是直接可以到達的地址,則會依本機即有的routingtable來將封包送出。如果該地址是本地地址(localaddress),則保留本地地址並將封包送原本指定的IP位址。這項設定通常用來和transparentproxy搭配使用。例如:
ipfwadd50000fwd127.0.0.1,3128tcpfrom\
192.168.1.0/24toany80
如果沒有設定port,則會依來源封包的port將封包送到指定的IP。使用這項規則時,必須在kernel中設定選項IPFIREWALL_FORWARD。
pipepipe_nr傳遞封包給dummynet(4)"pipe",用以限制頻寬。使用本語法必須先在核心中加入optionDUMMYNET。請manipfw及mandummynet。
基本語法是先將要設定頻寬的規則加入:
ipfwaddpipepipe_nr....
再設定該規則的頻寬:
ipfwpipepipe_nrconfigbwBdelayDqueueQPLRP
這裡的pipe_nr指的是pipe規則編號,從1~65535;B是指頻寬,可以表示為bit/s、Kbit/s、Mbit/s、Bytes/s、KBytes/s、或MBytes/s。D是延遲多少milliseconds(1/1000)。Q是queuesize的大小(單位為packages或Bytes)。P是要隨機丟棄的封包數量。
例如我們要限制內部網域的計算機對外上傳的最大頻寬是20KBytes:
ipfwaddpipe1ipfrom192.168.0.1/24toanyin
ipfwpipe1configbw20KBytes/s
log:
如果該規則有加上log這個關鍵字,則會將符合規則的封包記錄在/var/log/security中。前提是在核心中有設定IPFIREWALL_VERBOSE的選項。有時因為同樣的封包太多,會使記錄檔案保有大量相同的記錄,因此我們會在核心中再設定IPFIREWALL_VERBOSE_LIMIT這個選項,來限制要記錄多少相同的封包。
proto:
proto表示protocol,即網路協定的名稱,如果使用ip或all表示所有協定。可以使用的選項有ip,all,tcp,udp,icmp等。
src及dist:
src是封包來源;dist是封包目的地。在這二個項目可以用的關鍵字有any,me,或是以
ports的方式明確指定地址及連線埠號。
若使用關鍵字any表示使這條規則符合所有ip地址。若使用關鍵字me則代表所有在本系統接口的IP位址。而使用明確指定地址的方式有下列三種:
IP位址,指定一個IP,如168.20.33.45。
IP/bits,如1.2.3.4/24,表示所有從1.2.3.0到1.2.3.255的IP都符合規則。
IP:mask,由IP加上子網掩碼,如1.2.3.4:255.255.240.0表示從1.2.0.0到1.2.15.255都符合。
而在me,any及指定的ip之後還可以再加上連線埠編號(ports),指定port的方法可以是直接寫出port,如23;或給定一個範圍,如23-80;或是指定數個ports,如23,21,80以逗點隔開。或者是寫出在/etc/services中所定義的名稱,如ftp,在services中定義是21,因此寫ftp則代表port21。
interface-spec:
interface-spec表示我們所要指定的網路接口及流入或流出的網路封包。我們可以使用下列幾個關鍵字的結合:
關鍵字
in只符合流入的封包。
out只符合流出的封包。
viaifX封包一定要經過接口ifX,if為接口的代號,X為編號,如vr0。
viaif*表示封包一定要經過接口ifX,if為接口的代號,而*則是任何編號,如vr*代表vr0,vr1,...。
viaany表示經過任何界面的封包。
viaipno表示經過IP為ipno界面的封包。
via會使接口永遠都會被檢查,如果用另一個關鍵字recv,則表示只檢查接收的封包,而xmit則是送出的封包。這二個選項有時也很有用,例如要限制進出的接口不同時:
ipfwadd100denyipfromanytoanyoutrecvvr0xmited1
recv接口可以檢查流入或流出的封包,而xmit接口只能檢查流出的封包。所以在上面這裡一定要用out而不能用in,只要有使用xmit就一定要使用out。另外,如果via和recv或xmit一起使用是沒有效的。
有的封包可能沒有接收或傳送的接口:例如原本就由本機所送出的封包沒有接收接口,而目的是本機的封包也沒有傳送界面。
options:
我們再列出一些常用的option選項,更多選項請manipfw:
選項名稱
keep-state當符合規則時,ipfw會建立一個動態規則,內定是讓符合規則的來源及目的地使用相同的協定時就讓封包通過。這個規則有一定的生存期限(lifttime,由sysctl中的變數所控制),每當有新的封包符合規則時,便用重設生存期限。
bridged只符合bridged的封包。
established只適用於TCP封包,當封包中有RST或ACKbits時就符合。
uidxxx當使用者uid為xxx則符合該規則。例如,我們如果要限制AnonymousFTP的下載速度最大為64KB/s,則可以使用:
ipfwpipe1configbw512Kbit/s
ipfwaddpipe1tcpfrommetoanyuid21
上列規則第一行是先建一個編號為1的pipe,限制頻寬為512Kbit/s(也就是64KByte/s),接著第二條是當使用者uid為21時,從本機(me)下載的tcp封包都使用編號1的pipe。因為AnonymousFTP的使用者是ftp,它的預設uid為21,所以這條規則會被套用在AnonymousFTPuser上。
setup只適用於TCP封包,當封包中有SYNbits時就符合。
以上的說明只是manipfw中的一小部份。如果你想要對ipfw更了解,例如如何使用ipfw來限制頻寬等,建議你manipfw。
不知道您看了這么多的規則是否覺得眼花撩亂,如果不了解TCP/IP的原理,徹底了解ipfw的設定還真不容易。沒關係,我們下面將舉幾個簡單、常用的設定,這些範例應該夠平常使用了。
12.3.2範例
我將原本的/etc/rc.firewall備份成rc.firewal.old,並將它改成下列內容,請注意,這裡只是範例,只供參考:
設定我的IP
myip="1.2.3.4"
設定對外的網路卡代號
outif="vr0"
設定對內的網路上代號
inif="vr1"
清除所有的規則
/sbin/ipfw-fflush
ThrowawayRFC1918networks
adddenyipfrom10.0.0.0/8toanyinvia
adddenyipfrom172.16.0.0/12toanyinvia
adddenyipfrom192.168.0.0/16toanyinvia
只允許內部網路對192.168.0.1使用telnet服務
/sbin/ipfwadd200allowtcpfrom192.168.0.1/24to192.168.0.1telnet
拒絕其它人連到port23,並記錄嘗試在線上的機器
/sbin/ipfwadd300denylogtcpfromanytome23
拒絕任何ICMP封包
/sbin/ipfwadd400denyicmpfromanytoany
下面這台機器是壞人,不讓它進來,並記錄下來
/sbin/ipfwadd1100denylogallfrom211.21.104.102toany
NAT的設定
/sbin/ipfwadddivertnatdallfromanytoanyviavr0
限制內部網域對外下載最大頻寬為20KBytes/s,上傳最大頻寬為5KBytes/s
ipfwpipe20configbw20KBytes/s
ipfwaddpipe20ipfromanyto192.168.0.1/24out
ipfwpipe30configbw5KBytes/s
ipfwaddpipe30ipfrom192.168.0.1/24toanyin
允許本機對任何地方在線上
/sbin/ipfwaddcheck-state
/sbin/ipfwadd2000allowudpfromtoanykeep-state
/sbin/ipfwadd2100passipfromtoany
允許外界使用郵件服務
/sbin/ipfwadd3000passtcpfromanyto25invia
不允許內部的IP從外部連進來
/sbin/ipfwadd1200adddenyipfrom/24toanyinvia
其它都拒絕,如果沒有在kernel中設定
IPFIREWALL_DEFAULT_TO_ACCEPT則內定就有下列這一條
/sbin/ipfw65535adddenyallfromanytoany
存檔後就可以使用shrc.firewall來執行新的規則了。如果您將規則放在/etc/rc.firewall中,則開機時會自動執行。
小建議
在建立一個封包過濾的防火牆時,應該儘可能阻擋一些不必要的服務。避免開放port1024以下的TCP服務,例如只通過SMTP封包(port25)給郵件伺服器;拒絕所有UDP在線上(只有少部份服務如NFS會用到);一些只有內部才會使用的服務,如資料庫等也不必對外開放。
另外,同樣的防火牆限制可以使用不同的語法來展現,應該要試著讓規則數量越少越好,以加快處理速度。
在更新firewall規則時,如果規則沒有寫好,而你又是以遠程登入的方式修改規則,很可能會因此無法繼續登入。因此建議更新規則時最好在console前執行,若迫不得已一定要使用遠程登入,建議您執行/usr/share/examples/ipfw/change_rules.sh這支程式來編輯規則:
cd/usr/share/examples/ipfw
shchange_rules.sh
接著會出現文書編輯軟體並最動載入/etc/rc.firewall讓你編輯,結束離開後,會詢問是否要執行更新。如果執行新的規則後造成斷線,它會自動載入舊的規則,讓我們可以再次在線上。
封包過濾橋接器
如果您有三台機器全部都有publicIP,而您想使用其中一台做為防火牆,在不改變另外二台機器的設定下,我們可以使具封包過濾的橋接器來架設防火牆。只要將這台橋接器放在另外二台和對外網路之間即可。
另外,當我們的內部網路有不同class的主機時,例如內部有140.115.2.3及140.115.5.6這二台計算機時,就無法使用傳統的防火牆。如果要在這二台機器連到網際網路中途中使用防火牆,我們必須使用新的方式,也可以使用這裡介紹的橋接器。
我們可以使用FreeBSD為橋接器,利用它來做封包過濾的動作,而絲毫不影響內部的主機原本的設定。為了達到這個功能,我們必需要有二張支持promiscuousmode的網路卡,現在的網路卡大部份都有支持。二張網路卡當中,一張需要設定IP,另一張不需要。至於您要將IP設定在哪一張卡都可以,建議是設在對外的網路卡上。
首先,我們必須在核心中加入關於橋接器的設定:
支援橋接器
optionsBRIDGE
防火牆設定
optionsIPFIREWALL
optionsIPFIREWALL_VERBOSE
我們這裡不將防火牆預設為接收所有封包
optionsIPFIREWALL_DEFAULT_TO_ACCEPT
如果您要讓橋接器具有流量控制的功能,則可以加上之前提到的選項「optionsDUMMYNET」。重新編譯核心後,在重開機前,我們先設定一下/etc/rc.conf:
firewall_enable="YES"
firewall_type="open"
還有一件事要做,當在乙太網絡上跑IP協定時,事實上使用二種乙太網絡協定,一個是IP,另一個是ARP。ARP協定是當機器要找出給定IP位址所對應的乙太網絡地址時使用的。ARP並不是IP層的一部份,只是給IP套用在乙太網絡上運作。標準的防火牆規則中並未加入對於ARP的支持,幸運的是,高手們的在ipfirewall程式代碼中加入了對封包過濾橋接器的支持。如果我們在IP位址0.0.0.0上建立一個特別的UDP規則,UDP連線埠的號碼將被使用來搭配被橋接封包的乙太網絡協定號碼,如此一來,我們的橋接器就可以被設定成傳遞或拒絕非IP的協定。請在/etc/rc.firewall中接近檔案頂端處理lo0的那三行之下(就是有寫Onlyinrarecasesdoyouwanttochangetheserules的地方)加入下面一行:
addallowudpfrom0.0.0.02054to0.0.0.0
現在我們就可以重新開機了。重開機之後,先執行下列指令來啟動橋接器:
如果您使用的是FreeBSD4.x:
sysctl-wnet.link.ether.bridge_ipfw=1
sysctl-wnet.link.ether.bridge=1
如果您使用的是FreeBSD5.x:
sysctl-wnet.link.ether.bridge.ipfw=1
sysctl-wnet.link.ether.bridge.enable=1
現在我們可以將機器放在內外二個網域之間了。因為我們之前在/etc/rc.conf中,設定防火牆完全打開,不阻擋任何封包,所以放在二個網域之間時,運作應該沒有問題。我們之前只設了一張網路上的IP,而在執行了上述的指令之後,第二張網路卡便開始運作。
下一步就是將我們啟動橋接器的指令放在/etc/rc.local中,讓系統在開機時自動執行。或者,我們可以在/etc/sysctl.conf中加入下面二行:
如果您使用的是FreeBSD4.x
net.link.ether.bridge_ipfw=1
net.link.ether.bridge=1
如果您使用的是FreeBSD5.2以後的版本
net.link.ether.bridge.enable=1
net.link.ether.bridge.ipfw=1
接下來我們就可以依自己的需求在/etc/rc.firewall檔案的最後面加上我們自己想要的防火牆規則了。以下是一個簡單的設定規則,假設橋接器的IP是140.115.75.137,內部有二台主機,一台提供網頁服務,一台是BBS:
us_ip=140.115.75.137
basrv_ip=140.115.3.4
bbs_ip=140.115.5.6
OIF=fxp0
iif=fxp1
ipfw="/sbin/ipfw"
Thingsthatwe'vekeptstateonbeforegettogothroughinahurry.
1000addcheck-state
ThrowawayRFC1918networks
1100adddenyipfrom10.0.0.0/8toanyinvia
1200adddenylogipfrom172.16.0.0/12toanyinvia
1300adddenylogipfrom192.68.0.0/16toanyinvia
允許橋接器本身所有想做的在線上(keepstateifUDP)
1400addpassudpfromtoanykeep-state
1500addpassipfromtoany
允許內部網路任何想做的在線上(keepstateifUDP)
1600addpassudpfromanytoanyinviakeep-state
1700addpassipfromanytoanyinvia
允許任何的ICMP在線上
1800addpassicmpfromanytoany
不允許使用port888在線上
2000adddenylogtcpfromanyto888
TCPsection
任何地方都可以建立TCP在線上
3000addpasstcpfromanytoanyvia
Passthe"quarantine"range.
3100addpasstcpfromanytoany49152-65535invia
Passidentprobes.It'sbetterthanwaitingforthemtotimeout
3200addpasstcpfromanytoany113invia
PassSSH.
3300addpasstcpfromanytoany22invia
PassDNS.當內部網路有名稱伺服器時才需要
addpasstcpfromanytoany53invia
只傳遞SMTP給郵件伺服器
3400addpasstcpfromanyto25invia
3500addpasstcpfromanyto25invia
UDPsection
Passthe"quarantine"range.
4000addpassudpfromanytoany49152-65535invia
PassDNS.當內部網路有名稱伺服器時才需要
4100addpassudpfromanytoany53invia
其它的都拒絕
60000adddenyipfromanytoany