当您好不容易把 Linux 机器连上 Internet 之後,别以为就只有它一台机器能上网哦。下面教您的法子是如何用一台 Linux 机器让本地网路中所有的机器都能同时上网!
设定 Proxy
我们在“网路基础”中的“防火墙”那边已经知道,能做到以上要求的方法很多,之一是使用 proxy 。至於它的好处,前文已经说了许多,这里就不再重复了。假如您只想让其他机器到 Internet 上流览一下网页或透过 HTTP 下载档案,无疑 proxy 是个不错的主意,而且要设定这样一个 proxy 也是非常容易的,您只需确定 squid 套件有装好、并且能在开机的时候启动(执行 ntsysv 来选择)就行了。
然後,您要做的只是修改 /etc/squid/squid.conf 这个档案。虽然这个档案看上去很长,但是,如果仅是用来做 HTTP proxy 的话,您要修改的句子不超过两句:
# 在第 1163 行下面增加一行:
acl siyongc src 192.168.100.0/255.255.255.0 # 请修改为您自己的实际设定
# 然後在第 1197 行下面再增加一行:
http_access allow siyongc # 请修改为上一行定义的 acl 名称
然後重新跑 squid 就好了:
service squid restart
接下来,您将其他机器的流览器上之 proxy 功能打开,并将伺服器 IP 指向 squid 主机,同时将 port 设定为 3128 就可以了。如果您用 IE 的话,按‘工具’--> ‘Internet 选项’--> ‘连线’ --> ‘区域网路设定’ --> ‘使用 Proxy 伺服器’ --> 然後填好‘网址’与‘连接埠’:
这里只是最单纯的设定而已,事实上, squid 的设定非常多样和复杂,如果您有兴趣的话,可以参考‘ 优客笔记簿 ’中的文件。
正如您刚才发现的:在 Linux 上面设定一个简单的 proxy 看来是很容易的事情,但毕竟使用 proxy 是非常有限制的,例如,您要玩 ICQ、要使用 telnet、要上传 FTP 、要使用外面的 POP 信箱、要阅览新闻组或 BBS、等等,恐怕 proxy 就未必能全部应付得来了。不过,假如您使用 IP Masquerading ( IP 伪装 --- NAT 的一种)技术又如何呢?那情形几乎是:您在那台用来上网的 Linux 机器上所使用的 internet 功能,在其他机器也一样拥有!
前提准备
要设定 NAT ( Network Address Translation ) 就牵涉到防火墙装置了。老实说,要设定一个称职的防火墙并不是一件容易的事情,它要求您有非常丰富的 TCP/IP 基础、和严密的逻辑头脑、还得加上无以复加的细密测试。但是,我在这里教您的,并不是要真正的设定一个火墙,只是想让其他机器上网罢了。如果您真想设定防火墙,您可以看看 Firewall、IP Masquerade 和 IPchains 这几篇 HOWTO 文件,或许才会有些概念(但未必就设定得来)。而且,在阅读的时候,要注意文章引用的版本是否和您目前机器使用的相同,因为不同的版本所使用的工具和命令格式都相差很远,但基本的原理是不变的。
首先,您要知道,在 Linux 上面的防火墙程式都与核心版本非常密切:
如果您是用 2.0.x 的核心,那您只能使用 ipfwadm 程式;
如果是 2.2.x 的核心,则使用 ipchains 程式;
如果是 2.4.x 的核心,则使用 iptables 程式( ipchains 仍可以透过 modules 方式载入使用)。
因为 RedHat 7.1 预设是使用 2.4.2 核心,所以我们这里打算以 iptables 来做范例。如果您的核心参考 system 系列之‘ 编译核心 ’文章编的,那就已经具备防火墙功能了。如果您目前的版本是使用 2.2.x 核心的话,请参考 RH6.2 版本的 旧文章 ,这里将不再以 ipchains 来作说明了。
Tips:如果您对 ipchains 比较熟识、而决定继续在 2.4.x 核心上面使用 ipchains 的话,那也可以执行 modprobe ipchains 将 ipchains 的模组载入(但无论如何,ipchains 与 iptables 模组不能同时载入)。不过,2.2.x 核心原来提供的一些模组(如 ip_masq_*.o )就没办法使用了,这点请您留意。
另外,您还可以执行 ntsysv 将 ipchains 选择起来,并将 ipchains 的设定写进 /etc/sysconfig/ipchains 档案中(可执行 setup 选择 ‘Firewall Configuration’来设定这个档),那麽在开机的时候就会将 ipchains 跑起来了。
不过,我们这里只打算使用 iptables ,所以,以上方法仅供已有 ipchains 经验的朋友们参考,请勿和下面的说明搞混了。
好了,下面我跟大家说说如何在 RedHat7.1 上面用 iptables 来做一些防火墙和 NAT 的功能。但我必须再一次指出:设定防火墙需要您具备非常丰富的 TCP/IP 基础。最起码,您能理解如下的概念:
IP subneting 与 IP routing 的概念;
TCP 与 IP 封包 header 的各个栏位之名称与意义;
Socket Pair (Source Socket 与 Destination Socket) 的定义;
TCP 封包之 Sequence Number 与 Acknowledgement Number 的关系;
IP fragment 与 MTU/MRU 的关系;
ICMP 协定的 type & error code 之名称与意义;
Three-way Handshake 的过程与 SYNC 封包的意义;
TCP/IP 连线的不同连线状态(例如 NEW、WAITED、ESTABLISEHED、等)
各种服务的连线特征(尤以 FTP 服务为代表)。
假如您弄不懂以上所说的概念而硬要设定防火墙,那将是一件非常痛苦的事情(呵,我可把话说在前面了哦~~)。如果您真的决心弄明白这些概念,那您可以参考如下文章所列的书目清单:请推荐有关网路的书.... ,花点时间了解 TCP/IP 这对宝贝。要不然,当问题发生的时候,就算您要上来和大家请教和讨论,人家也不知道如何帮您解释才好、或是人家说了您却听不懂,因为都必须要牵涉到底层的 TCP/IP 基础知识。
好吧,我假设您已经补习好基础了哦~~ 然则的话,Let's go!
设定 IP Masquerading
我们这里的网路环境基本上与第一章所介绍的环境一样:
" target=_blank>
=780) window.open('');" src="" onload="javascript:if(this.width>'780')this.width='780';setTimeout('if(document.getElementById(\'\').height>\'700\')document.getElementById(\'\').height=\'700\';',500);" border=0>
这里,作为 NAT 机器的主机就是 RH71 ,它目前有三张网路卡,而我这里是将 adsl 接到 eth1 上面去,并且完成了 adsl-setup 、也已经顺利连上 Internet 了;执行 ipconfig 确定 ppp0 界面已经起来。
注:我们在接下来的叙述中,都一律以 ppp0 作为对外界面;如果您不是使用拨接连接,或是拨接界面不是 ppp0 的话,请您自行以该界面代替 ppp0 。
Tips:如果您使用拨接式 ADSL ,并向内部网路提供 NAT 服务的话,用一张网路卡就够了,反正 adsl 会使用 ppp0 而非 eth0 作为对外界面。但如果您是使用固接式 ADSL, 并且不是以 ppp 而是实体网路卡( ethX 界面)的话,那我建议您最好使用多片网路卡,分别用来连接外部和内部网路(甚至 DMZ 网路)。如果您用一张网路,然後以 IP Alias 方式同时连接外部和内部网路,单纯提供 IP 伪装服务而不设定封包过滤的话,那是可以的,但我还是不鼓励如此做法。
这时候在内部网路准备一台测试主机(跑 client 端程式),并确定它能够连上 RH71 主机、而且 default gw 也指向 RH71 的内部界面。
当我们的环境准备好之後,接下来就可以开始 NAT 的设定了:
无论如何,第一件事情要做的是确定您的系统上有安装 iptables 套件,否则请用 RPM 从 CD 里面安装(RH7.1 CD1)。
然後,您必须将 Firewall 功能编进核心里面去。如果您的核心是按照‘ 编译核心 ’文章的例子编的,应该就不成问题了:
Networking options --->[*] Network packet filtering (replaces ipchains)[*] TCP/IP networking
IP: Netfilter Configuration --->
Connection tracking (required for masq/NAT) (NEW)
FTP protocol support (NEW)
IP tables support (required for filtering/masq/NAT) (NEW)
limit match support (NEW)
MAC address match support (NEW)
netfilter MARK match support (NEW)
Multiple port match support (NEW)
TOS match support (NEW)
tcpmss match support (NEW)
Connection state match support (NEW)
Packet filtering (NEW)
REJECT target support (NEW)
Full NAT (NEW)
MASQUERADE target support (NEW)
REDIRECT target support (NEW)
Packet mangling (NEW)
TOS target support (NEW)
MARK target support (NEW)
LOG target support (NEW)
TCPMSS target support (NEW)
ipchains (2.2-style) support (NEW)
< > ipfwadm (2.0-style) support (NEW)
提示:假如您的系统并没作过任何的核心修改,换句话说:您一直都是使用系统安装时所准备的预设核心,那麽这个步骤可以省略了。除非您发现某些模组确实需要重编才有。
然後,检查一下 /etc/sysctl.conf 这个档案,看看 ‘net.ipv4.ip_forward=’是否为‘1’。如果是,跳到下一步;如果不是,将之改成‘1’,存档,然後执行 :
sysctl -w net.ipv4.ip_forward=1
Tips:上面这步骤是将 ip_forward 功能打开,您也可以用如下句子来完成:
echo "1" > /proc/sys/net/ipv4/ip_forward
执行 ntsysv 将 iptalbes 选择起来,并且确定 ipchains 没有选择。
检查一下 ipchains 的模组是否被‘意外’载入了:
lsmod | grep ipchains
如果输出结果没有发现 ipchains 的话,跳到下一步骤;如果有发现,则执行:
service ipchains stop
rmmod ipchains
并重复执行 lsmod 确定 ipchains 模组已经移除。
检查 iptables 的模组是否已经载入:
lsmod | grep ip_tables
如果有请跳到下一步骤;否则执行:
modprobe ip_tables
并再次执行 lsmod 确定 iptables 模组已经成功载入。
如果您不考虑任何安全性,现在执行:
iptables -t nat -A POSTROUTING -o ppp0 \
-s 192.168.100.0/24 -j MASQUERADE
就这样,您就可以让 NAT 後面的内部网路的机器连上 Internet 了!现在,您可以取消 proxy 设定并尝试一下 web 连线,或用邮件程式上网收发信件看看?甚至还可以打开 ICQ 和外面的朋友聊天呢!当然了,什麽 BBS 或新闻群组这些也都可以啦~~ ^_^
设定 Transparent Proxy
您或许会觉得继续使用 proxy 仍不失为一个好主意,尤其是当您考量到频宽问题的时候,甚至您还可以在使用者在不知情的情况下用 proxy 来浏漤 www 网站。这时候,您就可以考虑建立一个 Transparent Proxy 了:
首先,修改 /etc/squid/squid.conf,找到下面几行,并修改为如下样子:
httpd_accel_host rh71.siyongc.domain # 请修改为您的 squid 主机名称
httpd_accel_port 80
httpd_accel_with_proxy on
httpd_accel_uses_host_header on
并重新启动 squid 服务:
service squid restart
然後执行:
iptables -t nat -A PREROUTING -i eth0 -p tcp \
-s 192.168.100.0/24 \
--dport 80 -j REDIRECT --to-ports 3128
这时候,您的 Transparent Proxy 就起来了!如果您要测试它,可以取消 client 端的 proxy 设定,并将 squid 关闭,然後测试是否不能连线(建议用一个未曾浏览过的网址来测试)?然则,再将 squid 打开,如果能这样又能恢复连线的话,那就已经成功了!这样有一个好处是:以後您再也不必跑到 client 那边设定 proxy;而且,更好的地方在於:重复性的连线再也无需占用宝贵的对外频宽,速递当然也能‘假性’的获得提高。
工作原理
然而,以目前的设定来说,可以说是完全开放的,毫无安全性可言的。虽然我在这里并不想设定一个真正火墙,但起码的安全也是应该考虑的。当我们提到防火墙的时候,大部份都是指 Packet Filtering (封包过滤),这和前面提到的封包伪装(也就是 NAT 的一种技术)可不是一样的。这里面,我们不妨先了解一下封包过滤的工作原理,这对於我们日後的防火墙管理非常重要!
让我们回到 TCP/IP 的基础知识吧(顺便检查一下您是否能够正确理解我在前面列出的概念):
Socket Pair
首先,让我们了解一下什麽是 Socket Pair 。所谓 socket 就是一个‘ IP 位址’加上一个‘ TCP/UDP Port ’,代表了一个连线与哪台机器( IP 位址)、及与机器上那一苹程式( Port ) 相连的。我们同时也知道:一个连线必须有两个端点:来源地( Source ) 和 目的地( Destination ) 。换句话说,我们一个连线就是与一对 socket 相连著:分别是 Source Socket ( Source Address & Source Port) 与 Destination Socket ( Destination Address & Destination Port ) ,合起来我们称之为 Socket Pair :
" target=_blank>
=780) window.open('');" src="" onload="javascript:if(this.width>'780')this.width='780';setTimeout('if(document.getElementById(\'\').height>\'700\')document.getElementById(\'\').height=\'700\';',500);" border=0>
Tips:如果您了解 TCP 与 IP 封包 header 结构的话,都非常容易找到上述提到的栏位。一般而言,防火墙程式大都会检查这些栏位来进行判断,然後根据您的设计要求来处理封包。当然,事实上,防火墙程式除了根据 Socket 资讯来判决封包命运之外,几乎封包的每一个栏位都可以作为判断依据。这也就是防火墙(或封包过滤)程式能耐之处了。
事实上, Socket 的功能就是让网路程式存取网路使用的,对程式而言,关键是它所被分配的 Port 数值。而 port 也有分两种: TCP 和 UDP ;至於哪些服务用哪种 port,端视服务程式的要求(您都可以在 /etc/services 这个档案中找到)。一般而言,单一的机器最多能使用的 port 范围是在 1 到 65535 之间。其中的 1 ~ 1023 这段范围只能由 root 权限的服务程式使用,通常是一些常用的服务程式,如 HTTP、FTP、SMTP、等。我们称这些常用的 port 为 Well-known Port,基本上大家都会采用相同的数值,如 80、21、25 等(事实上是未必的,只要能让 client 找到就好,如地下网站就通常使用不为人知的 port 数值)。但是,作为 client 端程式所使用的 port ,我们没办法事先知道它会用哪一个 port 。因为 client 程式所使用的 port 会随机的从 1024 ~ 65535 之间挑选,只要那个 port 没被其它程式使用就行。
请您记住这些 port 的使用特征,我们在防火墙设计中常会用到。
连线方向
我们已经知道 Socket Pair 事实上就是两个 socket :一个是 Source、另一个是 Destination。但,这是相对而言的,为什麽这麽说?
因为,我们要成功的建立一个 TCP/IP 连线,其连线必须是双向的:假设连线是 A 与 B 之间的两台机器。当封包从 A 送往 B 的时候,A 的 Scocket 是 Source Socket、而 B 则是 Destination Socket ;反过来,当封包从 B 送回 A 的时候,那麽 A 的 Socket 就变成 Destination、而 B 则变成 Source 。
" target=_blank>
=780) window.open('');" src="" onload="javascript:if(this.width>'780')this.width='780';setTimeout('if(document.getElementById(\'\').height>\'700\')document.getElementById(\'\').height=\'700\';',500);" border=0>
请您务必搞懂这对 Socket 於连线方向时所使用的名称。这样,当您在防火墙墙上处理这些封包的时候,才知道哪边是 Source 、哪边是 Destination 。
连线界面
与刚才提到的连线方向相关的,就是连线界面了。一般情况下,防火墙都是处於内部网路与外部网路之间的位置,换句话说:它通常有不同的界面来连接不同的网路。
让我们再以图三为例:当封包从 A 送往 B 的时候,在防火墙左边的界面属於 incoming 界面(在 iptables 中以 -i 表示)、而右边的界面则是 outgoing 界面(在 iptables 以 -o 表示);但是,当封包从 B 送回 A 的时候,右边界面则成为 incoming 界面、左边的则成为 outgoing 界面。
Tips:请那些习惯了 ipchains 语法的朋友务必注意:在 ipchains 里面不管界面是属於 incoming 还是 outgoing ,一律以 -i 表示;但在 iptables 中,-i 只能代表传入界面、传出界面只能以 -o 表示。所以,在 iptables 中的 INPUT 不可能使用 -o 来指定界面;相反,-i 界面也不可能出现在 OUTPUT 中。
封包於防火墙中的流向
当您搞懂了 Source Socket 与 Destination Socket 、以及各界面之间的关系之後,接下来,再让我们仔细看看封包在防火墙中的流向。
当一个封包抵达防火墙的时候,必须从一个界面进入,然後经过路由判断之後,再从路由判断所指定的界面送出去。您或许很容易的认为:从界面传入的封包就是 INPUT ;从界面从出去的封包就是 OUTPUT;而从一个界面送到另一个界面就是 FORWARD :
" target=_blank>
=780) window.open('');" src="" onload="javascript:if(this.width>'780')this.width='780';setTimeout('if(document.getElementById(\'\').height>\'700\')document.getElementById(\'\').height=\'700\';',500);" border=0>
事实上是否如此呢?
哈,如果您对 ipchains 程式够了解的话,您肯定会认为如此。但,到了 iptables 的时候,却未必哦~~ ^_^ 在 iptables 中,所谓的 INPUT 与 OUTPUT,只对那些与本机 Local Process 相关的封包而言才是成立的(这也是 iptables 与 ipchains 最大分别之一)。也就是说:对於一个从界面传入的封包,如果它是送给在本机的,那才算是 INPUT,否则不算;至於将从一个界面送出的封包,如果这个封包是从本机产生的,才算 OUTPUT,否则不算;那些传入与传出都与本机无关的封包,则属於 FORWARD 了(注意:既不是 INPUT 也不是 OUTPUT 哦):
" target=_blank>
=780) window.open('');" src="" onload="javascript:if(this.width>'780')this.width='780';setTimeout('if(document.getElementById(\'\').height>\'700\')document.getElementById(\'\').height=\'700\';',500);" border=0>
事实上,图五中的三个圆角方块( INPUT、OUTPUT、FORWARD ), 分别是在 iptables 程式里面的三个内建防火链( Built-in Chains,我们後面再说明) 。
NAT 的原理
我们一般所说的 NAT 事实上只是一个 socket 替换机制:要麽将 Source Socket 换掉、要麽将 Destination Socket 换掉、要麽两个同时换掉。就这麽简单~~ ^_^
通常而言,当一个 NAT 在替换 Source Socket 的时候,我们称之为 SNAT ( Source NAT );如果在替换 Destination Socket 的时候,则为 DNAT ( Destination NAT );那如果 Source 与 Destination 一起换呢?哈~~ 这个留给聪明的您自己思考吧~~
究竟 NAT 是根据什麽来替换这些 socket 呢?这就是我们要花心思去设计的了。在前面我们曾经示范过所谓的‘IP 伪装 ( Masquerading )’设定范例:
iptables -t nat -A POSTROUTING -o ppp0 \
-s 192.168.100.0/24 -j MASQUERADE
事实上,这是一个 SNAT 的经典例子了:当 NAT 处理一个封包的时候,如果发现它是来自 192.168.100.0/24 这个网路,并且经由 ppp0 送出的话,那麽它的来源位址则换成 ppp0 的 IP 位址( socket 的元素之一)。至於 socket 的另一元素 --- TCP/UDP port --- 是否也需要换掉呢?得视 NAT 当时的 port 使用情况而定。聪明的 NAT 都会‘尽量’的保留原有的 port 数值,除非这个 port 已经被别的 Socket 使用了。不过,也有些 NAT 不管怎样,都同时将 IP 位址与 Port 给换掉。然而,不管哪种情形,只要 Socket 元素之一经过修改,那就是另外一个新的 Socket 。并且,NAT 必须为这个动作进行记录 (通常会保存在 NAT Table 之中)。
当被修改过 Socket 的封包送给远端主机的时候,对方只认得被替换之後的 Socket ,句话说:对方的回应只会送回 NAT 的外部 Socket 那边去(记住,此时,回应封包的 Source 与 Destination 位置会颠倒过来)。因为 NAT 已经有这个 socket 的 NAT 记录,所以,当这个回应送回到该 socket 的时候,NAT 会根据记录将 Destination 修改为原来的 Socket (也就是被修改了的 Source Socket),然後再送回给原来的内部主机:
" target=_blank>
=780) window.open('');" src="" onload="javascript:if(this.width>'780')this.width='780';setTimeout('if(document.getElementById(\'\').height>\'700\')document.getElementById(\'\').height=\'700\';',500);" border=0>
您会发现:在整个连线过程中,无论内部的主机或是外部的远端主机,事实上都不必知道 NAT 主机的存在!只要各自的程式在检查 Source Socket 与 Destination Socket 的时候,与自己所期待的设定一致就行了。
当您了解 SNAT 是怎麽一回事之後,再来理解 DNAT 就没什麽困难了:当 NAT 主机收到一个封包之後,按要求修改其 Destination Scocket 、并做好记录;完成後根据修改後的路由判断送到修改後的位址;然後,当目标主机处理完毕所送回的封包再回到 NAT 主机的时候,根据 NAT 记录再一次偷龙转凤、完成後送回原主机就好了。
事实上,NAT 所牵涉的知识面很广,需要您花大量时间学习的。我发现下面这个网页很不错:
http://www.ncu.edu.tw/~center5/livecd/ipnat/ 有空不妨去看看,尤其是如果您使用 FreeBSD 系统的话。
连线之建立
在这里,让我们认识一个 TCP 封包的特殊旗标( flag ) --- SYN bit 。究竟它是什麽东西呢?这就要我们回到 Three-Way Handshake 的工作原理去了:
每一个 TCP 连线,都必须由一端(通常为 client )发起请求,其 TCP 封包会将(且只将) SYN 旗标设定起来,这是整个连线的第一个封包;如果另一端(通常为 Server ) 接受这个请求的话,则会向请求端送回整个连线的第二个封包:其上除了 SYN 旗标之外,同时还将 ACK 旗标也设定起来,并同时在本机端建立资源,以待连线之需;然後,请求端获得服务端第一个回应封包之後,必须再回应对方一个确认封包,此时封包只带 ACK 旗标(事实上,後继连线中的所有封包都必须带有 ACK 旗标);只有当服务端收到请求端的确认( ACK )封包(也就是整个连线的第三个封包)之後,两端的连线才能正式建立。这就是所谓的 TCP 连线的‘三段式交握( Three-Way Handshake )’的原理。
Tips:如果 Three-Way Handshake 过程中的这三个封包,任意一个不能顺利送抵目的地的话,那整个连线就没办法建立。所以,如果您想在防火墙上挡掉所有来自外部的连线建立,只要单纯的将第一个封包(也就是仅带 SYN 旗标的封包)拦下来就可以了!不过,这样做会导致一些复杂的连线,例如 FTP ,不能正常服务。那就需要更加仔细的设定防火墙规则了。
另外,许多恶意的攻击者,也会利用 Three-Way Handshake 的工作原理来达到捣乱和破坏的目的。因为服务端只要收到 SYN 封包就会在系统上建立相关的资源,然後等待对方的 ACK 封包进行连线确认;如果确认迟迟不达,那服务端将等待一段时间才取消这个连线所占用的资源。如果攻击者一下子送进大量的 SYN 封包,然後又故意将 ACK 确认挂起来,这样会急剧的耗费服务端的资源,如果资源耗尽的话,将不能再提供任何新的连线服务、甚至让机器挂掉!这就是闻名的‘ SYN Flood ’攻击法了。
说实在,要防范 SYN Flood 攻击是很难的,只要您对外提供连线服务就有机会遭到攻击,主动权完全在攻击者手上。各家防火墙作者无不挖空心思以图降低这类攻击的损伤,可谓‘八仙过海、各显神通’,不一而足。不过,大部份方法不外乎两种:降低连线等待时间、或是限制 SYN 封包数量。当然,有些聪明的防火墙程式能够‘动态’的侦测此类攻击的特性,然後自动的启动防护机制。我们这里所介绍的 iptables 也有这个功能,如果您有兴趣的话,不妨参考 Pakcet Filtering HOWTO 文件第七章之“ 过滤规格 ”中关於 limit 的叙述。
理解 Three-Way Handshake 非常重要,这在日後整个防火墙管理生涯中都占据著举足轻重的地位。
连线状态
当我们了解 SYN 封包的作用之後,最後,让我们了解一些 TCP 连线状态吧。
我们已经知道一般的服务程式会使用 Well-known Port 等待 client 端的连线,通常,这些 Socket 会随著服务程式的载入而开启。那麽,当一个服务程式打算向外部提供服务的时候,其连线状态必须处於 LISTEN 状态。当您执行 netstat 程式的时候,就很容易找出机器目前有哪些服务对外提供,只要找出那些 LISTEN 状态的连线就知道了。
当一个 client 端需要建立连线的时候,会送出一个 SYN 封包,如果它还没完成 Three-Way Handshake 的时候,那麽该连线则会处於 SYN_SENT 状态;相对的,当 server 端收到这个 SYN 封包的话,那连线则处於 SYN_RECV 状态;假如 Three-Way Handshake 能顺利完成,换句话说,连线已经建立,那这时候则处於 ESTABLISHED 状态。
然而,当连线要结束的时候,其状态则复杂得多,大致有如下这些状态:FIN_WAIT1、FIN_WAIT2、TIME_WAIT、CLOSED、CLOSE_WAIT、LAST_ACK、CLOSING 。我这里不详细说明这些状态了,有兴趣请参考相关的 TCP/IP 书籍吧。至於完全找不到分类的,则归为 UNKNOWN 状态。
Tips:然而, iptables 对於连线状态的描述略有不同:
NEW
一个建立新连线的封包。
ESTABLISHED
一个属於成功建立连线之封包。
RELATED
一个与现有连线相关,但却并不限於其连线的封包,诸如 ICMP 错误,或是建立 FTP 数据连线的封包( FTP 模组已插入)。
INVALID
一个因某些原因不能被鉴别的封包:这包括记忆体不足和不能回应任何已知连线的 ICMP 错误。通常,这样的封包都会被丢弃掉。
您最好能够区分以上提到的连线状态,因为在防火墙设定中也常会用到。
好了,关於防火墙设定所需要的基本原理,我暂时介绍到这里。当然,还有许多其它相关概念还没来得及跟各位介绍的,那就请阁下自行研究和补充了。
使用 iptables
我们这里所介绍的防火墙工具就是 iptables ,所以,在我们真正动手实作之前,最好还是让我们了解一下这个工具的使用规则吧。
不管使用哪一种防火墙,基本上都是设定防火墙规则( RULES ) 来规范封包的处理而已。我们在 iptables 上也不例外,只是,iptables 会将不同的规则集合起来,放进不同的链( CHAINS ) 中以备查用。大家已经从前面的图五中看到了 iptables 的三个内建链 ( Built-in Chains ) 了,它们分别是:INPUT、OUTPUT、与 FORWARD 。它们在 iptables 跑起来之後就会建立(一开始里面是空的、不带任何规则),而且它们是不能删除的。至於它们三者的分别,我已经在前面说过了,如果您忘记了的话,最好卷回去温习一下(图五)。除了这些内建的链外,您可以在 iptables 任意建立自定链( User-defined Chains ) ,除了可以避免重复输入规则外,还能增加整链的处理速度 (哦,这要您非常了解 iptables 的规则运作才能体会啦~)。
Tips:如果您从 ipchains 转过来使用 iptables 的话,那您这里还必须要认识一个新的概念: tables 。我们知道 chains 里面包含的就是 rules 和 policies ,但 tables 又是什麽呢?哦~~ 它包含的是 chains !没错!这就是为什麽 iptables 之所以被称为 tables 的原因啦~~ ^_^
iptables 是使用 -t 参数来指定 table 的。在预设的情况下,iptables 的命令都是作用於 filter 这个 table 上,如果您要修改其他 table 的话,那就必须用 -t 来指定。我们前面介绍的 NAT 设定规则,都是在 nat 这个 tables 中进行的(细心的读者或许一早就在问那个 -t nat 是什麽了吧?)。除了 filter 与 nat 外,您还可能会使用到 mangle table 来进行其它的封包改写功能,不过我们这里不谈这个进阶题目了。
基本上,当一个封包进入防火墙之後,iptables 就将之丢进不同的链中、逐行检查规则、一旦找到符合设定的规则,那麽就会根据规则所定义的动作来处理这个封包:
ACCEPT
接受这个封包,也就是可以通过规则检验而放行、顺利通过这个链。
DROP
丢弃这个封包,也就不能通过规则检验而被挡掉。
REJECT
与 DROP 一样,但会向来源地送出 ICMP 封包,告之对方‘ port unreachable ’的错误信息。
REDIRECT
将封包重导至 本机端 的其它 port 。
QUEUE
将封包重导至 本机端 的伫列 ( queue ) 程式。
LOG
将该封包连线透过核心程式记录下来。
MARK
为封包进行标记,供後面其它规则使用,或是供其它封包处理程式使用。
SNAT / DNAT / MASQUERADE
这些都是 NAT 的处理,视要求而修改为特定的 Source Socket 或 Destination Socket 、或动态的根据路由判断後的界面而修改 Source Socket 。
[ User-Defined Chain ]
将封包转至自定的链中继续检验,该链事先必须用 -N 建立。如果在自定链中找不到符合的规则,则会自动返回原链的下一行规则继续检验:
" target=_blank>
=780) window.open('');" src="" onload="javascript:if(this.width>'780')this.width='780';setTimeout('if(document.getElementById(\'\').height>\'700\')document.getElementById(\'\').height=\'700\';',500);" border=0>
RETURN
直接跳离目前的链:如果是自定链,则返回原链的下一个行规则继续检验;如果是内建链,则参考原则( policy,下述 ) 来处理封包。
以上所列的处理动作,在 iptables 称为目标 ( TARGET ),在规则中以 -J ( jump ) 选项进入。事实上,还有许多其它种类的目标,有兴趣请自行 man iptables 慢慢参考吧。
假如封包经过一个链而找不到符合的规则,那麽,其最终命运则以该链的原则( POLICY ) 为依据(通常为 DROP 或 ACCEPT ),在 iptables 中以 -P 来定义(预设全为 ACCEPT )。请您记住一个很重要的特性 --- 规则检验的 First-Match 原则:一旦找到符合的规则将不再往下检查链中的其它规则(除非被指向其它链)。如何合理的安排规则的顺序就变得非常重要了!
接下来,让我们学习一些关於链的管理技巧:
建立一个新的(自定)链 ( -N )。
删除一个空的(自定)链 ( -X )。
改变一个内建链的原则 ( -P )。
列出一个链中的规则 ( -L )。
清除一个(内建)链中的所有规则 ( -F )。
归零( zero ) 一个链中所有规则的封包字节( byte ) 记数器 ( -Z )。
在一个链的最後面新增( append ) 一条规则 ( -A )。
在链内某个位置插入( insert ) 一条新规则( -I )。
在链内某个位置替换( replace ) 一条规则 ( -R )。
在链内某个位置删除( delete ) 一条规则 ( -D )。
删除(delete) 链内第一条符合的规则 (-D)。
当我们设定一条 iptables 规则的时候,一般而言,可以透过下面数个元素的组合来决定封包的目标(target):
封包於防火墙中的流向 ( INPUT、OUTPUT、FORWARD )
相关界面 ( -i 或 -o )
所属协定 ( -p )
连线类型 ( -m state )
封包类型 ( --syn )
来源地 ( -s )
来源埠口 ( --sport )
目的地 ( -d )
目的地埠口 ( --dport )
Tips:事实上,iptables 的规则依据非常多样,以上仅是少部份而已。例如,您可以透过 tcp-flag 、 icmp-type 、 mac 、 limit 、owner 、等元素进行更透彻的限制。更详细的说明,请参考 man page 或 HOWTO 文件。
下面,让我们以一些例子来说明好了:
iptables -A FORWARD -i ppp0 -p TCP -s xxx.xxx.xxx.xxx -d yyy.yyy.yyy.yyy -j DROP
这行规则是说:所有从 ppp0 传入并且不是送给本机的 TCP 封包(因为是 FORWARD 链),如果是从 xxx.xxx.xxx.xxx 传给 yyy.yyy.yyy.yyy 的话,一律给予丢弃( -j DROP)。因为我们是用 -A ( Append ) 来增加这行规则,它会加在 FORWARD (注意大小写!) 规则的最後一行。请注意,在路由过程中的封包位址是不会改变的,除非您经过了NAT 处理过(请参考後面的叙述)。在规则的位址,可以是一段网路位址( Net_ID/netmask )、也可以是一个 IP 位址(预设使用 32-bit mask )、甚至可以是一个主机名称(可查询到 IP 就行)。
假如您想做出更严格的限制,还可以增加一些元素进去:
iptables -A FORWARD -i ppp0 -p TCP -s xxx.xxx.xxx.xxx --sport 1024:65535 \
-d yyy.yyy.yyy.yyy --dport www -j DROP
这样的话,要是封包从 xxx.xxx.xxx.xxx 这个客户端(使用 1024 以上的 port ),传给 yyy.yyy.yyy.yyy 这台网页伺服器的 port 80 (也就是 www),一律给挡下来。您会发现:如果您不指定额外特定元素,iptables 内定会全部接受它们。如果这时候您再这样输入一行命令:
iptables -I FORWARD 1 -s xxx.xxx.xxx.xxx -j ACCEPT
因为这里只有一个来源( -s )有定义,那就不必管其他条件是怎麽样的了:不管用什麽 port、不管到哪里、不管用什麽协定、不管是什麽连线状态、不管 ....... 、只要来自 xxx.xxx.xxx 就一律 ACCEPT 。但这行规则会导致原本会被挡掉的封包,这时候就会先行被放行了。因为 " -I FORWARD 1" 是要将这行规则加在 FORWARD 链的第一行之上(如果您不指定行数,那预设就是第 1 行)。iptabless 在检查规则的时候,会先从第一行规则往後一直比对下去,只要条件符合,就不再往下检查其它规则了。如果所有规则都不符合,然後会检查原则( Policy )。所以,设定 iptables 的顺序要格外小心哦~~~ !
Tips:关於规则顺序情形,在 VPN 与 transparent proxy 的设计上很重要。以後者为例:假设您参考文章开头的介绍,用如下命令设计 transparent proxy :
iptables -t nat -A PREROUTING -i eth0 -p tcp -s 192.168.100.0/255.255.255.0 \
--dport 80 -j REDIRECT --to-ports 3128
如果这时候,您在 NAT 机器本机端就有提供 www 服务的话,那会导致内部主机连不上。这样的话,您只要在上面句子之前插入一行就行了:
iptables -t nat -I PREROUTING -i eth0 -p tcp -s 192.168.100.0/255.255.255.0 \
-d 192.168.100.23 --dport 80 -j ACCEPT
注:请将 192.168.100.23 修改为您的实际位址。并确定关於 lo 界面的 INPUT 与 OUTPUT 没有被挡掉。
通常来说,使用 iptables 的时候,我们最需要小心设计的就是设定从外部界面进入本机的 INPUT 规则、以及从外面送进内部网路的 FORWARD 规则;OUTPUT 规则及由内至外的 DORWARD 规则,相对来说可以松一点。但为了养成良好习惯,我们也应该严格的设定好每一个 OUTPUT 与 FORWARD 规则。如果路由上不能让封包顺利完成传递,例如使用私有 IP 的内部网路需要连接 Internet,那麽我们就要使用 NAT 来改写封包了。
在 iptables 中,与过往的 ipchains 比较起来,它在 NAT 的设定上明显多样化及复杂多了。刚从 ipchains 转换过来或许一下子不容易理解的,所以,我们有必要再花点时间学习一些 iptables 的 NAT 技巧。
先让我们看看封包在 NAT 中的处理过程吧:
" target=_blank>
=780) window.open('');" src="" onload="javascript:if(this.width>'780')this.width='780';setTimeout('if(document.getElementById(\'\').height>\'700\')document.getElementById(\'\').height=\'700\';',500);" border=0>
图中的三个灰色圆角方块,事实上就是 NAT 的三个内键链。其中的 OUTPUT 是 local process 产生的封包才会经过那里,而 PREROUTING 和 POSTROUTING 事实上也不难理解:在英文语法上,PRE 就是在什麽什麽之前、POST 就是在什麽什麽之後。那麽,PREROUTING 就是在 ROUTING 之前,通常是封包从一个界面进入之後,尚未交给路由判断之前的处理,您可以在这里进行 DNAT 的 socket 替换;而 POSTROUTING 则是在路由判断之後,通常是当封包离开界面之前的处理,您可以在这里进行 SNAT 的设定。
Tips:您会发现,这与前面(图五)的封包过滤流程不太一样,而许多朋友也经常搞不懂这两者的分别,并试图将两个图合并在一起。事实上,我认为那是不必的,因为我们可以简单的将 Packet Filtering 与 NAT 看成两个不同的系统(table),只是用同一工具来设定而已。当然了,要说完全将两者割裂,看来也是说不过去的。这似乎有点矛盾,只是,我个人觉得在追踪封包的处理过程的时候,分开两个系统来检查是比较容易一些。只要我们能够精确了解封包於每一个链中的状态、及其来龙去脉就好了。
那麽,我们如何用 iptables 来进行 SNAT 处理呢?看看下面的例子吧:
## 将来源位址换成 1.2.3.4 :
# iptables -t nat -A POSTROUTING -o eth0 -j SNAT --to 1.2.3.4
## 将来源位址换成 1.2.3.4、 或 1.2.3.5、 或 1.2.3.6 :
# iptables -t nat -A POSTROUTING -o eth0 -j SNAT --to 1.2.3.4-1.2.3.6
## 将来源位址换成 1.2.3.4 ,而 port 则换成 1 到 1023 之间:
# iptables -t nat -A POSTROUTING -p tcp -o eth0 -j SNAT --to 1.2.3.4:1-1023
## 将所有从 ppp0 界面送出之来源位址换成 ppp0 的位址:
# iptables -t nat -A POSTROUTING -o ppp0 -j MASQUERADE
我们在前面已经知道 SNAT 就是替换 Source Socket 而已。前面三个命令常用於静态(或指定范围)的 NAT 转换、而最後一行的 MASQUERADE 则是动态的根据 ppp0 的位址进行转换(因为 ppp0 可能每次拨接所获得的位址都不一样)。不管哪种 SNAT ,通常都是修改来自於内部网路的封包,让它能顺利到达外面的网路去。这除了可以解决路由的问题外,还可以节省 IP 位址,而且在安全上面也是有帮助的。因为内部网路使用的私有 IP 在 Internet 上面是不能路由的,所以,就算您的 FW 被攻破,除非入侵者能占据您的 NAT 主机且以它做据点,否则,他们还是不能连接到内部网路的主机。
然而,如果我有些服务,例如 WWW、MAIL 等,想架在内部网路呢?按惯例,通常会将那些伺服器架在 DMZ 里面。如果 DMZ 是可以直接路由到 Internet 上去的话,我们在 FORWARD 那里设 ACCEPT 规则就可以了。但是,如果我们的 DMZ 是用另外一个私有 IP 网路来架呢?那我们就需要使用 DNAT 技术,将那些传到 NAT 外面界面的某些 socket (可以为任意 port ,但通常是那些固定的服务埠口) 的封包,转到 DMZ 里面的机器去了。例如:
## 将目的地位址换成 5.6.7.8 :
# iptables -t nat -A PREROUTING -i eth1 -j DNAT --to 5.6.7.8
## 将目的地位址换成 5.6.7.8、或 5.6.7.9、或 5.6.7.10 :
# iptables -t nat -A PREROUTING -i eth1 -j DNAT --to 5.6.7.8-5.6.7.10
## 将送给目的地位址之 Web 封包送至 5.6.7.8 及 port 8080 那边:
# iptables -t nat -A PREROUTING -p tcp --dport 80 -i eth1 \
-j DNAT --to 5.6.7.8:8080
## 将送给 1.2.3.4 的本机重导致 loopback 界面:
# iptables -t nat -A OUTPUT -d 1.2.3.4 -j DNAT --to 127.0.0.1
## 将从 eth1 界面进入的要送给 port-80 web 封包重导给 squid (transparent) proxy :
# iptables -t nat -A PREROUTING -i eth1 -p tcp --dport 80 \
-j REDIRECT --to-port 3128
关於 FTP
事实上,仅知道如何操作 iptables 工具,还不足以设定好您的防火墙及 NAT ,您还要对各种 TCP/IP 服务都要有相当程度的了才行:您必须知道一个连线的建立过程是怎样的,每一个封包动作是怎样从一端送到另一端的。您除了要知道每一个封包的来源和目的位址,还要知道它们的来源埠口和目的埠口。而且,我们不要忘记了:所有连线都是双向的,您除了要照顾从客户端到伺服器端的请求,也要照顾从伺服器到客户端的回应。下面,让我们看一看火墙的设定如何影响 FTP 的运作的( FTP 是一个非常经典的 NAT/Firewall 设定范例)。
首先,我们要知道 FTP 的连线模式有两种:主动模式( active )和被动模式( passive )。要了解这两个模式的不同,得要了解 FTP 的连线是怎样建立的:
在正常模式下:
FTP client 开启一个随机选择的高於 1024 的 port 呼叫 FTP server 的 port 21请求连线。当顺利完成 Three-Way Handshake 之後,连线就成功建立,但这仅是命令通道的建立。
当两端需要传送资料的时候,client 透过命令通道用一个 port 命令告诉 server ,客户端可以用另一个高於 1024 的 port 做数据通道,并准备好 socket 资源。
然後 server 用 port 20 和刚才 client 所告知的 socket 建立数据连线。请注意:连线方向这是从 server 到 client 的,TCP 封包会有一个 SYN 旗标。
然後 client 会返回一个带 ACK 旗标的确认封包,并完成另一次的 Three-Way Handshake 手续。这时候,数据通道才能成功建立。
开始数据传送。
在 passive 模式下:
FTP client 开启一个随机选择的高於 1024 的 port 呼叫 FTP server 的 port 21请求连线,并完成命令通道的建立。
当两端需要传送资料的时候,client 透过命令通道送一个 PASV 命令给 server,要求进入 passive 传输模式。
然後 server 像上述的正常模式之第 2 步骤那样,挑一个高於 1024 的 port ,并用命令通道告诉 client 关於 server 端用以做数据通道的 socket。
然後 client 用另一个高於 1024 的 port 呼叫刚才 server 告知的 scoekt 来建立数据通道。此时封包带 SYN 标签。
server 确认後回应一个 ACK 封包。并完成所有交握手续、成功建立数据通道。
开始数据传送。
我们都知道:火墙的保护对象是内部网路的 client 主机。防火墙为了挡掉一些来自外面的危险动作,通常会限制那些来自外面的主动连线,也就是带 SYN 标签的封包(请参考 Three-Way Handshake 的过程)。在这样的情况下,iptables 或许有这样的一个设定:
iptables -I INPUT -i ppp0 -p TCP --syn -j DROP
iptables -I FORWARD -i ppp0 -p TCP ! --syn -j ACCEPT
在第二行中的那个 " ! " 就是 NOT 的意思," ! --syn " 就是 NOT SYN 之意,也就是只允许非主动连线的 TCP 封包从外部界面进入,这和第一行的意思一样:不接受来自外面主动建立的连线。
Tips:前面我们已经说过我们可以将传到 NAT 外部界面的连线转到 DMZ 里面去。假如我们有这样的要求,却继续沿用前面那行带 " ! -y " 的规则的话,外面的客户主机就无从建立连线来连接我们的服务主机了。但我们又不想取消它,以免危害到其它非 DMZ 的内部网路。
还记得 iptables 的行为习惯吗:它会自上而下的对比规则,找到符合的就不再往下找了。根据这样的特性,我们可以将 NAT 外部界面进入的某些连线,在那行 syn 过滤规则之前 ACCEPT 进来。不过,就要非常小心别漏了必要的限制元素,有可能的话,尽您想象把规则设得严密又严密吧。
假设我们这时候有一个在防火墙之後的 FTP client 要试图连接外面的 FTP server 。在正常模式下,(第 3 步)来自 server 的数据通道之建立请求就会被挡下来,这会导致数据通道无从建立。虽然您可以 login 进远端的 FTP 伺服器,但如果输入 ls 後,却看不到结果的。FTP 的 login 与 ls 是透过命令通道进行的,因为命令通道的建立并没问题,所以 server 可以收到命令。但 ls 的结果确需要透过数据通道送回来,然而数据通道却不能建立建立。所以 server 在传送失败後,再用命令通道告诉您: “Can't build data connection: Connection refused.”
明白了?是否解释了困绕您心目中以久的疑团呢? ^_^
好了,如果用 Passive 模式又会如何呢?让我们回去看看正常模式的第 3 步和 Passive 模式的第 4 步,然後检查它们进出防火墙的方向,以及防火墙如何处理 SYN 封包就知道了。我不想直接把答案告诉您。自己要懂得思考,才会有进步!不是吗?
Tips:不过,在 iptables 中有一个 RELATED 的 TCP 连线状态标签,可以让 ftp-data 连线顺利建立:
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
但是,我必须再指出:上面谈的是基於一个 routing 健全的情形下。如果用 NAT 的话,封包的位址将会有所改变。无论用正常模式或 Passive 模式,server 都看不到 client 的位址,server只看到 NAT 的位址而已。故此,就算您取消了入站封包的 SYN 限制,在正常模式下来自 server 的数据通道连线,封包只会送到 NAT 主机的外面界面那里。但问题是 NAT 并没有该连线的 NAT 记录,也就不会将修改封包 socket 将之送回里面的客户主机。除非,您能够帮 client 端做 ftp-data 的 DNAT 。然而,因为 client 所使用的 ftp-data socket 是随机挑选的一个大於 1024 的 port ,您事先没办法在 NAT 上知道 ftp client 用哪个 socket 的。然而,更重要的问题在於:client 在命令通道中所使用的 port 命令,其 IP 是原有的内部位址、而 NAT 根本不会将之修改过来!所以 server 在建立 ftp-data 连线的时候,根本就不可能路由到正确的位址!
那是否有办法解决呢?当然有的啦~~ 使用 passive 模式就可以了!为什麽?不知道您的脑筋是否能转得过来呢? 那是因为:在 passive 模式下面,所有连线的建立方向都是从内部的 client 送往外面的 server 的;因为这个连线需要做 SNAT,那麽,NAT 的 table 中有这个 socket 转换的记录了;然後从 server 所送回的回应封包,理所当然也能顺利完成 socket 的还原工作。
明白了?或许,您又会问:那如果在 NAT 情况下,不使用 passive 模式呢?是否还有办法解决 ftp 数据通道的问题?
搭配核心模组
呵~~ 就算不用 passive 也可以啦!只要能让 NAT 抓到 ftp client 在命令通道中送出的 port 命令,并修改原本在封包 body 中的 socket ,那就可以‘聪明的’在外部界面上建立 DNAT socket,以等待 server 那边的数据通道连线请求了。这工作,通常是由核心模组来完成的,在 2.4.x 核心上,您只需执行如下命令就可以了:
modprobe ip_conntrack_ftp
modprobe ip_nat_ftp
Tips:在 2.2.x 核心中所使用的模组名称为 ip_masq_ftp ,不过,它只能跟 ipchains 配合使用。就算您在 2.4.x 核心中能够载入 ipchains 模组,但因为 2.4.x 核心的 ip_nat_ftp 并不能与 ipchains 合作,所以也就没办法了。假如您有兴趣知道 ip_masq_ftp 这个模组会做些什麽?请参考网友 常锅面 兄的精解:
常锅面 wrote in message news:3g4d6d$1q1@bbs.cynix.com.tw...
> ip_masq_ftp.o 的工作,除了帮 masq 建立一个 port 20 的纪录
> 他最重要的工作还是跟 port 21 相关
> a.监视 client -> server destination port 21 的封包 (tcp of course)
> 并修改其中带有 PORT command 的封包...
> [IP header|TCP header|PORT xxx,xxx,xxx,xxx,xx,xxCRLF]
> b.修改此封包 IP header 中 total length 栏位
> 纪录 data length 的变动以维持 port 21 的连线不要断掉..
> 修改每一个 server -> client source port 21 封包的 ack number(TCP header)
> 修改每一个 client -> server destination port 21 封包的 seq number
> 毕竟 masq 纪录只有 IP header 的 IP add & TCP header 的 port number
> 其他都要靠 ip_masq_ftp.o
> 如果 ftp server port 不是 21 就完蛋了,因为拦不到 PORT command
> 就更别提 data connection 了...(dir, get 都会送 PORT command)
> 不过写得弹性一点(可设定)应该也不会困难才对...
文章虽然是描述 ip_masq_ftp 模组的,但其中的原理和 ip_nat_ftp 是一样的。
事实上,FTP 服务的设定,在防火墙与 NAT 设计中非常具有代表性。如果要测试自己对 NAT 的设定是否及格,用 FTP 服务来考自己就对了。如果您有兴趣,不妨做做下面这个习题哦:
NAT 的 FTP 设定命题
补充:在过往的测试中,我曾经作过如此猜测:
如果 server 和 client 两端都各自躲在 NAT 背後,无论用 ipchains 和
iptalbes 都无法用 passive 模式来建立 data channel。
然而,在最近的测试中,我发现 netfilter 所带来的 ip_conntrack_ftp 以及 ip_nat_ftp 模组已经能够成功的进行 server 端与 client 端的连线转换。
换而言之,我之前所提到的过滤:“client 与 server 双方都在 NAT 之後是否能连线?”现在已经不是问题了!实在非常感谢 netfilter 的工作者们。
p.s. 我所测试的核心为 2.4.16 以及 iptables-1.2.1a-1 。
如果您在参考答案之前能够搞定 FTP 服务的话,那基本上您就及格了。否则,我劝您还是别急著碰 NAT 的设定。
除了 FTP 之外,还有许多其它服务程式,也有类似的‘多通道连线’的特性,例如 netmeeting 、realaudio、网路游戏、等等。因为这些程式大都没有所谓的 passive 模式,所以,只能靠相关的模组帮忙了。如果您有相关问题,不妨到下面网站找找:
这个网站有许多奇奇怪怪的 IP 伪装技巧与模组,相信总会有您感兴趣的东西。
关於 netmeeting
下面,和大家说一说如何在 iptables 使用 netmeeting (H.323) 这个东西。因为, h.323 和 ftp 连线一样:也是由多个通道来建立连线的,而且大都使用 UDP 协定。假如不解决来自外部的通道连线问题的话,那麽,当您使用语音和视讯的时候,对方能听到、看到您的,但您却听不到、看不到对方!
在 kernel 2.2.x 的年代中,已经有人写出来 h323.o 的模组来解决这个问题,但到了 2.4.x 核心之後,一直到 2.4.16 这个版本才有较为完整的 patch 可以使用。所以,请您首先下载 2.4.16 的核心 source 回来,并解开在 /usr/src 目录中(步骤可以参考 system 系列之“ 编译核心 ”)。同时,再到 下载 newnat5-and-helpers-2.4.16.patch.gz 档案(请留意您下载的档案必须是 gzip 格式!因为我抓回来之後发现它是已经解压之後的档案。关於 newnat 的原始网站位於: )。要不然,您可以从 本站 下载 tar+gzip 的版本(下面例子所采用),同样置於 /usr/src/ 目录下面:
开始进行 patch 工作(假设您是从本站下载的 patch):
cd /usr/src/linux # 请确定为 2.4.16 版本
tar zxvf [url][/url]
cat newnat5-and-helpers-2.4.16.patch | linux patch -p1 -E
Tips:
# 不管您的 patch 从哪里获得,不妨先用 cat 看一看其内容,
# 如果您确定您的版本是 gzip 压缩档案(乱码),那最後两行可改为:
gzip -cd [url][/url] | patch -p1 -E
# 如果您抓下的档是已经解压的话(明码),那就改为:
cat [url][/url] | patch -p1 -E
(请确定您的当前目录是在 /usr/src/linux/ 那里。)
如果您还有其它 patch (例如 ipvsadm),也请一一安装。
然後执行核心编译,确定如下的选项:
Networking options --->
[*] Network packet filtering (replaces ipchains)
[*] TCP/IP networking
IP: Netfilter Configuration --->
Connection tracking (required for masq/NAT)
FTP protocol support
talk protocol support
H.323 (netmeeting) support
IRC protocol support
......
当然了,其它选项也请行选择。事实上,除了一些新选项外,大部份都和“ 编译核心 ”文章所列一样就行了。
完成您的核心、模组、LILO 的安装,重新开机,确定新核心无误。
然後依顺序载入如下模组:
modprobe ip_conntrack_h323
modprobe ip_nat_h323
这样就可以让您的 netmeeting 连到外面去了~~!
ICMP 的问题
在 NAT 设计中,除了要特别解决来自外面建立的连线问题外,还有一个 ICMP 的 Host Gateway Redirect 的问题。典型的设计是:您将 NAT 的一个外部 socket 经过 DNAT 重导进内部网路:
## 将外部的位址 1.2.3.4:80 重导至 192.168.1.1 上面去:
# iptables -t nat -A PREROUTING -d 1.2.3.4 \
-p tcp --dport 80 -j DNAT --to 192.168.1.1
然後再用内部网路的其它机器( 192.168.1.x) 尝试连接 NAT 这个外部 socket 。下面是我曾经做过的一个实验,兹整理出来供大家参考:
> 1,当客户端送出关於 HTTP (port 80) 的连线请求至 NAT 外部界面之後,也就是发出
> 一个带 SYN 的 TCP 封包,来源 port 为 1238,Sequence 为
> 43980464,Acknowledgement 为 0。
> 2,紧接著客户端就收到一个来自 NAT 内部界面的 ICMP 封包,类型为一个 Host
> Gateway 的 Redirect 封包,目的 IP 指向内部 server 的位址。
> 3,然後客户端改向内部 server 发出 SYN 的 TCP 封包,来源 port 依然为
> 1238,Sequence 也是 43980464, ACK 为 0。
> 4,接著客户端收到来自内部 server 的 ACK/SYN 封包,目的 port 为 1238,SEQ为
> 1579961125,ACK 为 43980465。
> 5,然而奇怪的事情来了:客户端却向内部 server 送出一个 Reset 封包,来源 port
> 为 1238,SEQ 为 43980465,ACK 和 SEQ 一样也是 43980465。
> 6,然後隔 6 秒後重复上述动作,之後再隔 12 秒重复,然後客户端出现不能连线错误
> 信息。
> 假如将 URL 改为直接向内部 server 查询,或是在 NAT 上启动另一界面於不同子网做
> 为 DMZ,再将 server 搬迁过去,其连线就不会再出现 ICMP 的 redirect,而且 TCP
> 的 SYN -- ACK/SYN -- ACK 这三段 handshake 都能顺利完成。
要了解这种现像的话,我们不妨追踪一下封包的传递过程:
内部主机将连线请求送至 NAT 主机;
然後 NAT 从 PREROUTING 规则中进行 DNAT ,将 Destination Socket 修改为内部 server 的位址;
修改後的封包经过路由判断发现目的地为同一网路的另一主机,於是还是经由内部界面送给内部 server ;
但因为发现这个封包的 source 与 destination 都在同一个 subnet ,认为後面的传递无需再经过本机。於是同时向 source 端送出一个 ICMP 封包,告诉它一个 Host Gateway Redirect 的信息,因为它们之间的传递有更好的路径 --- 就是直接传送。
内部 server 处理完封包之後,因为 source socket 并没修改,所以其回应就无需再经过 NAT ,而是直接送回 client 端。
但问题来了:这个回应封包的 source socket 却是内部 server 的,而不是 NAT 的外部 socket !但 client 所期待的回应封包,其 source socket 必须是它当初请求的 destination socket --- 也就是 NAT 的外部 socket 。就算 client 端已经获得 ICMP 的 redirect 通知,但事实上,socket 并没获得修正,而且 client 与 server 之间的连线依据:Sequence Number 与 Acknowledgement Number 也会发生冲突。因此,client 认为这个连线是非法的,於是就送出 RESET 封包,终止这个连线!
然而,client 端原本所其期待的回应却依然未获得。根据 TCP 协定的规范,client 在得不到回应的情况下,会相隔一段时间再重送请求,直至多次尝试均告失败为止。
然而,根据前面所设定的情况,这是一个无解的状态。於是连线永远无法建立。
要解决这个问题,在之前的 ipchains 年代中只能用 DNS 的欺骗手段来达成:也就是分开对外和对内的 DNS ,然後让内部网路使用内部的 DNS ,而这台 DNS 会将 IP 解释为内部位址(也就是让连线绕开 NAT 主机)。
不过,如果您使用 iptables 的话,可以参考 NAT HOWTO 之 第十章 的做法,同时修改内部封包的 Source Socket 就可以了:
# # 将内部封包的来源修改为 NAT 的内部界面(192.168.1.250):
# iptables -t nat -A POSTROUTING -d 192.168.1.1 -s 192.168.1.0/24 \
-p tcp --dport 80 -j SNAT --to 192.168.1.250
这样的话,内部 server 所回应的封包,将永远不会尝试直接将封包回给 client,而是回给 NAT 主机;既然 NAT 主机已经有这个连线的 NAT 记录,於是也能顺利的还原连线所期待的 socket 资讯。
关於 ICMP 的另一问题是:现在,我们的网路越来越不安全了,就算不使用 TCP/UDP 这些正常连线,许多无聊的骚扰者还可能透过 ICMP 连线进行干扰或捣乱。许多网路管理员在设定防火的时候,或许很单纯的将 ICMP 连线拦下来,比方说不想让别人来 ping 或 traceroute (请参考第一章“ 网路设定 ”)。
然而,事实上我们是不能单纯的不分青红皂白的将 ICMP 拦下来的。首先,如果我们需要对外 ping 的话,要将 echo-reply 的封包接收进来、要完成 tracertoute 的话,要将 time_exceeded 的封包接收进来。然而,更重要的,许多底层网路设备之间的通讯,都必须依靠 ICMP 的沟通来进行协调(事实上这也是 ICMP 最大的应用之一)。如果我们一概将 ICMP 拦掉,或许会在不经意之间导致某些连线出问题,这通常出现在那些需要 fragmentation 的封包上。
因此,一般而言,如下的 ICMP 封包我们需要确保它们能顺利的进出防火墙:
destination-unreachable
parameter-problem
source-quench
time-exceeded
echo-reply
怎样?您是否觉得我在文章一开始时说的话是正确的呢?我说:“要设定一个称职的火墙并不是一件容易的事情,它要求您有非常丰富的 TCP/IP 基础、和严密的逻辑头脑、还得加上无以复加的细密测试。”假如您对我刚才所解释的东西不能理解的话,那我还是建议您先补习 TCP/IP 基础之後再回来设定吧。关於更多的 iptables 技巧,请参考 Rusty 的原文 HOWTO 文件:
Linux 2.4 Packet Filtering HOWTO
Linux 2.4 NAT HOWTO
还有,小州兄的文章 关於 iptables 和 NAT 的一些技巧 也非常有助您迅速的掌握 iptables 的基本技巧。
设定封包过滤
当您具备了相当的 TCP/IP 基础知识、以及对 iptables 的使用有一定程度的熟识之後,接下来应该更好保护自己的网路了。您可以自己写一个类似下面的 script ,然後在开机的时候执行它。虽然还是挺简单的,但也应该难倒一般的骇客了吧:
#!/bin/bash
#
# Script name: ipt_masq
# A simple script for masquerading, used in Linux (kernel 2.4.x).
#
# Copyleft 2002 by netman (netman@study-area.org).
#
# Redistribution of this file is permitted under the terms of
# the GNU General Public License (GPL).
#
# Date: 2002/07/03
# Version: 1.3
PATH=/sbin:/usr/sbin:/bin:/usr/bin
RC_SQUID=/etc/rc.d/init.d/squid
EXT_IF=ppp0
INT_IF=eth0
ALLOWED_ICMP="0 3 3/4 4 11 12 14 16 18"
#
# ------------- ensure iptables ----------
which iptables &>/dev/null || {
echo
echo "$(basename $0): iptables program is not found."
echo " Please install the program first."
echo
exit 1
}
# ------------- disable ipchains ----------
lsmod | grep ipchains &>/dev/null && {
echo "Disabling ipchains..."
rmmod ipchains &>/dev/null
}
# ------------- modules -----------
echo "Loading modules..."
modprobe ip_tables &>/dev/null || {
echo -n "$(basename $0): loading ip_tables module failure."
echo " Please Fix it!"
exit 3
}
for file in /lib/modules/`uname -r`/kernel/net/ipv4/netfilter/ip_conntrack_*.o
do
module=$(basename $file)
modprobe ${module%.*} &>/dev/null
done
for file in /lib/modules/`uname -r`/kernel/net/ipv4/netfilter/ip_nat_*.o
do
module=$(basename $file)
modprobe ${module%.*} &>/dev/null
done
# ------------- ipforwarding -----------
echo "Turning on IP forwarding..."
echo "1" > /proc/sys/net/ipv4/ip_forward
# ------------- anti spoofing -----------
echo "Turning on anti-spoofing..."
for file in /proc/sys/net/ipv4/conf/*/rp_filter; do
echo "1" > $file
done
# ------------- flushing ----------
echo "Cleaning up..."
iptables -F -t filter
iptables -X -t filter
iptables -Z -t filter
iptables -F -t nat
iptables -X -t nat
iptables -Z -t nat
# ------------- policies -------------
echo "Setting up policies to ACCEPT..."
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -t nat -P PREROUTING ACCEPT
iptables -t nat -P POSTROUTING ACCEPT
iptables -t nat -P OUTPUT ACCEPT
# ------------- ICMP -------------
echo "Creating icmpfilter chain..."
iptables -N icmpfilter
for TYPE in $ALLOWED_ICMP; do
iptables -A icmpfilter -i $EXT_IF -p icmp \
--icmp-type $TYPE -j ACCEPT
done
# ------------- block -------------
echo "Creating block chain..."
iptables -N block
iptables -A block -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A block -m state --state NEW,INVALID -i $EXT_IF -j DROP
iptables -A block -m state --state NEW -i ! $EXT_IF -j ACCEPT
iptables -A block -j DROP
# ------------- filter -------------
echo "Filtering packets..."
iptables -A INPUT -j icmpfilter
iptables -A INPUT -j block
iptables -A FORWARD -j icmpfilter
iptables -A FORWARD -j block
# ------------- masq -------------
echo "Masquerading internel network..."
iptables -t nat -A POSTROUTING -o $EXT_IF -j MASQUERADE
# ------------- tproxy -------------
$RC_SQUID status | grep pid &>/dev/null && {
echo "Enabling transparent proxy..."
INT_IP=$(ifconfig | grep "$INT_IF " -A 1 \
| awk '/inet/ {print $2}' | sed -e s/addr\://)
if [ -z "$INT_IP" ]; then
echo
echo "$(basename $0): there is no IP found on $INT_IF."
echo " Please make sure $INT_IF is setup properly."
echo
exit 3
fi
iptables -t nat -A PREROUTING -d $INT_IP -i $INT_IF \
-p tcp -m tcp --dport 80 -j ACCEPT
iptables -t nat -A PREROUTING -i $INT_IF -p tcp -m tcp \
--dport 80 -j REDIRECT --to-ports 3128
}
exit 0
## EOS
(上面的 script 我将之取名为 ipt_masq ,您可以 点这里 下载使用。)
不过,您必须确定对外的界面必须是 ppp0 、对内地界面必须是 eth0,否则请自行修改。而且,上面这苹 script 是没有限制从里面到外面有哪些服务不能连接,如果您要限制的话,请自己增加 DROP 规则。
Tips:在我们设计防火墙的时候,通常有采用两种原则之一:
拒绝特定的,然後开放所有;
接受特定的,然後拒绝所有。
在我们前面的 script 中,虽然将 policy 设定为 ACCEPT ,但事实上,我们并非采用第一原则。因为,您仔细观测 block 链的设定,在最後面的一行规则,就是将所有连线 DROP 掉。所以,在这行拒绝所有的规则之前,我们必须先行将需要接受的连线 ACCEPT 进来。
您或许发现上面的 script 并没有对外开放任何服务,因为从外部界面进入的 NEW 连线都给 DROP 掉了。如果您需要在机器上提供相关服务,那您可以逐一加上,最後,您的 script 或许会长这个样子:
#!/bin/bash
#
# Script name: ipt_server
# A simple script for firewall, used in Linux (kernel 2.4.x),
# with certain services provided to outside world.
#
# Copyleft 2002 by netman (netman@study-area.org).
#
# Redistribution of this file is permitted under the terms of
# the GNU General Public License (GPL).
#
# Date: 2002/07/03
# Version: 1.4
PATH=/sbin:/usr/sbin:/bin:/usr/bin
RC_SQUID=/etc/rc.d/init.d/squid
EXT_IF=ppp0
INT_IF=eth0
TRUSTED_TCP_PORT="20 21 22 25 53 80 110 113 143 220 443 465 993 995"
TRUSTED_UDP_PORT="53"
ALLOWED_ICMP="0 3 3/4 4 11 12 14 16 18"
#
# ------------- ensure iptables ----------
which iptables &>/dev/null || {
echo
echo "$(basename $0): iptables program is not found."
echo " Please install the program first."
echo
exit 1
}
# ------------- disable ipchains ----------
lsmod | grep ipchains &>/dev/null && {
echo "Disabling ipchains..."
rmmod ipchains
}
# ------------- modules -----------
echo "Loading modules..."
modprobe ip_tables &>/dev/null || {
echo -n "$(basename $0): loading ip_tables module failure."
echo " Please Fix it!"
exit 3
}
for file in /lib/modules/`uname -r`/kernel/net/ipv4/netfilter/ip_conntrack_*.o
do
module=$(basename $file)
modprobe ${module%.*} &>/dev/null
done
for file in /lib/modules/`uname -r`/kernel/net/ipv4/netfilter/ip_nat_*.o
do
module=$(basename $file)
modprobe ${module%.*} &>/dev/null
done
# ------------- ipforwarding -----------
echo "Turning on IP forwarding..."
echo "1" > /proc/sys/net/ipv4/ip_forward
# ------------- anti spoofing -----------
echo "Turning on anti-spoofing..."
for file in /proc/sys/net/ipv4/conf/*/rp_filter; do
echo "1" > $file
done
# ------------- flushing ----------
echo "Cleaning up..."
iptables -F -t filter
iptables -X -t filter
iptables -Z -t filter
iptables -F -t nat
iptables -X -t nat
iptables -Z -t nat
# ------------- policies -------------
echo "Setting up policies to ACCEPT..."
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -t nat -P PREROUTING ACCEPT
iptables -t nat -P POSTROUTING ACCEPT
iptables -t nat -P OUTPUT ACCEPT
# ------------- ICMP -------------
echo "Creating icmpfilter chain..."
iptables -N icmpfilter
for TYPE in $ALLOWED_ICMP; do
iptables -A icmpfilter -i $EXT_IF -p icmp \
--icmp-type $TYPE -j ACCEPT
done
# ------------- services ------------
echo "Creating services chain...."
iptables -N services
for PORT in $TRUSTED_TCP_PORT; do
iptables -A services -i $EXT_IF -p tcp --dport $PORT -j ACCEPT
done
for PORT in $TRUSTED_UDP_PORT; do
iptables -A services -i $EXT_IF -p udp --dport $PORT -j ACCEPT
done
# ------------- block -------------
echo "Creating block chain..."
iptables -N block
iptables -A block -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A block -m state --state NEW -i ! $EXT_IF -j ACCEPT
iptables -A block -j DROP
# ------------- filter -------------
echo "Filtering packets..."
iptables -A INPUT -j icmpfilter
iptables -A INPUT -j services
iptables -A INPUT -j block
iptables -A FORWARD -j icmpfilter
iptables -A FORWARD -j block
# ------------- masq -------------
echo "Masquerading internel network..."
iptables -t nat -A POSTROUTING -o $EXT_IF -j MASQUERADE
# ------------- tproxy -------------
$RC_SQUID status | grep pid &>/dev/null && {
echo "Enabling transparent proxy...."
INT_IP=$(ifconfig | grep $INT_IF -A 1 \
| awk '/inet/ {print $2}' | sed -e s/addr\://)
if [ -z "$INT_IP" ]; then
echo
echo "$(basename $0): there is no IP found on $INT_IF."
echo " Please make sure $INT_IF is setup properly."
echo
exit 3
fi
iptables -t nat -A PREROUTING -d $INT_IP -i $INT_IF \
-p tcp -m tcp --dport 80 -j ACCEPT
iptables -t nat -A PREROUTING -i $INT_IF -p tcp -m tcp \
--dport 80 -j REDIRECT --to-ports 3128
}
exit 0
## EOS
(上面的 script 我将之取名为 ipt_server ,您可以 点这里 下载使用,您只需找到您想要提供的服务之 port number ,然後修改 script 前面的 TRUSTED/UNTRUSTED_PORT 变数就可以了。)
保存设定
刚才介绍的规则,您可以在任何时候执行它,这样就能够将防火墙设定起来,并能为内部网路提供 IP 伪装服务了。但是,如果您重新开机的话,那麽这些规则会全部还原为 ACCEPT ,也就是您必须再次执行这个个 script 才能重新将设定要回来。
那麽,您或许想知道如何在开机的时候如何自动的将防火墙设定起来吧?
事实上,要做到这个要求是有很多方法。聪明的您或许会首先打 /etc/rc.d/rc.local 的主意吧? ^_^ 没错,您甚至可以将整个 script 的内容(第一行除外)加进 rc.local 的最後面去。不过,更聪明的您应该会将 script 存放在一个目录下面,加上 +x 权限,然後在 rc.local 中将该 script 的路径加上去就好了。
那是否有更好的方法呢?有的!iptables 套件本身就有提供一个非常好用的工具:iptables-save 与 iptables-restore 。您可以在任何时候执行 iptables-save ?