1、前言
NAT 伺服器主要是用來簡化及保有 IP 位址,它可讓原本無法上網且使用內部IP位址的主機可以成功的連接 Internet。如此將大大減少 IP 位址的需求,因為基本上整個內部網路都可藉 NAT 上的一個外部 IP 來連至 Internet。IPFilter 因內建 NAT 功能,所以在 NAT(ipnat) 及防火牆 (ipf) 搭配上更有效率,且判斷封包的處理則使用快取與雜湊技術來達成,因此更能有效提升處理效率。本次實作以 IPFilter 實作 Protocol Filter 及 NAT 功能 (IPFilter 預設是 pass all),關於 NAT 可參考站內文章 NAT 伺服器的原理與運作流程。IPFilter 相關技術名詞:
- ipfilter: 主要作用為管制進出主機 Protocol (tcp,upd,icmp...)
- ipnat: 主要作用就是 NAT (Network Address Translator) 及 RDR (Redirect)
- ipfstat: 觀看所有封包狀態並顯示統計數值
- ipmon: 統計過濾 Log
文章目錄
1、前言2、實作環境
3、安裝及設定
步驟1.修改核心 or 啟動模組
步驟2.修改 /etc/rc.conf
步驟3.建立防火牆規則 (ipf.rules)
步驟4.建立 NAT / RDR 規則 (ipnat.rules)
步驟5.使用 ipfstat 查看過濾訊息
步驟6.建立 Log
4、參考
5、Me FAQ
Q1.無法使用載入核心模組 (load kernel module) 來運作IPFilter?
2、實作環境
- FreeBSD 5.x-RELEASE
- IP Filter: v3.4.35
3、安裝及設定
步驟 1. 修改核心 or 啟動模組
要使系統能支援 IPFilter 功能方法有二種,一是修改核心 (Static Build Kernel) 另一方式為直接載入核心模組 (Load Kernel Module)。修改核心 (Static Build Kernel): 為在核心加入下列下三行,並重新編釋核心即可 (cd /usr/src ; make kernel)。
options IPFILTER
options IPFILTER_LOG
options PFIL_HOOKS
options IPFILTER_DEFAULT_BLOCK //將由 pass all 變成 block all
編釋完成後,重新開機完成後至 /dev 下檢查是否出現下列檔案,出現下列檔案即可成功。
# ls -l ip*
crw------- 1 root wheel 79, 3 Feb 3 13:37 ipauth
crw------- 1 root wheel 79, 0 Feb 3 13:37 ipl
crw------- 1 root wheel 79, 1 Feb 3 13:37 ipnat
crw------- 1 root wheel 79, 2 Feb 3 13:37 ipstate
載入核心模組(load kernel module): 為載入模組 *.ko 方式 (不用修改及重新編譯 Kernel)。
# vi /boot/loader.conf //加入如下一行
ipl_load="YES"
重新開機完成後可查看 ipl.ko 模組是否載入成功。
# kldstat
Id Refs Address Size Name
1 4 0xc0400000 5cdad0 kernel
2 1 0xc09ce000 1a378 ipl.ko //出現此行則表示成功載入 ipl.ko 模組
3 14 0xc09e9000 537f0 acpi.ko
步驟 2. 修改 /etc/rc.conf
修改 /etc/rc.conf 以便系統重新開機時能自動啟動 IPFilter 及相關服務。 gateway_enable="YES" //開啟 NAT 功能
ipfilter_enable="YES" //開啟 ipf firewall 功能
ipnat_enable="YES" //開啟 nat/rdr rules 功能
ipmon_enable="YES" //開啟紀錄 Log 功能
ipmon_flags="Ds"
步驟 3. 建立防火牆規則 (ipf.rules)
在開始設定防火牆規則 (ipf.rules) 以前先了解一下 IPFilter 的規則由哪些部份組成 (也可參考 /usr/src/contrib/ipfilter/BNF 及 rules)。ACTION: 指定 IPFilter 動作 (放行或阻擋)
- pass: 放行
- block: 阻擋
DIRECTION: 指定過濾封包的方向
- in: Internet >> 本機
- out: 本機 >> internet
OPTIONS: 指定 IPFilter 作用於哪片網卡上
- log: 把封包傳至記錄檔
- quick: 套用這條規則
- on interface-name: 適用於哪片網卡
PROTOCOL: 指定要過濾的協定
- proto (protocol): 一般為 TCP、UDP、ICMP (其它可參考 /etc/protocols)
SOURCE: 指定封包來源及連接埠
- from source port: 來源為哪裡
DESTINATION: 指定封包目的地及連接埠
- to destination port: 目的地為哪裡
PACKET-OPTIONS: 指定處理封包時其它選項
- flags S/SA keep state: 讓 IPFilter 僅比對剛開始建立連線的封包,只要判斷為 Pass 便會記憶封包能被允許穿越 IPFilter 防火牆,並開放同一個連接埠組讓回傳的封包能快速通過不需在比對
因為 IPFilter 讀取 Rule 是以 Last Match 的方式 (即會去執行最後一條 Match 到的 Rule 所定義的事)。但可使用 Quick 關鍵字來跳離 Rule (簡單說就是讀到 Quick 這行Rule 就優先執行而不會再往下 Check Match Rule),了解後就可以開始設定 IPFilter 規則了,以下設定範例為此次實作環境 (實際狀況請依個人網路環境自行調整)。
# vi /etc/ipf.rules
#########################################
# Interface Information
# vr0:Public 61.60.59.58
# vr1:DMZ 192.168.78.1
# rl0:LAN 192.168.88.1,192.168.88.10
#########################################
### Deny LAN to Internet ICMP
block in quick on rl0 proto icmp from 192.168.88.0/24 to any icmp-type echo
### Deny Internet to Localhost ICMP
block in quick on vr0 proto icmp from any to 61.60.59.58 icmp-type echo
### Deny Internet to Localhost SSH
block in quick on vr0 proto tcp from any to 61.60.59.58 port = 22
由上述舉例可知道想禁止什麼樣的協定,要擋在那個網卡相信都了解了,但 IPFilter 預設是 pass all 的所以一個一個禁止協定個人覺得似乎太累了,不如反過來思考我先 pass 我要的之後全部 block 掉 (當然你可在編 kernel 時把 options IPFILTER_DEFAULT_BLOCK 編進去讓預設的 pass all 變 block all,但若您在遠端做這件事的話編譯好核心重開機時您就會斷線了!!),一個 Web Server 的基本 IPF 設定內容如下:
# vi /etc/ipf.rules
#########################################
# Interface Information
# em0:61.60.59.58
#########################################
### Pass Loopback
pass in quick on lo0 all
pass out quick on lo0 all
### Pass Ping
pass in quick on em0 proto icmp all
### Pass IP Range
pass in quick on em0 from 71.70.69.68/32 to 61.60.59.58/32 keep state
pass out quick on em0 from 61.60.59.58/32 to 71.70.69.68/32 keep state
### Pass HTTP(s) Service
pass in quick on em0 proto tcp from any to 61.60.59.58/32 port = 80 flags S keep state
pass out quick on em0 proto tcp from 61.60.59.58/32 port = 80 to any flags S keep state
pass in quick on em0 proto tcp from any to 61.60.59.58/32 port = 443 flags S keep state
pass out quick on em0 proto tcp from 61.60.59.58/32 port = 443 to any flags S keep state
### Block Internet Private Address to Me
block in quick on em0 from 192.168.0.0/16 to any
block in quick on em0 from 172.16.0.0/12 to any
block in quick on em0 from 10.0.0.0/8 to any
block in quick on em0 from 127.0.0.0/8 to any
block in quick on em0 from 0.0.0.0/8 to any
block in quick on em0 from 169.254.0.0/16 to any
block in quick on em0 from 192.0.2.0/24 to any
block in quick on em0 from 204.152.64.0/23 to any
block in quick on em0 from 224.0.0.0/3 to any
### Block frags
block in quick on em0 all with frags
### Block short tcp packets
block in quick on em0 proto tcp all with short
### Blcok source routed packets
block in quick on em0 all with opt lsrr
block in quick on em0 all with opt ssrr
### Block nmap OS fingerprint attempts
block in quick on em0 proto tcp all flags FUP
### Block anything with special options
block in quick on em0 all with ipopts
### Deny Another
block return-rst in quick on em0 proto tcp all
block return-icmp-as-dest(port-unr) in on em0 proto udp all
block in quick on em0 all
設定完後套用新規則執行指令 FreeBSD Man Pages - ipf。
# ipf -Fa -f /etc/ipf.rules //-Fa 清除所有過濾規則、-f 載入過濾規則
步驟 4. 建立 NAT / RDR 規則 (ipnat.rules)
建立 NAT 規則以下設定範例為此次實作環境 (實際狀況請依個人網路環境自行調整),為何 DMZ 跟 LAN 要在不同網段? 主要考量就是把 DMZ 跟 LAN 切開,而 LAN 跟 DMZ 溝通則由 Gateway bind 一個 LAN 網段的 IP 在利用 RDR 對應到 DMZ 實體 IP 且只開必要的 Port 通行即可,如此安全性將更加提升。當然你可以把 DMZ 跟 LAN 的 IP 在同一網段這樣你的 ipnat rules 將會更簡短。FreeBSD Man Pages - ipnat# vi /etc/ipnat.rules
#########################################
#Interface Information
#xl0:Public 61.60.59.58
#xl1:DMZ 192.168.78.1
#xl2:LAN 192.168.88.1,192.168.88.10
#########################################
### NAT
map xl0 192.168.0.0/16 -> 61.60.59.58/32 //讓 LAN 及 DMZ 出 Internet
### Public to DMZ (Port Redirector)
map xl0 192.168.78.10/32 -> 61.60.59.58/32
rdr xl0 61.60.59.58/32 port 25 -> 192.168.78.10 port 25 tcp
rdr xl0 61.60.59.58/32 port 110 -> 192.168.78.10 port 110 tcp
rdr xl0 61.60.59.58/32 port 80 -> 192.168.78.10 port 80 tcp
### LAN to DMZ (Port Redirector)
map xl0 192.168.78.10/32 -> 192.168.88.10/32 //讓 LAN 能跟 DMZ 溝通
rdr xl0 192.168.88.10/32 port 20 -> 192.168.78.10/32 port 20 tcp/udp
rdr xl0 192.168.88.10/32 port 21 -> 192.168.78.10/32 port 21 tcp/udp
rdr xl0 192.168.88.10/32 port 22 -> 192.168.78.10/32 port 22 tcp
rdr xl0 192.168.88.10/32 port 25 -> 192.168.78.10/32 port 25 tcp
rdr xl0 192.168.88.10/32 port 53 -> 192.168.78.10/32 port 53 tcp/udp
rdr xl0 192.168.88.10/32 port 80 -> 192.168.78.10/32 port 80 tcp
rdr xl0 192.168.88.10/32 port 110 -> 192.168.78.10/32 port 110 tcp
設定完後套用新規則執行指令 (更詳細參數可參考 man ipnat) 而有關 Port Number 及 tcp/udp 可參考 /etc/services。
# ipnat -CF //清除現有的轉換規則
# ipnat -f /etc/ipnat.rules //讀取轉換規則
# ipnat -l //查看當前設置生效的規則
# ipnat -s //查看進出狀態
步驟 5. 使用 ipfstat 查看過濾訊息
利用 ipfstat 指令來查看 IPFilter 過濾訊息,順便了解目前套用規則的過濾數以作為 Loading 的依據。FreeBSD Man Pages - ipfstat。# ipfstat -in //查看 IPF Rules Input 部份
# ipfstat -on //查看 IPF Rules Output 部份
# ipfstat -ih //查看 Input Rules 過濾數
# ipfstat -oh //查看 Output Rules 過濾數
步驟 6. 建立 Log
若前面修改 /etc/rc.conf 時有加入 ipmon 選項希望紀錄 IPFilter 的過濾訊息則必需有些相關設定如下。FreeBSD Man Pages - ipmon。# touch /var/log/ipfilter.log //建立 Log 檔
# vi /etc/syslog.conf //修改 syslog 設定
local0.* /var/log/ipfilter.log
# /etc/rc.d/syslogd restart //重新啟動 syslogd 服務
當然建立完成後別忘了去修改 /etc/newsyslog.conf 不然 ipfilter.log 將日漸肥大最後 /var 空間當然就是爆啦。
4、參考
- IPFilter 的官方網站
- IP Filter FAQ
- FreeBSD Handbook 24.5 The IPFILTER (IPF) Firewall
- FreeBSD使用大全-設置和使用ipfilter
5、Me FAQ
Q1. 無法使用載入核心模組 (load kernel module) 來運作IPFilter?
Error Message:發生開機訊息 (dmesg) 有下列錯誤,並且當您想手動載入模組 (kldload ipl.ko) 時也出現錯誤訊息 ?
KLD file ipl.ko - could not finalize loading
link_elf: symbol in6_cksum undefined
Ans:
基本上會出現這樣的錯誤是因為編 kernel 時有把 INET6 選項註解後,就會出現如上所述錯誤訊息。詳請參考 FreeBSD-Bug Could not load ipl.ko when no INET6 in the kernel、FreeBSD-Question ipfilter loading on 5.3。
解決方法有三種: (請依個人喜好擇一即可)
- 編 kernel 方式載入IPFILTER。
- 重新修改 kernel 時 INET6 選項不要註解。
- 修改 /etc/make.conf 加入如下行(這樣才會避免不打開 IPv6 支援)。
NOINET6=yes //5.x
NO_INET6=yes //6.x