<?xml version="1.0" encoding="gb2312"?>
<rss version="2.0">
   <channel>
      <title>CNFUG期刊</title>
      <link>/journal/</link>
      <description></description>
      <language>en</language>
      <copyright>Copyright 2006</copyright>
      <lastBuildDate>Wed, 07 Jun 2006 15:15:03 +0800</lastBuildDate>
      <generator>http://www.sixapart.com/movabletype/?v=3.2</generator>
      <docs>http://blogs.law.harvard.edu/tech/rss</docs> 

            <item>
         <title>使用OpenBSD自带的ftpd程序搭建ftp服务器</title>
         <description><![CDATA[<p>张煌彬</p>

<h5>写在前面</h5>

<p>本文参考OpenBSD 3.8的官方FAQ文档和ftpd的man文档写成，更全面的信息请参看这两个文件。<br />
·FAQ: Setting up Anonymous FTP Services<br />
·Manual Page: ftpd<br />
所有操作均在OpenBSD 3.8 Release上测试通过。<br />
注：本文可以任意转载，但请保留作者信息，谢谢。</p>

<h5>ftpd的三种启动方式</h5>

<p>OpenBSD的ftpd程序没有配置文件，就靠设置运行参数进行配置。所有参数的<br />
含义都可以在ftpd 的man文档中获得详细信息：<br />
$ man ftpd<br />
ftpd程序的启动有三种方法：<br />
·inetd方式<br />
·'rc'方式<br />
·直接在命令行下执行ftpd命令</p>

<p>1、inetd方式</p>

<p>在'etc/inetd.conf'文件中有这么一行：<br />
<div class="code">ftp stream tcp nowait root /usr/libexec/ftpd ftpd -US</div><br />
这里为'ftpd'传递了两个参数'-US'。当然，你还可以组合自己想要的参数。<br />
（一些常用参数的含义已在下文给出）。使用inetd方式，ftpd由inetd进程管<br />
理，所以需要启动inetd服务才能启动ftpd。在OpenBSD的默认设置中，inetd被<br />
配置为随系统启动。查看'/etc/rc.conf'中的'inetd'变量的值:<br />
<div class="code">inetd=YES</div></p>

<p>2、'rc'方式</p>

<p>所谓'rc'方式，指的就是通过修改'/etc/rc.conf'或者'/etc/rc.conf.local'文件中变<br />
量的值，为服务的启动添加'entry'，使服务能够随系统启动。使用'rc'方式启<br />
动fttpd，只需要将'/etc/rc.conf'中的'ftpd_flags'变量设置为自己组合好的参数<br />
即可。这种方法需要在系统重启后服务才会随系统启动（这里的参数暂时使用<br />
与'inetd'方式相同的参数）：<br />
<div class="code">ftpd_flags="-US"</div></p>

<p>3、直接在命令行执行ftpd命令</p>

<p>这种方法的好处是不需要重新启动系统就可以启动ftpd服务：<br />
<div class="code"># /usr/libexec/ftpd -4DllUS</div></p>

<h5>停止ftpd</h5>

<p>1、'/etc/rc.conf'</p>

<p>如果不需要ftpd随系统启动，则在'/etc/rc.conf'文件中将'ftpd_flags'变量的值设<br />
置为"NO"”：<br />
<div class="code">ftpd_flags="NO"</div></p>

<p>2、# kill PID<br />
如果需要现在就结束ftpd进程，则使用\kill PID"的方式来实现：<br />
<div class="code"># kill $(cat /var/run/ftpd.pid)</div></p>

<h5>ftpd常用参数</h5>

<p>以下是ftpd 常用的一些参数（这里列出的只是常用的，并不是全部。更多的参<br />
数请查看OpenBSD 的Manual Page。</p>

<p>· -4 如果指定了\-D"参数，则强制ftpd 只使用IPv4地址。<br />
· -6 和\-4"的解释类似；如果指定了\-D"参数，则强制ftpd只使用IPv6地址。<br />
· -A 只允许匿名登录（除非指定了\-n"选项）。<br />
· -D 如果指定了该参数，ftpd 将做为daemon运行，监听ftpd 端口并且fork子进程对连接进行处理。在繁忙的服务器上，这样可以减少系统负载，与使用inetd 方式启动ftpd比起来，这种方式使用更少的系统资源。<br />
· -d 使用LOG FTP将Debug信息写入syslog。<br />
· -l 每个成功和失败的ftp session 都将由LOG FTP 工具通过syslog记录下日志。如果这个选项被指定两次（-ll），所有get/put/append/delete/make、directory/remove、dire操作以及所操作的文件都将被记录进日志。<br />
· -U 每个并发的ftp session都被记录到日志文件/var/run/utmp，记录的格式就象who(1)命令的输出一样。<br />
· -n 禁止匿名登录。默认是允许的。<br />
· -S 如果设置了这个参数，ftpd 将会把所有匿名用户的下载情况记录在文件/var/log/ftpd 中（如果该文件存在的话）。<br />
· -T maxtimeout 连接超时的时间限制。默认是2小时。<br />
· -u mask 强制设置umask为指定的mask。而不是使用/etc/login.conf 中的设置（/etc/login.conf 中通常设置为022），并且不允许chmod。</p>

<h5>常用参数组合</h5>

<p>看了上面几个常用选项的含义，你应该可以组合出合适的选项来满足自己的功能需求了。</p>

<p>1、只允许使用系统帐号登录FTP<br />
因为匿名用户登录需要使用到系统中的一个名为"ftp"的帐户（更多关于该帐户的描述，请看本文下半部分），而OpenBSD系统中默认没有该帐户，需要手动建立并且设置正确的权限，所以如果只允许用户通过系统帐号登录FTP服务器，则只需要在/etc/rc.conf 中将ftpd °ags 的值简单地设置为"-D"即可（虽然这时候的设置仍然允许匿名用户登录，但是因为系统中没有"ftp"用户，所以无法登录）。当然你也可以多设置一些参数。比如：<br />
<div class="code">ftpd °ags="-4DllUSn"</div><br />
参考上面几个常用选项的说明，你就会明白这是什么意思了。<br />
2、允许匿名用户登录访问FTP资源<br />
因为匿名用户登录到FTP服务器后，实际上是以\ftp"用户的身份进行所有操作，所以出于安全考虑，这个用户的权限通常被设置得很低。比如：<br />
· 不为该用户提供一个可用的shell，使其无法登录系统；<br />
· 没有一个可用的密码（即FAQ上说的"This account shouldn't have a usable password；");<br />
· 登录系统后被chroot；<br />
· ......</p>

<h5>设置用于匿名访问FTP资源的'ftp'帐户</h5>

<p>接下来我们要做的就是按照上面列出的三个要求来添加和设置这个低权限、处处受限的"ftp"用户。<br />
1、添加"ftp"帐户以提供匿名访问<br />
在/etc/shells中添加一个无法实际使用的shell设置"ftp"使用这个shell的目的，是不允许它通过shell登录到系统中。通常我们会有两种选择：<br />
· /sbin/nologin<br />
· /usr/bin/false<br />
为了在添加用户时可以使用这两个shell，我们可以这样做：<br />
<div class="code"># echo '/sbin/nologin' >> /etc/shells<br />
# echo '/usr/bin/false' >> /etc/shells<br />
</div><br />
或者在未将它们加入到/etc/shells的情况下，在使用adduser添加帐户时加上"-shell"参数：<br />
<div class="code"># adduser -shell /sbin/nologin<br />
Enter username []: ftp<br />
Enter full name []: anonymous ftpd user<br />
Enter shell bash csh ksh nologin sh [/sbin/nologin]:<br />
......<br />
</div><br />
这里在询问该用户使用何种shell时就出现了/sbin/nologin 。如果不带这个参<br />
数，将无法使用它：<br />
<div class="code"># adduser<br />
Enter username []: ftp<br />
Enter full name []: anonymous ftpd user<br />
Enter shell bash csh ksh nologin sh [bash]: /sbin/nologin<br />
/sbin/nologin: is not allowed!<br />
Enter shell bash csh ksh nologin sh [bash]:<br />
</div><br />
这里就提示了不允许使用/sbin/nologin 做为shell使用。</p>

<p>添加帐户</p>

<p>这里使用"adduser -s shell /sbin/nologin"来添加一个这样的'ftp'帐户做为示例，并将该用户的$HOME目录设置为"/var/ftp"：<br />
<div class="code"># adduser -shell /sbin/nologin -home /var<br />
Use option \-silent" if you don't want to see all warnings and questions.<br />
Reading /etc/shells<br />
Check /etc/master.passwd<br />
Check /etc/group<br />
Ok, let's go.<br />
Don't worry about mistakes. I will give you the chance later to correct any input.<br />
Enter username []: ftp<br />
Enter full name []: anonymous ftpd user<br />
Enter shell bash csh ksh nologin sh [/sbin/nologin]:ENTER<br />
Uid [1001]:ENTER<br />
Login group ftp [ftp]:ENTER<br />
Login group is \ftp". Invite ftp into other groups: guest no<br />
no<br />
: ENTER<br />
Login class daemon default sta. [default]:ENTER<br />
Enter password []: ENTER # 在此直接按ENTER键。<br />
</div><br />
这样就可以设置一个不可用的密码。<br />
<div class="code"><br />
Set the password so that user cannot logon? (y/n) [n]: y<br />
Name: ftp<br />
Password: ****<br />
Fullname: anonymous ftpd user<br />
Uid: 1001<br />
Gid: 1001 (ftp)<br />
Groups: ftp<br />
Login Class: default<br />
HOME: /var/ftp<br />
Shell:<br />
OK? (y/n) [y]: y<br />
Added user \ftp"<br />
Copy ˉles from /etc/skel to /var/ftp<br />
Add another user? (y/n) [y]: n<br />
Goodbye!<br />
#<br />
</div><br />
到这里，添加用户的工作就完成了。还需要把系统从'/etc/skel' 复制到$HOME目录的一些"dot ˉles"给删除，以免暴露信息。'dot ˉles'的第一行通常会有一些系统的信息，比如' /.cshrc'文件中就有这么一句：<br />
<div class="code"># $OpenBSD: dot.cshrc,v 1.5 2005/02/16 06:56:57 matthieu Exp $</div><br />
这至少就告诉了匿名登录的用户，这是个OpenBSD系统。所以建议把它们删除：<br />
<div class="code"># rm -f /var/ftp/.*</div></p>

<h5>为FTP目录设置正确的权限</h5>

<p>1、'ftp'目录<br />
"ftp"目录表示"ftp"用户的主目录，在本例中就是"/var/ftp"目录。将它的owner设置为"root"，权限设置为任何人都不可写（555）：<br />
<div class="code"># chown -R root:wheel /var/ftp<br />
# chmod -R 555 /var/ftp<br />
</div><br />
2、'ftp/bin'目录<br />
这个目录并不是必须的。如果希望匿名用户登录到FTP后能够执行一些command，就可以将command 复制到这个目录下。所有的command的权限都应该设置为只允许执行（111）。<br />
<div class="code"># mkdir /var/ftp/bin<br />
# chown -R root:ftp /var/ftp/bin<br />
# chmod -R 111 /var/ftp/bin/*<br />
</div><br />
3、'ftp/etc'目录<br />
和"ftp/bin"目录一样，这也是个可选的，并不推荐创建它。（更多关于该目录设置的信息请查看"ftpd" 的man文档）。<br />
4、'ftp/pub"目录<br />
这个目录用来存放你希望被匿名用户访问的文件。权限应该设置为555。<br />
<div class="code"># chown -R root:ftp /var/ftp/pub<br />
# chmod -R 555 /var/ftp/pub<br />
</div><br />
这里虽然提到了创建三个目录，但是实际上我们只需要创建' ftp/pub'目录并设置好权限就可以了。</p>

<h5>chroot匿名登录的用户</h5>

<p>'ftpd'会将'/etc/ftpchroot'文件中列出的用户都chroot。要使'ftp'用户在登录ftp后被chroot，只需要简单地把用户名添加到这个文件中就可以了。这是一个示例文件：<br />
<div class="code"># file: /etc/ftpchroot<br />
# $OpenBSD: ftpchroot,v 1.3 1996/07/18 12:12:47 deraadt Exp $<br />
##<br />
list of users (one per line) given ftp access to a chrooted area.<br />
# read by ftpd(8).<br />
ftp<br />
bibby<br />
</div><br />
'ftpd'在启动时会读取这个文件，如果'ftp'和'bibby'这两个用户登录ftp，将被分<br />
别chroot到自己的$HOME目录下。</p>

<h5>其他一些相关文件</h5>

<p>· /etc/ftpusers { 列出了所有不受欢迎的用户。列在该文件中的用户都无法<br />
登录ftp服务器。<br />
· /etc/ftpwelcome { 欢迎信息。登录的用户都将在登录时看到这一信息。<br />
· /etc/motd { 如果'/etc/ftpwelcome'文件不存在，则使用'/etc/motd'文件的内容做为欢迎信息。<br />
2 .message { 这个文件可以被放置在' ftp'目录下的任何一个子目录中。用户进入该目录时就会显示这个文件中的内容。</p>]]></description>
         <link>/journal/systems/2006/000131.html</link>
         <guid>/journal/systems/2006/000131.html</guid>
         <category>issue0I</category>
         <pubDate>Wed, 07 Jun 2006 15:15:03 +0800</pubDate>
      </item>
            <item>
         <title>FreeBsd5.4+pf+squid反向代理实战笔记</title>
         <description><![CDATA[<p>盛世唐朝</p>

<p>1、硬件配置<br />
HP NETSERVER 800  PⅢ1000   内存256M   Inter82559网卡两张<br />
2、分区情况<br />
Filesystem     Size    Used   Avail Capacity  Mounted on<br />
/dev/da0s1a    248M     54M    174M    24%    /<br />
devfs          1.0K    1.0K      0B   100%    /dev<br />
/dev/da0s1f    4.8G    130M    4.3G     3%    /home<br />
/dev/da0s1d    248M     12K    228M     0%    /tmp<br />
/dev/da0s1g    4.8G    565M    3.9G    12%    /usr<br />
/dev/da0s1e    5.8G    410K    5.3G     0%    /var<br />
3、系统安装情况<br />
采用最小化安装<br />
并且安装src和ports（原本打算采用ports安装，但是不知道怎么搞的，竟然不能cvs源码，当然也就不能通过ports安装，无奈之下只能采用源码编译）<br />
4、内核编译<br />
没有对内核采用优化，这里只是为了验证pf和squid结合做反向代理的可行性，在实际的生产应用中应该对服务器内核做一定程度的优化。<br />
<div class="code">cd /usr/src/sys/i386/conf<br />
cp GENERIC cache<br />
</div><br />
编辑内核cache在内核中添加如下选项<br />
<div class="code">device pf<br />
device pflog<br />
device pfsync<br />
options ALTQ<br />
options ALTQ_CBQ<br />
</div><br />
编译内核<br />
<div class="code">/usr/sbin/config cache<br />
cd ../config/cache<br />
make depend<br />
make<br />
make install<br />
</div><br />
至此内核编译完毕<br />
<div class="code">reboot</div><br />
5、让系统自动加载pf<br />
编辑/etc/rc.conf<br />
<div class="code">usbd_enable="NO"<br />
defaultrouter="218.4.xxx.xxx"<br />
hostname="cache.aaa.com"<br />
ifconfig_fxp0="inet 218.4.xxx.xxx netmask 255.255.255.248"<br />
ifconfig_fxp1="inet 192.168.2.10 netmask 255.255.255.0"<br />
gateway_enable="YES"<br />
inetd_enable="YES"<br />
pf_enable="YES"<br />
pf_rules="/etc/pf.conf"<br />
pf_flags=""<br />
pflog_enable="YES"<br />
pflog_logfile="/var/log/pflog"<br />
sshd_enable="YES"<br />
</div><br />
6、打开ip转发<br />
在/etc/sysctl.conf中添加如下内容<br />
<div class="code">net.inet.ip.forwarding=1</div><br />
7、实现共享上网，最简单的pf设置<br />
<div class="code">wan_if="fxp0"<br />
lan_if="fxp1"<br />
inter_net="192.168.2.0/24"<br />
web_server="192.168.2.3"<br />
ftp_server="192.168.2.3"<br />
scrub in all<br />
nat on $wan_if from $inter_net to any -> fxp0</p>

<p>rdr on fxp1 proto tcp from $lan_if to any port 80 -> $lan_if port 80</p>

<p>rdr on fxp1 proto tcp from any to any port 21 -> 127.0.0.1 port 8021<br />
#rdr on fxp0 proto tcp from any to $wan_if port 80 ->$web_server port 8080<br />
#rdr on fxp1 proto tcp from $lan_if to $wan_if port 80 ->$web_server port 8080</p>

<p>rdr on $wan_if proto tcp from any to any port 21 -> $ftp_server port 21<br />
rdr on $wan_if proto tcp from any to any port 49152:65535 -> $ftp_server port 49152:65535</p>

<p># in on $wan_if<br />
pass in quick on $wan_if proto tcp from any to $ftp_server port 21 keep state<br />
pass in quick on $wan_if proto tcp from any to $ftp_server port > 49151 keep state</p>

<p># out on $lan_if<br />
pass out quick on $lan_if proto tcp from any to $ftp_server port 21 keep state<br />
pass out quick on $lan_if proto tcp from any to $ftp_server port > 49151 keep state</p>

<p>#Disable danger port<br />
#Danger_Port="{445 135 139 593 5554 9995 9996}"<br />
#block quick on $wan_if inet proto tcp from any to any port $Danger_Port<br />
#block quick on $wan_if inet proto tcp from any to any port $Danger_Port <br />
pass in all<br />
pass out all<br />
</div><br />
（最后这两条在实际的应用中是不可靠的，应该先限制所有，然后逐步打开自己需要的服务）<br />
pf的设置到此基本完毕<br />
下面开始squid部分<br />
1、安装squid<br />
<div class="code">./configure       --enable-useragent-log<br />
--enable-referer-log<br />
--enable-default-err-language=Simplify_Chinese<br />
--enable-err-languages="Simplify_Chinese English"<br />
--disable-internal-dns<br />
--enable-pf-transparent<br />
#make<br />
#make install<br />
#mkdir /home/cache(创建存放cache的目录)<br />
</div><br />
2、增加squid运行的用户和用户组（我的都设为squid）<br />
<div class="code">chown squid:squid /home/cache<br />
ee /usr/local/squid/etc/squid.conf<br />
</div><br />
在/etc/hosts中加入内部的DNS解析，比如我的：<br />
<div class="code">192.168.2.2 www.aaa.com <br />
192.168.2.3 mail.aaa.com<br />
</div><br />
3、下面开始配置squid.conf文件（下面是我的配置文件）<br />
<div class="code">visible_hostname cache . example.com<br />
cache_dir ufs /home/cache 1024 16 256<br />
cache_mem 100 MB<br />
cache_effective_user squid<br />
cache_effective_group squid</p>

<p>http_port 80</p>

<p>httpd_accel_host virtual<br />
httpd_accel_single_host off<br />
httpd_accel_port 80<br />
httpd_accel_uses_host_header on<br />
httpd_accel_with_proxy on<br />
# accelerater my domain only<br />
acl acceleratedHostA dstdomain . example1.com<br />
#acl acceleratedHostB dstdomain .example2.com<br />
#acl acceleratedHostC dstdomain .example3.com<br />
# accelerater http protocol on port 80<br />
acl acceleratedProtocol protocol HTTP<br />
acl acceleratedPort port 80<br />
# access arc<br />
acl all src 0.0.0.0/0.0.0.0<br />
# Allow requests when they are to the accelerated machine AND to the<br />
# right port with right protocol<br />
http_access allow acceleratedProtocol acceleratedPort acceleratedHostA<br />
#http_access allow acceleratedProtocol acceleratedPort acceleratedHostB<br />
#http_access allow acceleratedProtocol acceleratedPort acceleratedHostC<br />
# logging<br />
emulate_httpd_log on<br />
cache_store_log none<br />
# manager<br />
acl manager proto cache_object<br />
http_access allow manager all<br />
cachemgr_passwd pass all<br />
</div><br />
squid.conf文件配置完成<br />
4、目录权限设置<br />
<div class="code">chown –R squid:squid /home/cache</div><br />
创建日志文件，默认的在/usr/local/squid/var/access.log<br />
5、创建缓存目录：<br />
<div class="code">/usr/local/squid/sbin/squid -z</div><br />
启动squid<br />
<div class="code">/usr/local/squid/sbin/squid</div></p>

<p>在这个笔记中我的构建意图是<br />
web服务通过squid反向代理来完成<br />
至于其他（我现在只有ftp）服务则通过pf来完成<br />
那么为了完成这个目标我们还需要在pf规则中添加如下语句<br />
<div class="code">rdr on $lan_if proto tcp from $lan_if to any port 80 -> $lan_if port 80</div><br />
（$lan_if是我网关机的内网卡）凡是对80端口的访问，都统统转发到网关上Squid侦听端口80，而在pf规则中只允许ftp服务通过（疑问是外网访问呢，是否也需要添加类似的这句呢）</p>

<p>至此，FreeBsd5.4+pf+squid反向代理基本完成。</p>]]></description>
         <link>/journal/systems/2006/000130.html</link>
         <guid>/journal/systems/2006/000130.html</guid>
         <category>issue0I</category>
         <pubDate>Wed, 07 Jun 2006 15:11:01 +0800</pubDate>
      </item>
            <item>
         <title>FreeBSD5.4 + pf + oops实现透明代理</title>
         <description><![CDATA[<p>硬-盘 &lt; linuxmine # gmail.com &gt;</p>

<p>本文基本实现oops＋pf实现透明代理，oops其他认证，带宽管理都没用上，希望能够抛砖引玉，肯请用过oops的大侠指教！谢谢。</p>

<h5>安装过程</h5>

<p>1, cd /usr/ports/www/oops/  <br />
make config  选中  <br />
[X] DB4 Berkeley DB v4 storage  <br />
make install clean  <br />
2, 修改/usr/local/etc/oops/oops.cfg  <br />
3，cd /usr/local/sbin/  <br />
oops -z -c /usr/local/etc/oops/oops.cfg （创建其磁盘高速缓存）  <br />
4，vi /etc/rc.conf加入oops_enable="yes"  <br />
5，reboot  </p>

<h5>配置</h5>

<p>more /etc/rc.conf  <br />
defaultrouter="218.75.x.x"  <br />
gateway_enable="YES"  <br />
hostname="firewall.test.com"  <br />
ifconfig_fxp0="inet 218.75.y.y netmask 255.255.255.128"  <br />
ifconfig_fxp1="inet 192.168.0.1 netmask 255.255.255.192"  <br />
ifconfig_fxp1_alias0="inet 192.168.1.62 netmask 255.255.255.192"  <br />
ifconfig_fxp1_alias1="inet 192.168.2.62 netmask 255.255.255.192"  <br />
ifconfig_fxp1_alias2="inet 192.168.3.62 netmask 255.255.255.192"  <br />
sshd_enable="YES"  <br />
pf_enable="YES"  <br />
pflog_enable="YES"  <br />
pflog_logfile="/var/log/pflog"  <br />
sendmail_enable="NONE"  <br />
ntpdate_enable="YES" # Run ntpdate to sync time on boot (or NO).   <br />
ntpdate_flags="207.46.232.189" # time.windows.com  <br />
oops_enable="yes"  </p>

<p>more /etc/pf.conf  <br />
#firewall by tds 20050601  </p>

<p>#macros  <br />
wanif="fxp0"  <br />
lanif="fxp1"  <br />
oops="127.0.0.1"  <br />
tcpsrv="{22,113}"  <br />
lan0="{192.168.0.0/26}"  <br />
lan1="{192.168.2.0/26}"  <br />
lan3="{192.168.3.0/26}"  <br />
lan4="{192.168.1.0/26}"  <br />
ftpsrv="192.168.0.8"  <br />
bt1="192.168.0.38"  <br />
bt2="192.168.0.39"  <br />
noroute="{127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}"  </p>

<p>#options  <br />
set block-policy return  <br />
set loginterface $wanif  <br />
set optimization aggressive  </p>

<p>#scrub  <br />
scrub in all  </p>

<p>#nat and rdr  <br />
nat on $wanif from $lan0 to any -> $wanif  <br />
nat on $wa nif from $lan1 to any -> $wanif  <br />
nat on $wanif from $lan3 to any -> $wanif  <br />
nat on $wanif from $lan4 to any -> $wanif  </p>

<p>rdr on $lanif proto tcp from any to any port 80 -> $oops port 3128  <br />
rdr on $wanif proto tcp from any to any port 21 -> $ftpsrv  <br />
rdr on $wanif proto tcp from any to any port 18888 -> $bt2  <br />
rdr on $wanif proto tcp from any to any port 4662 -> $bt2  <br />
rdr on $wanif proto udp from any to any port 4672 -> $bt2  <br />
rdr on $wanif proto tcp  from any to any port 3389 -> $bt2  <br />
rdr on $wanif proto tcp from any to any port 3388 -> $bt1 port 3389  </p>

<p>#filter rules  <br />
block all  <br />
block drop in quick on $wanif from $noroute  <br />
block drop out quick on $wanif from any to $noroute  <br />
block drop out quick on $wanif from any to 202.103.67.53  <br />
pass quick on lo0 all  <br />
pass in quick on $lanif from $lanif:network to any keep state  <br />
pass out quick on $lanif from any to $lanif:network keep state  </p>

<p>pass in quick on $wanif proto tcp from an y to $wanif port $tcpsrv flags S/SA keep state  <br />
pass in quick on $wanif proto tcp from any to $ftpsrv port 21 flags S/SA keep state  <br />
pass in quick on $wanif proto tcp from any to $bt2 port {3389,4662,18888} flags S/SA keep state  <br />
pass in quick on $wanif proto tcp from any to $bt1 port 3389 flags S/SA keep state  <br />
pass in quick on $wanif proto udp from any to $bt2 port 4672 keep state  <br />
pass out on $wanif proto tcp all flags S/SA keep state  <br />
pass out on $wanif proto {udp,icmp} all keep state&n bsp; </p>

<p>more /usr/local/etc/oops/oops.cfg  </p>

<p>只记录修改部分  </p>

<p>nameserver 127.0.0.1  <br />
nameserver 220.168.208.3  <br />
nameserver 220.168.208.6  </p>

<p>http_port 3128  <br />
#icp_port 3130  <br />
userid oops  </p>

<p>logfile /var/log/oops/oops.log { 3 1m } unbuffered  <br />
accesslog /var/log/oops/access.log { 3 1m } unbuffered  <br />
pidfile /var/run/oops/oops.pid  <br />
statistics /var/run/oops/oops_statfile  <br />
mem_max 128m  <br />
lo_mark 80m  <br />
disk-low-free 3  <br />
disk-ok-free 5  </p>

<p>force_http11  <br />
force_completion 85  <br />
maxresident 1m  <br />
insert_x_forwarded_for no  <br />
insert_via no  <br />
always_check_freshness  </p>

<p>group mynet {  <br />
##  <br />
# You can describe  group ip adresses here, or using src_ip acl's  <br />
# with networks_acl directive.  <br />
# networks_acl always have higher preference (checked first) and  <br />
# are checked in the order of appearance.  <br />
# If host wil not fall in any networks_acl - we check in networks.  <br />
# networks are ordered by masklen - longest masks(most specific networks)  <br />
# are checked first.  <br />
##  <br />
networks 192.168/16 127/8 ;  <br />
redir_mods transparent;（添加此行实现透明代理）  <br />
# networks_acl LOCAL_NETWORKS !BAD_NETWORKS ;  <br />
badports [0:79],110,138,139,513,[6000:6010] ;  <br />
miss allow;  </p>

<p>module&nb sp;transparent { （实现透明代理）  <br />
# myport can have next form:  <br />
# myport [{hostname|ip_addr}:]port ...  <br />
myport 3128  <br />
# broken_browsers MSIE  <br />
}  </p>

<p>storage {  <br />
path /usr/local/oops/storages/oops_storage ;  <br />
# Size of the storage. Can be in bytes or 'auto'. Auto is  <br />
# usefull for pre-created storages or disk slices.  <br />
# NOTE: 'size auto' won't work for Linux on disk slices.  <br />
# To use large ( > 2G ) files run configure with --enable-large-files  </p>

<p>size 200m ; （磁盘高速缓存）  </p>

<h5>参考文章</h5>

<p>高性能、多线程的高速Web代理服务器--OOPS!  </p>]]></description>
         <link>/journal/systems/2006/000129.html</link>
         <guid>/journal/systems/2006/000129.html</guid>
         <category>issue0I</category>
         <pubDate>Wed, 07 Jun 2006 14:59:00 +0800</pubDate>
      </item>
            <item>
         <title>Qmail邮件系统的安全分析和改进研究</title>
         <description><![CDATA[<p>曾慧鹏</p>

<h5>摘要</h5>

<p>本文首先指出了著名的开源邮件系统Qmail在安全方面存在的问题，并借用UNIX系统上现有的安全工具对Qmail现存的安全隐患加以解决，接着介绍了一种比Base64编码效率更高的Base91编码技术对邮件编码进行改进。最后利用实验室的平台基本实现了该高速、简洁、安全的邮件系统。 </p>

<h5>引言</h5>

<p>邮件服务是Internet中使用率仅次于WWW的服务，一个邮件服务器主要包括三个主要的功能：邮件传输代理MTA（Mail Transport Agent），邮件分发代理MDA（Mail Delevery Agent），邮件用户代理MUA（Mail User Agent）。前两者是核心模块，负责邮件收发和处理。<br />
Qmail是全球安装使用量仅次于Linux/Unix上缺省安装的的Sendmail的邮件服务器软件，到目前为止Qmail的MTA依然是世界上转发速度最快的邮件传输代理[1]，Qmail几乎兼容所有的Linux/Unix类操作系统。由于在Linux/Unix类型的操作系统中，Sendmail被缺省安装，所以其使用量在该领域位居首位，但是其配置烦琐，而且仅仅支持单文件（/var/spool/mail/$USER）方式存储用户所有邮件，导致邮件收发仅能串行处理，效率低下，而且一旦存取该文件出错，用户的全部邮件将丢失。而Qmail支持以目录的形式管理用户的邮件[3]（$HOME/Maildir）,在数据安全和存取速度上比前者都有明显的优势。Qmail的整体模块如图1[2]所示,主要由MTA、MDA、MUA三大部分组成。</p>

<center><img alt="issue18_5_1.gif" src="/journal/attachments/issue18_5_1.gif" width="250" height="286" /><br>图1 Qmail系统框架图</center>

<p>由于体系结构比X.400 MHS(Message Handling System)体系简单，而且是基于TCP/IP协议，SMTP（Simple Mail Transfer Protocol）已成为事实上的邮件协议标准，由于该协议设计过于简单，其报文在网络上用明文方式传输，给网络监听者带来了极大的方便，Qmail系统的MTA协议用的就是SMTP，邮件在传输途中很容易被截获。<br />
目前的Internet上垃圾、反动、病毒邮件泛滥，Qmail在设计时对这三个方面的安全问题考虑不足。<br />
本文针对上述的问题，结合新的编码技术和UNIX系统现有的一些安全工具对其加以研究、改进。</p>

<h5>1 问题的提出</h5>

<p>垃圾邮件、反动邮件和病毒邮件是目前对所有邮件用户最大的威胁。从图1的框架图来看，qmail的MTA和MDA中没有任何对邮件的检测和过滤的措施，无疑对上述安全问题没有任何防护能力。<br />
1.1针对垃圾邮件<br />
Qmail缺省不支持对Smtp用户的认证，这就是说，任何能访问该Qmail服务器的用户均能利用它来向任何地址发送邮件，对于那些想利用邮件列表发布邮件广告的商人，或想借他人主机发送大量攻击性邮件的黑客来说，其可以被轻而易举地被拿来直接达到目的。事实统计90％的垃圾邮件是从那些打开了开放性转发（Open Relay）权限的服务器发出的。<br />
1.2针对反动、色情邮件等非法邮件<br />
反动、色情邮件主要是指邮件的内容中有违反国家法律的反动信息和黄色信息，这些信息通常也是使用邮件列表进行传播，其性质较商业广告垃圾邮件恶劣，对社会产生很大的负面影响。Qmail没有任何基于内容的过滤模块，不能拦截该类邮件。<br />
1.3针对病毒邮件<br />
病毒邮件是指携带了病毒体的邮件报文，该类邮件一般通过附件来携带病毒文件，如果要过滤该类恶意邮件，要求MTA、MDA、MUA三个模块中至少有一个模块拥有病毒查杀模块，Qmail设计中没有考虑到这一点，这也是Qmail系统存在的一大安全问题。<br />
1.3针对报文的明文传输<br />
基于SMTP协议的Qmail直接进行邮件收发对报文仅仅进行了简单的编码，对于邮件体甚至没有进行任何编码，利用网络窃听工具可以轻易获取邮件内容，图2是用著名的网络窃听器ethereal截获的邮件报文，从中我们知道这封邮件收发人的邮件地址、发送使用的MDA、编码方式、邮件体的类型、和信件的所有内容。如果有附件，我们可以根据已知的编码方式对其进行反编码获得最后的原始文件，所以毫无保密性可言。</p>

<center><img alt="issue18_5_2.gif" src="/journal/attachments/issue18_5_2.gif" width="265" height="271" /><br>图2 截获的明文格式的邮件报文</center>

<p>1.4针对用户的管理<br />
Qmail设计成和操作系统共用用户，UNIX操作系统的用户数据库是一个文件，缺省为/etc/passwd文件，随之带来的问题就是添加邮件的用户必须同时添加一个系统用户，因为对于服务器来说多一个系统用户就等于系统多一个可以被用来入侵的途径，就等于要多耗费一个用户配置文件的存储空间，这将主要导致安全隐患和系统资源的浪费。<br />
1.5针对访问权限的控制<br />
    Inetd是UNIX操作系统用来调用一些基本服务如Ftp、Telnet的特殊守护进程，Smtp和Pop(Post Office Protocol)协议也可以用其来启动，Qmail在没有第三方工具的支持时仅能使用Inetd来调用。随Inetd带来的问题就是不能控制访问邮件服务的IP地址的范围，在已知非法用户或恶意攻击者的IP地址的情况下，不能拒绝对他们提供服务，对服务器安全不利。</p>

<h5>2 现有的基于UNIX的安全工具</h5>

<p>UNIX系统下有很多开放源代码的软件包，他们一般由很多程序员自发组织进行开发。在安全领域，已有很多流行软件包可以解决Qmail存在的大部分安全问题。<br />
2.1密码认证工具<br />
2.1.1 checkpassword<br />
是一个基于/etc/passwd进行认证的工具，第三方的程序可以将其作为和系统用户数据库进行交互的代理，对系统用户进行认证操作。<br />
2.1.2 cmd5checkpw[4]<br />
cmd5checkpw是一个和checkpasswd兼容的支持CRAM-MD5算法进行认证的工具。只要支持checkpasswd进行系统用户认证的程序均可以使用其来支持CRAM-MD5算法对数据进行信息摘要，防止密码被盗取。<br />
2.1.3 vchkpw   <br />
vchkpw是Vpopmail工具集中一个认证工具，<br />
2.1.4 qmail-Smtp-auth[5]<br />
qmail-Smtp-auth是Mrs.Brisby开发的让Qmail支持Smtp认证的补丁程序的升级版本，后者仅仅支持基于LOGIN方式的认证,前者加入PLAIN和CRAM-MD5认证机制并且还支持认证方法的后续添加。<br />
2.2传输过程的加密工具<br />
2.2.1 OPENSSL[6]<br />
OPENSSL是由全世界自愿者开发的开放源代码的SSL（Secure Sockets Layer）协议的实现工具集，其支持SSL v2/v3和TLS v1（Transport Layer Security)协议并拥有一个非常强大的密码库。 <br />
SSL[7]是一个用将用户数据用非对称加密方法加密后在互联网上进行安全传输的协议，使用该协议能很好地杜绝窃听者用嗅探器抓取信息明文的事件发生。<br />
2.3访问控制工具<br />
2.3.1 Ucspi-tcp[8]<br />
   Ucspi-tcp是一个基于UNIX的用来给网络应用程序提供安全TCP连接的程序，主要包括服务模块（tcpserver）和客户模块（tcpclient）两个部分，其中在服务器模块中具有访问控制特性，此特性允许对客户端的访问权限进行限制。<br />
2.4用户管理工具<br />
2.4.1 vpopmail<br />
Vpopmail是UNIX上的一个创建和管理邮件虚拟域（Virtual Domain）的工具包，包括管理虚拟域、管理用户的工具和密码认证模块，它可以使一个邮件服务器提供多种“@domain”给用户选择，并且支持用数据库来存储和认证用户。<br />
2.4.2 Mysql数据库[9]<br />
Mysql是一个全世界使用最多的开源数据库软件，其运行方式是Server-Client方式，目前基于UNIX的大型应用程序均对其支持，包括前面提到的Vpopmail工具包。 </p>

<h5>3 问题的分析解决</h5>

<p>3.1问题的分析<br />
Qmail产生安全问题的关键在于其仅仅是一个出色MTA和MDA，其仅仅实现了邮件服务器的基本功能，在于没有考虑到现实网络中的安全问题。利用上面介绍的一些工具，可以把Qmail在安全方面的漏洞一一补全。<br />
3.2 问题的解决<br />
3.2.1针对垃圾邮件的解决方案<br />
qmail-Smtp-auth是针对qmail开放性转发无法关闭的漏洞设计的补丁，使用这个补丁生成的Smtpd守护进程要求邮件发送者不能直接发送邮件，必须在发送时同时提供有效的用户名和密码。不过该补丁无法提供用户身份鉴别功能，所以它必须结合一个密码验证工具作为和用户验证数据库之间的代理。<br />
在前面介绍的密码验证工具中checkpassword结合cmd5checkpw作为验证代理能提供对Linux/Unix的系统用户进行认证，基于这种方式进行认证的系统允许Linux/Unix操作系统内部用户使用Smtp发送和转发邮件；另外一种更好的密码验证方法在安装了vpopmail后可以使用，该方法的用一个可以从Mysql数据库取出用户数据来进行验证的工具vchkpw来实现，该工具比checkpassword好在可以让Qmail的用户数据不局限于用操作系统的系统用户，增强了用户认证的灵活性、安全性和高效性。<br />
3.2.2针对反动、色情邮件等非法邮件<br />
针对这个问题，还很少见现成的基于UNIX的工具可以支持对邮件的内容进行过滤，所以在这里本文仅能提一些设想。<br />
从图1我们可以看到，在框图的上部分Qmail的MTA负责把收到的邮件放入排队队列，之后排队程序按照邮件的目的地址决定邮件发往本地邮箱目录存储还是发往下一个邮件服务器。在这个排队队列的处理程序中，可以加入一些基于文本、图像、声音、视频等内容过滤的程序代码，从而实现对非法邮件的过滤。<br />
3.2.3针对病毒邮件<br />
对付病毒邮件的方法和前面的基于内容的过滤方式基本上相同，不过在排队过程中，邮件必须通过杀毒软件的检测，由于杀毒软件本身涉及庞大的病毒库，一般直接使用由杀毒软件公司提供的模块是比较可行的方案。<br />
3.2.4针对报文的明文传输<br />
    前面提到的SSL是专门用来对TCP协议的数据报进行用非对称密码进行加密传输。SMTP是运行在TCP协议之上的应用层协议，自然可以借助SSL来杜绝明文数据传输。在UNIX环境下，借助OPENSSL可以免费地获得这种服务。<br />
3.2.4针对用户的管理<br />
    为了节约系统资源、增强安全性和提高效率，应该尽量避免使用系统用户来作为邮件用户，vpopmail工具集提供的丰富的用户管理工具，利用vadduser、udeluser、vpasswd、vadddomain等工具可以轻松地增减邮件用户、修改密码和增减虚拟域，而且支持绝大多数的数据库，包括最著名的开源数据库软件Mysql。<br />
3.2.5针对访问权限的控制<br />
   由于Qmail缺省借用Inetd来同外界进行TCP连接，不能对来访的IP地址进行限定，导致安全隐患诸多。所以必须借助目前UNIX中广泛使用的TCP连接工具Ucspi-tcp来建立连接，其中的服务器模块tcpserver可以设定deny、allow等参数来拒绝和允许来访IP地址。<br />
3.3针对性能的一些改进<br />
   由于SMTP的传输机制是以7bit的二进制编码的ASCII字符为基础的，虽然SMTP扩展允许发送8bit的二进制数据，但是有一些非Internet网关是不能够正确处理的。因此要确保二进制信息在传输过程中的完整性必须先对其进行编码[10]。目前最常用的编码是quoted-printable和base64编码，前者主要针对非附件部分的文本进行编码，后者主要针对附件进行编码。由于base64是把6比特二进制数据用一个字节的ASCII码来表示，其带宽利用率仅仅为0.75，效率比较低，增加了网络的负担。现在一种名为base91[10]的新编码方法已在1999被西南交通大学的何大可教授提出了，该编码使用了92(包括空格)个可打印字符作为映射集，把输入的消息比特切分为13比特的分组作为映射源，编码的源字节数比较大（大于64K）的时候其编码的带宽利用率接近于81.25％，比base64提高了大约6个百分点，所以在对附件（一般大于64K）的编码方案上base91是Qmail比base64更好的选择。<br />
3.2.4对Webmail的支持<br />
Webmail是web和mail结合的产物，其作用是提供一个基于Web界面的MUA，用户可以登陆邮件服务器的网站来收发自己的信件，Qmail没有Webmail模块，这部分单独进行开发很容易，后台可以选用PHP、JSP、ASP、CGI等流行动态网页编程语言，它们都有相关函数对用户进行认证和对用户邮箱目录进行存取并结合数据库管理邮件用户的所有数据。</p>

<h5>4 安全邮件服务器的实现</h5>

<p>依照前面的分析，在本文的最后对上述的设想在作者本人实验室的服务器上加以了实现，具体框架图如图3所示。</p>

<center><img alt="issue18_5_3.jpg" src="/journal/attachments/issue18_5_3.jpg" width="269" height="294" /><br>图3 经过改造后的安全邮件系统架构</center>

<p>该系统运行的操作系统平台为Freebsd[11]， Webmail平台为Apache服务器，数据库选择了Mysql，使用Openssl作为TCP传输加密软件。<br />
从图3我们可以看到，外部服务器和客户端（因为对于本服务器它们都相当于客户角色，后面统称客户）通过Tcpserver来和本服务器连接并进行Smtp传输， 利用Tcpserver的allow、deny等命令参数可以限定客户的IP地址范围。在建立连接后，转向qmail-smtpd-auth模块进行认证，qmail-smtpd-auth借助vchkpw来用加密的方式将客户提供的用户名和密码与Mysql数据库进行对比认证，当认证通过后，客户的邮件报文被放入邮件队列等待处理。在这个阶段，可以加入基于文本、图像、声音或视频的过滤模块对邮件进行过滤，也可以放置病毒查杀模块对带病毒邮件进行处理，合法的邮件将由qmail-send模块根据邮件目的地址判断是转交给邮件外发模块还是存储到本地邮箱目录，在邮件外发模块qmail-remote将Base64编码方法替换成为Base91，提高了编码效率。<br />
邮件用户在发送邮件的时候可以用基于Smtp的客户端工具也可以直接使用Webmail，前者的流程刚才已经介绍过了，后者的认证依靠Webmail同Mysql用户数据库进行交互来判断用户是否合法，合法的邮件由qmail-inject模块交付qmail-queue进入邮件队列，接下来的过程同基于smtp的发送流程一致。<br />
邮件用户再收取邮件的时候可以使用Webmail和基于Pop协议的客户程序，前者在用户通过验证后根据用户名去读取该用户的邮件信息然后用Web页面的方式反馈给用户。后者首先用vchkpw来验证用户，然后通过Pop协议把该用户邮件下载到用户机本地磁盘上保存。<br />
经过安装和运行，基本上验证了该系统的安全性，由于没有庞大的用户群来做重负荷测试，对该系统的运行速度和负荷能力还未能测试，但官方的资料显示Qmail在16MB内存486/66级别的机器上拥有日发7万封邮件的能力[12]。基本的安全问题已经解决，如果在过滤和杀毒方面如果能加以很好实现，这将是一个非常完善的系统。</p>

<h5>参考文献</h5>

<p>[1]D. J. Bernstein.  Frequently asked questions of qmail. http://cr.yp.to/qmail/faq.html． <br />
[2]The big qmail picture. http://www.nrg4u.com/qmail/the-big-qmail-picture-103-p1.gif．<br />
[3]D. J. Bernstein. Building a POP toaster. http://cr.yp.to/qmail/toaster.html.<br />
[4]cmd5checkpw http://members.elysium.pl/brush/cmd5checkpw/  <br />
[5]qmail-Smtpd-auth http://members.elysium.pl/brush/qmail-Smtpd-auth/<br />
[6]Openssl http://www.openssl.org/ <br />
[7]Secure Sockets Layer http://www.webopedia.com/TERM/S/SSL.html<br />
[8]Ucspi-tcp http://cr.yp.to/ucspi-tcp.html<br />
[9] About Mysql AB http://www.Mysql.com/company/index.html<br />
[10]张晓鹏. 基于Base-91的安全电子邮件服务器研究与实现[D].硕士论文. 西南交通大学, U.D.C:621.316.9. 2003. <br />
[11]曾慧鹏. 用FreeBSD构建家庭网络世界.http://cnfug.org/journal/systems/2004/000046.html<br />
[12]D.J.Bernstein . Frequently asked questions<br />
The qmail security guarantee.http://cr.yp.to/qmail.html</p>]]></description>
         <link>/journal/systems/2006/000128.html</link>
         <guid>/journal/systems/2006/000128.html</guid>
         <category>issue0I</category>
         <pubDate>Wed, 07 Jun 2006 14:47:44 +0800</pubDate>
      </item>
            <item>
         <title>LAM/MPI CLuster System With FreeBSD 5.3 HOWTO</title>
         <description><![CDATA[<p> tonnyom &lt; tonnyom # hotmail.com &gt;</p>

<p>版权声明：可以任意转载，转载时请务必以文字形式标明文章原始出处和作者信息及本声明.</p>

<h5>前言</h5>

<p>MPI（Message Passing Interface）消息传送接口<br />
它不是一个协议，但它的地位已经实际上是一个协议了。它主要用于在分布式存储系统中的并行程序通信。MPI是一个函数库，它可以通过Fortran和C程序进行调用，MPI的好处是它速度比较快，而且移植性比较好。</p>

<p>Cluster<br />
目前常见的Cluster(集群)架构有两种，一种是Web/Internet Cluster System，这种架构主要是将资料放置在不同的主机上面，亦即由多部主机同时负责一项服务；而另外一种则是所谓的平行运算了(Parallel Algorithms Cluster System)！平行运算其实就是将同一个运算的工作，交给整个Cluster里面的所有CPU来进行同步运算的一个功能。由于使用到多个CPU的运算能力，所以可以加快运算的速度。</p>

<p>此文档所安装架设的LAM/MPI Cluster System属于后者，由于实验环境条件以及自身能力的限制，可能文档有部分解释不详尽，如有疑问请来信与我联系，我将尽力完善此文档，谢谢！</p>

<h5>软件及平台</h5>

<p>Server \\ FreeBSD 5.3 Stable<br />
IP:172.18.5.247 <br />
Hostname: center.the9.com</p>

<p>Client \\ FreeBSD 5.3 Release<br />
IP:172.18.5.80 <br />
Hostname: node1.the9.com</p>

<p>apache_1.3.29 \\ All Ports Install<br />
php4-4.3.10<br />
php4-gd-4.3.10<br />
php4-extensions-1.0<br />
lam-6.5.9<br />
ganglia-monitor-core-2.5.6<br />
ganglia-webfrontend-2.5.5</p>

<h5>目的</h5>

<p>架设一套基于FreeBSD 5.3的LAM/MPI Cluster System.</p>

<p><br />
<h5>安装及配置</h5><br />
一,各节点系统 /etc/hosts 的基本配置 \\ 如果内网有DNS,则配置好系统中的 /etc/resolv.conf 即可!<br />
center.the9.com<br />
<div class="code">#more /etc/hosts<br />
172.18.5.247 center.the9.com<br />
172.18.5.80 node1.the9.com<br />
</div></p>

<p>node1.the9.com<br />
<div class="code">#more /etc/hosts<br />
172.18.5.247 center.the9.com<br />
172.18.5.80 node1.the9.com<br />
</div></p>

<p>二,Apache+PHP Server 的架设<br />
center.the9.com<br />
<div class="code">#cd /usr/ports/www/apache13-modssl<br />
#make install clean                    \\ 安装 APACHE<br />
</div><br />
<div class="code">#cd /usr/ports/lang/php4-extensions<br />
#make install clean                    \\ 安装 PHP.  切记这里一定要选择安装GD库<br />
</div><br />
<div class="code">#vi /usr/local/etc/apache/http.conf     \\ 加入以下相关参数</p>

<p>    AddType application/x-httpd-php .php<br />
    AddType application/x-httpd-php-source .phps<br />
</div></p>

<p>三,NFS Server-Client 的架设<br />
NFS Server(center.the9.com)</p>

<div class="code">#vi /etc/rc.conf   \\ 加入以下相关参数

<p>nfs_server_enable="YES"<br />
nfs_server_flags="-u -t -n 4 -h 172.18.5.247"<br />
mountd_enable="YES"<br />
mountd_flags="-r -l"<br />
rpcbind_enable="YES"<br />
rpcbind_flags="-l -h 172.18.5.247"<br />
</div></p>

<div class="code">#vi /etc/exports     \\ 配置NFS共享目录

<p>/cluster -maproot=0:0 -network 172.18.5.0 -mask 255.255.255.0<br />
</div></p>

<div class="code">#/etc/rc.d/rpcbind start
#/etc/rc.d/mountd start
#/etc/rc.d/nfsd start     \\ 启动NFS Server
</div>

<p>NFS Client(node1.the9.com)</p>

<div class="code">#vi /etc/rc.conf   \\ 加入以下相关参数

<p>nfs_client_enable="YES"<br />
</div></p>

<div class="code">#vi /etc/fstab       \\ 加入以下相关参数
172.18.5.247:/cluster   /cluster        nfs     rw              0       0
</div>

<div class="code">#mount /cluster      \\ Mount /Cluster 目录</div>

<p>四,LAM/MPI Cluster System的架设<br />
Step 1:  基本安装<br />
center.the9.com<br />
<div class="code">#cd /usr/ports/net/lam<br />
#make install clean           \\ 安装 LAM</p>

<p>#cd /usr/ports/sysutils/ganglia-monitor-core <br />
#make install clean           \\ 安装Cluster System 所需的Monitor Core</p>

<p>#cd /usr/ports/sysutils/ganglia-webfrontend<br />
#make install clean            \\ 安装上面Monitor Core 所需的WEB GUI</p>

<p>node1.the9.com<br />
#cd /usr/ports/net/lam<br />
#make install clean           \\ 安装 LAM</p>

<p>#cd /usr/ports/sysutils/ganglia-monitor-core <br />
#make install clean           \\ 安装Cluster System 所需的Monitor Core<br />
</div></p>

<p>Step 2: 配置</p>

<p>center.the9.com<br />
<div class="code">#cd /usr/local/etc/<br />
#cp gmond.conf.sample gmond.conf<br />
#cp gmetad.conf.sample gmetad.conf</p>

<p>#vi gmond.conf          \\ 修改name和mcast_if 的参数</p>

<p># The name of the cluster this node is a part of<br />
# default: "unspecified"<br />
 name  "BSDCluster"</p>

<p># The multicast interface for gmond to send/receive data on<br />
# default: the kernel decides based on routing configuration<br />
 mcast_if  lnc0</p>

<p>#vi gmetad.conf          \\ 修改data_source 的参数</p>

<p># data_source "my cluster" 10 localhost  my.machine.edu:8649  1.2.3.5:8655<br />
# data_source "my grid" 50 1.3.4.7:8655 grid.org:8651 grid-backup.org:8651<br />
# data_source "another source" 1.3.4.7:8655  1.3.4.8</p>

<p>data_source "BSDCluster" 10 center.the9.com:8649 node1.the9.com:8649 </p>

<p>#vi /usr/local/etc/lam-bhost.def   \\ 加入各node 的hostname</p>

<p>center.the9.com<br />
node1.the9.com</p>

<p><br />
node1.the9.com      \\ 基本上,每个新增节点的配置都要和以上center.the9.com 的配置一致.<br />
node2.the9.com<br />
nodeX.the9.com      ........ <br />
</div></p>

<p>五,Monitor WEB GUI 的配置<br />
center.the9.com<br />
<div class="code">#vi /usr/local/etc/apache/http.conf     \\ 加入以下相关参数,配置Cluster Monitor Web的路径</p>

<p>    Alias /ganglia/ "/usr/local/www/ganglia/"</p>

<p>    <Directory "/usr/local/www/ganglia"><br />
        Options Indexes FollowSymlinks MultiViews<br />
        AllowOverride None<br />
        Order allow,deny<br />
        Allow from all<br />
    </Directory><br />
</div></p>

<div class="code">#vi /etc/rc.conf    \\ 加入以下参数

<p>apache_enable="YES"<br />
apache_flags="-DSSL"<br />
apache_pidfile="/var/run/httpd.pid"<br />
</div></p>

<div class="code">#/usr/local/etc/rc.d/apache.sh start   \\ 启动APACHE</div>

<p>六,启动并调试Cluster System以及检查测试<br />
center.the9.com node1.the9.com nodeX.the9.com etc....</p>

<div class="code">#/usr/local/etc/rc.d/gmetad.sh start
#/usr/local/etc/rc.d/gmond.sh start     \\ 启动Cluster 各Node的Monitor Core
</div>

<p>center.the9.com<br />
<div class="code">$lamboot -dv           \\ 启动各节点的lam daemon</div></p>

<p>LAM 6.5.9/MPI 2 C++/ROMIO - Indiana University</p>

<p>lamboot: boot schema file: /usr/local/etc/lam-bhost.def<br />
lamboot: opening hostfile /usr/local/etc/lam-bhost.def<br />
lamboot: found the following hosts:<br />
lamboot:   n0 center.the9.com<br />
lamboot:   n1 node1.the9.com<br />
lamboot: resolved hosts:<br />
lamboot:   n0 center.the9.com --> 172.18.5.247<br />
lamboot:   n1 node1.the9.com --> 172.18.5.80<br />
lamboot: found 2 host node(s)<br />
lamboot: origin node is 0 (center.the9.com)<br />
Executing hboot on n0 (center.the9.com - 1 CPU)...<br />
lamboot: attempting to execute "hboot -t -c lam-conf.lam -d -v -I " -H 172.18.5.247 -P 53433 -n 0 -o 0     ""<br />
hboot: process schema = "/usr/local/etc/lam-conf.lam"<br />
hboot: found /usr/local/bin/lamd<br />
hboot: performing tkill<br />
hboot: tkill <br />
hboot: booting...<br />
hboot: fork /usr/local/bin/lamd<br />
[1]  28338 lamd -H 172.18.5.247 -P 53433 -n 0 -o 0 -d<br />
hboot: attempting to execute <br />
Executing hboot on n1 (node1.the9.com - 1 CPU)...<br />
lamboot: attempting to execute "/usr/bin/ssh node1.the9.com -n echo $SHELL"<br />
lamboot: got remote shell /bin/sh<br />
lamboot: attempting to execute "/usr/bin/ssh node1.the9.com -n (. ./.profile; hboot -t -c lam-conf.lam -d -v -s -I "-H 172.18.5.247 -P 53433 -n 1 -o 0    " )"<br />
hboot: process schema = "/usr/local/etc/lam-conf.lam"<br />
hboot: found /usr/local/bin/lamd<br />
hboot: performing tkill<br />
hboot: tkill <br />
hboot: booting...<br />
hboot: fork /usr/local/bin/lamd<br />
[1]  43110 lamd -H 172.18.5.247 -P 53433 -n 1 -o 0 -d<br />
topology done      <br />
lamboot completed successfully</p>

<p><br />
<div class="code">$lamhalt -dv           \\ 停止各节点的lam daemon</div></p>

<p>LAM 6.5.9/MPI 2 C++/ROMIO - Indiana University</p>

<p>Shutting down LAM<br />
lamhalt: sending HALT to n1 (node1.the9.com)<br />
lamhalt: waiting for HALT ACKs from remote LAM daemons<br />
lamhalt: received HALT ACK from n1 (node1.the9.com)<br />
lamhalt: sending final HALT to n0 (center.the9.com)<br />
lamhalt: local LAM daemon halted<br />
LAM halted</p>

<div class="code">$lamnodes              \\ 查看node info
$lamexec N echo "hello"   \\ 查看node run status</div>

<p><br />
center.the9.com<br />
<div class="code">#ps ax<br />
28338  ??  I      0:00.04 /usr/local/bin/lamd -H 172.18.5.247 -P 53433 -n 0 -o 0 -d</div></p>

<p>node1.the9.com<br />
<div class="code">#ps ax<br />
43110  ??  S      0:00.05 /usr/local/bin/lamd -H 172.18.5.247 -P 53433 -n 1 -o 0 -d</div></p>

<p><br />
Cluster Monitor WEB GUI</p>

<p>http://center.the9.com/ganglia/     \\ 用这个查看系统数据,还是很直观的,是以RRDTool 生成的 images. :)</p>

<p>CPUs Total: 2 <br />
Hosts up: 2 <br />
Hosts down: 0 <br />
   <br />
Avg Load (15, 5, 1m):<br />
  1%, 4%, 0% <br />
Localtime:<br />
  2004-12-31 10:50 </p>

<p>Total CPUs: 2<br />
Total Memory: 0.2 GB<br />
 Total Disk: 8.0 GB<br />
Most Full Disk: 61.2% Used</p>

<h5>参考</h5>
http://lam-mpi.org/ lam-mpi
http://www.beowulf.org/ beowulf FAQ
http://www.lasg.ac.cn/cgi-bin/forum/topic.cgi?forum=4&topic=2247 MPI Cluster With RH9
http://lists.freebsd.org/mailman/listinfo/freebsd-cluster freebsd cluster maillist]]></description>
         <link>/journal/systems/2006/000127.html</link>
         <guid>/journal/systems/2006/000127.html</guid>
         <category>issue0I</category>
         <pubDate>Wed, 07 Jun 2006 14:37:46 +0800</pubDate>
      </item>
            <item>
         <title>BSD radix树路由表的设计原理</title>
         <description><![CDATA[<p>雨丝风片</p>

<p>在这里写下我的关于BSD radix树路由表的设计原理的文章之前，首先要感谢一个人：xie_minix，http://blog.chinaunix.net/index.php?blogId=2681。在我刚开始接触BSD 的radix树路由表的时候，是xie_minix发表在 http://www.cnfreeos.org/bsdsrc/ 的文章给我指明了最初的方向。滴水之恩，涌泉相报。自受惠于xie_minix公开的成果之日起，在下就决定将日后的心得也如xie_minix一般公之于众，以期能给其他的爱好、钟情、研究、使用BSD的朋友们一些帮助。</p>

<p>当然，还有一个人也是需要感谢的，W.Richard Stevens，没有他的书，也就没有了这里写下的一切。</p>

<p>写这篇文章的时候使用的是FreeBSD5.1的代码，举例用的是IPv6地址。</p>

<p>BSD路由表使用的是radix树，这种树的设计思想来自Donald R.Morrison于1968年提出的Patricia树（Practical  Algorithm To Retrieve Information Coded In Alphanumeric）。这是一种基于以二进制表示的键值的查找树，尤其适合于处理非常长的、可变长度的键值。Partricia的基本思想是构建一个二叉树，在每个节点中都存储有进行下一次的bit测试之前需要跳过的bit数目，以此来避免单路分支（即避免二叉树的某一段呈现只往左或者只往右生长的趋势）。因此，一般意义上的Patricia树由内部节点和外部节点组成，内部节点用于指示需要进行bit测试的位置，并依据bit测试结果决定查找操作的前进方向，而外部节点则用于存储键值，查找操作将于外部节点处终止。</p>

<p>BSD正是借用了Partricia树的思想来组织路由表，但考虑到路由表的特殊性，即需要存储掩码并实现最长匹配路由查找，于是对Partricia树进行了改造，形成了BSD的radix树。在BSD的radix树中的路由查找操作将分为三个步骤，第一个步骤即是Partricia查找，终结于某个叶子节点，判断该叶子节点是否与查找键相同。第二个步骤，如果找到的叶子节点与查找键无法匹配，则在这个叶子节点的重复键链表中寻找网络匹配的可能。第三个步骤，如果找到的叶子节点及其重复键与查找键不满足网络匹配条件，则向树顶回溯，继续寻找网络匹配的可能。</p>

<h5>1 BSD路由表的表头</h5>

<p>BSD路由表的头指针存放在rt_tables[]数组中，这是一个radix_node_head结构体类型的指针数组。在BSD的协议栈中，所有协议的路由表都是用radix树进行组织的，而这些radix树的头指针就都存放在rt_tables[]数组中，IPv6路由表的头指针只是其中之一，即下标为AF_INET6的数组元素。</p>

<p>radix_node_head结构体的内存布局如图1所示。rnh_treetop是指向路由表顶部节点的指针。在这个结构体中还定义了8个函数指针，分别指向路由表提供的8个操作函数。在这个结构体的尾部还有三个radix_node结构体，这就是radix树的初始三节点，它们的rn_flags字段将被设置成RNF_ROOT，表示这是radix树的根节点。这三个节点是在路由表跏蓟鄙傻摹?<br />
<img alt="issue18_4_1.jpg" src="/journal/attachments/issue18_4_1.jpg" width="327" height="442" /></p>

<p>路由表初始化完成后，这三个根节点的链接关系如图3所示。从这个图中我们可以看出，在三个根节点组成的数组rnh_nodes[3]中，第二个根节点作为路由表的顶部节点，由rnh_treetop指针指向，它将是对路由表的所有操作的开始处。此外，第一个根节点被初始化为顶部节点的左孩子，第三个根节点被初始化为顶部节点的右孩子，而这三个初始根节点的父指针都指向了中间的那个顶部节点。这实际上已经搭起了一个radix树的基本框架。如图2所示。</p>

<p><img alt="issue18_4_2.jpg" src="/journal/attachments/issue18_4_2.jpg" width="244" height="207" /></p>

<p>在图2中，我们将中间的作为路由树顶的根节点用圆圈表示，因为它属于内部节点。而另外的两个根节点我们用方框表示，它们属于叶子节点。在本文档中将始终按照这一约定来图示内部节点和叶子节点。</p>

<h5>2 BSD路由表的节点</h5>

<p>BSD路由表的radix树实际上就是由一系列的内部节点和叶子节点组成的。内部节点位于树的中间位置，每个内部节点都会指定一个bit测试位置，即当从树的顶端开始向下查找，遇到这个内部节点时需要判断是0还是1的bit位置，接下来的查找将根据bit测试的结果来决定是向左走还是向右走。叶子节点位于树的边缘，用于存储路由表键值，即IP地址。</p>

<p>2.1 内部节点</p>

<p>前已述及，内部节点在radix树中用于表示一个bit测试位置。</p>

<p>内部节点和叶子节点使用的都是radix_node结构体，只是少数字段的定义有所不同。我们首先通过内部节点来查看一下radix_node结构体中的各个字段。在radix树中的3个根节点中，位于中间的那个顶端节点就是内部节点，因此我们的描述就以图3为例。</p>

<p>rn_mklist:这个指针指向的是一个radix_mask结构体链表。对于内部节点来说这个链表上的掩码都取自它的子树中的叶子节点所对应的掩码，但只有那些在做逻辑AND运算时能够把这个内部节点的测试bit变成0的掩码才能够加入到这个链表中，这类掩码的作用将在路由查找时的回溯过程中体现出来。对于叶子节点，如果它的掩码满足上述条件而被选入它的某一级父节点的掩码链表的话，那么它的rn_mklist指针就会指向这个掩码链表中对应于它自己的掩码的那个节点。</p>

<p>rn_parent:这是节点的父指针，从叶子节点一直向上指到树的顶端节点，而树的顶端节点的父指针是指向它自己的。路由查找时的回溯过程将沿父指针进行。</p>

<p>rn_bit:对于内部节点，这个值大于等于0，用于指示在这个内部节点处需要进行测试的bit位置。这个位置是从用于存储IP地址的socket地址结构体的起始位置开始计算的。对于叶子节点，这个值是一个负数，具体数值就是-1减去这个叶子节点所对应的掩码的索引值。而所谓掩码的索引值就是指这个掩码中第一次出现0的bit位置，这个位置也是从socket地址结构体的起始位置开始计算的。</p>

<p><img alt="issue18_4_3.jpg" src="/journal/attachments/issue18_4_3.jpg" width="579" height="661" /></p>

<p>rn_bmask:这是一个1字节的掩码，其中只有一个bit为1。在实际的路由查找中，为了加快查找速度，就是使用这个字段直接对指定的字节进行bit测试，而不是指定bit进行测试。对于叶子节点，这个字段为0。</p>

<p>rn_flags:这个字段的可能取值一共有3个，RNF_NORMAL，表示这是一个含有常规路由的叶子节点；RNF_ROOT，表示这是一个位于radix_node_head结构体中的节点，即路由表中的三个根节点；RNF_ACTIVE，表示这条路由的状态是好的。</p>

<p>rn_Off:这是内部节点独有的字段，表示一个从socket地址结构体的起始位置开始计算的字节偏移量，用于指定在这个内部节点处需用rn_bmask进行单字节比较的字节偏移量。</p>

<p>rn_L:这是内部节点独有的字段，指向这个内部节点的左孩子。</p>

<p>rn_R:这是内部节点独有的字段，指向这个内部节点的右孩子。</p>

<p>2.2 叶子节点</p>

<p>叶子节点和内部节点的大部分字段都是一样的，只是最后三个字段的定义不一样。相同字段的定义已在前面的内部节点部分进行了描述，最后三个字段的定义如下。</p>

<p>rn_Key:这个字段就内存位置而言相当于内部节点中的rn_Off字段。这个字段用于指向存储着叶子节点键值（即IP地址）的socket地址结构体。</p>

<p>rn_Pfxlen: 这个字段就内存位置而言相当于内部节点中的rn_L字段。这个字段用于存储前缀长度。</p>

<p>rn_Dupedkey: 这个字段就内存位置而言相当于内部节点中的rn_R字段。当路由表中有重复键情况出现的时候，即有多个叶子节点的键值（IP地址）相同，这些叶子节点是以链表的形式挂接在树中的某个叶子节点下的，rn_Dupedkey即指向重复键链表中的下一个叶子节点。</p>

<p>在radix树中，左右两个根节点即属于叶子节点。</p>

<p>2.3 根节点</p>

<p>radix树中的根节点即是在图2和图3中给出的3个节点。这三个节点都被设置了RNF_ROOT标志，以表示它们是根节点。</p>

<p>中间的那个根节点是radix树的顶部节点，所有的路由查找操作都是从它开始的。我们可以看到，这个根节点的bit测试位置是64，也就是说，对于存储在socket地址结构体中的BSD地址而言，实际的BSD地址开始的第一个bit在结构体中的偏移量是64，整个radix树的bit比较算法就是从这第64 bit开始。前已述及，为了加快查找速度，实际的查找操作中使用的是字节偏移和字节掩码。因此，第64 bit对应的字节偏移就是8，而字节掩码就是二进制的1000 0000。</p>

<p>另外的两个根节点分列于树的最左端和最右端。我们可以看到，它们的rn_bit字段都小于0，表明这两个根节点属于叶子节点。事实上，它们一个对应的是全0键值，一个对应的是全1键值。</p>

<p>在路由查找操作中，任何时刻都不能返回根节点本身。如果查找操作定位到了根节点，将代之以返回空指针。</p>

<p>2.4 重复键节点</p>

<p>BSD路由表中的重复键节点是指路由树中键值（IP地址）完全相同的叶子节点。</p>

<p>这些重复键节点由各自的rn_Dupedkey指针串成一个链表，通过位于链表头部的叶子节点挂在路由树中。位于链表中的重复键节点是按照掩码的精确程度从高到低排列的，即位于链表头部的叶子节点的掩码最精确，对于掩码连续的情况而言也就是它的掩码最长。这样在路由查找的时候如果找到了这串重复键节点，就可以保证掩码最长的路由最先匹配。</p>

<h5>3 BSD路由表的路由条目</h5>

<p>如前所述，BSD路由表是由一系列的内部节点和叶子节点组织起来的，这是BSD路由表的逻辑结构。如果从内存布局来讲，BSD路由表中的路由条目则是通过rtentry结构体来存放的，我们前面提到的内部节点和外部节点实际上都是存放在这个结构体中的。rtentry结构体的组成如图4所示。</p>

<p>我们可以看到，在rtentry结构体的头部就是由两个radix_node结构体组成的数组rt_nodes[2]。在这个数组中，第一个元素是叶子节点，负责存储路由表键值，即IP地址，第二个元素是内部节点，负责完成树的连接。因此，就一般情况而言，每当往路由树中添加一条路由的时候，我们实际上添加的是两个节点，一个叶子节点和一个内部节点，只不过这两个节点的存储空间是我们事先用rtentry结构体分配好了的。虽然这两个节点在物理上是紧挨着的，但是由于后续路由条目的加入，它们之间就可能会插入一系列的内部节点，而这些内部节点又分别属于各自的rtentry结构体，并对应着各自的叶子节点。</p>

<p><img alt="issue18_4_4.jpg" src="/journal/attachments/issue18_4_4.jpg" width="292" height="388" /></p>

<p>在rtentry结构体的剩余部分中存储的是这条路由的接口、接口地址以及网关路由等关键信息。</p>

<h5>4 BSD路由表的路由查找</h5>

<p>前已述及，BSD路由表使用的是经BSD修改之后的Patricia树，也就是BSD radix树。在这种树中，内部节点用于指定需要对查找键进行测试的一系列bit位置，而外部节点则用于存储键值。为了适合路由表的需求，每个叶子节点都会有一个与之对应的掩码。内部节点可能有也可能没有对应的掩码，这取决于在它的子树中是否存在能够将它的测试bit逻辑AND成0的掩码。于是，根据这些特性，BSD路由表中的路由查找就分成了三个步骤，即找到叶子节点进行精确匹配、在重复键链表中进行网络匹配和通过回溯过程进行网络匹配。</p>

<p>4.1 第一步：寻叶</p>

<p>这一步实际上就是Patricia查找，即从路由表的顶部节点开始，根据所遇内部节点中指示的bit测试位置进行测试，根据该bit是0还是1来决定继续往右走还是往左走，直至到达一个叶子节点为止。</p>

<p>当radix_node结构体作为内部节点的时候，它的bit测试位置是由rn_bit字段来给出的。但是，仅仅是这样一个bit测试位置对于有多个字节的IP地址来说显然是不合适的，在查找的时候再去定位这个bit会影响查找效率，所以在添加这个内部节点的时候就会根据bit测试位置计算一个字节偏移量和相应的字节掩码，即rn_Off和rn_bmask字段。字节偏移量用于指定bit测试位置所在字节相对于socket地址结构体起始处的偏移量，而字节掩码则是一个8 bit的掩码，其中只有一个bit为1，在通过字节偏移量定位了字节之后，即可由字节掩码进行bit测试操作。</p>

<p><img alt="issue18_4_5.jpg" src="/journal/attachments/issue18_4_5.jpg" width="194" height="318" /></p>

<p>举例而言，BSD路由表的局部如图5所示。图中位于最上方的标记有64的那个内部节点就是radix树的顶端根节点，即在初始化路由表时生成的三个根节点中的中间一个，64这个数字是IPv6地址的第一个bit在socket地址结构体中的偏移量。由于所有的路由查找操作都要从树的顶端开始向下进行，所以不管查找哪条路由，都会从IPv6地址的第一个bit开始进行比较。</p>

<p>假设我们现在需要在这个radix树中查找FE80::210:5CFF:FEC2:38E7这条路由。从树的顶端开始，根据沿途内部节点指定的bit进行测试。这条路由的第64 bit为1，向右。第65 bit为1，继续向右。第71 bit为0，向左。第128 bit为0，继续向左。第160 bit为1，向右。这时，将会遇到一个rn_bit字段为负值的节点，即叶子节点，于是查找操作将停止在此处。</p>

<p>接下来的操作就是比较找到的这个叶子节点与我们的查找键是否匹配。比较操作是以字节为单位进行的，参与比较的字节数将以叶子节点掩码的最后一个非0字节为限，即若在此范围内叶子节点与查找键相同则认为匹配，查找操作成功结束，否则认为不匹配，并记录下查找键与叶子节点键值第一次出现不同的字节位置。在返回查找结果时有一点需要注意，即在任何时候都不能返回根节点自身，如果在这一步找到的叶子节点是一个根节点，那么就必须返回它的重复键指针rn_dupedkey，而不是它自己。</p>

<p>第一步的查找路径在图5中用红色曲线进行了标识。</p>

<p>4.2 第二步：辨重</p>

<p>如果在第一步中找到的叶子节点与查找键不满足匹配的条件，则需要遍历这个叶子节点的重复键链表。由于重复键链表中的叶子节点与第一步中找到的叶子节点的键值（也就是IP地址）是完全相同的，只是掩码呈逐渐缩短的趋势，因此可能在重复键链表中存在网络匹配的可能。重复键处理的过程如图6所示。</p>

<p><img alt="issue18_4_6.jpg" src="/journal/attachments/issue18_4_6.jpg" width="194" height="444" /></p>

<p>图6是在图5的基础上绘制的。对于图中所示的三个重复键节点，我们用绿色的长短表示了它们各自掩码的长短，可以看出，掩码是呈逐渐变短趋势的。</p>

<p>由于在第一步中我们已经确定了查找键和叶子节点键值第一次出现不同的字节位置，因此可以方便地换算出查找键和叶子节点键值第一次出现不同的bit位置。前已述及，在叶子节点的radix_node结构体中，rn_bit字段就是由叶子节点的掩码中第一次出现0的bit位置换算出来的。因此，在接下来的重复键处理中，我们只需要把查找键和叶子键值第一次出现不同的bit位置跟叶子节点的rn_bit字段进行比较就可以方便地确定某个重复键节点是否满足网络匹配的条件，而不需要进行实际的掩码操作。</p>

<p>如果在重复键链表中有某个叶子节点满足网络匹配条件，则向调用者返回这个叶子节点。否则查找操作将回到最初找到的那个叶子节点（也就是重复键链表上的第一个节点），准备开始向树顶回溯了。</p>

<p>第二步的查找路径在图6中依然用红色曲线进行标识，实际上是将图5中的红色曲线延伸至重复键链表中。</p>

<p>4.3 第三步：回溯</p>

<p>到目前为止，我们只是使用作为查找键的IP地址在radix树中根据内部节点指示的bit测试位置找到了某个叶子节点，并进行了重复键处理，仍然没有找到匹配的叶子节点。这并不能排除在radix树中还存在有其它可能满足网络匹配条件的叶子节点，因此就需要沿着来时的内部节点路径向树顶回溯，寻求网络匹配的可能。回溯过程如图7所示。</p>

<p><img alt="issue18_4_7.jpg" src="/journal/attachments/issue18_4_7.jpg" width="428" height="444" /></p>

<p>回溯过程在图7中用蓝色曲线标识，方向从下到上。我们可以看出，回溯过程是从第一步中找到的那个叶子节点处开始，沿着每个节点的父指针rn_parent向树顶前进，这实际上就是我们在第一步中从树顶找到叶子节点所经由的路径，因此才把这一步称为回溯。</p>

<p>回溯途中经过的是一系列的内部节点，对于每一个内部节点，将会判断它是否挂的有掩码链表，即它的rn_mklist字段是否为空。掩码链表在图7中用粉红色表示。没有掩码链表的内部节点将不予考虑，直接通过。如果某个内部节点挂的有掩码链表，那说明在它的子树中可能存在着网络匹配的可能，需要停下来做一下判断再决定是否继续回溯。</p>

<p>这里首先就遇到了一个问题，究竟什么样的内部节点才挂有掩码链表？这实际上就是要回答回溯的必要性和可行性，这和radix树的组织方式即路由添加时的设计方法有关。首先需要明确下面两个问题。</p>

<p>为什么要回溯？</p>

<p>在第一步“寻叶”中，我们根据查找键的一系列bit测试结果找到了一个叶子节点，具体找到哪个叶子节点完全取决于radix树当时的组织形态，但有一点是可以确定的，那就是这个叶子节点的键值在其起点之后的某个长度之内一定和我们的查找键是相同的。如果这个长度就是叶子键值的长度，那我们在第一步中就匹配成功了，否则我们就要在树中寻找满足以下三个条件的叶子节点：</p>

<p>    .它和我们在第一步中找到的叶子节点有共同部分；<br />
    .它的掩码短于或等于它和第一步中的叶子节点的共同部分；<br />
    .它的掩码短于或等于查找键和第一步中的叶子节点的共同部分。</p>

<p>为什么可以回溯？</p>

<p>如果radix树中存在满足上述条件的叶子节点，那我们通过回溯算法就一定可以找到它，这是由radix树的路由添加算法决定的。在这里我们只需要明确一个事实，那就是对于一个测试bit为n的内部节点的子树中的所有子孙叶子节点的键值而言，从实际IP地址的开始bit到第n-1 bit都是相同的。举例而言，如果一个内部节点的测试bit是71，那么它的子树中的所有子孙叶子节点的键值的第64到70共 7个bit的内容都是相同的。因此，如果这个内部节点的子树中有某个叶子节点的掩码短于或等于7的话，那它的键值事实上就是这个子树中所有路由的公共部分，而它本身也就成了这个子树中所有路由的普适路由。正是radix树中的这些普适路由组成了回溯查找时的候选路由的集合。</p>

<p>一个内部节点的掩码链表正是它的子树中所有普适路由的记录链表，而对于一条普适路由来说，则会将其挂在它所能作为普适路由的最大可能的子树的顶端内部节点的掩码链表中。</p>

<p>对于回溯过程中遇到的每一个掩码链表非空的内部节点，我们都需要遍历它的掩码链表，只要发现一个满足前述三个条件的节点，就表明我们对查找键的网络匹配已经成功，回溯过程结束。</p>

<h5>5 BSD路由表的路由添加</h5>

<p>在对路由查找中的回溯过程的描述中我们已经提到一个事实，即对于一个测试bit为n的内部节点的子树中的所有子孙叶子节点的键值而言，从实际IP地址的起始bit到第n-1 bit都是相同的。这个事实就是由路由添加操作来保证的。路由添加操作可以分为寻叶求异、存异求同和普适提升三个阶段。</p>

<p>5.1 第一步：寻叶求异</p>

<p>向路由表中添加一条路由的目的就是为了以后来查找它，因此，为了确定新路由在路由表中的位置，首先就要对新键值执行查找操作，也就是我们在路由查找部分中描述的第一步：寻叶。这一步实现的是Patricia查找，即从树顶开始按照内部节点的指示对新键值进行一系列的bit测试，直到遇见一个叶子节点为止。</p>

<p>假设有如图8所示的一个路由表局部，这和描述路由查找时使用的实际上是一个图，图中叶子节点存储的键值是FE80::8210:0C00:7EC2:3800。现在假设我们要向路由表中添加FE80::8210:0:0:0/76这条路由，先对这个新键值执行Patricia查找，以实际IP地址的起始bit为第64 bit：第64 bit为1，向右；第71 bit为0，向左；第128 bit为1，向右；第144 bit为0，向左；第160 bit为0，继续向左。因此，这个查找将沿着图8中的红色曲线一直找到存放着FE80::8210:0C00:7EC2:3800键值的那个叶子节点。</p>

<p>找到这个叶子节点之后，就需要判断它的键值和新键值是否相等。如果相等，则表示出现了重复键的情况，将根据新路由的掩码长度将其挂接在该叶子节点的重复键链表中的适当位置。在我们的例子中，叶子节点的键值与新键值并不相等，于是就要记录下它们第一次出现不同的bit位置，这也就是“寻叶”之后的“求异”操作。</p>

<p>新键值FE80::8210:0:0:0和叶子节点键值FE80::8210:0C00:7EC2:3800的比较结果如图9所示。在这个图中，蓝色部分是用二进制表示的。我们可以看出，在现有radix的所有测试bit上，新键值和叶子节点键值都是相同的，因此才会找到这个叶子节点。但它们的键值实际并不相同，而这两个键值的第一个不同bit就是第148 bit。</p>

<p><img alt="issue18_4_8.jpg" src="/journal/attachments/issue18_4_8.jpg" width="280" height="319" /></p>

<p>确定了新键值和前面找到的那个叶子节点键值的第一个不同bit，实际上也就是确定了这两个键值的共同部分。这些信息将决定新路由在radix树中的位置，具体操作则可以归纳为路由添加的第二步：存异求同。</p>

<p><img alt="issue18_4_9.jpg" src="/journal/attachments/issue18_4_9.jpg" width="599" height="220" /></p>

<p>5.2 第二步：存异求同</p>

<p>首先，让我们结合例子再来回顾一下前面提到的那个事实：对于一个测试bit为n的内部节点的子树中的所有子孙叶子节点的键值而言，从实际IP地址的起始bit到第n-1 bit都是相同的，参见图10，图中用不同颜色的圆环来抽象表示各个内部节点的子树。测试bit为64的那个内部节点是树的顶点，属于特例，它的子树（也就是整个radix树）中的IP地址是没有公共部分可言的。测试bit为71的那个内部节点的子树中的节点的第64到第70 bit都是相同的，以此类推。这是从添加第一条路由开始就必须遵从的规则，显然，此时对新路由的添加也必须遵从于这一规则。</p>

<p>我们在第一步中已经计算出Patricia找到的叶子节点键值和新键值第一次出现不同的bit位置，即我们例子中的第148 bit。为了不违反上述事实，新键值就不能属于测试bit在第148 bit右边的内部节点的子树，而只能属于测试bit在第148 bit左边的内部节点的子树。因此，我们接下来的工作就是确定新键值究竟应该属于哪个内部节点的子树，而实现方法就是沿着来时的路径向树顶回溯，找到正好把第148 bit卡在中间的两个内部节点。注意，在这个回溯过程中是不可能有哪个内部节点的测试bit正好等于第148 bit的，因为如果那样的话，在第一步的Patricia查找中就不会走到存放着FE80::8210:0C00:7EC2:3800键值的那个叶子节点那儿了。</p>

<p><img alt="issue18_4_a.jpg" src="/journal/attachments/issue18_4_a.jpg" width="565" height="445" /></p>

<p>回溯过程是从前面找到的那个叶子节点处开始的，在图10中用蓝色曲线表示。首先遇到的是测试bit为160的内部节点，它的子树中从第64到159 bit都是相同的。由于新路由和叶子节点键值的第一个不同bit是第148 bit，属于第64到159 bit的范围，因此新路由不能属于它的子树，否则就会违反前述规则。继续向树顶回溯，这次遇到的是测试bit为144的内部节点，它的子树中从第64到143 bit都是相同的，因此新路由至少要属于这个内部节点的子树才能遵从前述规则。</p>

<p>就我们的例子来说，此时已经可以确定新路由应该插入在测试bit为144和160的两个内部节点之间，这是因为对新路由的第64、71、128和144 bit进行测试的结果都和前述叶子节点相同，我们必须保证路由查找操作在第144 bit之后、第160 bit之前对第148 bit进行测试，这样才能找到我们新加的这条路由。我们在前面提到过，每当往radix树中新加一条路由时，实际上都需要插入两个节点，即一个内部节点和一个叶子节点，或者说，路由条目的添加是以rtentry结构体为单位的，新加的两个节点正是这个结构体中的两个radix_node[2]结构体数组。</p>

<p>我们先来看看新路由的内部节点。它的测试bit显然就是第148 bit，而它就将替代测试bit为第160 bit的内部节点作为测试bit 为144 bit的那个内部节点的新的左孩子，而测试bit为第160 bit的那个内部节点则将成为它的某个孩子。</p>

<p>我们再来看看新路由的叶子节点，它存储着FE80::8210:0:0:0这个键值，而它此时显然也是新路由的内部节点的某个孩子。为了确定新叶子和测试bit为第160 bit的那个内部节点的左右名分，我们只需要判断新键值在新路由的内部节点的测试bit上是0还是1。对于我们的例子，新键值在第148 bit上为0，因此新叶子将作为新内部节点的左孩子，而测试bit为第160 bit的内部节点则作为右孩子。我们之所以可以在这里直接根据新键值就做出左右划分，是因为我们已经确定测试bit为第160 bit的内部节点的子树中所有叶子的第148 bit都是相同的，而新键值和这些叶子的第148 bit都是不同的。插入新路由之后的radix树如图11所示。</p>

<p><img alt="issue18_4_b.jpg" src="/journal/attachments/issue18_4_b.jpg" width="535" height="352" /</p>

<p>5.3 第三步：普适提升</p>

<p>在经过寻叶求异和按异求同两个步骤之后，路由添加的操作只能说是完成了一半。之所以这么说，是因为前面的操作对于只有键值没有掩码的Patricia树来说是够了，但对于作为路由表的radix树来说则还不行，因为路由查找操作是以最长匹配为原则的，在键值匹配失败的情况下我们还要去尝试网络匹配的可能，这也就是我们在路由查找部分描述的寻叶、辨重和回溯三个步骤。所以，在把新路由插入radix树之后，我们还要继续处理它的掩码，处理的结果可能会影响到新路由的某个父节点的掩码链表，关于掩码链表的使用可以参见路由查找部分的回溯步骤。</p>

<p>我们知道，所谓路由的最长匹配查找，就是用某个路由条目的掩码和查找键进行逻辑与，然后再把逻辑与的结果和路由条目的键值进行比较，如果相同，则说明满足网络匹配条件，而查找操作最后返回的就是满足上述条件的掩码最长的那条路由。现在，我们假设A是radix树中的最外层内部节点之一，它有B和C两个作为叶子节点的孩子，如图12所示。</p>

<p><img alt="issue18_4_c.jpg" src="/journal/attachments/issue18_4_c.jpg" width="249" height="174" /></p>

<p>在图12中用红色曲线给出了路由查找的第一步“寻叶”，这一步是只用查找键进行而与掩码无关的。我们假设叶子节点C的键值和查找键匹配失败，在重复键处理过程中也没有匹配成功，这时我们就需要继续寻找网络匹配的可能。前面已经提到，所谓网络匹配就是叶子节点和查找键的带掩码的比较，这实际上可以理解为用查找键和叶子掩码进行逻辑与之后的结果在树中再次进行查找，对于一个满足网络匹配条件的叶子节点来说，这样的查找必然就会找到它那儿。让我们再来看图12，要让这样的查找能够找到叶子节点B的条件之一就是B的掩码能够改变查找键在内部节点A处的行进方向，也就是说，B的掩码要能够把内部节点A的测试bit逻辑与成0，图中蓝色曲线表示的就是这种带掩码的查找过程。满足上述条件的路由B就称为节点A的子树中的普适路由。</p>

<p><img alt="issue18_4_d.jpg" src="/journal/attachments/issue18_4_d.jpg" width="249" height="174" /></p>

<p>显然，一条路由的掩码越短，它就越可能作为更大一些的子树中的普适路由。而所谓普适提升就是在每次新加入一条路由的时候，我们都需要确定它究竟最大能作为哪一层子树中的普适路由，确定子树之后，这条新路由的掩码就会记录在作为该子树的顶点的内部节点的掩码链表中。</p>

<p>结合我们在前面举的例子，在radix树中加入FE80::8210:0:0:0/76这条路由之后，我们就需要对其进行普适提升操作。这条路由的掩码是76 bit，也就是说，它的掩码中的第一个0 bit将出现在第140 bit，参见图9。普适提升的过程如图13所示。我们从新路由开始向树顶回溯，第一个遇到的是测试bit为第148 bit的内部节点，显然，新路由的掩码可以把它的测试bit与成0，因此，新路由可以作为它的子树中的普适路由。继续回溯，遇到的是测试bit为第144 bit的内部节点，同样，新路由也可以作为它的子树中的普适路由。下一个遇到的是测试bit为第128 bit的内部节点，这一次新路由的掩码就不能把这个内部节点的测试bit与成0了，因此，新路由最大只能作为测试bit为第144 bit的内部节点的子树中的普适路由，新路由的掩码将记录在它的掩码链表中。这条普适提升的行进路径在图13中用蓝色曲线表示，而内部节点的掩码链表在图中用粉色表示。</p>

<p>一个内部节点的掩码链表中将记录它的子树中的所有满足普适条件的掩码。链表上的节点是按照掩码从长到短的顺序排列的，每个链表节点都有一个指针指向它们各自对应的叶子节点，而叶子节点也有一个指针指向这个掩码链表节点。</p>

<p>在路由查找的回溯过程中，每当我们遇到掩码链表非空的内部节点的时候，就会遍历它的掩码链表，直接判断每个链表节点的掩码是否满足我们在描述路由查找的回溯步骤时列出的三个条件，一旦满足，我们就可以通过指针直接找到对应的叶子节点，而这样找到的第一个叶子节点就是我们要查找的最长匹配路由。</p>

<h5>6 BSD路由表的路由删除</h5>

<p>BSD路由表中的路由删除操作涉及的内容是和路由添加相对应的。在描述路由删除步骤之前，我们需要明确一个事实，在前面已经见过，路由条目的内存管理是以rtentry结构体为单位的，但radix树的操作却看不到这个结构体的存在，它只看得到一系列的内部节点和叶子节点。因此，同一个rtentry结构体内的两个radix_node结构体在该路由刚加入radix树的时候是直接相连的，但随着之后的操作，这两个radix_node结构体可能就相隔很远了。如图14所示。</p>

<p><img alt="issue18_4_e.jpg" src="/journal/attachments/issue18_4_e.jpg" width="485" height="194" /></p>

<p>在图14中，左边的图用绿色表示刚加入radix树中的一对radix_node结构体，其中，内部节点的测试bit为第148 bit。右边的图表示在这之后又加入了一对radix_node结构体，用粉色表示，其中，内部节点的测试bit为第150 bit。我们可以看到，由于新路由的加入，属于同一个rtentry结构体的两个绿色的节点在逻辑上被分开了。在删除路由的时候，我们就必须对这种情况进行处理，把rtentry结构体作为一个整体从radix树中摘除，具体做法因被删除节点的位置的不同而不同。</p>

<p>6.1 独有一叶</p>

<p>一般情况下，一个rtentry结构体内的两个radix_node结构体将同时出现在radix树中，但有一个例外，那就是出现了重复键的情况。参见路由查找以及路由添加部分的描述，在添加路由的时候，如果遇到了重复键的情况，我们就会把新路由直接挂到重复键链表中的适当位置。注意，重复键链表中都是叶子节点，它们是作为一个链表挂在同一个内部节点下的。因此，作为重复键加入的路由条目中的内部节点部分是闲置不用的。</p>

<p><img alt="issue18_4_f.jpg" src="/journal/attachments/issue18_4_f.jpg" width="526" height="256" /></p>

<p>非重复键链表头节点的删除过程如图15所示。在图中我们用绿色表示准备删除的路由条目对应的叶子节点，而用虚线在这个叶子节点的旁边画了一个内部节点，表示它虽然在物理上存在着，但却没有使用，不是radix树的一部分。对于这种情况，我们只需要直接将指定路由叶子节点从树中摘除，并将rtentry结构体释放即可。</p>

<p>6.2 父子同源无后继</p>

<p>除了前一节中描述的“独有一叶”的情况之外，其余的路由删除就需要牵扯到内部节点的处理了。所谓“父子同源无后继”指的是被删除的叶子节点是重复键链表上的唯一节点，而且它和作为父节点的内部节点同属于一个rtentry结构体，此时也只需要直接把这两个节点从radix树中摘除即可，如图16所示，图中用绿色表示一对属于同一个rtentry结构体的叶子节点和内部节点。</p>

<p><img alt="issue18_4_g.jpg" src="/journal/attachments/issue18_4_g.jpg" width="380" height="152" /></p>

<p>6.3 父子同源有后继</p>

<p>所谓“父子同源有后继”指的是被删除的叶子节点位于重复键链表头部，在它之后还有其它的重复键节点。对于这种情况，我们就不能把内部节点一删了事，而需要用替补上来的那个重复键节点对应的闲置内部节点空间去把被删除的叶子节点对应的内部节点替换下来。如图17所示，图中分别用绿色和粉色表示两对属于同一个rtentry结构体的叶子节点和内部节点，其中，绿色表示将要删除的路由对应的两个节点。</p>

<p><img alt="issue18_4_h.jpg" src="/journal/attachments/issue18_4_h.jpg" width="565" height="244" /></p>

<p>我们可以看到，在图17中，除了用粉色的叶子节点替换掉绿色的叶子节点作为重复键链表上的第一个节点之外，还要用原来闲置的粉色内部节点空间去把绿色的内部节点从radix树中替换下来，以便将绿色的节点对应的rtentry结构体空间作为一个整体释放。注意，这里对内部节点的替换是完全拷贝，因此粉色内部节点将完全替代绿色内部节点在radix树组织结构中的作用。</p>

<p>6.4 父子异源无后继</p>

<p>所谓“父子异源无后继”是指被删除的叶子节点是重复键链表中的唯一节点，且它和它的父节点不属于同一个rtentry结构体，也就是说，和它同属一个rtentry结构体的内部节点空间肯定在radix树中其它的某个地方。在图18中，我们分别用绿色和粉色表示两对属于同一个rtentry结构体的叶子节点和内部节点，其中，绿色表示将要删除的路由条目对应的两个节点。</p>

<p><img alt="issue18_4_i.jpg" src="/journal/attachments/issue18_4_i.jpg" width="521" height="222" /></p>

<p>这里的删除过程是这样的：首先是把绿色的叶子节点和粉色的内部节点从radix树中摘除，使粉色的叶子节点作为绿色内部节点的左孩子，然后再用粉色内部节点的空间去把绿色内部节点从radix树中替换下来。</p>

<p>6.5 父子异源有后继</p>

<p>所谓“父子异源有后继”是指被删除路由的叶子节点后面还有重复键节点，而它的父节点和它不属于同一个rtentry结构体。在图19中，我们分别用绿色和粉色表示两对属于同一个rtentry结构体的叶子节点和内部节点，其中，绿色表示将要删除的路由对应的两个节点，而粉色则表示将要替补上来的重复键叶子节点及其闲置内部节点。</p>

<p><img alt="issue18_4_j.jpg" src="/journal/attachments/issue18_4_j.jpg" width="635" height="293" /></p>

<p>具体删除过程是这样的：首先是把绿色的叶子节点从radix树中摘除，将粉色的叶子节点替补成测试bit为第150 bit的内部节点的左孩子，然后再用本来闲置的粉色内部节点空间将绿色内部节点从radix树中替换下来。<br />
</p>]]></description>
         <link>/journal/systems/2006/000126.html</link>
         <guid>/journal/systems/2006/000126.html</guid>
         <category>issue0I</category>
         <pubDate>Wed, 07 Jun 2006 11:13:36 +0800</pubDate>
      </item>
            <item>
         <title>FreeBSD虚存系统splay树的代码分析</title>
         <description><![CDATA[<p>雨丝风片</p>

<h5>已完成的内容</h5>

<p>1、vm_map_entry结构体中的adj_free和max_free<br />
2、vm_map_entry_splay()函数<br />
3、vm_map_findspace()函数<br />
......</p>

<p>1、vm_map_entry结构体中的adj_free和max_free</p>

<p>vm_map_entry结构体同时按两种数据结构进行组织，一种是双向链表（通过prev和next指针），一种是二叉查找树（通过left和right指针）。其中的二叉查找树就是由Daniel Dominic Sleator和Robert Endre Tarjan提出的splay树，他们的论文题目把这种树叫做自调整二叉查找树。</p>

<p>splay树的基本原理请参见原始论文【Self-Adjusting Binary Search Trees】和我写的一些笔记【FreeBSD虚存系统splay树的基本原理】</p>

<p>FreeBSD是从5.0开始将splay树引入VM系统的，在5.3的时候又加入了基于树状结构的空闲空间算法。本文就从这个空闲空间算法讲起。</p>

<p>vm_map_entry结构体的主要内容如下：<br />
<div class="code"><br />
_____________________________________________________________________FreeBSD6.0<br />
093  /*<br />
094   *    Address map entries consist of start and end addresses,<br />
095   *    a VM object (or sharing map) and offset into that object,<br />
096   *    and user-exported inheritance and protection information.<br />
097   *    Also included is control information for virtual copy operations.<br />
098   */<br />
099  struct vm_map_entry {<br />
100      struct vm_map_entry *prev;    /* previous entry */<br />
101      struct vm_map_entry *next;    /* next entry */<br />
102      struct vm_map_entry *left;    /* left child in binary search tree */<br />
103      struct vm_map_entry *right;    /* right child in binary search tree */<br />
104      vm_offset_t start;        /* start address */<br />
105      vm_offset_t end;        /* end address */<br />
106      vm_offset_t avail_ssize;    /* amt can grow if this is a stack */<br />
107      vm_size_t adj_free;        /* amount of adjacent free space */<br />
108      vm_size_t max_free;        /* max free space in subtree */<br />
_______________________________________________________/usr/src/sys/vm/vm_map.h<br />
</div></p>

<p>空闲空间算法向vm_map_entry结构体中添加了两个字段，adj_free和max_free。adj_free是和这个map entry相邻且紧跟其后（位于较高地址）的空闲空间的大小。注意，adj_free字段取决于链表结构，而不是splay树。这个链表是按照地址的升序来排列的，因此adj_free可以通过下述方式来计算：</p>

<div class="code">
_____________________________________________________________________FreeBSD6.0<br>
789      entry->adj_free = (entry->next == &map->header ? map->max_offset :<br>
790          entry->next->start) - entry->end;<br>
_______________________________________________________/usr/src/sys/vm/vm_map.c<br>
</div>

<p>也就是说，一个map entry的adj_free字段的值等于它在链表中的下一个map entry的起始地址减去它自己的终止地址。</p>

<p>下图画出了一个FreeBSD VM系统splay树的局部示意图，图中虚拟地址空间中的地址数值仅做算法示例用，不具备任何真实地址的属性。<br />
<img alt="issue18_3_1.jpg" src="/journal/attachments/issue18_3_1.jpg" width="748" height="556" /><br />
图中给出了三个map entry，分别表示了虚拟地址空间中三个区间。entry和对应地址空间使用相同颜色标注，分别为蓝色、绿色和粉色。绿色entry和粉色entry分别作为蓝色entry在splay树中的左孩子和右孩子，它们自己则无后继，左右孩子指针皆为空，图中用红色箭头标出了splay树的链接关系。对于链表结构而言，排在最前面的是绿色entry，其后继为蓝色entry，绿色entry则作为蓝色节点的后继，图中用蓝色箭头标出了链表结构的链接关系。</p>

<p>我们可以看到，绿色entry所表示的虚拟地址空间的起始地址为100，终止地址为300。蓝色entry的起始地址为500，终止地址为600。粉色entry的起始地址为850，终止地址为1050。根据上面给出的计算方法，绿色entry的adj_free字段的值就应该是200，蓝色entry的adj_free就是250，粉色entry的adj_free就是300。</p>

<p>max_free字段则是基于splay树的，表示这个map entry的子树中的相邻空闲空间的最大值。其计算方法是取这个entry自己的adj_free和其左、右子树的max_free三者之中的最大值。这也就是vm_map_entry_set_max_free()函数做的事情：<br />
<div class="code"><br />
_____________________________________________________________________FreeBSD6.0<br />
574  /*<br />
575   *    vm_map_entry_set_max_free:<br />
576   *<br />
577   *    Set the max_free field in a vm_map_entry.<br />
578   */<br />
579  static __inline void<br />
580  vm_map_entry_set_max_free(vm_map_entry_t entry)<br />
581  {<br />
582  <br />
583      entry->max_free = entry->adj_free;<br />
584      if (entry->left != NULL && entry->left->max_free > entry->max_free)<br />
585          entry->max_free = entry->left->max_free;<br />
586      if (entry->right != NULL && entry->right->max_free > entry->max_free)<br />
587          entry->max_free = entry->right->max_free;<br />
588  }<br />
_______________________________________________________/usr/src/sys/vm/vm_map.c<br />
</div></p>

<p>根据上述算法，我们就可以分别计算出上图中三个map entry的max_free字段的值：绿色entry为200，粉色entry为300，而蓝色entry则为200、300、250三者之最大值，即300。</p>

<p>引入了这两个字段之后，我们就可以很方便地判断出一个子树中是否存在足够大的空闲空间。这使得我们只需在树中从上至下走一次即可找到满足first-fit原则的空闲区域。这个算法的具体实现在vm_map_findspace()函数中。但我们先来看看vm_map_entry_splay()函数。</p>

<p><br />
vm_map_entry_splay()函数</p>

<p>vm_map_entry_splay()是splay树算法的核心所在。给定一个地址addr和splay树的根节点root，这个函数能够把包含或者是与这个地址相邻的map entry搬移成树根。</p>

<div class="code">
_____________________________________________________________________FreeBSD6.0<br>
607  static vm_map_entry_t<br>
608  vm_map_entry_splay(vm_offset_t addr, vm_map_entry_t root)<br>
609  {<br>
610      vm_map_entry_t llist, rlist;<br>
611      vm_map_entry_t ltree, rtree;<br>
612      vm_map_entry_t y;<br>
613  <br>
614      /* Special case of empty tree. */<br>
615      if (root == NULL)<br>
616          return (root);<br>
_______________________________________________________/usr/src/sys/vm/vm_map.c<br>
</div>

<p>这个函数的算法需要在树中从上到下和从下到上走两遍。下面是第一遍的代码段：</p>

<div class="code">
_____________________________________________________________________FreeBSD6.0<br>
618      /*<br>
619       * Pass One: Splay down the tree until we find addr or a NULL<br>
620       * pointer where addr would go.  llist and rlist are the two<br>
621       * sides in reverse order (bottom-up), with llist linked by<br>
622       * the right pointer and rlist linked by the left pointer in<br>
623       * the vm_map_entry.  Wait until Pass Two to set max_free on<br>
624       * the two spines.<br>
625       */<br>
626      llist = NULL;<br>
627      rlist = NULL;<br>
628      for (;;) {<br>
629          /* root is never NULL in here. */<br>
630          if (addr < root->start) {<br>
631              y = root->left;<br>
632              if (y == NULL)<br>
633                  break;<br>

<p>634              if (addr < y->start && y->left != NULL) {<br />
635                  /* Rotate right and put y on rlist. */<br />
636                  root->left = y->right;<br />
637                  y->right = root;<br />
638                  vm_map_entry_set_max_free(root);<br />
639                  root = y->left;<br />
640                  y->left = rlist;<br />
641                  rlist = y;<br />
642              } else {<br />
643                  /* Put root on rlist. */<br />
644                  root->left = rlist;<br />
645                  rlist = root;<br />
646                  root = y;<br />
647              }<br />
648          } else {<br />
649              y = root->right;<br />
650              if (addr < root->end || y == NULL)<br />
651                  break;<br />
652              if (addr >= y->end && y->right != NULL) {<br />
653                  /* Rotate left and put y on llist. */<br />
654                  root->right = y->left;<br />
655                  y->left = root;<br />
656                  vm_map_entry_set_max_free(root);<br />
657                  root = y->right;<br />
658                  y->right = llist;<br />
659                  llist = y;<br />
660              } else {<br />
661                  /* Put root on llist. */<br />
662                  root->right = llist;<br />
663                  llist = root;<br />
664                  root = y;<br />
665              }<br />
666          }<br />
667      }<br />
_______________________________________________________/usr/src/sys/vm/vm_map.c<br />
</div></p>

<p>我们还是以一种形象化的方法来描述这个“splay”过程。假设我们现在有一棵splay树，它的局部如图vm_map_entry_splay_001所示。同样，图中的所有数字都仅仅用于算法示例，不具有任何实际地址的属性。每个map entry的起始地址和终止地址用“100-200”的格式标注，其adj_free和max_free字段的值也都根据前面讲到的规则进行了计算和标注。</p>

<p><img alt="issue18_3_2.jpg" src="/journal/attachments/issue18_3_2.jpg" width="376" height="486" /></p>

<p>假设vm_map_entry_splay函数的入参addr为1000，也就是说，我们要拿1000这个值来对这颗树执行splay操作，当前的树根是A entry。我将用以下这种格式来描述整个过程，首先给出一些关键变量的当前值，然后按代码执行顺序描述相关流程，左边的数字是代码所在行数，右边是对应的实际操作。一个阶段的操作完成之后我将给出当前的树的形态图示。</p>

<p>第一次执行for循环体：</p>

<div class="code">
_______________________________________________________________________________<br>
预置条件：llist = rlist = NULL; root = A;<br>
-------------------------------------------------------------------------------<br>
630    1000 < 2400<br>
631    y = B<br>
632    y != NULL<br>
634    1000 < 1700 且 C != NULL <br>
636    A->left = E<br>
637    B->right = A<br>
638    重新计算A entry的max_free字段的值<br>
_______________________________________________________________________________<br>
</div>

<p>经过上述操作之后的树的形态如图vm_map_entry_splay_002所示。我们可以看到，A的子树已经发生了变化，而且我们已经在第634行确定了在B之后仍将继续向左走，因此余下的操作不会再对A的子树造成影响，于是就在这里对A的max_free字段进行重新计算。计算是通过调用vm_map_entry_set_max_free()函数来进行的，这个函数的算法我们已经在前面讲过了，就是取E和C的max_free以及A的adj_free三者之最大值。此处就是把A的max_free从原来的300更新成了200，表示在A的新子树中的最大相邻空闲空间为200，在图中用红色标出。除此之外，节点B的子树也发生了变化，但由于后面的操作还有可能会影响到B的子树结构，所以此处并不急于更新B的max_free字段。</p>

<p><img alt="issue18_3_3.jpg" src="/journal/attachments/issue18_3_3.jpg" width="448" height="486" /></p>

<p>继续：</p>

<div class="code">
_______________________________________________________________________________<br>
预置条件：llist = rlist = NULL; y = B; root = A;<br>
-------------------------------------------------------------------------------<br>
639    root = D<br>
640    B->left = NULL<br>
641    rlist = B<br>
_______________________________________________________________________________<br>
</div>

<p>经过上述操作之后的树的形态如图vm_map_entry_splay_003所示。我们可以看到，此时的splay树已被一分为二：一边是已经遍历过的以B为顶点的子树，这个子树以rlist指针指向；一边是尚未遍历过的以D为顶点的子树，这个子树以root指针指向。</p>

<p><img alt="issue18_3_4.jpg" src="/journal/attachments/issue18_3_4.jpg" width="520" height="462" /></p>

<p>for循环的第一次执行到次就结束了。下面是第二次：</p>

<div class="code">
_______________________________________________________________________________<br>
预置条件：llist = NULL; rlist = B; root = D;<br>
-------------------------------------------------------------------------------<br>
630    1000 > 300<br>
649    y = G<br>
650    1000 > 400 且 G != NULL<br>
652    1000 > 800 且 I != NULL<br>
654    D->right = NULL<br>
655    G->left = D<br>
656    重新计算D的max_free字段的值<br>
_______________________________________________________________________________<br>
</div>

<p>经过上述操作之后的树的形态如图vm_map_entry_splay_004所示。此时D的子树也发生了变化，且我们已经在第652行确定在G处将继续向右走，因此D的子树不会再有变化，于是就在这里对D的max_free进行更新，只不过更新前后的值都是300，在图中用红色标出。</p>

<p><img alt="issue18_3_5.jpg" src="/journal/attachments/issue18_3_5.jpg" width="520" height="462" /></p>

<p>继续：</p>

<div class="code">
_______________________________________________________________________________<br>
预置条件：llist = NULL; rlist = B; root = D; y = G;<br>
-------------------------------------------------------------------------------<br>
657    root = I<br>
658    G->right = NULL<br>
659    llist = G<br>
_______________________________________________________________________________<br>
</div>

<p>经过上述操作之后的树的形态如图vm_map_entry_splay_005所示。我们可以看到，新的root已经指向了I，而已经遍历过的部分则被分成了两个子树，分别由rlist和llist指针指向。我们还可以初步看出这两个子树的性质，rlist指向的子树中的地址都是大于我们的入参地址100的，而llist指向的子树中的地址都是小于1000的。</p>

<p><img alt="issue18_3_6.jpg" src="/journal/attachments/issue18_3_6.jpg" width="508" height="462" /></p>

<p>下面是for循环的第三次执行：</p>

<div class="code">
_______________________________________________________________________________<br>
预置条件：llist = G; rlist = B; root = I;<br>
-------------------------------------------------------------------------------<br>
630    1000 < 1050<br>
631    y = J<br>
632    J != NULL<br>
634    1000 > 900 <br>
635    I->left = B<br>
636    rlist = I<br>
637    root = J<br>
_______________________________________________________________________________<br>
</div>

<p>经过上述操作之后的树的形态如图vm_map_entry_splay_006所示。我们可以看到，新的root已经指向了J节点，当然，对于我们的例子而言，这也是最后一个节点。遍历过的部分仍然被分成了由rlist和llist两个指针指向的子树。刚才遍历过的I及其右子树被“倒置”在了B及其子树的上面。连接I和B的指针被标成了红色，因为这条边等会还有用，别忘了，用这条边串起来的节点的max_free字段的值还都没有确定呢，等会就要通过这条边来更新这些节点的max_free字段。同样，在llist指向的子树中也会有这么一条边，只是对于我们的例子而言就无法画出来了。另外还有一点需要明确，rlist的子树中的“这条边”是由一系列的left指针连成的，而llist的子树中的“那条边”则是由一系列的right指针连成的。</p>

<p><img alt="issue18_3_7.jpg" src="/journal/attachments/issue18_3_7.jpg" width="484" height="462" /></p>

<p>下面是for循环的第四次执行：</p>

<div class="code">
_______________________________________________________________________________<br>
预置条件：llist = G; rlist = I; root = J;<br>
-------------------------------------------------------------------------------<br>
630    1000 > 900<br>
649    y = NULL<br>
650    Y = NULL<br>
651    终止for循环<br>
_______________________________________________________________________________<br>
</div>

<p>由于我们只剩最后一个J entry了，于是for循环到此结束。实际上也就完成了对splay树的“第一遍”处理。</p>

<p>下面是“第二遍”的代码：</p>

<div class="code">
_____________________________________________________________________FreeBSD6.0<br>
669      /*<br>
670       * Pass Two: Walk back up the two spines, flip the pointers<br>
671       * and set max_free.  The subtrees of the root go at the<br>
672       * bottom of llist and rlist.<br>
673       */<br>
674      ltree = root->left;<br>
675      while (llist != NULL) {<br>
676          y = llist->right;<br>
677          llist->right = ltree;<br>
678          vm_map_entry_set_max_free(llist);<br>
679          ltree = llist;<br>
680          llist = y;<br>
681      }<br>
682      rtree = root->right;<br>
683      while (rlist != NULL) {<br>
684          y = rlist->left;<br>
685          rlist->left = rtree;<br>
686          vm_map_entry_set_max_free(rlist);<br>
687          rtree = rlist;<br>
688          rlist = y;<br>
689      }<br>
_______________________________________________________/usr/src/sys/vm/vm_map.c<br>
</div>

<p>这部分代码比较简单，而且一些基本思想我们已经在前面介绍过了，所以这里就不再做详细的逐步分析，仅从大的概念上来谈谈。</p>

<p>splay树的基本思想是什么？就是假设我这次访问的节点在将来被继续访问的可能性极高，因此需要把这个节点移往树根，虽然这次的移动要花点时间，但如果前面的假设成立，这种算法就能获得极好的“摊平效率”，也就是说，从一段长时间的连续操作来看，它的平均操作时间是相当低的。此处，我们的splay操作到J entry那儿就结束了，这个entry要么就包含我们的入参地址，要么就是和我们的入参地址相邻，也就是说，按照splay树的算法，它即将成为新的树根。但一人得道，鸡犬不能升天，J entry可以成为树根，但它的子树却没有沾光的权利，它们仍将留在树中相对较低的位置。</p>

<p>我们前面已经提到过，llist指向的子树中用right指针串起来的那些entry和rlist指向的子树中用left指针串起来的那些entry的max_free字段都还没定呢，现在就要分别确定它们。这两个子树是互不相干的，因为一边是小于入参地址的，一边是大于入参地址的。但是有个问题，别忘了当前root也即J的子树。虽然在我们的例子里J是没有子树的，但在实际当中是完全可能有的。因此这次的计算也得把它们捎上。这两个子树显然也是一边小于入参地址，一边大于入参地址，所以它们将分别加入llist和rlist指向的子树。在两个while循环开始之前的ltree = root->left;和rtree = root->right;这两条语句就是为了让llist和rlist分别接管当前root的这两个子树。</p>

<p>另外还要注意的就是，llist和rlist指向的子树都是”倒置的“。比如在我们的例子中，I的子树放在了B的子树的上面，这种倒置的设计就是为了这第二遍的计算。因为第一遍的splay操作是从上到下，而第二遍对max_free的更新必须从下到上更新才行。要达到这个目的，我们就得知道来时的路径。这里的设计和“栈”很像，先入后出，既保存了路径，又省掉了一个父指针。</p>

<p>所以，在上面的两个while循环里，就沿着right指针遍历llist链表，沿着left指针遍历rlist链表，在对所经过的entry的max_free进行更新之后，就把顺序纠正过来，该在上面的还在上面，该在下面的还在下面。完成之后的树的形态如图vm_map_entry_splay_007所示。</p>

<p><img alt="issue18_3_8.jpg" src="/journal/attachments/issue18_3_8.jpg" width="562" height="474" /></p>

<p>llist和rlist指针被用来遍历链表了，最后肯定要变成空指针，所以拨乱反正之后的两个子树就分别用ltree和rtree指针来指向。我们可以看到，通过这次更新计算，G entry的max_free从200变成了300，而B entry的max_free则从300变成了200，在图中用红色标出。</p>

<p>现在两边躯体都已经弄好了，就差把“头”装上去了：</p>

<div class="code">
_____________________________________________________________________FreeBSD6.0<br>
691      /*<br>
692       * Final assembly: add ltree and rtree as subtrees of root.<br>
693       */<br>
694      root->left = ltree;<br>
695      root->right = rtree;<br>
696      vm_map_entry_set_max_free(root);<br>
697  <br>
698      return (root);<br>
_______________________________________________________/usr/src/sys/vm/vm_map.c<br>
</div>

<p>对于我们的例子来说，就是把ltree指向的子树作为J的左子树，把rtree指向的子树作为J的右子树。最后还别忘了更新J的max_free，从50改成300。最终的树的形态如图vm_map_entry_splay_008所示。</p>

<p><img alt="issue18_3_9.jpg" src="/journal/attachments/issue18_3_9.jpg" width="556" height="414" /></p>

<p>vm_map_entry_splay()函数到此结束。</p>

<p>vm_map_findspace()函数</p>

<p>vm_map_findspace()函数的功能就是对于给定的map映射，找到其虚拟地址空间中位于start地址之后的第一个满足length长度要求的空闲空间。</p>

<div class="code">
_____________________________________________________________________FreeBSD6.0<br>
1005  int<br>
1006  vm_map_findspace(vm_map_t map, vm_offset_t start, vm_size_t length,<br>
1007      vm_offset_t *addr)    /* OUT */<br>
1008  {<br>
1009      vm_map_entry_t entry;<br>
1010      vm_offset_t end, st;<br>
_______________________________________________________/usr/src/sys/vm/vm_map.c<br>
</div>

<p>首先是对所请求的数据进行判断。即所请求的起始地址必须大于该map的最低地址，而所请求的地址范围必须落在该map的最高地址之内，且所请求的地址范围不会跨越vm_size_t类型的表达极限。若树为空，则表示该map的地址空间尚是一块完整的没有开垦过的土地，因此返回其最低地址，查找成功结束。</p>

<div class="code">
_____________________________________________________________________FreeBSD6.0<br>
1012      /*<br>
1013       * Request must fit within min/max VM address and must avoid<br>
1014       * address wrap.<br>
1015       */<br>
1016      if (start < map->min_offset)<br>
1017          start = map->min_offset;<br>
1018      if (start + length > map->max_offset || start + length < start)<br>
1019          return (1);<br>
1020  <br>
1021      /* Empty tree means wide open address space. */<br>
1022      if (map->root == NULL) {<br>
1023          *addr = start;<br>
1024          goto found;<br>
1025      }<br>
_______________________________________________________/usr/src/sys/vm/vm_map.c<br>
</div>

<p>接下来是用入参地址start去对树进行splay操作，参见前面对vm_map_entry_splay()函数的分析，这个操作之后，新的树根所表示的地址区间要么就包含start，要么就紧接在start所属的空闲区域之前或者之后。首先假设新的树根位于start之后，如果start之后的空闲空间满足我们的需求，查找操作成功结束。如果start属于新树根的地址区间，我们就以新树根的终止地址作为请求的起始地址，否则仍然以start作为起始地址。map->root->end + map->root->adj_free实际上就找到了新树根在链表中的下一个entry的起始地址，也就是虚拟地址空间中位于新树根的地址区间之后的下一个非空闲区域的起始地址，用这个地址减去我们前面算出的请求起始地址，就能判断出这个空闲区间内是否有足够的空间满足我们的请求。如果满足，则查找成功结束。</p>

<div class="code">
_____________________________________________________________________FreeBSD6.0<br>
1027      /*<br>
1028       * After splay, if start comes before root node, then there<br>
1029       * must be a gap from start to the root.<br>
1030       */<br>
1031      map->root = vm_map_entry_splay(start, map->root);<br>
1032      if (start + length <= map->root->start) {<br>
1033          *addr = start;<br>
1034          goto found;<br>
1035      }<br>
1036  <br>
1037      /*<br>
1038       * Root is the last node that might begin its gap before<br>
1039       * start, and this is the last comparison where address<br>
1040       * wrap might be a problem.<br>
1041       */<br>
1042      st = (start > map->root->end) ? start : map->root->end;<br>
1043      if (length <= map->root->end + map->root->adj_free - st) {<br>
1044          *addr = st;<br>
1045          goto found;<br>
1046      }<br>
_______________________________________________________/usr/src/sys/vm/vm_map.c<br>
</div>

<p>流程走到这里，说明在和新root相邻的空闲空间中没有找到满足要求的空闲区域，需要我们继续往树中查找。注意，vm_map_findspace()函数只在入参start之后的地址空间中查找空闲区域，经过前面用start对整个树的splay处理，我们现在就可以确定，这样的空间如果存在，那它只能出现在当前树根的右子树中，因为这半个子树表示的才是地址空间中大于start的部分。如果右子树为空，那么我们就可以直接断定查找失败。如果右子树非空，但它的子树中没有超过我们所请求的length的空闲空间，那么我们也可以直接断定查找失败。此处的判断也正是前面所讲的vm_map_entry_splay()函数中计算max_free字段的意义所在。</p>

<div class="code">
_____________________________________________________________________FreeBSD6.0<br>
1048      /* With max_free, can immediately tell if no solution. */<br>
1049      entry = map->root->right;<br>
1050      if (entry == NULL || length > entry->max_free)<br>
1051          return (1);<br>
_______________________________________________________/usr/src/sys/vm/vm_map.c<br>
</div>

<p>下面就是遍历右子树的代码，清晰明了。注意，FreeBSD查找可用空闲空间时使用的是first fit原则，即从低地址开始，返回满足条件的第一个空闲空间。由于splay树本质上就是一个二叉查找树，左小右大，所以遍历操作也按左、中、右的顺序来安排。其中的注释也提到了我们之前的分析，即右子树中的所有区域的地址都是大于start的。显然，之所以能进入这个遍历循环，是因为我们在第1050行判断过这个右子树中一定存在大于length的空闲空间，因此，遍历操作不可能以失败结束。所以在while循环之后会有一个panic，如果执行到了那条语句，那就说明max_free出问题了。注意，我们遍历每一级的时候都是用max_free字段来判断子树中是否有满足条件的空闲空间的。</p>

<div class="code">
_____________________________________________________________________FreeBSD6.0<br>
1053      /*<br>
1054       * Search the right subtree in the order: left subtree, root,<br>
1055       * right subtree (first fit).  The previous splay implies that<br>
1056       * all regions in the right subtree have addresses > start.<br>
1057       */<br>
1058      while (entry != NULL) {<br>
1059          if (entry->left != NULL && entry->left->max_free >= length)<br>
1060              entry = entry->left;<br>
1061          else if (entry->adj_free >= length) {<br>
1062              *addr = entry->end;<br>
1063              goto found;<br>
1064          } else<br>
1065              entry = entry->right;<br>
1066      }<br>
1067  <br>
1068      /* Can't get here, so panic if we do. */<br>
1069      panic("vm_map_findspace: max_free corrupt");<br>
_______________________________________________________/usr/src/sys/vm/vm_map.c<br>
</div>

<p>如果vm_map_findspace()函数的查找操作是针对内核映射而言，则需要将所请求的空间向上调整为页尺寸的整数倍，并根据实际情况决定是否增加内核的地址空间。</p>

<div class="code">
_____________________________________________________________________FreeBSD6.0<br>
1071  found:<br>
1072      /* Expand the kernel pmap, if necessary. */<br>
1073      if (map == kernel_map) {<br>
1074          end = round_page(*addr + length);<br>
1075          if (end > kernel_vm_end)<br>
1076              pmap_growkernel(end);<br>
1077      }<br>
1078      return (0);<br>
_______________________________________________________/usr/src/sys/vm/vm_map.c<br>
</div>
]]></description>
         <link>/journal/systems/2006/000125.html</link>
         <guid>/journal/systems/2006/000125.html</guid>
         <category>issue0I</category>
         <pubDate>Wed, 07 Jun 2006 10:07:15 +0800</pubDate>
      </item>
            <item>
         <title>Trac on FreeBSD实践</title>
         <description><![CDATA[<p>薛建敏 &lt; xuejm # cnfug.org &gt;</p>

<h5>概要</h5>

<p>本文简要介绍了如何在FreeBSD上面安装Trac。Trac是一个具有增强的Wiki和问题跟踪系统的基于WWW的项目管理系统。</p>

<p>·它无缝集成了SVN版本控制系统，并且提供了一个浏览SVN源代码的页面。<br />
·它提供了一系列的报表来协助项目的管理。<br />
·它允许在问题描述中嵌入Wiki格式的内容。<br />
·它允许在向SVN提交更改的信息中嵌入Wiki格式的内容。 </p>

<h5>目的</h5>

<p>通过阅读或者参考本文，您可以最终在FreeBSD系统上面安装和运行一个Trac项目管理系统。 </p>

<h5>前置条件</h5>

<p>安装这个系统需要您对于FreeBSD有基本了解，知道Python编程语言。在安装的过程中如果出现问题能够自己参考FreeBSD手册来解决。 </p>

<h5>系统需求</h5>

<p>在安装Trac的时候，需要以下系统或者软件并且所有软件将会从FreeBSD Ports中来安装： </p>

<p>·FreeBSD 5.3最小安装版：如果使用最小安装版，您在安装的时候需要拥有连接因特网。 <br />
·portmanager <br />
·Apache2 <br />
·Python <br />
·SQLite: Trac用来存储数据的数据库系统。 <br />
·SVN: 也就是Subversion, 在CVS基础上重新设计的新一代版本控制系统。 <br />
·ClearSilver-python <br />
·py-PySQLite: 用来支持通过Python语言访问SQLite数据库。 <br />
·Trac </p>

<h5>详细步骤</h5> 

<p>1. 安装FreeBSD5.3。建议在安装之前阅读FreeBSD手册，因为我是从头安装起来的因此不存在保存、备份以前数据的问题。如果你没有空闲的机器，那么希望你能够找到其他方法；如果你已经有现成的FreeBSD5.3系统，则可以跳过第1节。 </p>

<p>1.1 将安装光盘放入CD-ROM然后重新启动机器，如果你已经设置了光驱启动功能，那么机器重新启动后就可以进入内核配置界面截图如下：<br />
<img alt="issue_18_2_1.png" src="/journal/attachments/issue_18_2_1.png" width="640" height="400" /><br />
我们不关系内核配置，因此选择第一行跳过内核配置进入安装界面截图如下：<br />
<img alt="issue_18_2_1.png" src="/journal/attachments/issue_18_2_1.png" width="640" height="400" /></p>

<p>1.2 在安装界面中选择Express进入安装过程，在这个过程中他会提示你进行磁盘分区等操作。遇到这些操作的时候按照提示进行即可。 </p>

<p>1.3 进入选择安装方式的时候，系统提供你几个配置包供你选择。因为我们安装的是一个台项目管理服务器，因此不需要图形界面。因此我们选择安装最小的二进制文件和文档(User)就可以了。 </p>

<p>1.4 在选择了安装User包以后，系统会提示你是否需要安装Ports集合，因为我们所有的基础系统都是从Ports中安装，因此这里选择“是(Yes)”。 </p>

<p>1.5 在系统提示选择安装媒介的时候选择CD/DVD。然后经过大约十几分钟的安装，我们的FreeBSD系统安装差不多了好了。 </p>

<p>1.6 系统安装完成以后我们还需要做几件事情：设置root用户密码; 激活网络，这样我们可以访问因特网已安装其他软件。</p>

<p>2. 安装Portmanager。由于是第一次使用ports来安装软件，因此我们先要找找这个portmanager在那里： <br />
<div class="code">#whereis portmanager<br />
#portmanager: /usr/ports/sysutils/portmanager<br />
</div></p>

<p>通过运行上面的命令，我们知道portmanager在/usr/ports/sysutils/portmanager目录里面，于是我们进入这个目录。然后运行命令<br />
<div class="code">#make install clean</div><br />
这样经过一段时间portmanager就安装好了。</p>

<p>3. 安装Apache2。同样方法我们找到apache软件在目录/usr/ports/www/apache2中，然后我们执行一下命令安装apache2:<br />
<div class="code">#cd /usr/ports/www/apache2<br />
#make WITH_BERKELEYDB=db4 install clean<br />
</div></p>

<p>这里我们在安装Apache2的时候，指定Apache2需要支持Berkeley DB4。因为后面安装的SVN系统需要使用db4，如果我们这里不一次方式安装，安装SVN的过程中会出现错误。</p>

<p>4. 安装Python：<br />
<div class="code">#cd /usr/ports/lang/python<br />
#make install clean<br />
</div><br />
5. 安装SQLite:<br />
<div class="code">#cd /usr/ports/database/sqlite<br />
#make install clean<br />
</div><br />
6. 安装SVN: 这里我安装的时候直接选择支持Python和Apache，这样子安装以后我们就只要在小修改一下httpd.conf就可以让Apache和SVN一起协作了。<br />
<div class="code">#cd /usr/ports/devel/subversion<br />
#make WITH_PYTHON=yes WITH_MOD_DAV_SVN=yes install clean <br />
</div><br />
提示：如果前面我们在安装Apache2的时候没有支持bdb4，那么SVN安装的过程中将出现错误以致无法继续。</p>

<p>7. 安装ClearSilver-python: 在Trac的官方安装文档上面将clearsilver和python支持库的安装时分开的，由于我不是很了解他们之间的关系，而且ports里面提供了clearsilver-python的安装包，因此我直接采用这个。<br />
<div class="code">#cd /usr/ports/www/clearsilver-python<br />
#make install clean<br />
</div><br />
8. 安装pysqlite：因为Trac是用Python语言编写的，而且在其运行过程中需要访问SQLite数据库，我们也必须提供python 的支持。<br />
<div class="code">#whereis py-PySQLite<br />
#py-PySQLite: /usr/ports/database/py-PySQLite<br />
#cd /usr/ports/database/py-PySQLite<br />
#make install clean<br />
</div><br />
9. 配置Apache2以支持SVN: 打开配置文件httpd.conf，找到LoadModule那部分将将一下模块的加载语句前面的注释去除：<br />
<div class="code">LoadModule dav_module ...<br />
LoadModule dav_fs_module ...<br />
LoadModule dav_svn_module ...<br />
LoadModule authz_svn_module ...<br />
</div><br />
下来我们需要添加配置以便让Apache直到我们的SVN仓库的位置信息，添加以下内容到httpd.conf<br />
<div class="code">&lt;Location /svn&gt;<br />
DAV svn<br />
SVNPath /usr/home/svn<br />
&lt;/Location&gt;<br />
</div><br />
注意：这里没有关于安全认证的配置，关于这部分清参考书籍:Version Control with Subversion </p>

<p>10. 添加用户对svn进行管理：这里我建议不要用root来运行所有的软件，这样可以增强系统的安全性。因此我们这里需要增加新用户来管理svn. <br />
10.1 增加用户组svn <br />
10.2 增加用户svn, 将用户svn添加到组svn中。</p>

<p>11. 添加第一个SVN项目仓库：<br />
<div class="code">#su - svn<br />
$makedir cnfug<br />
$svnadmin create cnfug<br />
</div><br />
12. 添加完了仓库以后我们需要允许apachd2来读写这个仓库，因此我们需要通过以下方式来实现：<br />
12.1 将apache2运行的账户www添加到组svn中来。<br />
12.2 将目录/usr/home/svn/cnfug及其子目录的权限更改为“组可写”<br />
<div class="code">#pw groupmod svn -M www<br />
#su - svn<br />
$chmod -R g+w cnfug<br />
</div><br />
13. 添加第一个项目到SVN仓库cnfug:<br />
<div class="code">#su - svn<br />
$cd /tmp<br />
$mkdir helloworld<br />
$touch helloworld/helloworld.py<br />
$svn import helloworld http://localhost/svn/cnfug/helloworld -m "Importing of 1st project"<br />
</div><br />
<img alt="issue_18_2_3.png" src="/journal/attachments/issue_18_2_3.png" width="640" height="400" /></p>

<p>14. 重新启动Apache2：<br />
<div class="code">#/usr/local/sbin/apachectl restart</div></p>

<p>15. 访问http://localhost/svn/cnfug, 你应该可以看到我们刚才添加的目录helloworld。这个表明我们对于SVN和Apache2的安装已经成功！</p>

<p>16. 创建用户trac和用户组trac, 将用户trac添加到svn用户组中，以便于trac访问svn仓库。</p>

<p>17. 安装配置Trac，最后一步了加油阿！ </p>

<p>17.1 安装Trac：<br />
<div class="code">#whereis trac<br />
#trac: /usr/ports/www/trac<br />
#cd /usr/ports/www/trac<br />
#make install clean<br />
</div><br />
17.2 配置Trac并创建第一个项目：<br />
<div class="code">#su - trac<br />
$trac-admin /usr/home/trac/cnfug initenv<br />
</div><br />
下来Trac在环境初始化的时候会询问你几个问题包括：项目名称，SVN仓库路径，Trac模版目录。然后环境就创建成功了。</p>

<p>18. 运行第一个Trac项目：<br />
<div class="code">$tracd --port 8080 cnfug</div></p>

<p>19. 这样子我们第一个Trac项目就运行起来了！！让我们登陆到http://localhost:8080/上去就可以看到如下图片中的内容。<br />
<img alt="issue_18_2_4.jpg" src="/journal/attachments/issue_18_2_4.jpg" width="500" height="285" /><br />
<img alt="issue_18_2_5.jpg" src="/journal/attachments/issue_18_2_5.jpg" width="500" height="375" /><br />
<img alt="issue_18_2_6.jpg" src="/journal/attachments/issue_18_2_6.jpg" width="500" height="201" /></p>

<p>经过以上的步骤，我们终于让我们的第一个Trac系统运行起来了。可能大家这里也注意到了我们这个Trac是基于tracd运行的，而更多人希望是能够和Apache结合起来。 </p>

<p>我也希望在下一篇文章中能够介绍一下如何在现在的基础上和Apache2集成起来运行我们的Trac! </p>

<h5>参考资料及资源</h5>

<p>在本文的编写过程中参考或者使用了因特网上面的资源或文档，这里本人尊重相关资源所有者的所有权利并无意侵犯。这里列举其中一些已表示对于作者的感谢。 </p>

<p>·FreeBSD ISO: ftp://ftp.freebsd.org/pub/FreeBSD/releases/i386/ISO-IMAGES/5.4/ <br />
·FreeBSD手册: http://www.freebsd.org/doc/en_US.ISO8859-1/books/handbook/index.html <br />
·Trac: http://projects.edgewall.com/trac/ <br />
·Trac Installation Guide: http://projects.edgewall.com/trac/wiki/TracInstall <br />
·SVN: http://subversion.tigris.org/ <br />
·SQLite: http://www.sqlite.org/ <br />
·PySQLite: http://sourceforge.net/projects/pysqlite <br />
·ClearSilver: http://www.clearsilver.net/ <br />
·Python: http://www.python.org/ <br />
·Apache2: http://httpd.apache.org/ <br />
</p>]]></description>
         <link>/journal/systems/2006/000124.html</link>
         <guid>/journal/systems/2006/000124.html</guid>
         <category>issue0I</category>
         <pubDate>Fri, 02 Jun 2006 11:55:29 +0800</pubDate>
      </item>
            <item>
         <title>PXE全自动安装FreeBSD操作实例</title>
         <description><![CDATA[<p>Matthew &lt; matthew # cnfug.org &gt;</p>

<h5>0、概述</h5>

<p>当需要在多台计算机上安装FreeBSD操作系统时，我们可以使用PXE全自动安装方式，来减少重复工作量，这里以实例介绍PXE安装FreeBSD的实现过程。<br />
整体思路是：<br />
　　　　　PXE客户端启动 -> 从PXE服务端获取IP地址和pxeboot -> <br />
　　　　　pxeboot开始运行，以TFTP方式获取/boot/loader.rc -> <br />
　　　　　按照loader.rc设置启动系统 -> 运行sysinstall，通过FTP获取安装文件，开始自动安装</p>

<p>接下来，让我们按照这个思路来一步步实现。</p>

<h5>1、安装DHCP服务</h5>

<p>PXE启动主要是通过DHCP服务实现，所以我们首先需要安装DHCP服务，这里使用isc-dhcp，并使用ports方式安装。</p>

<div class="code"># cd /usr/ports/net/isc-dhcp3-server
# make install clean
</div>

<p>出现ports选项，使用默认值。</p>

<p>安装完成后，就是配置DHCP服务了，DHCP的默认配置文件是/usr/local/etc/dhcpd.conf，本例中的配置如下：</p>

<div class="code">	option domain-name "cnfug.org";
	option domain-name-servers ns1.cnfug.org, ns2.cnfug.org;
	default-lease-time 600;
	max-lease-time 7200;
	authoritative;
	ddns-update-style none;
	log-facility local7;

<p>	server-name "PXE Server";<br />
	server-identifier 192.168.7.1;</p>

<p>	subnet 192.168.7.0 netmask 255.255.255.0 {<br />
	  range 192.168.7.10 192.168.7.250;<br />
	  option routers 192.168.7.1;<br />
	  option broadcast-address 192.168.7.255;<br />
	  filename "boot/pxeboot";<br />
	  next-server 192.168.7.1;<br />
	}<br />
</div></p>

<h5>2、准备PXE环境</h5>

<p>我们将PXE环境所需要的文件全部放于/home/pxe目录下，首先要准备的就是pxeboot文件，pxeboot实际就是loader，只是它用于PXE环境。<br />
标准的pxeboot默认使用NFS获取启动文件、内核等文件，但我们这里设计的是TFTP方式，所以我们需要重新编译pxeboot，让它使用TFTP来获取启动文件，按照下面的步骤操作：<br />
<div class="code"># cd /usr/src/sys/boot<br />
# vi i386/libi386/pxe.c<br />
</div><br />
将第339行<br />
<div class="code">pxe_setnfshandle(rootpath);</div><br />
注释掉或者删除，这样可以避免pxeboot尝试使用NFS而带来的超时等待。<br />
<div class="code"># setenv LOADER_TFTP_SUPPORT<br />
# make clean && make<br />
</div><br />
接下来把pxeboot放到/home/pxe中<br />
<div class="code"># mkdir -p /home/pxe/boot<br />
# cp /usr/src/sys/boot/i386/pxeldr/pxeboot /home/pxe/boot/<br />
</div><br />
现在/home/pxe/boot目录中有了pxeboot文件，现在我们需要准备/home/pxe/boot目录中的其它文件，这里我们使用安装软盘中的文件。</p>

<div class="code"># mkdir -p /mnt/cd
# mkdir -p /mnt/fd
# mount -t cd9660 /dev/acd0 /mnt/cd
# cp /mnt/cd/floppies/boot.flp /tmp
# mdconfig -a -t vnode -u0 -f /tmp/boot.flp
# mount /dev/md0 /mnt/fd
# cp -RpP /mnt/fd/boot/* /home/pxe/boot/
# cp /boot/kernel/kernel /home/pxe/
# cp /mnt/fd/mfsroot.gz /home/pxe/
# umount /dev/md0
# mdconfig -d -u0
</div>

<p>修改 /home/pxe/boot/loader.conf 为</p>

<div class="code">bootfile="/kernel"
acpi_load="YES"
acpi_name="/acpi.ko"
mfsroot_load="YES"
mfsroot_type="mfs_root"
mfsroot_name="/mfsrot"
</div>
<h5>3、设置自动安装</h5>

<p>sysinstall在执行安装操作前会检查/install.cfg文件是否存在，如果存在则按照文件中的设置，执行全自动安装。我们所使用的是标准安装程序，整个安装所需要的基本环境存放在 /home/pxe/mfsroot.gz 中，由于它没有设置自动安装，所以我们需要修改 mfsroot.tgz 为其增加 install.cfg 文件。</p>

<div class="code"># cd /home/pxe
# gzip -d mfsroot.gz
# mdconfig -a -t vnode -u0 -f mfsroot
# mount /dev/md0 /mnt/fd
# vi /mnt/fd/install.cfg
</div>
然后输入以下内容：

<div class="code">	# 使用DHCP配置网络
	tryDHCP=YES

<p>	hostname=pxe.cnfug.org<br />
	domainname=cnfug.org</p>

<p>	# 设置安装方式为FTP和FTP服务器的地址<br />
	_ftpPath=ftp://192.168.7.1/<br />
	netDev=lnc0<br />
	mediaSetFTP</p>

<p>	# 设置安装类型为最小安装，如果你需要全部内容，请改为 distSetDeveloper<br />
	distSetMinimum</p>

<p>	# 磁盘分区配置<br />
	# 这里设置FreeBSD使用整个硬盘，并且安装bootmgr<br />
	disk=ad0<br />
	partition=all<br />
	bootManager=booteasy<br />
	diskPartitionEditor</p>

<p><br />
	# 设置disklabel<br />
	# 注意：所有的大小以512byte为单位</p>

<p>	# 为 / 分配 512MB 的空间<br />
	ad0s1-1=ufs 1048576 /<br />
	# 512MB 交换区<br />
	ad0s1-2=swap 1048576 none<br />
	# 剩余的磁盘空间全部分配给/usr，并且开启Soft-Updates<br />
	ad0s1-3=ufs 0 /usr 1<br />
	diskLabelEditor</p>

<p>	# 开始执行安装<br />
	installCommit<br />
</div><br />
配置中已经简单的说明每个配置的作用，更详细的说明请参照sysinstall(8)</p>

<p>现在重新压制mfsroot.gz</p>

<div class="code"># umount /mnt/fd
# mdconfig -d -u0
# gzip mfsroot
</div>
<h5>4、开启相关服务</h5>

<p>(1) DHCP服务<br />
首先要设置网卡IP为我们在dhcpd.conf中设置的IP 192.168.7.1，本文是在VMware中验证的，所以这里以lnc0为例<br />
<div class="code"># ifconfig lnc0 192.168.7.1<br />
# /usr/local/etc/rc.d/isc-dhcpd.sh start<br />
</div><br />
(2) TFTP服务<br />
我们设置/home/pxe为TFTP服务的根目录（即PXE启动环境的根目录），编辑/etc/inetd.conf，找到tftp那行，修改为：<br />
<div class="code">tftp	dgram	udp	wait 	root	/usr/libexec/tftpd	tftpd -l -s /home/pxe</div><br />
现在可以启动tftp服务了<br />
<div class="code"># /usr/sbin/inetd</div></p>

<p>(3) FTP服务<br />
我们设置了安装程序使用FTP的方式获取安装文件，所以我们需要在这台服务器上开启FTP服务。<br />
首先增加系统用户ftp及ftp组，并设置它的home目录为/mnt/cd，因为我们所有的安装程序都在光盘上，加上先前我们已经将光盘加载到/mnt/cd目录中，所以这里设置ftp帐号的home目录为/mnt/cd，就是设置了Anonymous FTP服务的根目录为/mnt/cd，当PXE客户端安装程序启动时，安装程序会从这里取得安装需要的文件<br />
<div class="code"># pw groupadd ftp -g 21<br />
# pw useradd ftp -u 21 -g ftp -s /usr/sbin/nologin -d /mnt/cd -w no<br />
</div><br />
最后一步，启动FTP服务<br />
<div class="code"># /usr/libexec/ftpd -DA4</div></p>

<p>现在一切准备已经完成，找到台支持PXE的机器，以PXE方式引导，你就可以看到FreeBSD在它上面自动完成整个安装过程了。</p>]]></description>
         <link>/journal/systems/2006/000123.html</link>
         <guid>/journal/systems/2006/000123.html</guid>
         <category>issue0I</category>
         <pubDate>Fri, 02 Jun 2006 10:21:32 +0800</pubDate>
      </item>
            <item>
         <title>架构基于FreeBSD和Postfix的IGENUS Webmail邮件系统</title>
         <description><![CDATA[<p><span class="author">杨廷勇 &lt; scyz # toping.net &gt;</span></p>

<p>Copyright &copy; 2004、2005、2006</p>

<p>本文介绍使用FreeBSD+Postfix+Cyrus-sasl+Courier-imap+igenus+spamassassin+Clamav+mailscanner+mailscanner-mrtg+mailman<br />
来架构一个具有多域名，有邮件列表、webmail、防病毒、防垃圾邮件、web管理界面的邮件系统。<br />
Jacky, $Revision: 4.51 bate $Date: 2005-12-03</p>

<p>系统主要采用MailScanner+clamav+Spamd+APF来对病毒过滤和垃圾邮件过滤。</p>

<p>本文在4.10、5.3、5.4、6.0上安装测试通过，病毒过滤放弃采用amavisd。主要采用执行效率更高的MailSanner来对邮件过滤和垃圾邮件过滤，配置更容易，并且降低了系统开消。让系统更加稳定，经过严格病毒邮件测试成功率达到了100%。垃圾邮件过滤基本上达到了95%的成功率。</p>

<p>Table of Contents <br />
Chapter 1. 系统安装<br />
1.1 安装MySQL<br />
1.2 安装Apache<br />
1.3 安装PHP<br />
1.4 安装zend<br />
1.5 安装openssl<br />
1.6 安装phpMyAdmin<br />
1.7 通过phpMyadmin设置数据库<br />
1.8安装Courier-imap<br />
1.9安装 postfix 和 cyrus-sasl<br />
1.10 安装expect<br />
Chapter 2. 配置邮件服务器<br />
2.1 配置rc.conf<br />
2.2 配置postfix 和 cyrus-sasl<br />
2.3 配置Courier-imap<br />
Chapter 3. 手动设置第一个用户并测试<br />
Chapter 4. 安装postfix管理工具<br />
4.1安装本人开发的postfix管理工具<br />
4.2 用户登录测试<br />
Chapter 5. 防病毒与防垃圾邮件<br />
5.1 安装Clamav<br />
5.2 安装MailScanner<br />
5.3 安装配置Spamassassin<br />
5.4修改Postfix设定档main.cf<br />
5.5修改mailscanner.conf<br />
5.6新增MailScanner所要用到的资料夹<br />
5.7把病毒提示信息改为中文<br />
5.8MailScanner监管进出邮件<br />
5.9 邮件流量监控（mailscanner-mrtg）安装与设置<br />
5.10．安装APF防垃圾邮件<br />
Chapter 6. 安装webmail <br />
Chapter 7邮件列表（mailman）<br />
Chapter 8. 查看系统状态</p>

<p>Chapter 1. 系统安装</p>

<p>安装之前：因用户数据都保存在/var目录下，因此安装FreeBSD时/var的空间应尽量大。FreeBSD的版本为5.3，按最小化安装，软件包只安装cvsup，安装结束后用cvsup更新ports树。在文档中假设服务器的ip地址为192.168.0.2，域名为toping.net，主机名为mail.toping.net。</p>

<p>请兄弟们仔细一些，注意空格和TAB。</p>

<p>祝兄弟们好运。本人水平有限。如果发现文章中有什么错误和不当的地方请发邮件：scyz@toping.net。我会在第一时间给予答复。</p>

<p><br />
1.1 安装MySQL<br />
mail# cd /usr/ports/databases/mysql40-server<br />
mail# make install clean</p>

<p>编辑/etc/rc.conf，加入<br />
mysql_enable="YES" </p>

<p>1.2 安装Apache<br />
mail# cd /usr/ports/www/apache2<br />
mail# make install clean</p>

<p>编辑/etc/rc.conf，加入<br />
Apache2_enable="YES" </p>

<p>1.3 安装PHP<br />
mail# cd /usr/ports/www/mod_php4<br />
mail# make install clean</p>

<p>我的选择：（注意别选DEBUG，否则会和ZEND有冲突）<br />
[X] APACHE2   Use apache 2.x instead of apache 1.3.x</p>

<p>安装需要的PHP扩展模块<br />
mail# cd /usr/ports/lang/php4-extensions<br />
mail# make install clean</p>

<p>我选择了下面的模块：</p>

<p>[X] BCMATH     bc style precision math functions <br />
[X] BZ2       bzip2 library support <br />
[X] CALENDAR   calendar conversion support <br />
[X] CRACK     crack support <br />
[X] CTYPE     ctype functions<br />
[X] CURL       CURL support<br />
[X] FTP       FTP support<br />
[X] GD       GD library support<br />
[X] GETTEXT     gettext library support <br />
[X] FILEINFO   fileinfo support<br />
[X] IMAP       IMAP support<br />
[X] MBSTRING   multibyte string support <br />
[X] MCAL       Modular Calendar Access Library support <br />
[X] MCRYPT     Encryption support <br />
[X] MCVE       MCVE support <br />
[X] MHASH     Crypto-hashing support <br />
[X] MYSQL     MySQL database support<br />
[X] PCRE       Perl Compatible Regular Expression support <br />
[X] POSIX     POSIX-like functions<br />
[X] SESSION     session support <br />
[X] TOKENIZER   tokenizer support <br />
[X] XML       XML support <br />
[X] ZLIB       ZLIB support</p>

<p>最后在编辑/usr/local/etc/apache2/httpd.conf最后加入：<br />
DirectoryIndex index.html index.html.var index.php</p>

<p>#注：在DirectoryIndex这里加入index.php,是为了让apache支持首页为index.php的首页文件</p>

<p>AddType application/x-httpd-php .php<br />
AddType application/x-httpd-php-source .phps</p>

<p>Group www、User www修改为： Group postfix、User postfix<br />
注：以上这一步要在postfix安装后再操作</p>

<p>1.4.安装zend<br />
mail# cd /usr/ports/devel/ZendOptimizer<br />
mail# make install clean</p>

<p>因为版权的问题，他不会自动下载。这里你需要到他的官方网站去下载ZendOptimizer-2.5.10a-freebsd4.3-i386.tar.gz到/usr/ports/distfiles目录下面后再安装。<br />
下载地址：<br />
http://downloads.zend.com/optimizer/2.5.10/ZendOptimizer-2.5.10a-freebsd4.3-i386.tar.gz</p>

<p>完成后在/usr/local/etc/php.ini中加入：<br />
[Zend]<br />
zend_optimizer.optimization_level=15<br />
zend_extension_manager.optimizer="/usr/local/lib/php/20020429/Optimizer"<br />
zend_extension_manager.optimizer_ts="/usr/local/lib/php/20020429/Optimizer_TS"<br />
zend_extension="/usr/local/lib/php/20020429/ZendExtensionManager.so"<br />
zend_extension_ts="/usr/local/lib/php/20020429/ZendExtensionManager_TS.so"</p>

<p>重启apache安装完成。</p>

<p>1.5 安装openssl<br />
mail# cd /usr/ports/security/openssl<br />
mail# make install clean</p>

<p>1.6 安装phpMyAdmin<br />
mail# cd /usr/ports/databases/phpmyadmin<br />
mail# make fetch<br />
注：(在这里建议直接下载后复制安装)</p>

<p>mail# cd /usr/ports/distfiles<br />
mail# tar –zxvf PhpMyadmin-x.tar.gz<br />
mail# mv /usr/local/www/phpMyAdmin-x /usr/local/www/data/dbadmin</p>

<p>修改/usr/local/www/data/dbadmin/config.inc.php<br />
$cfg['PmaAbsoluteUri'] = 'http://192.168.0.2/dbadmin/';<br />
$cfg['Servers'][$i]['auth_type']   = 'http';   // Authentication method (config, http or cookie based)?<br />
注：指定phpmyadmin的认证方式为http方式。</p>

<p>在浏览器输入http://192.168. 0.2/dbadmin/，首次进行登入的用户名为root密码为空，登入后可以修改你的密码。</p>

<p>1.7 通过phpMyadmin设置数据库</p>

<p>建立postfix数据库(注意：数据库名称为postfix)：</p>

<p>mail# mysql –u root –p<br />
mysql# CREATE DATABASE `postfix` ; <br />
mysql# use postfix;</p>

<p>下面为sql语句：</p>

<p>CREATE TABLE domaininfo (<br />
domain_id int(5) NOT NULL auto_increment,<br />
domain varchar(25) NOT NULL default '',<br />
alias varchar(30) default NULL,<br />
passwd varchar(35) NOT NULL default '',<br />
usernum int(5) NOT NULL default '0',<br />
quota int(11) NOT NULL default '0',<br />
des varchar(30) default NULL,<br />
expire date NOT NULL default '0000-00-00',<br />
active tinyint(1) NOT NULL default '1',<br />
create_time datetime default NULL,<br />
PRIMARY KEY (domain_id),<br />
UNIQUE KEY domain (domain),<br />
KEY domain_id (domain_id)<br />
) TYPE=MyISAM COMMENT='domain information';</p>

<p>INSERT INTO domaininfo VALUES (1,'admin',NULL,'$1$.j3.t12.$I7MGf7ZD2HrWwUWQF88Mg1',0,0,'Super Admin','0000-00-00',1,'0000-00-00 00:00:00');</p>

<p>CREATE TABLE userinfo (<br />
id int(11) NOT NULL auto_increment,<br />
userid varchar(20) NOT NULL default '',<br />
domain_id int(5) NOT NULL default '0',<br />
address varchar(50) NOT NULL default '',<br />
alias varchar(60) default NULL,<br />
passwd varchar(35) NOT NULL default '',<br />
realname varchar(20) default NULL,<br />
quota int(11) NOT NULL default '0',<br />
active tinyint(1) NOT NULL default '0',<br />
homedir varchar(60) NOT NULL default '',<br />
maildir varchar(60) NOT NULL default '',<br />
create_time datetime NOT NULL default '0000-00-00 00:00:00',<br />
`fax` varchar(20) NOT NULL default '',<br />
`telephone` varchar(15) NOT NULL default '',<br />
`sex` int(1) NOT NULL default '0',<br />
`year` int(4) NOT NULL default '0',<br />
`MONTH` int(2) NOT NULL default '0',<br />
`DAY` int(2) NOT NULL default '0',<br />
`education` varchar(4) NOT NULL default '',<br />
`marital` int(1) NOT NULL default '0',<br />
`occupation` varchar(15) NOT NULL default '',<br />
`companyname` varchar(30) NOT NULL default '',<br />
`province` varchar(6) NOT NULL default '',<br />
PRIMARY KEY (id),<br />
UNIQUE KEY address (address)<br />
) TYPE=InnoDB COMMENT='User Information';</p>

<p>注：对于初学者，建议以上操作都在phpmyadmin中操作更加的简便，如果后面要使用igenus请导入原来igenus的sql。</p>

<p>建立数据库用户并授以相应的权限</p>

<p>mail# mysql –u root –p<br />
mysql# use mysql;<br />
mysql# INSERT INTO user (host,user,password) VALUES('localhost','postfix','');<br />
mysql# update user set password=password('postfix') where User='postfix';<br />
mysql# GRANT ALL ON postfix.* TO postfix@localhost IDENTIFIED BY "postfix";<br />
注：这里加用户名和密码都为：postfix。并授权对postfix数据库进行操作</p>

<p>1.8 安装Courier-imap<br />
mail# cd /usr/ports/mail/courier-imap<br />
mail# make install clean<br />
我的选择：<br />
[X] OPENSSL     Build with OpenSSL support<br />
[X] AUTH_MYSQL   MySQL support</p>

<p>在/etc/rc.conf中加入：<br />
courier_authdaemond_enable="YES"<br />
courier_imap_pop3d_enable="YES"<br />
courier_imap_imapd_enable="YES"</p>

<p>mail# cd /usr/local/etc/courier-imap<br />
mail# cp imapd.cnf.dist imapd.cnf<br />
mail# cp pop3d.cnf.dist pop3d.cnf <br />
mail# /usr/local/etc/rc.d/courier-authdaemond.sh start</p>

<p>注：此时会在/var/run/authdaemond/下产生socket，如果没有下面这一步下面的认证无法通过。</p>

<p>mail# chmod +x /var/run/authdaemond</p>

<p>1.9 安装 postfix 和 cyrus-sasl<br />
mail# cd /usr/ports/security/cyrus-sasl2<br />
mail# make install WITH_AUTHDAEMON=yes<br />
mail# make clean</p>

<p>创建/usr/local/lib/sasl2/smtpd.conf</p>

<p>pwcheck_method: authdaemond<br />
log_level: 3<br />
mech_list: PLAIN LOGIN<br />
authdaemond_path:/var/run/authdaemond/socket</p>

<p>更详细的参数设置请看：<br />
http://www.toping.net/bbs/htm_data/7/0508/330.html</p>

<p>至此，认证部分基本完成。</p>

<p>安装postfix<br />
mail# cd /usr/ports/mail/postfix<br />
mail# make install clean</p>

<p>我的选择：<br />
[X] VDA       VDA (Virtual Delivery Agent)<br />
[X] MySQL     MySQL map lookups (choose version with WITH_MYSQL_VER) <br />
[X] TLS       SSL and TLS<br />
[X] SASL2     Cyrus SASLv2 (Simple Authentication and Security Layer)</p>

<p>回答下面的两问题：<br />
You need user "postfix" added to group "mail".[是否将postfix用户加到mail用户组]<br />
Would you like me to add it [y]? y<br />
Would you like to activate Postfix in /etc/mail/mailer.conf [n]? n</p>

<p>在/etc/rc.conf中加入postfix启动所需的启动选项<br />
在/etc/rc.conf中加入：</p>

<p>sendmail_enable="YES"<br />
sendmail_flags="-bd"<br />
sendmail_pidfile="/var/spool/postfix/pid/master.pid"<br />
sendmail_procname="/usr/local/libexec/postfix/master"<br />
sendmail_outbound_enable="NO"<br />
sendmail_submit_enable="NO"<br />
sendmail_msp_queue_enable="NO"</p>

<p>设置postfix启动所需<br />
mail# ln -s /usr/local/sbin/sendmail /usr/sbin/sendmail</p>

<p>注：如果/usr/sbin/sendmail存在就删了再做上链接，如果升级内核和升级系统后要重新做这一步。</p>

<p>mail# echo ‘postfix: root’ >> /etc/aliases<br />
mail# /usr/local/bin/newaliases<br />
mail# chown postfix:postfix /etc/opiekeys</p>

<p>1.10 安装expect<br />
用于Web客户端建立邮件用户<br />
mail# cd /usr/ports/lang/expect<br />
mail# make install clean</p>

<p>Chapter 2. 配置邮件服务器</p>

<p>本节主要讲述各种服务的参数配置。</p>

<p>2.1 配置rc.conf，编辑/etc/rc.conf</p>

<p>下面是前面所装软件都加入了启动选项的rc.conf配置：</p>

<p>mysql_enable="YES"<br />
apache2_enable="YES"<br />
courier_authdaemond_enable="YES"<br />
courier_imap_pop3d_enable="YES"<br />
courier_imap_imapd_enable="YES"<br />
sendmail_enable="YES"<br />
sendmail_flags="-bd"<br />
sendmail_pidfile="/var/spool/postfix/pid/master.pid"<br />
sendmail_procname="/usr/local/libexec/postfix/master"<br />
sendmail_outbound_enable="NO"<br />
sendmail_submit_enable="NO"<br />
sendmail_msp_queue_enable="NO"</p>

<p>2.2 配置postfix 和 cyrus-sasl<br />
(1)修改/usr/local/etc/postfix/main.cf，在文件最后加入以下内容<br />
mail# ee /usr/local/etc/postfix/main.cf<br />
smtpd_helo_required = yes<br />
strict_rfc821_envelopes = yes<br />
smtpd_etrn_restrictions = permit_mynetworks, reject<br />
#=====================BASE=====================<br />
myhostname = mail.toping.net<br />
mydomain = toping.net<br />
mydestination = $myhostname<br />
local_recipient_maps =<br />
command_directory = /usr/local/sbin<br />
local_transport = virtual<br />
#=====================MySQL=====================<br />
virtual_alias_maps = mysql:/usr/local/etc/postfix/mysql_virtual_alias_maps.cf<br />
virtual_gid_maps = static:125<br />
virtual_mailbox_base = /<br />
virtual_mailbox_domains = mysql:/usr/local/etc/postfix/mysql_virtual_domains_maps.cf<br />
virtual_mailbox_limit = 51200000<br />
virtual_mailbox_maps = mysql:/usr/local/etc/postfix/mysql_virtual_mailbox_maps.cf<br />
virtual_minimum_uid = 125<br />
virtual_transport = virtual<br />
virtual_uid_maps = static:125<br />
#=====================Quota=====================<br />
virtual_create_maildirsize = yes<br />
virtual_mailbox_extended = yes<br />
virtual_mailbox_limit_maps = mysql:/usr/local/etc/postfix/mysql_virtual_mailbox_limit_maps.cf<br />
virtual_mailbox_limit_override = yes<br />
virtual_maildir_limit_message = Sorry, the user's maildir has overdrawn his diskspace quota, please try again later.<br />
virtual_overquota_bounce = yes<br />
#====================SASL=====================<br />
smtpd_sasl_auth_enable = yes<br />
smtpd_sasl_security_options = noanonymous<br />
broken_sasl_auth_clients = yes<br />
smtpd_delay_reject=yes <br />
smtpd_recipient_restrictions = permit_mynetworks,permit_sasl_authenticated,permit_auth_destination,reject <br />
smtpd_client_restrictions = permit_sasl_authenticated</p>

<p>更详细的参数设置请看论坛：<br />
http://www.toping.net/bbs/htm_data/7/0601/871.html</p>

<p>(4)编辑/usr/local/etc/postfix/mysql_virtual_alias_maps.cf<br />
mail# ee /usr/local/etc/postfix/mysql_virtual_alias_maps.cf<br />
user = postfix<br />
password = postfix<br />
hosts = localhost<br />
dbname = postfix<br />
query = SELECT alias FROM userinfo WHERE address='%s' AND active = 1</p>

<p>(5)编辑/usr/local/etc/postfix/mysql_virtual_domains_maps.cf<br />
mail# ee /usr/local/etc/postfix/mysql_virtual_domains_maps.cf<br />
user = postfix<br />
password = postfix<br />
hosts = localhost<br />
dbname = postfix<br />
query = SELECT domain FROM domaininfo WHERE domain='%s'</p>

<p>(6)编辑/usr/local/etc/postfix/mysql_virtual_mailbox_maps.cf<br />
mail# ee /usr/local/etc/postfix/mysql_virtual_mailbox_maps.cf<br />
user = postfix<br />
password = postfix<br />
hosts = localhost<br />
dbname = postfix<br />
query = SELECT maildir FROM userinfo WHERE address='%s' AND active = 1</p>

<p>(7)编辑/usr/local/etc/postfix/mysql_virtual_mailbox_limit_maps.cf<br />
mail# ee /usr/local/etc/postfix/mysql_virtual_mailbox_limit_maps.cf<br />
user = postfix<br />
password = postfix<br />
hosts = localhost<br />
dbname = postfix<br />
query = SELECT quota FROM userinfo WHERE address='%s'</p>

<p>2.3 配置Courier-imap</p>

<p>(1)修改Courier相关设置，/usr/local/etc/courier-imap/imapd：</p>

<p>IMAP_CAPABILITY="IMAP4rev1 CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA" </p>

<p>(2)修改/usr/local/etc/courier-imap/pop3d</p>

<p>POP3AUTH="LOGIN CRAM-MD5 CRAM-SHA1"</p>

<p>(3)编辑修改/usr/local/etc/authlib/authmysqlrc<br />
mail# mv /usr/local/etc/authlib/authmysqlrc /usr/local/etc/authlib/authmysqlrc_bak<br />
mail# ee /usr/local/etc/authlib/authmysqlrc<br />
MYSQL_SERVER     localhost                         //数据库主机地址<br />
MYSQL_USERNAME       postfix                       //数据库用户名<br />
MYSQL_PASSWORD       postfix                       //数据库密码<br />
MYSQL_PORT       0<br />
MYSQL_OPT   0<br />
MYSQL_DATABASE       postfix                       //数据库名称<br />
MYSQL_USER_TABLE   userinfo<br />
MYSQL_CRYPT_PWFIELD   passwd<br />
MYSQL_UID_FIELD     '125'<br />
MYSQL_GID_FIELD     '125'<br />
MYSQL_LOGIN_FIELD address<br />
MYSQL_HOME_FIELD   homedir<br />
MYSQL_NAME_FIELD   realname<br />
MYSQL_MAILDIR_FIELD   maildir<br />
MYSQL_QUOTA_FIELD quota<br />
注：这里得用tab键来跳格</p>

<p>(4)编辑/usr/local/etc/authlib/authdaemonrc<br />
mail# mv /usr/local/etc/authlib/authdaemonrc /usr/local/etc/authlib/authdaemonrc_bak<br />
mail# ee /usr/local/etc/authlib/authdaemonrc<br />
authmodulelist="authmysql"<br />
authmodulelistorig="authmysql"<br />
version="authdaemond.mysql"<br />
daemons=5<br />
authdaemonvar=/var/run/authdaemond<br />
subsystem=mail<br />
DEBUG_LOGIN=0<br />
DEFAULTOPTIONS="wbnodsn=1"</p>

<p>重启服务器</p>

<p>Chapter 3.手动设置第一个用户并测试 </p>

<p>本章介绍如何开通用户，并且测试系统是否正常。</p>

<p>注：增加用户时请到这里生成加密后的密码后直接插入到数据库中就可以了。<br />
http://www.toping.net/soft<br />
mail# mysql<br />
mysql> use postfix;<br />
mysql> show tables;<br />
+-------------------+<br />
| Tables_in_postfix |<br />
+-------------------+<br />
| address       |<br />
| admin         |<br />
| card         |<br />
| domaininfo     |<br />
| lastauth       |<br />
| logs         |<br />
| message       |<br />
| personal       |<br />
| scheduler       |<br />
| stow         |<