如何用老奔腾机和Linux构建防火墙
时间:2007-02-17 来源:PHP爱好者
Rawn Shah
技术专家和自由撰稿人
内容:
嘿,我在说您那台老旧的奔腾计算机呢!别因为老奔腾机不如现今花哨的新硬件动力十足就把它抛弃。它完全可以胜任小型商业或家庭办公室的网络防火墙工作。ipchains 是 Linux 下的防火墙软件,其源码可以免费获得,并能够在老式奔腾机的 T-1 连网环境下工作。与其花上几千美元从软件提供商处购买定制的防火墙软件,还不如试试这种最实惠的解决方案。下面,我将一步步地教您如何安装和配置 ipchains 软件。
老式奔腾机并不只是堵门搁脚的垃圾,它们仍可用来计算!实际上,一个装有 32MB 内存和 200-400MB 硬盘的的小机器就可以胜任小型办公网络防火墙的任务。 Linux 的紧凑版本只含有重要的系统应用程序,而没有图形用户接口和用户应用程序。安装了紧凑版本的 Linux 之后,您就可以漂亮地把老机器变成防火墙了。
完成这些只需要一个叫 ipchains 的公开源码的软件包,它是由 Paul "Rusty" Russell 提供的。这个软件具备了许多商业防火墙产品的特征:允许自定义网络通信量的流向,及哪些访问者可以获准进出。
早期的 ipfwadm (IP Firewall Administration) 可以运行在 2.0.X 及更低的版本的 Linux 上。后来 ipchains 取代了 ipfwadm,且能工作在所有 2.1.x 和 2.2.x 版本的 Linux 下。随着内核升级,它还将升级到 2.3.x 下的 netfilter 或其他更高版本。这么频繁更新的原因在于,防火墙的数据包过滤是基于内核本身的,因此防火墙软件就必须亦步亦趋地紧随内核变动。本文讨论了 ipchains 是什么,它能做什么以及如何应用于不同场合。
Ipchains 和防火墙
ipchains 本质上是包过滤器。它检查到达网络接口的 IP 包,根据事先定义好的规则进行修改,然后再转发给其它接口。
每个 IP 包都含有报头 (header),里面含有该包的目的地、及如何处理等控制信息(参见下图)。需要传送的数据则放在有效段(或称为包体)中。通常情况下,有效段可以包含更高一级的包。例如,一个 TCP 包就总是包含于 IP 包的有效段中,该 TCP 包拥有自己的报头和有效段。我们将会看到,ipchains 软件可以改变 IP 报头、TCP 报头、UDP 报头(未显示)和 ICMP 报头内的一些域值.
ipchains 的名称来自其工作特点。它能够创建合理过滤步骤,根据用户定义的规则来处理包。这些步骤被"链接"在一起来创建包处理的完整规则体系。这个处理"链条"可以与具体的 IP 地址,或者网络地址相结合。如下所示,系统中可以有很多这样的"链条"来处理每个进入的 IP 包:
运行 ipchains 的机器可以拥有许多网络接口,每个接口都连接在不同的网络上。任何可用的防火墙至少应有两个独立的接口,一个连到内部网络,另一个连到外部网络。数据包从一个接口进入,经由过滤"链条" 传给另一个接口。
最简单的情况下,ipchains 只执行三种策略:接受、和拒绝。它能接受来自指定 IP 地址或网络的所有包,否决策略丢弃来自特定地方的所有包。拒绝策略则丢弃来自指定源头的包,并且通知该源头其请求的连接被拒绝.
有三类基本的链:输入链、正向链和输出链。输入和输出链分别处理对应于进入和流出接口的包的执行策略。正向链直接将通信量传送给另一机器(在其执行了输入链后)。这里的"另一"机器多数情况下是路由器。ipchains 本身并不希望成为完全的路由引擎,所以它把包转交给本机上的真正的软件路由器或者另一节点上的硬件路由器。完整的ipchains引擎处理流程图如下所示:
防火墙是可以那些能够操纵输入数据流和选择可流出数据流的设备的通称。在执行过程中,它可能会改变输入或输入数据流的状态,以此隐藏机器的具体信息或防止非法入侵。
ipchains 有两种运行方式:代理服务器和网络地址转换器。前者接收来自受防火墙保护的网络内部机器的数据流,使用用户定义的规则对其进行过滤处理,然后发送给外部网络。总之,内部哪些机器可以访问外部网络是由代理服务器控制的,反之亦然。
当您没有足够的网络地址,或者不愿对您的内部网络使用公共因特网址时,可以采用网络地址转换器 (NAT) 或是 IP 屏蔽 (Masqing) 的方法。它能把内部私有的地址转换成合法的公共地址,即将多个内部地址映射成唯一的外部地址。这样就不能从外部网络上直接访问到内部网络的某个机器,从而达到保护内部机器的目的。
安装防火墙
把一台 linux 机器配置成防火墙不是那么容易的。您应该重新安装稳定的 Linux 版本(即并非最新最强大的版本,可以试试 2.2.12),而不是转变已有的 Linux 机器。在安装过程中,最好只安装那些最基本的系统组件。除非不得已的情况,一般不要安装网络服务器、NFS服务器或者编译程序等其他组件,甚至不能运行Telnet服务器。如果您必须通过网络登录到本机,那就安装"SSH Secure Shell" (ssh) 远程登录系统吧。总之需要记住,防火墙永远不应该执行除了处理包和网络安全之外的任务。
您使用的 Linux 中可能附带着 ipchains 软件。如果您是偏执狂,可以在确认主要的Linux提供商已经认证其合格后,才使用该版本。
首先要做的事情是确认您的机器上是否已经安装了防火墙。请查看 /etc/rc.d/init.d 目录下有没有类似于 packetfilter、ipchains、pifwadm、firewall 或 proxy 等名字的"开始/关闭"脚本。如果存在这样的文件,并且有来自 /etc/rc.d/rc2.d 或 /etc/rc.d/rc3.d 的符号连接,那么该机器上很可能已安装了防火墙,也许它还正运行在没有任何具体防火墙策略的环境下呢。要验证这一点,可以敲入如下命令:
# cat /proc/sys/net/ipv4/ip_forward
如果找不到这个文件,说明防火墙尚未安装。如果返回值为0,则意味着防火墙已经安装但没有运行;返回值为1表示正在运行。
您也可以下载 ipchains 的源码来自己编译。这么的时候,要运行校验和程序来确认这份源码是可靠的。(通常采用 md5sum 命令)。ipchains 网站上列出了校验和的有效值,您校验的结果应该和它们完全匹配。当前的 ipchains 版本是 1.3.9。
如果这些值全部无法匹配,就得查查编译内核时是否把 Masquerading 打开了。缺省情况下,这些选项在编译内核时是打开的。但如果您决定自己来编译 Linux 2.2.x 版本的内核,就必须对如下选项说 YES :
CONFIG_EXPERIMENTAL
CONFIG_MODULES
CONFIG_NET
CONFIG_FIREWALL
CONFIG_INET
CONFIG_IP_FORWARD
CONFIG_IP_MASQUERADE
CONFIG_IP_MASQUERADE_IPPORTFW
CONFIG_IP_MASQUERADE_IPAUTOFW
CONFIG_IP_MASQUERADE_ICMP
CONFIG_IP_ALWAYS_DEFRAG
CONFIG_DUMMY
CONFIG_IP_MASQUERADE_MFW
内核编译完毕,就要把防火墙安装为系统服务的一项。这样系统初始化时就会启动它。系统服务是系统启动过程中由系统初始进程执行的,不同的系统服务有不同的初始化优先级别。级别 0 表示系统完全卸载可以关断电源;级别 1 则表示没有网络支持的单用户模式;级别 2 和 3 一般用作网络支持的多用户模式,也就是多数机器的运行模式。还有一些其他的级别,但都不常用。
为了创建防火墙服务的开始脚本并安装在级别 2 和 3 下,首先要如下所示创建 ipchains 的系统"开始/关闭"脚本文件,并把它保存在 /etc/rc.d/init.d 或 /etc/init.d/ 目录下(保存在哪个目录据所使用 Linux 版本而定)。
#!/bin/sh
# Firewalling with ipchains, startup/shutdown script
PATH=/sbin:/bin:/usr/sbin:/usr/bin
[ -f /etc/packetfiler.rules ] || exit 0
case "$1" in
start)
echo -n "Starting ipchains firewall:"
/sbin/depmod -a
# Comment the following line if you do not intend to support
# using FTP through the firewall
/sbin/modprobe ip_masq_ftp
# Insert lines to support other application protocols here
/sbin/ipchains-restore </etc/firewall.rules || exit 1
echo "1" > /proc/sys/net/ipv4/ip_forward
# If you do NOT use PPP, SLIP or DHCP for any of your network
# interfaces on this machine, comment the following line
echo "1" > /proc/sys/net/ipv4/ip_dynaddr
echo "."
;;
stop)
echo -n "Shutting down ipchains firewall:"
echo "0" > /proc/sys/net/ipv4/ip_forward
# Comment the following line if you do NOT use PPP, SLIP or DHCP
echo "0" > /proc/sys/net/ipv4/ip_dynaddr
/sbin/ipchains -X
/sbin/ipchains -F
/sbin/ipchains -P input ACCEPT
/sbin/ipchains -P output ACCEPT
/sbin/ipchains -P forward ACCEPT
echo "."
;;
*)
echo "Usage: /etc/rc.d/init.d/packetfilter {start|stop}"
exit 1
;;
esac
exit 0
在要从不同级别执行包过滤服务之前,您得建立符号链接。网络已经启动的情况下,首先确认包过滤器已运行在初始级别2和3下。
# ln -s /etc/rc.d/init.d/packetfilter /etc/rc.d/rc2.d/S09packetfilter
# ln -s /etc/rc.d/init.d/packetfilter /etc/rc.d/rc3.d/S09packetfilter
以上两条命令建立了符号链接,从而保证在 Red Hat Linux 的网络有效前启动包过滤器。某种网络服务的名称应该类似于 SXXnetwork,其中的 "XX" 指定了该服务的启动次序。请选择一个其他启动脚本内未指定的,比所有网络服务启动次序号都低的"XX"。上面的例子中,网络启动脚本命名为 "S10network" 。
类似的,在网络服务关闭后也应该停止包过滤器的符号连接。该例子中,网络接口关闭位于脚本 K90network,因此包过滤器脚本链接为 K91packetfilter。
# ln -s /etc/rc.d/init.d/packetfilter /etc/rc.d/rc0.d/K91packetfilter
# ln -s /etc/rc.d/init.d/packetfilter /etc/rc.d/rc1.d/K91packetfilter
最后,在 Red Hat 系统中,您务必确保文件 /etc/sysconfig/network 中含有下列条目:
FORWARD_IPV4="yes"
一旦重新启动机器,包过滤器就启动了。到目前为止,您还没有制定任何过滤规则。下面我们将继续这个规则设置步骤。
配置防火墙
如果您用了防火墙,就可以使用公共因特网上无效的某段网址,或者用 NAT 。这样,主干网的主要路由器将视那些来自私人地址的包为非法而予以丢弃。这些段址已经定义并在文献 RFC 1918 列举出来(参看表 1)。这样的段址共有三套,但对于绝大多数公司来说,192.168.0.0 就足够了。在下面的例子中我们也将采用这套段址。
表 1: 分配给私有网络的 IP 地址
起始地址
结束地址
CIDR 段址前缀
地址数目
10.0.0.0
10.255.255.255
10.0.0.0/8
~ 16,000,000
172.16.0.0
172.31.255.255
172.16.0.0/12
~ 4,000,000
192.168.0.0
192.168.255.255
192.168.0.0/16
~ 65,000
您的内部网的全部机器都应该用这些给定地址范围来标识。比如 192.168.1.22 防火墙的内部接口应占用第一个地址 192.168.0.1。以 .0.0、.0、.255 和 .255.255 结尾的是系统保留地址,所以不要把他们分配给任何机器。在起始地址和结束地址范围之内的其他 IP 地址可按个人喜好分配.
下面的例子包括四个台式机和四个服务器。名为 stranger 的台式机禁止进入防火墙保护的内部网络,而 familiar 则可以。名为 Freedom 的服务器位于公司内部网络,但不受防火墙保护,这对向公开的网站来说是常见的。分配给这些机器的网络地址是随便给定的,但是它们被划分成不同的网络地址集合 (192.168.x.x 分配给防火墙内部网, 216.19.15.x 分配给防火墙外部但仍处于公司内部的网络, 198.102.x.x 指的是公共因特网址)。
表 2: 链操作命令
命令
陈述
ipchains -N
创建一个新链
ipchains -X
删除已有链
ipchains -L
列出链中的所有规则
ipchains -F
删除链中的所有规则,但仍保留该链
ipchains -Z
删除与链相关的统计数据
ipchains -P
设置链的全部策略(接受 ACCEPT, 否决 DENY, 拒绝 REJECT, 屏蔽 MASQ, 改向 REDIRECT, 返回 RETURN 等)
ipchains -M -L
列出所有使用屏蔽的链
ipchains -M -S
对所有使用屏蔽的链,根据协议不同而分配若干超时限制
表 3: 定义过滤规则的命令
命令
陈述
ipchains -A
将规则添加到链中
ipchains -D
根据规则在链中的位置,删除该规则
ipchains -R
根据规则在链中的位置,用新规则取代它
ipchains -I
将新规则插入到链中的指定位置
ipchains -C
使用链中的规则来过滤 protocol-info 中的内容,从而测试该链
表 3 中的过滤规则命令需要指定源和目的地址、协议类型和相关的端口号、其他IP协议的标志、以及所施行于它们的规则。这些规则条目可以逐一列出,以便或含糊或准确的描述被过滤掉的包的类型。
源地址 (-s) 和目的地址 (-d) 具有相同的格式。您可以具体指定单个主机地址或某网络的一组主机地址。前一种情况,只需要插入主机地址;后一种情况则需要指定网络地址和网络地址掩码或比特掩码。比特掩码和网络掩码的作用是一样的,不同于网络掩码给定四组十进制数,它从网络掩码的左端开始计入比特数。例如,网络掩码 255.255.255.0 等效于比特掩码 24,255.255.224.0 等效于 19。不方便的是,您不能指定任意范围的网络地址,比如从 192.168.1.1 到 192.168.1.133 —— 因为您无法用合适的网络掩码来表示它们。这个软件不得不这么工作,因为想要使它有效地过滤任意一段网络地址实在是太困难了。
规则
陈述
-d 192.168.1.24 -j ACCEPT
接受所有发送到 192.16.1.24 (Jane) 的包
-d 192.168.0.0/255.255.224.0 -j DENY
拒绝所有试图到达 192.168.1.0 到 192.168.31.255 范围中某个地址的数据包,这就意味着 Bob, Fred 和 Jane 的主机都被保护起来,而 Olivia 则没有。
-d 192.168.0.0/19 -j DENY
和上一命令相同,只是用比特掩码来表示范围的结束地址。
上面的例子展示了目的地址的细节(用 -d ),但和源地址的用法是相同的(只是代之以 -s )。下一个策略组件是协议类型。ipchains 支持三种常用的传输协议类型:TCP、UDP 和 ICMP。其他协议仍处于试用阶段,这里略过不谈。
-p TCP -s 192.168.1.24 -j ACCEPT
接受所有来自 Jane (192.168.1.24) 的包
-p TCP -d 192.168.0.0/19 -j DENY
否决所有到达 Bob, Fred 和 Jane 的 TCP 包
-p TCP -d 192.168.1.24 80 -j ACCEPT
接受到达 Jane 机器80端口的所有 TCP 包
-p TCP -d 192.168.1.24 www -j ACCEPT
和上一命令类似,但用 www 来表示缺省的 Web 服务器端口。在 /etc/services 文件中定义了此端口。
-p TCP -d 192.168.1.24 0:1024 -j DENY
否决所有要到 Jane 的0到1024范围中任何一个端口的 TCP 包。
-p TCP -d 192.168.1.24 www -j ACCEPT
接收要到 Jane 机器上 www 端口(缺省情况下即80端口)的所有 TCP 包。
-p TCP -d 192.168.1.24 ! www -j DENY
D 否决要到 Jane 机器上 www 之外任何端口的 TCP 包。命令中的感叹后(!)表示对它后面的值取非,这里即用 ! www 来表示除了 www 之外的所有端口值。
-p TCP -d ! 192.168.1.24 www -j DENY
否决要到除了 Jane 之外的其他机器 www 端口的所有 TCP 包。注意这条命令与上一条并不相同。
-p TCP -d ! 192.168.1.24 ! www -j DENY
否决所有要到 Jane 之外的其他机器,并且非 www 端口的 TCP 包。也就是说,它允许那些到达 Jane 的 www 端口的 TCP 包。尽量不要用这样的规则,以免引起混乱。
-p ! TCP -d 192.168.1.24 -j DENY
否决所有要到 Jane 机器上的 TCP 之外的数据包。
对于 UDP 协议,其工作情况类似。而对于支持 ping 和 traceroute 的 ICMP 协议则需要指定端口.
-p ICMP -d 192.168.1.24 0 -j ACCEPT
-p ICMP -d 192.168.1.24 8 -j ACCEPT
接受对 Jane 的 ping 包。为了让 ping 正常工作,这两条规则(ICMP 端口0 和 8)必须分开写。
-p ICMP -d 192.168.1.24 3 -j ACCEPT
如果 Jane 机器上的用户试图访问一个远程站点,所返回的“无法到达” ICMP 消息允许通过。访问一些远程机器的 telnet 或其他服务时,常有这种没有回应的情况发生,因此通常都会使用这条规则。
-p ICMP -d 192.168.1.24 11 -j DENY
否决来自防火墙外部 traceroute 命令的数据包。
除了地址和协议信息,您还可以指定通过包的网络接口驱动程序。如果要加入考虑数据包的来源,是比较容易的,在指定过滤规则应用的网络端口时也很有用。
-i eth0 -s 192.168.1.24 -j ACCEPT
使用第一块以太网卡 (eth0) 来接受所有来自 Jane 的数据包。
-i eth+ -s 192.168.1.24 -j ACCEPT
可以用任何一块网卡 (eth+) 来接受来自 Jane 的数据包。
-i ppp0 -p TCP -s 198.102.68.2 1025:65535 -j ACCEPT
通过 ppp 接口,来接受所有来自 familiar (198.102.68.2) 1025 到 65535 端口之间的 TCP 包。
有两种特殊的策略组件指定 TCP_SYN 标志 (-y) 和碎片包 (-f)。当 TCP 客户端第一次试图连接远程 TCP 服务器时,TCP_SYN 标志被设置。不幸的是,黑客会利用它攻击远程服务器,其做法是发送大量 TCP_SYN 消息而不真正连接服务器,从而锁住所有服务器进程,使其速度变慢或使服务器崩溃。只要为该组件指定 TCP 组件 -p,就可以阻止外部的 TCP_SYN 攻击。
碎片包过滤规则组件是必要的,因为 IP 包常常通过不同容量的的网络连接。有时他们不得不分成一些名为碎片的更小的包以便顺利通过小容量的连接通道。在 PPP 协议下用调制解调器连接网络时常发生这种情况。例如,以太网可以传送长为 1500 字节的 IP 包,而调制解调器(串行线)的包通常是 256 字节;以太网量级的包不得不分成六份以符合调制解调器量级的碎片包。到达目的机器后,这些碎片包就被组合起来。因此,在 PPP 或 SLIP 协议下使用调制解调器连接网络时,您应该指定这个规则组件,这样防火墙才会把包视作一体。也只有如此它才能正确辨识传输层协议( TCP, UDP, ICMP) 信息,从而实施其他规则。
在命令行下运行以上规则,并用 /sbin/ipchains -L 命令检查其顺序是否正确。接着还要按照"开始/关闭"脚本指定的文件名来保存这些规则(我们的例子中,该文件是 /etc/firewall.rules )。下次系统启动时就会使用这些规则了。需要注意的是,每次您定义新规则都必须运行以下命令:
# /sbin/ipchains-save > /etc/firewall.rules
接下来给出一些配置实例,您可以看到如何为防火墙制定规则。
一些配置实例
这个例子是最简单的防火墙模型,只允许位于保护范围内的机器访问外部网络,但禁止任何外部机器进入内部网络。您首先要拦截由任何接口流出的所有包,然后只让那些具有转换过的地址信息的包通过。
# /sbin/ipchains -P forward DENY
# /sbin/ipchains -A forward -f -i ppp0 -j MASQ
采用以上两个规则,则在包流出之前,所有安装了防火墙的机器(Bob,Fred,Jane 和 Olivia)都被屏蔽了。同时禁止防火墙外部的机器接触任何内部设备。这里并没有涉及路由器,实际上本例中的防火墙是通过调制解调器建立到因特网 PPP 连接的。
嵌入路由器的防火墙代码并不少见。很多时候,防火墙和路由器可能位于同一机器。另一方面,利用 PPP 的调制解调器方案,防火墙就成为路由器,路由指令把所有非本地数据流转发到 PPP 端口。
您可能希望防火墙的功能复杂些,例如所有保护范围内的机器都可连到外部去,而只有指定的外部机器才能连接进来。下面的例子准许名为 familiar 的机器 (198.102.68.2) 连接到防火墙内部机器的某些指定端口 (telnet、FTP 和 WWW 等服务的公共端口)。
# /sbin/ipchains -P forward DENY
# /sbin/ipchains -A forward -f -i eth0 -j MASQ
# /sbin/ipchains -A input -i eth0 -s 198.102.68.2 -j ACCEPT
# /sbin/ipchains -A output -i eth1 -s 198.102.68.2 -d 0/0 -j DENY
# /sbin/ipchains -A output -i eth1 -s 198.102.68.2 -d 0/0 telnet -j MASQ
# /sbin/ipchains -A output -i eth1 -s 198.102.68.2 -d 0/0 ftp -j MASQ
# /sbin/ipchains -A output -i eth1 -s 198.102.68.2 -d 0/0 ftp-data -j MASQ
# /sbin/ipchains -A output --i eth1 s 198.102.68.2 -d 0/0 www -j MASQ
在我们的例子中,有台名为 Freedom 的服务器位于防火墙网络外但处于公司网络内部。这是一台任何机器都可达的公共网页服务器,它和互联网上其他计算机一样,可能遭受外部世界的攻击和破坏。不过我们可以假定它足够安全,并允许其连接到防火墙内部网络。这就需要添加下列规则:
# /sbin/ipchains -A input -i eth0 -s 216.19.15.32 -j ACCEPT
# /sbin/ipchains -A output -i eth1 -s 216.19.15.32 -d 0/0 -j MASQ
应用协议
有些应用要求协议包含正确的主机地址信息,否则无法工作。这些应用可能会将网络或主机地址信息嵌入到 TCP 或 UDP 包里,但包过滤器一般不深入探究这些包中的内容。
如此一来,为了过滤这些应用协议内核还需要加载另外的模块。这些模块包含某些协议,能够支持 FTP、RealAudio、IRC、VDO Live、CU SeeMe 甚至某些流行的游戏 Quake 等。
为了加载这些模块,可以在先前的"开始/关闭"脚本中加入以下用于特殊协议的代码行:
/sbin/modprobe ip_masq_raudio
/sbin/modprobe ip_masq_irc
/sbin/modprobe ip_masq_cuseeme
/sbin/modprobe ip_masq_vdolive
/sbin/modprobe ip_masq_quake
/sbin/modprobe ports=ip_masq_quake 26000,27000,27910
ipchains 具有十分强大的功能,可以把几组规则一次"链接"。当您需要面对许多内部节点,并为它们分别定义网络数据包策略时,这种建立多链条的能力就更加重要。这些链条被指派给特定的网络接口,改变接口开关的安全规则(例如网络连接接通或断开)。
ipchains 将继续升级。随着下一内核版本 (2.3.x 和 2.4.x) 一起发布 ipchains 新版本,名为netfilter。新版本软件将能执行一些设计精巧的组件,例如逆向网络地址转换 (reverse NATs),用于使一组内部网机器为某个地址进行负载平衡。
一般情况下,有效使用 ipchains 可以让您的内部网络细节受到防火墙良好的保护。它赋予您那台"慢吞吞"的老机器以崭新的使命,变成网络里相当有用的一员。
资料
ipchains 主页和下载站点
Rusty Russell 的 ipchains 指南(写得稍有些复杂)
Rusty Russell 的一些防火墙项目
"Securing Your Linux Box," Linux Gazette, 1998年11月
"Securing Linux, Part 1," in LinuxWorld 杂志,1999年5月
"Securing Linux, Part 2," in LinuxWorld 杂志,1999年7月
关于作者
Rawn Shah,是一名网络管理员、系统程序员、网络架构专家和因特网服务提供商。从 1993 年起他就为十来家以英语及其他语言发行的因特网杂志撰写文章。最近刚写完名为《UNIX和Windows 2000集成的套装工具包》(UNIX and Windows 2000 Integration Toolkit, John-Wiley &Sons,1999) 的书籍。现在他是亚利桑那州 Tucson 的一名技术专家,负责撰写有关网络和交叉平台集成的专题文章。您可以通过 [email protected] 与他联系。
php爱好者站 http://www.phpfans.net 文章|教程|下载|源码|论坛.