首页 > Computer > Squid中文权威指南
2013
04-01

Squid中文权威指南

译者序:

本人在工作中维护着数台Squid服务器,多次参阅Duane Wessels(他也是Squid的创始人)的这本书,原书名是”Squid: The Definitive Guide”,由O’Reilly出版。我在业余时间把它翻译成中文,希望对中文Squid用户有所帮助。对普通的单位上网用户,Squid可充当代理服务器;而对Sina,NetEase这样的大型站点,Squid又充当WEB加速器。这两个角色它都扮演得异常优秀。窗外繁星点点,开源的世界亦如这星空般美丽,而Squid是其中耀眼的一颗星。

对本译版有任何问题,请跟我联系,我的Email是:yonghua_peng@yahoo.com.cn

彭勇华

第1章 介绍

第1章是Squid的介绍性描述,与技术关联不多,我不准备多翻译。

1.1 Web缓存

这节里需要明白3个概念:

cache命中在squid每次从它的缓存里满足HTTP请求时发生。cache命中率,是所有HTTP请求中命中的比例。Web缓存典型的cache命中率在30%到60%之间。另一个相似的度量单位叫做字节命中率,描绘了cache提供服务的数据容量(字节数)。

cache丢失在squid不能从它的缓存里满足HTTP请求时发生。cache丢失的理由有很多种。最明显的,当squid第一次接受到对特殊资源的请求时,就是一个cache丢失。类似的情况是,squid会清除缓存以释放空间给新对象。另外的可能是资源不可到达。原始服务器会指示cache怎样处理响应。例如,它会提示数据不能被缓存,或在有限的时间内才被重复使用,等等。

cache确认保证squid不对用户返回过时数据。在重复使用缓存对象时,squid经常从原始服务器确认它。假如服务器指示squid的拷贝仍然有效,数据就发送出去。否则,squid升级它的缓存拷贝,并且转发给客户。

1.2 Squid的简明历史

对本节感兴趣的读者请阅读英文原文档。

1.3 硬件和操作系统要求

Squid运行在所有流行的Unix系统上,也可以在Microsoft Windows上运行。尽管squid的Windows支持在不断改进,但也许在Unix上容易一些。假如你有一个喜欢的操作系统,我建议你使用那个。否则,假如你找人推荐,我很喜欢FreeBSD。

squid对硬件要求不算高。内存是最重要的资源。内存短缺会严重影响性能。磁盘空间也是另一个重要因素。更多的磁盘空间意味着更多的缓存目标和更高的命中率。快速的磁盘和驱动器也是有利的。如果你舍得花钱,SCSI磁盘比ATA的执行性能好。当然快速的CPU也是好的,但它并不是提高性能的关键因素。

因为squid对每个缓存响应使用少数内存,因此在磁盘空间和内存要求之间有一定联系。基本规则是,每G磁盘空间需要32M内存。这样,512M内存的系统,能支持16G的磁盘缓存。你的情况当然会不同。内存需求依赖于如下事实:缓存目标大小,CPU体系(32位或64位),同时在线的用户数量,和你使用的特殊功能。

人们经常问如此问题:“我的网络有X个用户,需要配备什么样的硬件给squid?”因为许多理由,这样的问题好难回答。特别的,很难说X个用户将产生多少流量。我告诉人们去建立一个有足够磁盘空间,可存储3-7天web流量数据的系统。例如,假如你的用户每天8小时耗费1M流量(仅仅HTTP和FTP传输),那就是每天大约3.5G。所以,我可以说,每兆web传输你需要10到25G的磁盘空间。

1.4 squid是开源的

Squid是自由软件和合作项目。假如你觉得squid有用,请考虑以下面一种或几种方法来回报该项目:

  • 1.参与squid用户讨论列表,回答问题和帮助新用户。
  • 2.测试新版本,报告bug或其他问题。
  • 3.致力于在线文档和FAQ。假如你发现错误,将它报告给维护者。
  • 4.将你的局部修改提交给开发者。
  • 5.对开发者提供财政支持。
  • 6.告诉开发者你想要的新功能。
  • 7.告诉你的朋友和同学,Squid非常Cool。

Squid是在GNU公用许可证(GPL)下发行的自由软件。关于GPL的更多信息请见: http://www.gnu.org/licenses/gpl-faq.html

1.5 Squid的Web主页

Squid的主页在http://www.squid-cache.org 你自己阅读该站点吧。

1.6 获取帮助
1.6.1 FAQ

Squid的FAQ文档在http://www.squid-cache.org/Doc/FAQ/FAQ.html,是对新用户的好信息资源。

1.6.2 邮件列表

Squid有三个邮件列表可用。邮件列表主页在: http://www.squid-cache.org/mailing-lists.html

1.6.2.1 Squid用户

订阅该邮件列表,发邮件到 squid-users-subscribe@squid-cache.org

1.6.2.2 Squid公告

订阅该邮件列表,发邮件到 squid-announce-subscribe@squid-cache.org

1.6.2.3 Squid开发

加入该邮件列表有所限制。它的内容发布在 http://www.squid-cache.org/mail-archive/squid-dev/

1.6.3 职业支持

即付费的支持。 职业支持服务提供商列表,请见http://www.squid-cache.org/Support/services.html

1.7 启动Squid

请按下面的章节一步一步来吧。

第2章 获取Squid

2.1 版本和发布

Squid开发者定期发布源代码。每一个发布版有一个版本号,例如2.5.STABLE4。版本号的第三部分以STABLE或DEVEL(短期开发版本)开头。

也许你能猜到,DEVEL版本倾向于拥有更新,更试验性的功能。但也许它们有更多的bugs。无经验的用户不应该运行DEVEL版本。假如你选择运行一个DEVEL版本,并且遇到了问题,请将问题报告给Squid维护者。

在一段时间的开发期后,Squid版本号变为STABLE。该版本适合于普通用户。当然,即使稳定版可能也有一些bugs。高的稳定版本(例如STABLE3,STABLE4)应该bugs更少。假如你特别关心稳定性,你应该使用这些最近发布版本中的一个。

2.2 使用源代码

为什么你不能copy一份预编译的二进制代码到你的系统中,并且期望它运行良好呢?主要理由是squid的代码需要知道特定操作系统的参数。实际上,最重要的参数是打开文件描述符的最大数量。Squid的./configure脚本在编译之前侦察这些值。假如你获取一个已编译的使用某个参数值的squid到另一个使用不同参数值的系统中,可能会遇到问题。

另一个理由是许多squid功能在编译时必须被激活。假如你获取一个别人已编译的squid文件,它不包含你所需要的功能,那么你又得再编译一遍。

最后,共享库的问题可能使得在系统之间共享可执行文件困难。共享库在运行时被装载,如已知的动态链接一样。squid在编译时会侦察你系统中的C库的某些功能(例如它们是否被提供,是否能运行等)。尽管库功能不常改变,但两个不同的系统的C库之间可能有明显的区别。如果两个系统差别太大,就会对Squid造成问题。

获取squid的源代码是非常容易的。请访问squid的首页:http://www.squid-cache.org。首页有链接指向不同的稳定版和开发版。假如你不在美国,那么请访问squid的众多镜像站点中的一个。镜像站点通常以”wwwN.CC.squid-cache.org”命名,N是数字,CC是国家的两位代码。例如,www1.au.squid-cache.org是澳大利亚的镜像站点,在主页上有链接指向不同的镜像站点。

每一个squid发布版分支(例如Squid-2.5)有它自己的HTML页面。该页面有链接指向源代码,以及与其他发布版的差别。假如你从一个发布版升级到下一个,你应该下载这些差别文件,并且打上补丁,请见3.7章节中的描述。每个版本的发布页描述新功能和重要的改进,也有链接指向已经修正的bugs。

如果web访问不可行,你能从ftp://ftp.squid-cache.org的FTP服务器获取源代码,或者使用其他FTP镜像。要获取当前版本,请访问pub/squid-2/DEVEL 或 pub/squid-2/STABLE 目录。FTP镜像也在许多国家有,你能用同样的国家代码去猜测一些FTP镜像站点,例如ftp1.uk.squid-cache.org。

当前的Squid发布版本大约1M大小。在下载完压缩的打包文件后,你能继续第3章。

2.3 预编译的二进制文件

一些Unix发布版可能预包含了Squid的编译版。对Linux系统,你可以找到Squid的RPM包。通常squid RPM包含在你所买的Linux光碟里。Freebsd/Netbsd/OpenBSD也在它们的ports或者packages里面包含了squid。

虽然RPM或者预编译的packages能节省你一些时间,但它们也有一些弊端。就像我提过的一样,在你开始编译squid之前,某些功能必须被激活或禁止。而你安装的预编译的包可能不包含你想要的特定功能。而且,squid的./configure脚本侦察你系统中的特定参数,这些在你系统中的参数可能与编译它的机器的参数不同。

最后,假如你想对squid打补丁,你必须等某个人编译更新的RPM或packages,或者你还得自己找源代码编译。

我强烈建议你从源代码编译squid,当然怎样选择由得你。

2.4 匿名CVS

你能匿名访问squid的CVS文件(只读)以保持你的源代码同步更新。使用CVS的有利面是你能轻易获取当前运行版本的补丁。这样就容易发现近来改变了什么。

将这些补丁打到你所运行的版本中,有效的保持你的源代码和官方版本的同步。

CVS使用树型索引系统,树干叫做头分支。对Squid而言,这里也是所有的新改变和新功能的存放之地。头分支通常包含试验性的,也许不太稳定的代码。稳定的代码通常在其他分支上。

为了有效的使用squid的匿名CVS,你首先应知道版本和分支是怎样被标明不同的。例如,版本2.5分支被命名为SQUID_2_5。具体的发布有长的命名,例如SQUID_2_5_STABLE4。为了得到squid版本2.5.STABLE4,请使用SQUID_2_5_STABLE4标签;使用SQUID_2_5得到最近的2.5分支的代码。

为了使用squid匿名CVS服务,你首先必须设置CVSROOT环境变量:

csh% setenv CVSROOT :pserver:anoncvs@cvs.squid-cache.org:/squid

或者,对Bourne shell用户:

sh$ CVSROOT=:pserver:anoncvs@cvs.squid-cache.org:/squid

sh$ export CVSROOT

然后你就可以登陆到服务器:

% cvs login

(Logging in to anoncvs@cvs.squid-cache.org)

CVS password:

在提示符下,敲入anoncvs作为密码。现在你可以用这个命令检查源代码树:

% cvs checkout -r SQUID_2_5 -d squid-2.5 squid

-r选项指定获取修订标签。省略-r选项你将获得头分支。-d选项改变存放文件的顶级目录名。假如你省略-d选项,顶级目录名就与模块名字一样。最后的命令行参数(squid)是要检查的模块名字。

一旦你检查完squid源代码树,你能运行cvs update命令去升级你的文件,和保持文件同步。其他命令包括:cvs diff, cvs log, 和 cvs annotate。

想获取更多CVS知识,请访问:http://www.cvshome.org

2.5 devel.squid-cache.org

Squid的开发者维持一个独立的站点,当前运行在SourceForge,提供了试验性的squid功能。请检查它们在http://devel.squid-cache.org.在这里你能发现许多正在开发的工程,它们还未集成到squid的官方源代码里。你能通过SourceForge的匿名CVS服务来访问这些工程,或者下载与标准版本不同的差别文件。

第3章 编译和安装

3.1 安装之前

假如你使用unix有一段时间,并且已编译过许多其他软件包,那么只需快速的扫描本章。编译安装squid的过程与安装其他软件相似。

为了编译squid,你需要一个ANSI C编译器。不要被ANSI字眼吓倒。假如你已经有一个编译器,它顺从ANSI指令,那么也一样。GNU C编译器(gcc)是很好的选择,它被广泛使用。大部分操作系统在其标准安装中附带了C编译器,不过Solaris和HP-UX除外。假如你使用这样的操作系统,那可能没有安装编译器。

理论上你应该在即将运行squid的机器上编译squid。安装过程侦察你的操作系统以发现特定的参数,例如可用文件描述符的数量。然而,假如你的系统没有C编译器存在,你也许会在其他机器上编译squid,然后把二进制代码copy回来。如果操作系统不同,那么squid可能会遇到问题。假如操作系统有不同的内核配置,squid会变得混乱。

除了C编译器,你还需要perl和awk。awk是所有unix系统的标准程序,所以你不必担心它。perl也是相当普及的,但它也许没有默认安装在你的系统上。你需要gzip程序来解压源代码发布文件。

对Solaris用户,请确认/usr/ccs/bin包含在你的PATH环境变量里,即使你使用gcc编译器。为了编译squid,make和ar程序需要在这个目录找到。

3.2 解开源代码包

在下载完源代码后,你需要在某个目录解开它。具体哪个目录无关紧要。你能解开squid在你的家目录或任何其他地方,大概需要20M的自由磁盘空间。我个人喜欢用/tmp。使用tar命令来展开源代码目录:

% cd /tmp

% tar xzvf /some/where/squid-2.5.STABLE4-src.tar.gz

一些tar程序不支持z选项,该选项自动解压gzip文件。如果这样,你需要运行如下命
令:

% gzip -dc /some/where/squid-2.5.STABLE4-src.tar.gz | tar xvf -

一旦源代码被展开,下一步通常是配置源代码树。然而,假如这是你第一次编译squid,你应确认特定的内核资源限制足够高。怎样发现,请继续。

3.3 调整内核

Squid在高负载下,需要大量的内核资源。特别的,你需要给你的系统配置比正常情况更高的文件描述符和缓存。文件描述符的限制通常很恼人。你最好在开始编译squid之前来增加这些限制的大小。

因为这点,你可能为了避免重建内核的麻烦,而倾向于使用预编译的二进制版本。不幸的是,不管如何你必须重建一个新内核。squid和内核通过数据结构来交换信息,数据结构的大小不能超过设置的文件描述符的限制。squid在运行时检查这些设置,并且使用最安全的(最小的)值。这样,即使预编译的二进制版本有比你的内核更高的文件描述符,但还是以你系统内核的实际数值为主。

为了改编一些参数,你需要重建新内核。这个过程在不同的操作系统之间不同。假如需要,请参阅Unix系统管理员手册(Prentice Hall出版)或者你的操作系统文档。假如你正使用Linux,可能不必重建内核。

3.3.1 文件描述符

文件描述符是一个简单的整数,用以标明每一个被进程所打开的文件和socket。第一个打开的文件是0,第二个是1,依此类推。Unix操作系统通常给每个进程能打开的文件数量强加一个限制。更甚的是,unix通常有一个系统级的限制。

因为squid的工作方式,文件描述符的限制可能会极大的影响性能。当squid用完所有的文件描述符后,它不能接收用户新的连接。也就是说,用完文件描述符导致拒绝服务。直到一部分当前请求完成,相应的文件和socket被关闭,squid不能接收新请求。当squid发现文件描述符短缺时,它会发布警告。

在运行./configure之前,检查你的系统的文件描述符限制是否合适,能给你避免一些麻烦。大多数情况下,1024个文件描述符足够了。非常忙的cache可能需要4096或更多。在配置文件描述符限制时,我推荐设置系统级限制的数量为每个进程限制的2倍。

通常在你的Unix shell中能找到系统的文件描述符限制。所有的C shell及其类似的shell有内建的limit命令。更新的Bourne shell及其类似的shell有一条叫做ulimit的命令。为了发现你的系统的文件描述符限制,试运行如下命令:

csh% limit descriptors unlimited

csh% limit descriptors

descriptors    4096

或者

sh$ ulimit -n unlimited

sh$ ulimit -n

4096

在Freebsd上,你能使用sysctl命令:

% sysctl -a | grep maxfiles

kern.maxfiles: 8192

kern.maxfilesperproc: 4096

如果你不能确认文件描述符限制,squid的./configure脚本能替你做到。当你运行./configure时,请见3.4章节,观察末尾这样的输出:

checking Maximum number of file descriptors we can open... 4096

假如其他的limit,ulimit,或者./configure报告这个值少于1024,你不得不在编译squid之前,花费时间来增加这个限制值的大小。否则,squid在高负载时执行性能将很低。

增加文件描述符限制的方法因系统不同而不同。下面的章节提供一些方法帮助你开始。

3.3.1.1 Freebsd,NetBSD,OpenBSD

编辑你的内核配置文件,增加如下一行:

options       MAXFILES=8192

在OpenBSD上,使用option代替options。然后,configure,编译,和安装新内核。最后重启系统以使内核生效。

3.3.1.2 Linux

在Linux上配置文件描述符有点复杂。在编译squid之前,你必须编辑系统include文件中的一个,然后执行一些shell命令。请首先编辑/usr/include/bits/types.h文件,改变_ _FD_SETSIZE 的值:

#define _ _FD_SETSIZE    8192

下一步,使用这个命令增加内核文件描述符的限制:

# echo 8192 > /proc/sys/fs/file-max

最后,增加进程文件描述符的限制,在你即将编译squid的同一个shell里执行:

sh# ulimit -Hn 8192

该命令必须以root运行,仅仅运行在bash shell。不必重启机器。使用这个技术,你必须在每一次系统启动后执行上述echo和ulimit命令,或者至少在squid启动之前。假如你使用某个rc.d脚本来启动squid,那是一个放置这些命令的好地方。

3.3.1.3 Solaris

增加该行到你的/etc/system文件:

set rlim_fd_max = 4096

然后,重启机器以使改动生效。

3.3.2 Mbuf Clusters

BSD基础的网络代码使用一个叫做mbuf(参阅W.R.Stevens的TCP/IP描述卷2)的数据结构。Mbuf典型的是小块内存(例如128字节)。较大的网络包的数据存储在mbuf clusters里。内核可能给系统可用的mbuf clusters的总数量强加一个最高限制。你能使用netstat命令来发现这个限制:

% netstat -m

196/6368/32768 mbufs in use (current/peak/max):

        146 mbufs allocated to data

        50 mbufs allocated to packet headers

103/6182/8192 mbuf clusters in use (current/peak/max)

13956 Kbytes allocated to network (56% of mb_map in use)

0 requests for memory denied

0 requests for memory delayed

0 calls to protocol drain routines

在这个例子里,有8192个mbuf clusters可用,但是永远不会同时用到6182个。当系统用尽mbuf clusters时,I/O机制例如read()和write()返回“无缓存空间可用”的错误信息。

NetBSD和OpenBSD使用netstat -m不会显示mbuf的输出。代替的,它们在syslog里报告:”WARNING: mclpool limit reached” 。

为了增加mbuf clusters的数量,你必须在内核配置文件里增加一个选项:

options         NMBCLUSTERS=16384
3.3.3 临时端口范围

临时端口是TCP/IP栈分配给出去连接的本地端口。换句话说,当squid发起一条连接到另一台服务器,内核给本地socket分配一个端口号。这些本地端口号有特定的范围限制。例如,在FreeBSD上,默认的临时端口范围是1024-5000。

临时端口号的短缺对非常忙的代理服务器(例如每秒数百个连接)来说,会较大的影响性能。这是因为一些TCP连接在它们被关闭时进入TIME_WAIT状态。当连接进入TIME_WATI状态时,临时端口号不能被重用。

你能使用netstat命令来显示有多少个连接进入这个状态:

% netstat -n | grep TIME_WAIT

Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)

tcp4       0      0  192.43.244.42.19583    212.67.202.80.80       TIME_WAIT

tcp4       0      0  192.43.244.42.19597    202.158.66.190.80      TIME_WAIT

tcp4       0      0  192.43.244.42.19600    207.99.19.230.80       TIME_WAIT

tcp4       0      0  192.43.244.42.19601    216.131.72.121.80      TIME_WAIT

tcp4       0      0  192.43.244.42.19602    209.61.183.115.80      TIME_WAIT

tcp4       0      0  192.43.244.42.3128     128.109.131.47.25666   TIME_WAIT

tcp4       0      0  192.43.244.42.3128     128.109.131.47.25795   TIME_WAIT

tcp4       0      0  192.43.244.42.3128     128.182.72.190.1488    TIME_WAIT

tcp4       0      0  192.43.244.42.3128     128.182.72.190.2194    TIME_WAIT

注意这个例子中既有客户端连接又有服务器端的连接。客户端连接有3128作为临时端口号,服务器端连接有80作为远程主机的端口号。临时端口号出现在本地地址栏里。在该例子里,它们是19000秒。

如果你没有看到数千个临时端口在TIME_WAIT状态,那也许不必增加这个端口范围。在Freebsd上,用如下命令增加临时端口范围:

 # sysctl -w net.inet.ip.portrange.last=30000

在OpenBSD上,命令类似,但sysctl变量有不同的名字:

# sysctl -w net.inet.ip.portlast=49151

在NetBSD上,事情稍有不同。默认的值是49152-65535.为了增加这个范围,需改变最低限制:

# sysctl -w net.inet.ip.anonportmin=10000

在Linux上,简单的写一对数字到下列指定文件:

# echo "1024 40000" > /proc/sys/net/ipv4/ip_local_port_range

不要忘记将这些命令加到你的系统启动脚本中,以使机器每一次重启后都生效。

3.4 Configure脚本

象许多其他Unix软件一样,squid在开始编译之前使用./configure脚本来了解操作系统信息。./configure脚本由流行的GNU autoconf程序产生。当script运行时,它用不同的方法来侦察系统,以发现关于库,函数,类型,参数,和有没有功能被提供等。./configure所做的第一件事情是去找一个C编译器。假如C编译器没有找到,或者编译一个简单的测试程序失败,./configure脚本不能继续。.

/configure脚本有大量的选项。最重要的是安装prefix。在运行./configure之前,你需要决定squid被安装在哪里。prefix选项指定squid日志,二进制文件,和配置文件的默认位置。你可以在安装之后改变这些文件的位置,但假如你现在决定,事情更容易。

默认的安装位置是/usr/local/squid.squid将文件放在prefix指定目录下面的7个子目录:

% ls -l /usr/local/squid

total 5

drwxr-x---  2 wessels  wheel  512 Apr 28 20:42 bin

drwxr-x---  2 wessels  wheel  512 Apr 28 20:42 etc

drwxr-x---  2 wessels  wheel  512 Apr 28 20:42 libexec

drwxr-x---  3 wessels  wheel  512 Apr 28 20:43 man

drwxr-x---  2 wessels  wheel  512 Apr 28 20:42 sbin

drwxr-x---  4 wessels  wheel  512 Apr 28 20:42 share

drwxr-x---  4 wessels  wheel  512 Apr 28 20:43 var

Squid使用bin,etc,libexec,man,sbin,和share目录存放一些相对较小的文件(或其他目录),这些文件不经常改变。但var目录的文件别有洞天。这里你可以发现squid的日志文件,它增长得非常大(数十或数百兆)。var也是实际磁盘cache的默认位置。你也许想将var目录放在磁盘空间足够的位置,这样做较容易的方法是使用–localstatedir选项:

% ./configure --localstatedir=/bigdisk/var

当配置squid时,你不必对这些路径名称担心太多。你以后可以在squid.conf文件里改变这些路径名。

3.4.1 configure选项

./configure脚本有大量的不同选项,它们以-开始。当你敲入./configure –help时,能看到选项的完整列表。一些选项对所有configure脚本是通用的,还有一些是squid专有的。下面是你可能用得到的标准选项:

--perfix =PREFIX

如前面描述的一样,这里设置安装目录。安装目录是所有可执行文件,日志,和配置文件的默认目录。在整本书中,$prefix指你选择的安装目录。

--localstatedir =DIR

该选项允许你改变var目录的安装位置。默认是$prefix/var,但也许你想改变它,以使squid的磁盘缓存和日志文件被存储在别的地方。

--sysconfdir =DIR

该选项允许你改变etc目录的位置。默认的是$prefix/etc.假如你想使用/usr作为安装位置,你也许该配置–sysconfdir为/etc. 以下是squid的专有./configure选项:

--enable-dlmalloc[=LIB]

在一些系统上,内建的内存分配机制(malloc)在使用squid时表现不尽人意。使用–enable-dlmalloc选项将squid源代码包中的dlmalloc包编译和链接进来。假如你的系统中已安装dlmalloc,你能使用=LIB参数指定库的路径。请见http://g.oswego.edu/dl/html/malloc.html更多关于dlmalloc的信息。

--enable-gnuregex

在访问控制列表和其他配置指令里,squid使用正则表达式作为匹配机制。GNU的正则表达式库包含在squid的源代码包里;它可以在没有内建正则表达式的操作系统中使用。./configure脚本侦察你系统中的正则表达式库,假如必要,它可以激活使用GNU正则表达式。如果因为某些理由,你想强制使用GNU正则表达式,你可以将这个选项加到./configure命令后。

--enable-carp

Cache数组路由协议(CARP)用来转发丢失的cache到父cache的数组或cluster。在10.9章有更多关于CARP的细节。

--enable-async-io[=N_THREADS]

异步I/O是squid技术之一,用以提升存储性能。aufs模块使用大量的线程来执行磁盘I/O操作。该代码仅仅工作在linux和solaris系统中。=N_THREADS参数改变squid使用的线程数量。aufs和异步I/O在8.4章中被讨论。

请注意–enable-async-io是打开其他三个./configure选项的快捷方式,它等同于:

 --with-aufs-threads=N_THREADS

--with-pthreads

--enable-storeio=ufs,aufs

--with-pthreads

该选项导致编译过程链接到你系统中的P线程库。aufs存储模块是squid中唯一需要使用线程的部分。通常来说,如果你使用–enable-saync-io选项,那么不必再单独指定该选项,因为它被自动激活了。

--enable-storeio=LIST

Squid支持大量的不同存储模块。通过使用该选项,你告诉squid编译时使用哪个模块。在squid-2.5中,支持ufs,aufs,diskd,和null模块。通过查询src/fs中的目录,你能得到一个模块列表。

LIST是一个以逗号分隔的模块列表,例如:

% ./configure --enable-storeio=afus,diskd,ufs

ufs模块是默认的,看起来问题最少。不幸的是,它性能有限。其他模块可能在某些操作系统中不必编译。关于squid存储模块的完整描述,请见第8章。

--with-aufs-threads=N_THREADS

指定aufs存储机制使用的线程数量(见8.4章)。squid默认根据缓存目录的数量,自动计算需要使用多少线程。

--enable-heap-replacement

该选项不再使用,但被保留用于向后兼容性。你该使用–enable-removal-policies来代替。

--enable-removal-policies=LIST

排除策略是squid需要腾出空间给新的cache目标时,用以排除旧目标的机制。squid-2.5支持3个排除策略:最少近期使用(LRU),贪婪对偶大小(GDS),最少经常使用(LFU)。然而,因为一些理由,./configure选项使指定的替代策略和需要执行它们的基本数据结构之间的差别模糊化。LRU是默认的,它以双链表数据结构执行。GDS和LFU使用堆栈的数据结构。

为了使用GDS或LFU策略,你指定:

% ./configure --enable-removal-policies=heap

然后你在squid的配置文件里选择使用GDS或LFU。假如你想重新使用LRU,那么指定:

% ./configure --enable-removal-policies=heap,lru

更多的关于替换策略的细节请见7.5章。

--enable-icmp

如在10.5章中描述的一样,squid能利用ICMP消息来确定回环时间尺寸,非常象ping程序。你能使用该选项来激活这些功能。

--enable-delay-pools

延时池是squid用于传输形状或带宽限制的技术。该池由大量的客户端IP地址组成。当来自这些客户端的请求处于cache丢失状态,他们的响应可能被人工延迟。关于延时池的更多细节请见附录C。

--enable-useragent-log

该选项激活来自客户请求的HTTP用户代理头的日志。更多细节请见13.5章。

--enable-referer-log

该选项激活来自客户请求的HTTP referer日志。更多细节请见13.4章。

--disable-wccp

Web cache协调协议(WCCP)是CISCO的专有协议,用于阻止或分发HTTP请求到一个或多个caches。WCCP默认被激活,假如你愿意,可以使用该选项来禁止该功能。

--enable-snmp

简单网络管理协议(SNMP)是监视网络设备和服务器的流行方法。该选项导致编译过程去编译所有的SNMP相关的代码,包括一个裁切版本的CMU SNMP库。

--enable-cachemgr -hostname[=hostname]

cachemgr是一个CGI程序,你能使用它来管理查询squid。默认cachemgr的hostname值是空的,但你能使用该选项来指定一个默认值。例如:

% ./configure --enable-cachemgr-hostname=mycache.myorg.net

--enable-arp-acl

squid在一些操作系统中支持ARP,或者以太地址访问控制列表。该代码使用非标准的函数接口,来执行ARP访问控制列表,所以它默认被禁止。假如你在linux或solaris上使用squid,你可能用的上这个功能。

--enable-htcp

HTCP是超文本缓存协议–类似于ICP的内部缓存协议。更多细节请见10.8章。

--enable-ssl

使用该选项赋予squid终止SSL/TLS连接的能力。注意这仅仅工作在web加速器中用以加速请求。更多细节请见15.2.2章节。

--with-openssl[=DIR]

假如必要,你使用该选项来告诉squid到哪里找到OpenSSL库或头文件。假如它们不在默认位置,在该选项后指定它们的父路径。例如:

% ./configure --enable-ssl --with-ssl=/opt/foo/openssl

在这个例子中,你的编译器将在/opt/foo/openssl/include目录中找头文件,在/opt/foo/openssl/lib中找库文件。

--enable-cache-digests

Cache消化是ICP的另一个替代,但有着截然不同的特性。请见10.7章。

--enable-err-languages="lang1 lang2 ..."

squid支持定制错误消息,错误消息可以用多种语言报告。该选项指定复制到安装目录($prefix/share/errors)的语言。假如你不使用该选项,所有可用语言被安装。想知道何种语言可用,请见源代码包里errors目录下的目录列表。如下显示如何激活多种语言:

% ./configure --enable-err-languages="Dutch German French" ...

--enable-default-err-language=lang

该选项设置error_directory指令的默认值。例如,假如你想使用荷兰语,你能这样指定:

% ./configure --enable-default-err-language=Dutch

你也能在squid.conf里指定error_directory指令,在附录A中有描述。假如你忽略该选项,英语是默认错误语言。

--with-coss-membuf-size=N

循环目录存储系统(coss)是squid的试验性存储机制。该选项设置coss缓存目录的内存缓冲大小。注意为了使用coss,你必须在–enable-storeio选项里指定存储类型。

该参数以字节形式赋值,默认是1048576字节或1M。你能指定2M缓冲如下:

% ./configure --with-coss-membuf-size=2097152

--enable-poll

unix提供两个相似的函数用以在I/O事件里扫描开放文件描述符:select()和poll()./configure脚本通常能非常好的计算出何时使用poll()来代替select().假如你想强制使用poll(),那么指定该选项。

--desable-poll

类似的,如果不使用poll(),那么指定该选项。

--disable-http-violations

squid默认可以被配置成违背HTTP协议规范。你能使用该选项来删除违背HTTP协议的代码。

--enable-ipf-transparent

在第9章中,我将描述如何配置squid来拦截缓存。一些操作系统使用IP Filter包来协助拦截缓存。在这些环境下你应该使用该./configure选项。如果你使用了该选项,但是编译器提示src/client_side.c文件出错,那是因为IP Filter包没有或没有正确的安装在你的系统中。

--enable-pf-transparent

你可能需要指定该选项,使用PF包过滤器在操作系统中拦截HTTP。PF是OpenBSD的标准包过滤器,也可能被发布到其他系统中。假如你使用该选项,但是编译器提示src/client_side.c文件出错,那是因为PF没有实际安装到你的系统中。

--enable-linux-netfilter

Netfilter是linux 2.4系列内核的包过滤器名字。假如你想在linux2.4或以后的版本中使用HTTP拦截功能,那么激活该选项。

--disable-ident-lookups

ident是一个简单的协议,允许服务器利用客户端的特殊TCP连接来发现用户名。假如你使用该选项,编译器将把执行这些查询的代码排除出去。即使你在编译时保留了这些代码,除非你在squid.conf文件里指定,squid不会执行ident查询。

--disable-internal-dns

squid源代码包含两个不同的DNS解决方案,叫做“内部的”和“外部的”。内部查询是默认的,但某些人可能要使用外部技术。该选项禁止内部功能,转向使用旧的方式。

内部查询使用 squid自己的DNS协议执行工具。也就是说,squid产生未完成的DNS查询并且将它们发送到一个解析器。假如超时,它重新发送请求,你能指定任意数量的解析器。该工具的有利处之一是,squid获得准确无误的DNS响应的TTLs。

外部查询利用C库的gethostbyname()和gethostbyaddr()函数。squid使用一个外部进程池来制造并行查询。使用外部DNS解析的主要弊端是你需要更多的辅助进程,增加squid的负载。另一个麻烦是C库函数不在响应里传输TTLs,这样squid使用postive_dns_ttl指令提供的一个常量值。

--enable-truncate

truncate()系统调用是unlink()的替代品。unlink()完全删除cache文件,truncate()将文件大小设为零。这样做释放了分配给该文件的磁盘空间,但留下适当的目录接口。该选项存在的理由是,某些人相信(或希望)truncate()比unlink()性能表现更好。然而,压力测试显示两者有很少的或根本没有区别。

--disable-hostname-checks

默认的,squid要求URL主机名在一定程度上遵守古老的RFC 1034规范:

标签必须遵循下列ARPANET主机名规则。它们必须以字母开始,以字母或数字结尾,仅仅包含字母,数字和下划线。

这里字母意味着ASCII字符,从A到Z。既然国际域名日益流行,你可能希望使用该选项来移除限制。

--enable-underscores

该选项控制squid针对主机名里下划线的行为。通用的标准是主机名里不包含下划线字符,尽管有些人不赞成这点。squid默认会对URL主机名里带下划线的请求产生一条错误消息。你能使用该选项,让squid信任它们,把它们当作合法的。然而,你的DNS解析器也许强迫使用非下划线请求,并且对带下划线的主机名解析失败。

--enable-auth[=LIST]

该选项控制在squid的二进制文件里支持哪种验证机制。你能选择下列机制的任意组合:basic,digest,ntlm.假如你忽略该选项,squid仅仅支持basic验证。假如你使用不带参数的–enable-auth选项,编译进程将增加对所有验证机制的支持。你可以使用以逗号分隔的验证机制列表:

% ./configure --enable-auth=digest,ntlm

我在第六章和第十二章里会谈得更多。

--enable-auth-helpers=LIST

这个旧选项现在已舍弃了,但为了保持向后兼容性仍保留着。你可以使用–enable-basic-auth-helperes=LIST来代替。

--enable-basic-auth-helpers=LIST

使用该选项,你能将helpers/basic_auth目录的一个或多个HTTP Basic验证辅助程序编译进来。请见12.2章找到它们的名字和描述。

--enable-ntlm-auth-helpers=LIST

使用该选项,你能将helpers/ntlm_auth目录的一个或多个HTTP NTLM验证辅助程序编译进来。请见12.4章找到它们的名字和描述。

--enable-digest-auth-modules=LIST

使用该选项,你能将helpers/digest_auth目录的一个或多个HTTP Digest验证辅助程序编译进来。请见12.3章找到它们的名字和描述。

--enable-external-acl-helpers=LIST

使用该选项,你能编译一个或多个扩展ACL辅助程序,这些在12.5章中讨论。例如:

% ./configure --enable-external-acl-helpers=ip_user,ldap_group

--disable-unlinkd

unlinkd是另一个squid的外部辅助进程。它的基本工作是对cache文件执行unlink()或truncate()系统调用。通过在外部进程里执行文件删除工作,能给squid带来明显的性能提升。使用该选项来禁止外部unlink进程功能。

--enable-stacktrace

某些系统支持在程序崩溃时,自动产生数据追踪。当你激活该功能后,如果squid崩溃,数据追踪信息被写到cache.log文件。这些信息对开发和程序bug调试有用。

--enable-x-accelerator-vary

该高级功能可能在squid被配置成加速器时使用。它建议squid在响应请求时,从后台原始服务器中寻找X-Accelerator-Vary头。请见15.5章。

3.4.2 运行configure

现在我们准备运行./configure脚本。进入源代码的顶级目录敲入./configure,后面跟上前面提到过的任意选项,例如:

% cd squid-2.5.STABLE4

% ./configure --enable-icmp --enable-htcp

./configure的工作就是侦察你的操作系统,以发现什么东西可用,什么不可用。它首先做的事情之一就是确认你的C编译器可用。假如./configure检测到你的C编译器有问题,脚本会退出,返回如下错误:

configure: error: installation or configuration problem: C compiler

cannot create executables.

很可能你从不会看到这个消息。假如看到了,那意味着你的系统中没有C编译器存在,或者编译器没有正确安装。请见config.log文件找到解决问题的建议。假如你的系统中有多个C编译器,你可以在运行./configure之前设置CC环境变量,来告诉./configure使用哪个:

% setenv CC /usr/local/bin/gcc

% ./configure ...

在./configure检查完该编译器后,它查找头文件,库文件和函数的长列表。通常你不必担心该部分。在某些实际情况中,./configure会终止以引起你的注意,某些事情可能有问题,例如没有足够的文件描述符。假如你指定不完整的或不合理的命令行选项,它也会终止。假如有错误发生,请检查config.log输出。./configure的最终任务是创造Makefiles和其他文件,这些文件基于squid从你系统中了解到的知识。到此为止,你准备做编译工作。

3.5 编译

一旦./configure完成了它的工作,你简单的敲入make开始编译源代码:

%make

正常来说,该过程很顺利,你可以见到大量的滚动行。

你也许见到一些编译器警告。大多数情况下,可以安全的忽略这些。假如这些警告非常多,并且一些看起来非常严重,请将它们报告给开发者,在第16.5章中有描述。

假如编译过程没有错误,你可以转移到下一节,描述如何安装你刚才编译的程序。

为了验证编译是否成功,你可以再次运行make。你将看到如下输出:

% make

Making all in lib...

Making all in scripts...

Making all in src...

Making all in fs...

Making all in repl...

'squid' is up to date.

'client' is up to date.

'unlinkd' is up to date.

'cachemgr.cgi' is up to date.

Making all in icons...

Making all in errors...

Making all in auth_modules...

因为许多理由,编译步骤也许会失败,包括:

源代码bugs

通常squid源代码是完整的调试过的。然而,你也许会遇到某些bugs或问题从而阻止你编译。这种问题在新的开发版本中更容易出现,请将它们报告给开发者。

编译器安装问题

不正确安装的C编译器不能够编译squid或其他软件包。通常编译器随着操作系统预安装,所以你不必担心它。然而,假如你在操作系统安装完后,试图升级编译器,那么可能会犯错误。绝对不要把已经安装好的编译器从一台机器拷贝到另一台,除非你绝对清楚你在做什么。我觉得在每台机上独立的安装编译器总是最好的。

请确认你的编译器的头文件总是与库文件同步。头文件通常在/usr/include目录,而库文件在/usr/lib目录。Linux的流行RPM系统允许它去升级其中之一,但并非另一个。假如库文件基于不同的头文件,squid不能编译。

假如你想在开源BSD变种之一中升级编译器,请确认在/usr/src目录中运行make world,这好过从/usr/src/lib或/usr/src/include中运行。

如下是一些通用的编译器问题和错误消息:

Solaris: make[1]: *** [libmiscutil.a] Error 255

这意味着./configure不能发现ar程序。请确认/usr/ccs/bin位于你的PATH环境变量里。假如你没有安装Sun的编译器,那么需要GNU的工具。(http://www.gnu.org/directory/binutils.html).

Linux: storage size of 'rl' isn't known

这是因为头文件和库文件不匹配所致,象前面描述的一样。请确认同时升级两者。

Digital Unix: Don't know how to make EXTRA_libmiscutil_a_SOURCES. Stop.

Digital Unix的make程序不能兼容automake包产生的Makefile文件。例如,lib/Makefile.in包含如下行:

noinst_LIBRARIES = \

        @LIBDLMALLOC@ \

        libmiscutil.a \

        libntlmauth.a \

        @LIBREGEX@

在替换后,当lib/Makefile被创建时,它看起来如下:

noinst_LIBRARIES = \

        \

        libmiscutil.a \

        libntlmauth.a \

        <TAB>

象上面显示的一样,最后一行包括一个不可见的TAB字符,它阻止了make。通过安装和使用GNU make,或者手工编辑lib/Makefile如下,来解决这个问题:

noinst_LIBRARIES = \

        \

        libmiscutil.a \

        libntlmauth.a

假如你在编译squid时遇到问题,请先检查FAQ。你也许该在Squid的web站点上搜索(使用主页里的搜索栏)。最后,假如你仍有问题,请发邮件到squid-users@squid-cache.org列表。

3.6 安装

在编译完后,你需要把程序安装到指定的目录。可能需要超级用户权限来把它们放置到安装目录。所以,请先切换到root:

%su
password:
#make install

假如你通过使用–enable-icmp选项,激活了squid的ICMP衡量功能,那么必须安装pinger程序。pinger程序必须以超级用户权限安装,因为仅仅允许root来发送和接受ICMP消息。下列命令以相应的许可来安装pinger程序:

#make install-pinger

在安装完后,你将在squid的安装目录里(默认是/usr/local/squid)见到下列目录和文件:

sbin

sbin目录的程序正常只能被root启动

sbin/squid

Squid的主程序

bin

bin目录包含对所有用户可用的程序

bin/RunCache

RunCache是一个shell脚本,你能用它来启动squid。假如squid死掉,该脚本自动重启它,除非它检测到经常的重启。RunCache是一个时间遗留的产物,那时Squid还不是后台服务进程。在最近的版本里,RunCache很少用到,因为Squid自动重启它自身,当你不使用-N选项时。

bin/RunAccel

RunAccel与RunCache几乎一致,唯一的不同是它增加了一个命令行参数,告诉squid在哪里侦听HTTP请求。

bin/squidclient

squidclient是个简单的HTTP客户端程序,你能用它来测试squid。它也有一些特殊功能,用以对运行的squid进程发起管理请求。

libexec

libexec目录传统的包含了辅助程序。有一些命令你不能正常的启动。然而,这些程序通常被其他程序启动。

libexec/unlinkd

unlinkd是一个辅助程序,它从cache目录里删除文件。如你后面看到的一样,文件删除是个性能瓶颈。通过在外部进程里执行删除操作,Squid提升了一些执行性能。

libexec/cachemgr.cgi

cachemgr.cgi是Squid管理功能的CGI接口。为了使用它,你需要拷贝该程序到你的WEB服务器的cgi-bin目录。在14.2章中有更多描述。

libexec/diskd(optional)

假如你指定了–enable-storeio=diskd,你才能看到它。

libexec/pinger(optional)

假如你指定了–enable-icmp,你才能看到它。

etc

etc目录包含squid的配置文件。

etc/squid.conf

这是squid的主要配置文件。初始的该文件包含了大量的注释,用以解释每一个选项做什么。在你理解了这些配置指令后,建议你删除这些注释,让配置文件更小和更容易阅读。注意假如该文件存在,安装过程不会覆盖该文件。

etc/squid.conf.default

这是从源代码目录中拷贝过来的默认配置文件。在升级了squid安装后,你也许发现有一份当前默认配置文件的拷贝是有用的。可能会增加新的配置指令,一些存在的旧指令可能有所改变。

etc/mime.conf

mime.conf文件告诉squid对从FTP和Gopher服务器获取的数据使用何种MIME类型。该文件是一个关联文件名扩展到MIME类型的表。正常而言,你不必编辑该文件。然而,你可能需要增加特殊文件类型的接口,它们在你的组织内使用。

etc/mime.conf.default

这是从源代码目录里拷贝过来的默认mime.conf文件。

share

share目录通常包括squid的只读数据文件。

share/mib.txt

这是squid的SNMP管理信息基础(MIB)文件。squid自身不使用该文件,然而,你的SNMP客户端软件(例如snmpget和多路由走向图(MRTG))需要该文件,用以理解来自squid的SNMP对象可用。

share/icons

share/icons目录包含大量的小图标文件,squid用在FTP和Gopher目录列举里。正常而言,你不必担心这些文件,但如果需要,你可以改变它们。

share/errors

share/errors目录包含了squid显示给用户看的错误消息模板。这些文件在你安装squid时,从源代码目录拷贝而来。如果需要你可以编辑它们。然而,在每次运行make install时,安装过程总会覆盖它们。所以假如你想定制错误消息,建议你把它们放在不同的目录。

var

var目录包含了不是很重要的和经常变化的文件。这些文件你不必正常的备份它们。

var/logs

var/logs目录是squid不同日志文件的默认位置。当你第一次安装squid时,它是空的。一旦squid开始运行,你能在这里看到名字为access.log,cache.log和store.log这样的文件。

var/cache

假如你不在squid.conf文件里指定,这是默认的缓存目录(cache_dir)。第七章有关于缓存目录的所有细节。

3.7 打补丁

在你运行squid一段时间后,你可能发现需要打源代码补丁,用以修正bug或者增加试验性的功能。在squid-cache.org站点上,对重要的bug修正会发布补丁。假如你不想等到下一个官方发布版本,你能下载补丁,并且打到你的源代码中。然后你需要重新编译squid。

为了打补丁-或者有时候叫差别文件-你需要一个叫做”patch”的程序。你的操作系统必须有该程序。如果没有,你可以从GNU工具集里下载(http://www.gnu.org/directory/patch.html). 注意假如你在使用匿名CVS(见2.4节),你不必担心补丁文件。当你升级源代码树时,CVS系统自动升级了补丁。

为了打补丁,你必须把补丁文件存放在系统中某处。然后进入到squid的源代码目录,运行如下命令:

% cd squid-2.5.STABLE4
% patch < /tmp/patch_file

默认的,在patch程序运行时,它告诉你它正在做什么。通常输出滚动非常快,除非有问题。你能安全的忽略它输出的offset NNN lines警告。假如你不想见到所有这些输出,使用-s选项选择安静模式。

当补丁更新了源代码后,它创造了原始文件的拷贝。例如,假如你对src/http.c打一个补丁,备份文件名就是src/http.c.orig.这样,假如你在打了补丁后想撤销这个操作,简单的重命名所有的.orig文件到它们以前的格式。为了成功的使用该技术,建议你在打补丁之前删除所有的.orig文件。

假如patch程序遇到问题,它停止运行并且给出建议。通常问题如下:

  • 在错误的目录运行patch程序。解决的方法是,进入到正确的目录,或者使用patch的-p选项。
  • 补丁已打过。patch会告诉你是否已打过补丁文件。在这样的情况下,它会问你是否撤销这个文件的补丁。
  • patch程序不能理解你赋给它的文件。补丁文件通常有三个风格:正常的,context的和unified的。旧版本的patch程序可能不理解后两者的差异输出。从GNU的FTP站点获取最近的版本能解决该问题。
  • 损坏的补丁文件。假如你在下载和存储补丁文件时不小心,它有可能被损坏。有时候人们以email消息发送补丁文件,在新的窗口里,它们被简单的剪切和粘贴。
  • 在这样的系统中,剪切和粘贴能将Tab字符改变为空格,或者不正确的捆绑长行。这些改变混乱了patch。-l选项也许有用,但最好是正确的拷贝和存储补丁文件。

某些时候patch不能应用部分或所有的差别文件。在这样的情况下,你能见到类似于Hunk 3 of 4 failed的消息。失败的部分被存储在命名为.rej的文件里。例如,假如在处理src/http.c时失败,patch程序将该差别文件片断存为src/http.c.rej.在这样的情况下,你也许能手工修正这些问题,但它通常不值得这么做。假如你有大量的”failed hunks”或者.rej文件,建议你去下载最近源代码版本的完整新拷贝。

在你打完补丁后,你必须重新编译squid。make的先进功能之一就是它仅仅编译改变了的文件。但有时候make不能理解错综复杂的依赖关系,它没有完整的重编译所需文件。为了安全起见,通常建议你去重编译所有文件。最好的方法是在开始编译之前清除源代码树:

%make clean

%make
3.8 重运行configure

有时候你可能发现有必要重新运行./configure。例如,假如你调整了内核参数,你必须再次运行./configure以使它能发现新设置。当你阅读本书时,你也发现你必须使用./configure选项来激活所需的功能。

以相同的选项重运行./configure,使用如下命令:

%config.status --recheck

另一个技术是`touch config.status`文件,它更新了该文件的时间戳。这导致make在编译源代码之前,重新运行./configure脚本:

% touch config.status

% make

如果增加或删除./configure选项,你必须重新敲入完整的命令行。假如你记不住以前的选项,请查看config.status文件的顶部。例如:

% head config.status

#! /bin/sh

# Generated automatically by configure.

# Run this file to recreate the current configuration.

# This directory was configured as follows,

# on host foo.life-gone-hazy.com:

#

# ./configure  --enable-storeio=ufs,diskd --enable-carp \

#   --enable-auth-modules=NCSA

# Compiler output produced by configure, useful for debugging

# configure, is in ./config.log if it exists.

在运行./configure之后,你必须再次编译和安装squid。安全起见,建议先运行make clean:

%make clean

%make

请回想一下,./configure会缓存它在你系统中发现的东西。在这样的形式下,你可能想清除这些缓存,从头开始编译过程。假如喜欢,你可以简单的删除config.cache文件。然后,下一次./configure运行时,它不会使用以前的数值。你也能恢复squid源代码树到它的configure之前的状态,使用如下命令:

%make distclean

这将删除所有的目标文件和其他被./configure和make程序产生的文件。

第4章 快速配置向导

4.1 squid.conf语法

Squid的配置文件相对规范。它与其他许多unix程序相似。每行以配置指令开始,后面跟着数字值或关键字。在读取配置文件时,squid忽略空行和注释掉的行(以#开始)。如下是一些配置行示例:

cache_log /squid/var/cache.log

# define the localhost ACL

acl Localhost src 127.0.0.1/32

connect_timeout 2 minutes

log_fqdn on

某些指令取唯一值。在这些情形下,重复赋予该指令不同的值,将覆盖前面的值。例如,下面是一个连接超时值。第一行无效,因为第二行覆盖了它:

connect_timeout 2 minutes

connect_timeout 1 hour

另外,某些指令取列表值。在这些情形下,每一个新增的值都有效。”扩展方式”指令以这种方法工作:

extension_methods UNGET

extension_methods UNPUT

extension_methods UNPOST

对这些基于列表的指令,你通常能在同一行中赋予多个值:

extension_methods UNGET UNPUT UNPOST

许多指令有通用类型。例如,连接超时值是一个时间规范,在数字后面跟着时间单元。例如:

connect_timeout 3 hours

client_lifetime 4 days

negative_ttl 27 minutes

类似的,大量的指令指向文件大小或者内存额度。例如,你可以这样编写大小规范:十进制数字后面跟bytes,KB,MB或GB.例如:

minimum_object_size 12 bytes

request_header_max_size 10 KB

maximum_object_size 187 MB

另一种值得提起的类型是触发器,它的值是on或者off。许多指令使用该类型。例如:

server_persistent_connections on

strip_query_terms off

prefer_direct on

通常,配置文件指令能以任何顺序出现。然而,如果某个指令指向的值被其他指令所定义,那么顺序就很重要。访问控制列表是个好的例子。acl被用在http_access规则之前必须被定义:

acl Foo src 1.2.3.4

http_access deny Foo

squid.conf文件里的许多东西是大小写敏感的,例如指令名。你不能将http_port写成HTTP_port。

默认的squid.conf文件包含了对每个指令的大量注释,以及指令的默认值。例如:

#  TAG: persistent_request_timeout

#       How long to wait for the next HTTP request on a persistent

#       connection after the previous request completes.

#

#Default:

# persistent_request_timeout 1 minute

每次安装squid后,当前默认配置文件存放在$prefix/etc目录下的squid.conf.default。既然指令每次都有所改变,你能参考该文档,以获取最近的更新。

该章剩下的部分是关于在开始运行squid之前,你必须知道的少数指令。

4.2 User ID

你可能知道,unix进程和文件拥有文件和组属主的属性。你必须选择某个用户和组给squid。该用户和组的组合,必须对大部分squid相关的文件和目录有读和写的权限。

我高度推荐创建名为”squid”的用户和组。这避免了某人利用squid来读取系统中的其他文件。假如不止一个人拥有对squid的管理权限,你可以将他们加到squid组里。

unix进程继承了它们父进程的属主属性。那就是说,假如你以joe用户来启动squid,squid也以joe来运行。假如你不想以joe来运行squid,你需要预先改变你的用户ID。这是su命令的典型功能。例如:

joe% su - squid

squid% /usr/local/squid/sbin/squid

不幸的是,运行squid并非总是如此简单。在某些情况下,你必须以root来启动squid,这依赖于你的配置。例如,仅仅root能绑定TCP套接字到特权端口上,如80。假如你必须以root来启动squid,你必须设置cache_effective_user指令。它告诉squid,在执行完需要特别权限的任务后,变成哪个用户。例如:

cache_effective_user squid

你提供的该名字必须是有效用户(在/etc/passwd文件里)。请注意仅仅当你以root来启动squid时,你才需要用到该指令。仅仅root有能力来随意改变用户身份。假如你以joe来启动squid,它不能改变到squid用户。

你可能尝试不设置cache_effective_user,直接以root来运行squid。假如你试过,你会发现squid拒绝运行。这违背了安全规则。假如外部攻击者有能力危及或利用squid,他能获取对系统的全部访问权。尽管我们努力使squid安全和少bug,但还是稳重点好。

假如你没有设置cache_effective_user,以root来启动squid,squid使用nobody作为默认值。不管你选择什么用户ID,请确认它有对下面目录的读访问权:$prefix/etc,$prefix/libexec,$prefix/share.该用户ID也必须有对日志文件和缓存目录的写访问权。

squid也有一个cache_effective_group指令,但你也许不必设置它。默认的,squid使用cache_effective_user的默认组(从/etc/passwd文件读取)。

4.3 端口号

http_port指令告诉squid在哪个端口侦听HTTP请求。默认端口是3128:

http_port 3128

假如你将squid作为加速器运行(见15章),你也许该将它设为80。

你能使用附加的http_port行,来指示squid侦听在多个端口上。假如你必须支持客户组(它们被配置得不一致),这点就经常有用。例如,来自某个部门的浏览器发送请求到3128,然而另一个部门使用80端口。简单的将两个端口号列举出来:

http_port 3128

http_port 8080

你也能使用http_port指令来使squid侦听在指定的接口地址上。当squid作为防火墙运行时,它有两个网络接口:一个内部的和一个外部的。你可能不想接受来自外部的http请求。为了使squid仅仅侦听在内部接口上,简单的将IP地址放在端口号前面:

http_port 192.168.1.1:3128
4.4 日志文件路径

我将在第13章讨论所有squid的日志细节。你现在你关注的唯一事情是,squid将它的日志放在何处。默认的日志目录是squid安装位置下的logs目录。例如,假如你在./configure时没有使用–prefix=选项,那么默认的日志文件路径是/usr/local/squid/var/logs.

你必须确认日志文件所存放的磁盘位置空间足够。在squid写日志时如果接受到错误,它会退出和重启。该行为的主要理由应引起你的注意。squid想确认你不会丢失任何重要的日志信息,特别是你的系统被滥用或者被攻击时。

squid有三个主要的日志文件:cache.log,access.log,store.log.第一个文件即cache.log,包含状态性的和调试性的消息。当你刚开始运行squid时,你应密切的关注该文件。假如squid拒绝运行,理由也许会出现在cache.log文件的结尾处。在正常条件下,该文件不会变得很大。也请注意,假如你以-s选项来运行squid,重要的cache.log消息也可被送到你的syslog进程。通过使用cache_log指令,你可以改变该日志文件的路径:

cache_log /squid/logs/cache.log

access.log文件包含了对squid发起的每个客户请求的单一行。每行平均约150个字节。也就是说,在接受一百万条客户请求后,它的体积约是150M。请使用cache_access_log指令来改变该日志文件的路径:

cache_access_log /squid/logs/access.log

假如因为某些理由,你不想squid记录客户端请求日志,你能指定日志文件的路径为/dev/null. store.log文件对大多数cache管理员来说并非很有用。它包含了进入和离开缓存的每个目标的记录。平均记录大小典型的是175-200字节。然而,squid不在store.log里对cache点击创建接口,所以它比access.log包含少得多的记录。请使用cache_store_log指令来改变它的位置:

cache_store_log /squid/logs/store.log

通过指定路径为none,你能轻易的完全禁止store.log日志:

cache_store_log none

假如你不小心,squid的日志文件增加没有限制。某些操作系统对单个文件强制执行2G的大小限制,即使你有充足的磁盘空间。超过该限制会导致写错误,这样squid就会退出。为了保证日志文件大小合理,你应创建任务来有规律的重命名和打包日志。squid有内建功能来使这个容易做到。请见13.7章关于日志轮循的解释。

4.5 访问控制

在第6章里有更多的关于访问控制的描述。现在,我只讲述少量的访问控制方法,以使热心的读者能快速开始使用squid。

squid默认的配置文件拒绝每一个客户请求。在任何人能使用代理之前,你必须在squid.conf文件里加入附加的访问控制规则。最简单的方法就是定义一个针对客户IP地址的ACL和一个访问规则,告诉squid允许来自这些地址的HTTP请求。squid有许多不同的ACL类型。src类型匹配客户IP地址,squid会针对客户HTTP请求检查http_access规则。这样,你需要增加两行:

acl MyNetwork src 192.168.0.0/16

http_access allow MyNetwork

请将这些行放在正确的位置。http_access的顺序非常重要,但是acl行的顺序你不必介意。你也该注意默认的配置文件包含了一些重要的访问控制,你不应该改变或删除它们,除非你完全理解它们的意义。在你第一次编辑squid.conf文件时,请看如下注释:

# INSERT YOUR OWN RULE(S) HERE TO ALLOW ACCESS FROM YOUR CLIENTS

在该注释之后,以及”http_access deny all”之前插入你自己的新规则。为了彻底说明,如下是一个合理的初始访问控制配置,包括推荐的默认控制和早先的例子:

acl All src 0/0

acl Manager proto cache_object

acl Localhost src 127.0.0.1/32

acl Safe_ports port 80 21 443 563 70 210 280 488 591 777 1025-65535

acl SSL_ports 443 563

acl CONNECT method CONNECT

acl MyNetwork src 192.168.0.0/16

http_access allow Manager Localhost

http_access deny Manager

http_access deny !Safe_ports

http_access deny CONNECT !SSL_ports

http_access allow MyNetwork

http_access deny All
4.6 可见主机名

希望你不必担心visible_hostname指令。然而,假如squid不能发现它所运行的机器的主机名,你就必须设置它。如果发生这样的事,squid抱怨和拒绝运行:

% squid -Nd1

FATAL: Could not determine fully qualified hostname.  Please set 'visible_hostname'

有大量的理由使squid需要知道主机名:

  • 主机名出现在squid的错误消息里,这帮助用户验证潜在问题的源头。
  • 主机名出现在squid转发的cache单元的HTTP Via头里。当请求到达原始主机时,Via头包含了在传输过程中涉及的代理列表。squid也使用Via头来检测转发环路。我将在第10章里讨论转发环路。
  • squid对特定事务使用内部URL,例如FTP目录列表的图标。当squid对FTP目录产生HTML页面时,它插入小图标用以指明该目录中的文件类型。图标URL包含了cache的主机名,以便web浏览器能直接从squid请求它们。
  • 每个从squid响应的HTTP回复包含了X-Cache头。这并非官方HTTP头。它是一个扩展头,用以指明该响应是cache点击还是cache丢失。既然请求和响应可能经过多个cache,每个X-Cache头包含了cache报告点击或丢失的名字。如下是一个通过2个cache的响应示例:
  • HTTP/1.0 200 OK
    
    Date: Mon, 29 Sep 2003 22:57:23 GMT
    
    Content-type: text/html
    
    Content-length: 733
    
    X-Cache: HIT from bo2.us.ircache.net
    
    X-Cache: MISS from bo1.us.ircache.net
  • squid在启动时试图自动获取主机名。首先它调用gethostname()函数,这通常能返回正确的主机名。接着,squid调用gethostbyname()函数尝试对主机名进行DNS查询。该函数典型的返回IP地址和系统的规范名。假如gethostbyname()成功,squid在错误消息里,Via头里等地方使用这个规范名。
  • 因为大量的理由,squid可能不能检测到它的规范主机名,包括:
  • 主机名可能未设置。
  • 主机名可能从DNS区域或/etc/hosts文件里丢失。
  • squid系统的DNS客户端配置可能不正确或丢失。在unix系统上,你该检查/etc/resolv.conf和/etc/host.conf文件。

假如你看到上述的致命错误,你必须修正主机名和DNS信息,或者显式的给squid指明主机名。在大多数情况下,请确认”hostname”命令返回一个完全规范的主机名,并且在/etc/hosts文件里增加这个接口。假如这样不成功,请在squid.conf里设置可见主机名:

visible_hostname squid.packet-pushers.net
4.7 管理联系信息

你应该设置cache_mgr指令作为对用户的帮助。它是一个email地址,假如问题发生,用户能写信给它。cache_mgr地址默认出现在squid的错误消息里。例如:

cache_mgr squid@web-cache.net
4.8 下一步

在创建了初步的配置文件后,你多少准备首次运行squid了。请遵循下面章节的建议。当你掌握了启动和停止squid后,你该花费一些时间来改善配置文件。你可能想增加更高级的访问控制,这在第6章里有描述。既然我在这里没有讨论磁盘cache,你该花些时间阅读第7和第8章。

第5章 运行Squid

5.1 squid命令行选项

在开始其他事情之前,让我们先看一下squid的命令行选项。这里的许多选项你从不会使用,另外有些仅仅在调试问题时有用。

-a port

指定新的http_port值。该选项覆盖了来自squid.conf的值。然而请注意,你能在squid.conf里指定多个值。-a选项仅仅覆盖配置文件里的第一个值。(该选项使用字母a是因为在Harvest cache里,HTTP端口被叫做ASCII端口)

-d level

让squid将它的调试信息写到标准错误(假如配置了,就是cache.log和syslog)。level参数指定了显示在标准错误里的消息的最大等级。在多数情况下,d1工作良好。请见16.2章关于调试等级的描述。

-f file

指定另一个配置文件。

-h

显示用法。

-k function

指示squid执行不同的管理功能。功能参数是下列之一:reconfigure, rotate, shutdown, interrupt, kill, debug, check, or parse. reconfigure导致运行中的squid重新读取配置文件。rotate导致squid滚动它的日志,这包括了关闭日志,重命名,和再次打开它们。shutdown发送关闭squid进程的信号。interrupt立刻关闭squid,不必等待活动会话完成。kill发送KILL信号给squid,这是关闭squid的最后保证。debug将squid设置成完全的调试模式,假如你的cache很忙,它能迅速的用完你的磁盘空间。check简单的检查运行中的squid进程,返回的值显示squid是否在运行。最后,parse简单的解析squid.conf文件,如果配置文件包含错误,进程返回非零值。

-s

激活将日志记录到syslog进程。squid使用LOCAL4 syslog设备。0级别调试信息以优先级LOG_WARNING被记录,1级别消息以LOG_NOTICE被记录。更高级的调试信息不会被发送到syslogd.你可以在/etc/syslogd.conf文件里使用如下接口:

local4.warning			/var/log/squid.log

-u port

指定另一个ICP端口号,覆盖掉squid.conf文件里的icp_port。

-v

打印版本信息。

-z

初始化cache,或者交换,目录。在首次运行squid,或者增加新的cache目录时,你必须使用该选项。

-C

阻止安装某些信号句柄,它们捕获特定的致命信号例如SIGBUS和SIGSEGV。正常的,这些信号被squid捕获,以便它能干净的关闭。然而,捕获这些信号可能让以后调试问题困难。使用该选项,致命的信号导致它们的默认动作,通常是coredump。

-D

禁止初始化DNS测试。正常情况下,squid直到验证它的DNS可用才能启动。该选项阻止了这样的检测。你也能在squid.conf文件里改变或删除dns_testnames选项。

-F

让squid拒绝所有的请求,直到它重新建立起存储元数据。假如你的系统很忙,该选项可以减短重建存储元数据的时间。然而,如果你的cache很大,重建过程可能会花费很长的时间。

-N

阻止squid变成后台服务进程。

-R

阻止squid在绑定HTTP端口之前使用SO_REUSEADDR选项。

-V

激活虚拟主机加速模式。类似于squid.conf文件里的httpd_accel_host virtual指令。

-X

强迫完整调试模式,如你在squid.conf文件里指定debug_options ALL,9一样。

-Y

在重建存储元数据时,返回ICP_MISS_NOFETCH代替ICP_MISS.忙碌的父cache在重建时,该选项可以导致最少的负载。请见10.6.1.2章。

5.2 对配置文件查错

在开启squid之前,你应该谨慎的验证配置文件。这点容易做到,运行如下命令即可:

%squid -k parse

假如你看不到输出,配置文件有效,你能继续后面的步骤。然而,如果配置文件包含错误,squid会告诉你:

squid.conf line 62: http_access allow okay2

aclParseAccessLine: ACL name 'okay2' not found.

这里你可以看到,62行的http_access指令指向的ACL不存在。有时候错误信息很少:

FATAL: Bungled squid.conf line 76: memory_pools

在这个情形里,我们忘记了在76行的memory_pools指令后放置on或off. 建议你养成习惯:在每次修改配置文件后,使用squid -k parse。假如你不愿麻烦,并且你的配置文件有错误,squid会告诉你关于它们而且拒绝启动。假如你管理着大量的cache,也许你会编辑脚本来自动启动,停止和重配置squid。你能在脚本里使用该功能,来确认配置文件是有效的。

5.3 初始化cache目录

在初次运行squid之前,或者无论何时你增加了新的cache_dir,你必须初始化cache目录。命令很简单:

%squid -z

对UFS相关的存储机制(ufs,aufs,and diskd;见第8章),该命令在每个cache_dir下面创建了所需的子目录。你不必担心squid会破坏你的当前cache目录(如果有的话)。

在该阶段属主和许可权是通常遇到的问题。squid在特定的用户ID下运行,这在squid.conf文件里的cache_effective_user里指定。用户ID必须对每个cache_dir目录有读和写权限。否则,你将看到如下信息:

Creating Swap Directories

FATAL: Failed to make swap directory /usr/local/squid/var/cache/00:

    (13) Permission denied

在这样的情形下,你该确认/usr/local/squid/var/cache目录的所有组成都可被squid.conf给定的用户ID访问。最终的组件–cache目录–必须对该用户ID可写。

cache目录初始化可能花费一些时间,依赖于cache目录的大小和数量,以及磁盘驱动器的速度。假如你想观察这个过程,请使用-X选项:

%squid -zX
5.4 在终端窗口里测试squid

一旦你已经初始化cache目录,就可以在终端窗口里运行squid,将日志记录到标准错误。这样,你能轻易的定位任何错误或问题,并且确认squid是否成功启动。使用-N选项来保持squid在前台运行,-d1选项在标准错误里显示1级别的调试信息。

%squid -N -d1

你将看到类似于以下的输出:

2003/09/29 12:57:52| Starting Squid Cache 

version 2.5.STABLE4 for i386-unknown-freebsd4.8...

2003/09/29 12:57:52| Process ID 294

2003/09/29 12:57:52| With 1064 file descriptors available

2003/09/29 12:57:52| DNS Socket created on FD 4

2003/09/29 12:57:52| Adding nameserver 206.107.176.2 from /etc/resolv.conf

2003/09/29 12:57:52| Adding nameserver 205.162.184.2 from /etc/resolv.conf

2003/09/29 12:57:52| Unlinkd pipe opened on FD 9

2003/09/29 12:57:52| Swap maxSize 102400 KB, estimated 7876 objects

2003/09/29 12:57:52| Target number of buckets: 393

2003/09/29 12:57:52| Using 8192 Store buckets

2003/09/29 12:57:52| Max Mem  size: 8192 KB

2003/09/29 12:57:52| Max Swap size: 102400 KB

2003/09/29 12:57:52| Rebuilding storage in /usr/local/squid/var/cache (DIRTY)

2003/09/29 12:57:52| Using Least Load store dir selection

2003/09/29 12:57:52| Set Current Directory to /usr/local/squid/var/cache

2003/09/29 12:57:52| Loaded Icons.

2003/09/29 12:57:52| Accepting HTTP connections at 0.0.0.0, port 3128, FD 11.

2003/09/29 12:57:52| Accepting ICP messages at 0.0.0.0, port 3130, FD 12.

2003/09/29 12:57:52| WCCP Disabled.

2003/09/29 12:57:52| Ready to serve reques

假如你看到错误消息,你该首先修正它。请检查输出信息的开始几行以发现警告信息。最普通的错误是文件/目录许可问题,和配置文件语法错误。假如你看到一条不引起注意的错误消息,请见16章中关于squid故障处理的建议和信息。如果还不行,请检查squid FAQ,或查找邮件列表来获得解释。

一旦你见到”Ready to serve requests”消息,就可用一些HTTP请求来测试squid。配置你的浏览器使用squid作为代理,然后打开某个web页面。假如squid工作正常,页面被迅速载入,就象没使用squid一样。另外,你可以使用squidclient程序,它随squid发布:

% squidclient http://www.squid-cache.org/

假如它正常工作,squid的主页html文件会在你的终端窗口里滚动。一旦确认squid工作正常,你能中断squid进程(例如使用ctrl-c)并且在后台运行squid。

5.5 将squid作为服务进程运行

正常情况下你想将squid以后台进程运行(不出现在终端窗口里)。最容易的方法是简单执行如下命令:

%squid -s

-s选项导致squid将重要的状态和警告信息写到syslogd。squid使用LOCAL4设备,和LOG_WARNING和LOG_NOTICE优先权。syslog进程实际可能会或不会记录squid的消息,这依赖于它被如何配置。同样的消息被写进cache.log文件,所以假如你愿意,忽略-s选项也是安全的。

当你不使用-N选项来启动squid,squid自动在后台运行并且创建父/子进程对。子进程做所有的实际工作。父进程确认子进程总在运行。这样,假如子进程意外终止,父进程启动另外一个子进程以使squid正常工作。通过观察syslog消息,你能看到父/子进程交互作用。

Jul 31 14:58:35 zapp squid[294]: Squid Parent: child process 296 started

这里显示的父进程ID是294,子进程是296。当你查看ps的输出,你可以看到子进程以(squid)形式出现:

% ps ax | grep squid
  294  ??  Is     0:00.01 squid -sD
  296  ??  S      0:00.27 (squid) -sD (squid)

假如squid进程意外终止,父进程启动另一个。例如:

Jul 31 15:02:53 zapp squid[294]: Squid Parent: child process 296 exited due to signal 6
Jul 31 15:02:56 zapp squid[294]: Squid Parent: child process 359 started

在某些情形下,squid子进程可能立即终止。为了防止频繁的启动子进程,假如子进程连续5次没有运行至少10秒钟,父进程会放弃。

Jul 31 15:13:48 zapp squid[455]: Squid Parent: child process 474 exited with status 1
Jul 31 15:13:48 zapp squid[455]: Exiting due to repeated, frequent failures

如果发生这样的事,请检查syslog和squid的cache.log以发现错误。

5.5.1 squid_start脚本

当squid以后台进程运行时,它查找squid执行程序目录下的名为squid_start的文件。假如发现,该程序在父进程创建子进程之前被执行。你能使用该脚本完成特定的管理任务,例如通知某人squid在运行,管理日志文件等。除非squid_start程序存在,squid不会创建子进程。

squid_start脚本在你使用绝对或相对路径启动squid时才开始工作。换句话说,squid不使用PATH环境变量来定位squid_start.这样,你应该养成习惯这样启动squid:

% /usr/local/squid/sbin/squid -sD

而不要这样:

%squid -sD
5.6 启动脚本

通常你希望squid在每次计算机重启后自动启动。对不同的操作系统,它们的启动脚本如何工作也很不同。我在这里描述一些通用的环境,但对你自己的特殊操作系统,也许该有特殊的处理方法。

5.6.1 /etc/rc.local

最容易的机制之一是/etc/rc.local脚本。这是个简单的shell脚本,在每次系统启动时以root运行。使用该脚本来启动squid非常容易,增加一行如下:

/usr/local/squid/sbin/squid -s

当然你的安装位置可能不同,还有你可能要使用其他命令行选项。不要在这里使用-N选项。

假如因为某些理由,你没有使用cache_effective_user指令,你可以尝试使用su来让squid以非root用户运行:

/usr/bin/su nobody -c '/usr/local/squid/sbin/squid -s'
5.6.2 init.d和rc.d

init.d和rc.d机制使用独立的shell脚本来启动不同的服务。这些脚本通常在下列目录之中:/sbin/init.d, /etc/init.d, /usr/local/etc/rc.d.脚本通常获取单一命令行参数,是start或stop。某些系统仅仅使用start参数。如下是启动squid的基本脚本:

#!/bin/sh
# this script starts and stops Squid
case "$1" in
start)
          /usr/local/squid/sbin/squid -s
          echo -n ' Squid'
          ;;
stop)
          /usr/local/squid/sbin/squid -k shutdown
          ;;
esac

Linux用户可能在启动squid之前需要设置文件描述符限制。例如:

echo 8192 > /proc/sys/fs/file-max
limit -HSn 8192

为了使用该脚本,先找到脚本存放的目录。给它一个有意义的名字,类似于其他的系统启动脚本。可以是S98squid或squid.sh。通过重启计算机来测试该脚本,而不要假想它会正常工作。

5.6.3 /etc/inittab

某些操作系统支持另一种机制,是/etc/inittab文件。在这些系统中,init进程启动和停止基于运行等级的服务。典型的inittab接口类似如此:

sq:2345:once:/usr/local/squid/sbin/squid -s

使用该接口,init进程启动squid一次并且随后忘记它。squid确认它驻留在运行状态,象前面描述的一样。或者,你能这样做:

sq:2345:respawn:/usr/local/squid/sbin/squid -Ns

这里我们使用了respawn选项,假如进程不存在init会重启squid。假如使用respawn,请确认使用-N选项。

在编辑完inittab文件后,使用下面的命令来使init重新读取它的配置文件和启动squid:

# init q
5.7 chroot环境

某些人喜欢在chroot环境运行squid。这是unix的功能,给予进程新的root文件系统目录。在squid受安全威胁时,它提供额外等级的安全保护。假如攻击者在某种程度上通过squid获取了对操作系统的访问权,她仅仅能访问在chroot文件系统中的文件。在chroot树之外的系统文件,她不可访问。

最容易在chroot环境里运行squid的方法是,在squid.conf文件里指定新的root目录,如下:

chroot /new/root/directory

chroot()系统调用需要超级用户权限,所以你必须以root来启动squid。

chroot环境不是为unix新手准备的。它有点麻烦,因为你必须在新的root目录里重复放置大量的文件。例如,假如默认的配置文件正常在/usr/local/squid/etc/squid.conf,并且你使用chroot指令,那么文件必须位于/new/root/directory/usr/local/squid/etc/squid.conf.你必须将位于$prefix/etc,$prefix/share,$prefix/libexec下的所有文件拷贝到chroot目录。请确认$prefix/var和cache目录在chroot目录中存在和可写。

同样的,你的操作系统需要将大量的文件放在chroot目录里,例如/etc/resolv.conf和/dev/null.假如你使用外部辅助程序,例如重定向器(见11章)或者验证器(见12章),你也需要来自/usr/lib的某些共享库。你可以使用ldd工具来查找给定的程序需要哪些共享库:

% ldd /usr/local/squid/libexec/ncsa_auth
/usr/local/squid/libexec/ncsa_auth:

        libcrypt.so.2 => /usr/lib/libcrypt.so.2 (0x28067000)

        libm.so.2 => /usr/lib/libm.so.2 (0x28080000)

        libc.so.4 => /usr/lib/libc.so.4 (0x28098000)

你可以使用chroot命令来测试辅助程序:

# chroot /new/root/directory /usr/local/squid/libexec/ncsa_auth

/usr/libexec/ld-elf.so.1: Shared object "libcrypt.so.2" not found

更多的关于chroot的信息,请见你系统中chroot()的manpage.

5.8 停止squid

最安全的停止squid的方法是使用squid -k shutdown命令:

%squid -k shutdown

该命令发送TERM信号到运行中的squid进程。在接受到TERM信号后,squid关闭进来的套接字以拒收新请求。然后它等待一段时间,用以完成外出请求。默认时间是30秒,你可以在shutdown_lifetime指令里更改它。

假如因为某些理由,squid.pid文件丢失或不可读,squid -k命令会失败。在此情形下,你可以用ps找到squid的进程ID,然后手工杀死squid。例如:

%ps ax |grep squid

假如你看到不止一个squid进程,请杀死以(squid)显示的那个。例如:

% ps ax | grep squid

  294  ??  Is     0:00.01 squid -sD

  296  ??  S      0:00.27 (squid) -sD (squid)

% kill -TERM 296

在发送TERM信号后,你也许想查看日志,以确认squid已关闭:

% tail -f logs/cache.log

2003/09/29 21:49:30| Preparing for shutdown after 9316 requests

2003/09/29 21:49:30| Waiting 10 seconds for active connections to finish

2003/09/29 21:49:30| FD 11 Closing HTTP connection

2003/09/29 21:49:31| Shutting down...

2003/09/29 21:49:31| FD 12 Closing ICP connection

2003/09/29 21:49:31| Closing unlinkd pipe on FD 9

2003/09/29 21:49:31| storeDirWriteCleanLogs: Starting...

2003/09/29 21:49:32| Finished.  Wrote 253 entries.

2003/09/29 21:49:32| Took 0.1 seconds (1957.6 entries/sec).

2003/09/29 21:49:32| Squid Cache (Version 2.5.STABLE4): Exiting normally.

假如你使用squid -k interrupt命令,squid立即关闭,不用等待完成活动请求。这与在kill里发送INT信号相同。

5.9 重配置运行中的squid进程

在你了解了更多关于squid的知识后,你会发现对squid.conf文件做了许多改动。为了让新设置生效,你可以关闭和重启squid,或者在squid运行时,重配置它。

重配置运行中的squid最好的方法是使用squid -k reconfigure命令:

%squid -k reconfigure

当你运行该命令时,HUP信号被发送到运行中的squid进程。然后squid读取和解析squid.conf文件。假如操作成功,你可以在cache.log里看到这些:

2003/09/29 22:02:25| Restarting Squid Cache (version 2.5.STABLE4)...

2003/09/29 22:02:25| FD 12 Closing HTTP connection

2003/09/29 22:02:25| FD 13 Closing ICP connection

2003/09/29 22:02:25| Cache dir '/usr/local/squid/var/cache' size remains unchanged

                     at 102400 KB

2003/09/29 22:02:25| DNS Socket created on FD 5

2003/09/29 22:02:25| Adding nameserver 10.0.0.1 from /etc/resolv.conf

2003/09/29 22:02:25| Accepting HTTP connections at 0.0.0.0, port 3128, FD 9.

2003/09/29 22:02:25| Accepting ICP messages at 0.0.0.0, port 3130, FD 11.

2003/09/29 22:02:25| WCCP Disabled.

2003/09/29 22:02:25| Loaded Icons.

2003/09/29 22:02:25| Ready to serve requests.

在使用reconfigure选项时你须谨慎,因为所做的改变可能会导致致命错误。例如,请注意squid关闭和重新打开进来的HTTP和ICP套接字;假如你将http_port改变为squid不能打开的端口,它会发生致命错误并退出。

在squid运行时,某些指令和和选项不能改变,包括:

  • 删除cache目录(cache_dir指令)
  • 改变store_log指令
  • 改变coss cache_dir的块大小数值。事实上,无论何时你改变了该值,你必须重新初始化coss cache_dir.
  • coredump_dir指令在重配置过程中不被检查。所以,在squid已经启动了后,你不能让squid改变它的当前目录。

solaris用户在重配置squid过程中可能遇到其他问题。solaris的stdio执行组件里的fopen()调用要求使用小于256的未用文件描述符。FILE结构以8位值存储该文件描述符。正常情况下这不构成问题,因为squid使用底层I/O(例如open())来打开cache文件。然而,在重配置过程中的某些任务使用fopen(),这就有可能失败,因为前面的256个文件描述符已被分配出去。

5.10 滚动日志文件

除非你在squid.conf里禁止,squid会写大量的日志文件。你必须周期性的滚动日志文件,以阻止它们变得太大。squid将大量的重要信息写入日志,假如写不进去了,squid会发生错误并退出。为了合理控制磁盘空间消耗,在cron里使用如下命令:

%squid -k rotate

例如,如下任务接口在每天的早上4点滚动日志:

0 4 * * * /usr/local/squid/sbin/squid -k rotate

该命令做两件事。首先,它关闭当前打开的日志文件。然后,通过在文件名后加数字扩展名,它重命名cache.log,store.log,和access.log。例如,cache.log变成cache.log.0,cache.log.0变成cache.log.1,如此继续,滚动到logfile_rotate选项指定的值。

squid仅仅保存每个日志文件的最后logfile_rotate版本。更老的版本在重命名过程中被删除。假如你想保存更多的拷贝,你需要增加logfile_rotate限制,或者编写脚本用于将日志文件移动到其他位置。 请见13.7章关于滚动日志的其他信息。

6.访问控制

6.1 访问控制元素

ACL元素是Squid的访问控制的基础。这里告诉你如何指定包括IP地址,端口号,主机名,和URL匹配等变量。每个ACL元素有个名字,在编写访问控制规则时需要引用它们。基本的ACL元素语法如下:

acl name type value1 value2 ...

例如:

acl Workstations src 10.0.0.0/16

在多数情况下,你能对一个ACL元素列举多个值。你也可以有多个ACL行使用同一个名字。例如,下列两行配置是等价的:

acl Http_ports port 80 8000 8080

acl Http_ports port 80

acl Http_ports port 8000

acl Http_ports port 8080
6.1.1 一些基本的ACL类型

Squid大约有25个不同的ACL类型,其中的一些有通用基本类型。例如,src和dst ACL使用IP地址作为它们的基本类型。为避免冗长,我首先描述基本类型,然后在接下来章节里描述每种ACL类型。

6.1.1.1 IP地址

使用对象:src,dst,myip

squid在ACL里指定IP地址时,拥有强有力的语法。你能以子网,地址范围,域名等形式编写地址。squid支持标准IP地址写法(由”.”连接的4个小于256的数字)和无类域间路由规范。另外,假如你忽略掩码,squid会自动计算相应的掩码。例如,下例中的每组是相等的:

acl Foo src 172.16.44.21/255.255.255.255

acl Foo src 172.16.44.21/32

acl Foo src 172.16.44.21

acl Xyz src 172.16.55.32/255.255.255.248

acl Xyz src 172.16.55.32/28

acl Bar src 172.16.66.0/255.255.255.0

acl Bar src 172.16.66.0/24

acl Bar src 172.16.66.0

当你指定掩码时,squid会检查你的工作。如果你的掩码在IP地址的非零位之外,squid会告警。例如,下列行导致告警:

acl Foo src 127.0.0.1/8

aclParseIpData: WARNING: Netmask masks away part of the specified IP in 'Foo'

这里的问题是/8掩码(255.0.0.0)在最后三个字节里都是零值,但是IP地址127.0.0.1不是这样的。squid警告你这个问题,以便你消除歧义。正确的写法是:

acl Foo src 127.0.0.1/32

or:

acl Foo src 127.0.0.0/8

有时候你可能想列举多个相邻子网,在这样的情况下,通过指定地址范围很容易做到。例如:

acl Bar src 172.16.10.0-172.16.19.0/24

这等价但高效于下面的行:

acl Foo src 172.16.10.0/24

acl Foo src 172.16.11.0/24

acl Foo src 172.16.12.0/24

acl Foo src 172.16.13.0/24

acl Foo src 172.16.14.0/24

acl Foo src 172.16.15.0/24

acl Foo src 172.16.16.0/24

acl Foo src 172.16.18.0/24

acl Foo src 172.16.19.0/24

注意使用IP地址范围,掩码只能取一个。你不能为范围里的地址设置多个不同掩码。 你也能在IP ACL里指定主机名,例如:

acl Squid dst www.squid-cache.org

squid在启动时,将主机名转换成IP地址。一旦启动,squid不会对主机名的地址发起第二次DNS查询。这样,假如在squid运行中地址已改变,squid不会注意到。

假如主机名被解析成多个IP地址,squid将每一个增加到ACL里。注意你也可以对主机名使用网络掩码。

在基于地址的ACL里使用主机名通常是坏做法。squid在初始化其他组件之前,先解析配置文件,所以这些DNS查询不使用squid的非阻塞IP缓存接口。代替的,它们使用阻塞机制的gethostbyname()函数。这样,将ACL主机名转换到IP地址的过程会延缓squid的启动。除非绝对必要,请在src,dst,和myip ACL里避免使用主机名。

squid以一种叫做splay tree的数据结构在内存里存储IP地址ACL(请见http://www.link.cs.cmu.edu/splay/)。splay tree有一些有趣的自我调整的特性,其中之一是在查询发生时,列表会自动纠正它自己的位置。当某个匹配元素在列表里发现时,该元素变成新的树根。在该方法中,最近参考的条目会移动到树的顶部,这减少了将来查询的时间。

属于同一ACL元素的所有的子网和范围不能重迭。如果有错误,squid会警告你。例如,如下不被允许:

acl Foo src 1.2.3.0/24

acl Foo src 1.2.3.4/32

它导致squid在cache.log里打印警告:

WARNING: '1.2.3.4' is a subnetwork of '1.2.3.0/255.255.255.0'

WARNING: because of this '1.2.3.4' is ignored to keep splay tree searching

         predictable

WARNING: You should probably remove '1.2.3.4' from the ACL named 'Foo'

在该情形下,你需要修正这个问题,可以删除其中一个ACL值,或者将它们放置在不同的ACL列表中。

6.1.1.2 域名

使用对象:srcdomain,dstdomain,和cache_host_domain指令

域名简单的就是DNS名字或区域。例如,下面是有效的域名:

www.squid-cache.org

squid-cache.org

org

域名ACL有点深奥,因为相对于匹配域名和子域有点微妙的差别。当ACL域名以”.”开头,squid将它作为通配符,它匹配在该域的任何主机名,甚至域名自身。相反的,如果ACL域名不以”.”开头,squid使用精确的字符串比较,主机名同样必须被严格检查。

表6-1显示了squid的匹配域和主机名的规则。第一列显示了取自URL请求的主机名(或者srcdomain ACL的客户主机名)。第二列指明是否主机名匹配lrrr.org。第三列显示是否主机名匹配.lrrr.org ACL。你能看到,唯一的不同在第二个实例里。

Table 6-1. Domain name matching

URL hostname
Matches ACL lrrr.org?
Matches ACL .lrrr.org?

lrrr.org
Yes
Yes

i.am.lrrr.org
No
Yes

iamlrrr.org
No
No

域名匹配可能让人迷惑,所以请看第二个例子以便你能真正理解它。如下是两个稍微不同的ACL:

acl A dstdomain foo.com

acl B dstdomain .foo.com

用户对http://www.foo.com/的请求匹配ACL B,但不匹配A。ACL A要求严格的字符串匹配,然而ACL B里领头的点就像通配符。

另外,用户对http://foo.com/的请求同时匹配A和B。尽管在URL主机名里的foo.com前面没有字符,但ACL B里领头的点仍然导致一个匹配。

squid使用splay tree的数据结构来存储域名ACL,就像它处理IP地址一样。然而,squid的域名匹配机制给splay tree提供了一个有趣的问题。splay tree技术要求唯一键去匹配任意特定搜索条目。例如,让我们假设搜索条目是i.am.lrrr.org。该主机名同时匹配.lrrr.org和.am.lrrr.org。事实上就是两个ACL值匹配同一个主机名扰乱了splay机制。换句话说,在配置文件里放置如下语句是错误的:

acl Foo dstdomain .lrrr.org .am.lrrr.org

假如你这样做,squid会产生如下警告信息:

WARNING: '.am.lrrr.org' is a subdomain of '.lrrr.org'

WARNING: because of this '.am.lrrr.org' is ignored to keep splay tree searching predictable

WARNING: You should probably remove '.am.lrrr.org' from the ACL named 'Foo'

在该情况下你应遵循squid的建议。删除其中一条相关的域名,以便squid明确知道你的意图。注意你能在不同的ACL里任意使用这样的域名:

acl Foo dstdomain .lrrr.org

acl Bar dstdomain .am.lrrr.org

这是允许的,因为每个命名ACL使用它自己的splay tree.

6.1.1.3 用户名

使用对象:ident,proxy_auth

该类型的ACL被设计成匹配用户名。squid可能通过RFC 1413 ident协议或者通过HTTP验证头来获取用户名。用户名必须被严格匹配。例如,bob不匹配bobby。squid也有相关的ACL对用户名使用正则表达式匹配(ident_regex和proxy_auth_regex)。

你可以使用单词”REQUIRED”作为特殊值去匹配任意用户名。假如squid不能查明用户名,ACL不匹配。当使用基于用户名的访问控制时,squid通常这样配置。

6.1.1.4 正则表达式

使用对象:srcdom_regex, dstdom_regex, url_regex, urlpath_regex, browser, referer_regex, ident_regex, proxy_auth_regex, req_mime_type, rep_mime_type

大量的ACL使用正则表达式来匹配字符串(完整的正则表达式参考,请见O’Reilly的Mastering Regular Expressions一书)。对squid来说,最常使用的正则表达式功能用以匹配字符串的开头或结尾。例如,^字符是特殊元字符,它匹配行或字符串的开头:

^http://

该正则表达式匹配任意以http://开头的URL。$也是特殊的元字符,因为它匹配行或字符串的结尾:

.jpg$

实际上,该示例也有些错误,因为.字符也是特殊元字符。它是匹配任意单个字符的通配符。我们实际想要的应该是:

\.jpg$

反斜杠对这个”.”进行转义。该正则表达式匹配以.jpg结尾的任意字符串。假如你不使用^或$字符,正则表达式的行为就象标准子串搜索。它们匹配在字符串里任何位置出现的单词或词组。

对所有的squid正则表达式类,你可以使用大小写敏感的选项。匹配是默认大小写敏感的。为了大小写不敏感,在ACL类型后面使用-i选项。例如:

acl Foo url_regex -i ^http://www
6.1.1.5 TCP端口号

使用对象:port,myport

该类型是相对的。值是个别的端口号或端口范围。回想一下TCP端口号是16位值,这样它的值必须大于0和小于65536。如下是一些示例:

acl Foo port 123

acl Bar port 1-1024
6.1.1.6 自主系统号

使用对象:src_as,dst_as

Internet路由器使用自主系统(AS)号来创建路由表。基本上,某个AS号指向被同一组织管理的IP网络范围。例如,我的ISP分配了如下网络块:134.116.0.0/16, 137.41.0.0/16, 206.168.0.0/16,和其他更多。在Internet路由表里,这些网络被公布为属于AS 3404。当路由器转发包时,它们典型的选择经过最少AS的路径。假如这些对你不重要,请不必关注它们。AS基础的ACL仅仅被网络gurus使用。

如下是基于AS的类型如何工作的:当squid首先启动时,它发送一条特殊的查询到某个whois服务器。查询语句基本是:“告诉我哪个IP网络属于该AS号”。这样的信息被RADB收集和管理。一旦Squid接受到IP网络列表,它相似的将它们作为IP基础的ACL对待。基于AS的类型仅仅在ISP将他们的RADB信息保持与日更新时才工作良好。某些ISP更新RADB比其他人做得更好;而许多根本不更新它。请注意squid仅仅在启动或者reconfigure时才将AS号转换为网络地址。假如ISP更新了它的RADB接口,除非你重启或者重配置squid,squid不会知道这个改变。

另外的情况是,在你的squid启动时,RADB可能不可到达。假如Squid不能联系上RADB服务器,它从访问控制配置里删除AS接口。默认的whois服务器是whois.ra.net,对许多用户来说太遥远了而不可信赖。

6.1.2 ACL类型

现在我们能把焦点放在ACL类型自身上。我在这里按照重要性的降序来列举它们。

6.1.2.1 src

IP地址在访问控制元素里是最普遍使用的。大部分站点使用IP地址来控制客户允许或不允许访问Squid。src类型指客户源IP地址。也就是说,当src ACL出现在访问控制列表里时,squid将它与发布请求的客户IP地址进行比较。

正常情况下你允许来自内网中主机的请求,并阻塞其他的。例如,假如你的单位使用192.168.0.0子网,你可以这样指定ACL:

acl MyNetwork src 192.168.0.0

假如你有许多子网,你能在同一个acl行里面列举它们:

acl MyNetwork src 192.168.0.0 10.0.1.0/24 10.0.5.0/24 172.16.0.0/12

squid有许多其他ACL类型用以检查客户地址。srcdomain类型比较客户的完整可验证域名。它要求反向DNS查询,这可能会延缓处理该请求。srcdom_regex ACL是类似的,但它允许你使用正则表达式来匹配域名。最后,src_as类型比较客户的AS号。

6.1.2.2 dst

dst类型指向原始服务器(目标)IP地址。在某些情况下,你能使用该类型来阻止你的用户访问特定web站点。然而,在使用dst ACL时你须谨慎。大部分squid接受到的请求有原始服务器主机名。例如:

GET http://www.web-cache.com/ HTTP/1.0

这里,www.web-cache.com是主机名。当访问列表规则包含了dst元素时,squid必须找到该主机名的IP地址。假如squid的IP缓存包含了该主机名的有效接口,这条ACL被立即检测。否则,在DNS查询忙碌时,squid会延缓处理该请求。这对某些请求来说会造成延时。为了避免延时,你该尽可能的使用dstdomain ACL类型来代替dst。

如下是简单的dst ACL示例:

acl AdServers dst 1.2.3.0/24

请注意,dst ACL存在的问题是,你试图允许或拒绝访问的原始服务器可能会改变它的IP地址。假如你不关心这样的改变,那就不必麻烦去升级squid.conf。你可以在acl行里放上主机名,但那样会延缓启动速度。假如你的ACL需要许多主机名,你也许该预处理配置文件,将主机名转换成IP地址。

6.1.2.3 myip

myip类型指Squid的IP地址,它被客户连接。当你在squid机上运行netstat -n时,你见到它们位于本地地址列。大部分squid安装不使用该类型。通常所有的客户连接到同一个IP地址,所以该ACL元素仅仅当系统有多个IP地址时才有用。

为了理解myip为何有用,考虑某个有两个子网的公司网络。在子网1的用户是程序员和工程师。子网2包括会计,市场和其他管理部门。这样情况下的squid有三个网络接口:一个连接子网1,一个连接子网2,第三个连接到外部因特网。

Figure 6-1. An application of the myip ACL

::__IHACKLOG_REMOTE_IMAGE_AUTODOWN_BLOCK__::0

当正确的配置时,所有在子网1的用户连接到squid位于该子网的IP地址,类似的,子网2的用户连接到squid的第二个IP地址。这样你就可以给予子网1的技术部员工完全的访问权,然而限制管理部门的员工仅仅能访问工作相关的站点。ACL可能如下:

acl Eng myip 172.16.1.5

acl Admin myip 172.16.2.5

然而请注意,使用该机制你必须特别小心,阻止来自某个子网的用户连接squid位于另一子网的IP地址。否则,在会计和市场子网的聪明的用户,能够通过技术部子网进行连接,从而绕过你的限制。

6.1.2.4 dstdomain

在某些情况下,你发现基于名字的访问控制非常有用。你可以使用它们去阻塞对某些站点的访问,去控制squid如何转发请求,以及让某些响应不可缓存。dstdomain之所以非常有用,是因为它检查请求url里的主机名。然而首先我想申明如下两行的不同:

acl A dst www.squid-cache.org

acl B dstdomain www.squid-cache.org

A实际上是IP地址ACL。当Squid解析配置文件时,它查询www.squid-cache.org的IP地址,并将它们存在内存里。它不保存名字。假如在squid运行时IP地址改变了,squid会继续使用旧的地址。

然而dstdomain ACL以域名形式存储,并非IP地址。当squid检查ACL B时,它对URL的主机名部分使用字符串比较功能。在该情形下,它并不真正关心是否www.squid-cache.org的IP地址改变了。

使用dstdomain ACL的主要问题是某些URL使用IP地址代替主机名。假如你的目标是使用dstdomain ACL来阻塞对某些站点的访问,聪明的用户能手工查询站点的IP地址,然后将它们放在URL里。例如,下面的2行URL带来同样的页面:

http://www.squid-cache.org/docs/FAQ/

http://206.168.0.9/docs/FAQ/

第一行能被dstdomain ACL轻易匹配,但第二行不能。这样,假如你依靠dstdomain ACL,你也该同样阻塞所有使用IP地址代替主机名的请求。请见6.3.8章节。

6.1.2.5 srcdomain

srcdomain ACL也有点麻烦。它要求对每个客户IP地址进行所谓的反向DNS查询。技术上,squid请求对该地址的DNS PTR记录。DNS的响应–完整可验证域名(FQDN)–是squid匹配ACL值的东西。(请参考O’Reilly’s DNS and BIND找到更多关于DNS PTR记录的信息)使用dst ACL,FQDN查询会导致延时。请求会被延缓处理直到FQDN响应返回。FQDN响应被缓存下来,所以srcdomain查询通常仅在客户首次请求时延时。

不幸的是,srcdomain查询有时不能工作。许多组织并没有保持他们的反向查询数据库与日更新。假如某地址没有PTR记录,ACL检查失败。在该情形下,请求可能会延时非常长时间(例如2分钟)直到DNS查询超时。假如你使用srcdomain ACL,请确认你自己的DNS in-addr.arpa区域配置正确并且在工作中。假如这样,你可以使用如下的ACL:

acl LocalHosts srcdomain .users.example.com
6.1.2.6 port

你很可能想使用port ACL来限制对某些原始服务器端口号的访问。就像我即将讲到的,squid其实不连接到某些服务,例如email和IRC服务。port ACL允许你定义单独的端口或端口范围。例如:

acl HTTPports port 80 8000-8010 8080

HTTP在设计上与其他协议类似,例如SMTP。这意味着聪明的用户通过转发email消息到SMTP服务器能欺骗squid。Email转发是垃圾邮件的主要原因之一,我们必须处理它们。历史上,垃圾邮件有真正的邮件服务器。然而近来,越来越多的垃圾邮件制造者使用开放HTTP代理来隐藏他们的踪迹。你肯定不想Squid被当成垃圾邮件转发器。假如是这样,你的IP地址很可能被许多邮件转发黑名单冻结(MAPS,ORDB,spamhaus等)。除email之外,还有其他许多TCP/IP服务是squid不与其通信的。这些包括IRC,Telnet,POP,和NNTP。你的针对端口的策略必须被配置成拒绝已知危险端口,并允许剩下的;或者允许已知安全端口,并拒绝剩下的。

我的态度比较保守,仅仅允许安全的端口。默认的squid.conf包含了下面的安全端口ACL:

acl Safe_ports port 80          # http

acl Safe_ports port 21          # ftp

acl Safe_ports port 443 563     # https, snews

acl Safe_ports port 70          # gopher

acl Safe_ports port 210         # wais

acl Safe_ports port 1025-65535  # unregistered ports

acl Safe_ports port 280         # http-mgmt

acl Safe_ports port 488         # gss-http

acl Safe_ports port 591         # filemaker

acl Safe_ports port 777         # multiling http

http_access deny !Safe_ports

这是个较明智的配置。它允许用户连接到任何非特权端口(1025-65535),但仅仅指定的特权端口可以被连接。假如你的用户试图访问某个URL如下:http://www.lrrr.org:123/,squid会返回访问拒绝错误消息。在某些情形下,为了让你的用户满意,你可能需要增加另外的端口号。

宽松的做法是,拒绝对特别危险的端口的访问。Squid FAQ包括了如下示例:

acl Dangerous_ports 7 9 19 22 23 25 53 109 110 119

http_access deny Dangerous_ports

使用Dangerous_ports的弊端是squid对几乎每个请求都要搜索整个列表。这对CPU造成了额外的负担。大多数情况下,99%到达squid的请求是对80端口的,它不出现在危险端口列表里。所有请求对该表的搜索不会导致匹配。当然,整数比较是快速的操作,不会显然影响性能。

(译者注:这里的意思是,两者都要对列表进行搜索和匹配。在第一种情况下,它搜索安全端口列表并匹配80,显然第一个元素就匹配成功了。而第二种情况中,会搜索危险端口列表并试图匹配80,当然危险端口不会包括80,所以每次对80的请求都要搜索完整个列表,这样就会影响性能。)

6.1.2.7 myport

squid也有myport ACL。port ACL指向原始服务器的端口号,myport指向squid自己的端口号,用以接受客户请求。假如你在http_port指令里指定不止一个端口号,那么squid就可以在不同的端口上侦听。

假如你将squid作为站点HTTP加速器和用户代理服务器,那么myport ACL特别有用。你可以在80上接受加速请求,在3128上接受代理请求。你可能想让所有人访问加速器,但仅仅你自己的用户能以代理形式访问squid。你的ACL可能如下:

acl AccelPort myport 80

acl ProxyPort myport 3128

acl MyNet src 172.16.0.0/22

http_access allow AccelPort         # anyone

http_access allow ProxyPort MyNet   # only my users

http_access deny ProxyPort          # deny others
6.1.2.8 method

method ACL指HTTP请求方法。GET是典型的最常用方法,接下来是POST,PUT,和其他。下例说明如何使用method ACL:

acl Uploads method PUT POST

Squid知道下列标准HTTP方法:GET, POST, PUT, HEAD, CONNECT, TRACE, OPTIONS和DELETE。另外,squid了解下列来自WEBDAV规范,RFC 2518的方法:PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK。某些Microsoft产品使用非标准的WEBDAV方法,所以squid也了解它们:BMOVE, BDELETE, BPROPFIND。最后,你可以在extension_methods指令里配置squid去理解其他的请求方法。请见附录A。

注意CONNECT方法非常特殊。它是用于通过HTTP代理来封装某种请求的方法(请见RFC 2817:Upgrading to TLS Within HTTP/1.1)。在处理CONNECT方法和远程服务器的端口号时应特别谨慎。就像前面章节讲过的一样,你不希望squid连接到某些远程服务。你该限制CONNECT方法仅仅能连接到HTTPS/SSL或NNTPS端口(443和563)。默认的squid.conf这样做:

acl CONNECT method CONNECT

acl SSL_ports 443 563

http_access allow CONNECT SSL_ports

http_access deny CONNECT

在该配置里,squid仅仅允许加密请求到端口443(HTTPS/SSL)和563(NNTPS)。CONNECT方法对其他端口的请求都被拒绝。

PURGE是另一个特殊的请求方法。它是Squid的专有方法,没有在任何RFC里定义。它让管理员能强制删除缓存对象。既然该方法有些危险,squid默认拒绝PURGE请求,除非你定义了ACL引用了该方法。否则,任何能访问cache者也许能够删除任意缓存对象。我推荐仅仅允许来自localhost的PURGE:

acl Purge method PURGE

acl Localhost src 127.0.0.1

http_access allow Purge Localhost

http_access deny Purge

关于从squid的缓存里删除对象,请见7.6章。

6.1.2.9 proto

该类型指URI访问(或传输)协议。如下是有效值:http, https (same as HTTP/TLS), ftp, gopher, urn, whois, 和cache_object。也就是说,这些是被squid支持的URL机制名字。例如,假如你想拒绝所有的FTP请求,你可以使用下列指令:

acl FTP proto FTP

http_access deny FTP

cache_object机制是squid的特性。它用于访问squid的缓存管理接口,我将在14.2章讨论它。不幸的是,它并非好名字,可能会被改变。默认的squid.conf文件有许多行限制缓存管理访问:

acl Manager proto cache_object

acl Localhost src 127.0.0.1

http_access allow Manager Localhost

http_access deny Manager

这些配置行仅允许来自本机地址的缓存管理请求,所有其他的缓存管理请求被拒绝。这意味着在squid机器上有帐号的人,能访问到潜在的敏感缓存管理信息。你也许想修改缓存管理访问控制,或对某些页面使用密码保护。我将在14.2.2章里谈论到。

6.1.2.10 time

time ACL允许你控制基于时间的访问,时间为每天中的具体时间,和每周中的每天。日期以单字母来表示,见如下表。时间以24小时制来表示。开始时间必须小于结束时间,这样在编写跨越0点的time ACL时可能有点麻烦。

Table 6-2. Day codes for the time ACL

Code
Day

S
Sunday

M
Monday

T
Tuesday

W
Wednesday

H
Thursday

F
Friday

A
Saturday

D
All weekdays (M-F)

日期和时间由localtime()函数来产生。请确认你的计算机位于正确的时区,你也该让你的时钟与标准时间同步。

为了编写time ACL来匹配你的工作时间,你可以这样写:

acl Working_hours MTWHF 08:00-17:00

or:

acl Working_hours D 08:00-17:00

让我们看一个麻烦的例子。也许你是某个ISP,在下午8点到早上4点这段不忙的时间内放松访问。既然该时间跨越子夜,你不能编写“20:00-04:00”。代替的,你需要把它们分成两个ACL来写,或者使用否定机制来定义非忙时。例如:

acl Offpeak1 20:00-23:59

acl Offpeak2 00:00-04:00

http_access allow Offpeak1 ...

http_access allow Offpeak2 ...

另外,你可以这样写:

acl Peak 04:00-20:00

http_access allow !Peak ...

尽管squid允许,你也不应该在同一个time ACL里放置多个日期和时间范围列表。对这些ACL的解析不一定是你想象的那样。例如,假如你输入:

acl Blah time M 08:00-10:00 W 09:00-11:00

实际能做到的是:

acl Blah time MW 09:00-11:00

解析仅仅使用最后一个时间范围。正确的写法是,将它们写进两行:

acl Blah time M 08:00-10:00

acl Blah time W 09:00-11:00
6.1.2.11 ident

ident ACL匹配被ident协议返回的用户名。这是个简单的协议,文档是RFC 1413。它工作过程如下:

  • 1.用户代理(客户端)对squid建立TCP连接。
  • 2.squid连接到客户系统的ident端口(113)。
  • 3.squid发送一个包括两个TCP端口号的行。squid端的端口号可能是3128(或者你在squid.conf里配置的端口号),客户端的端口号是随机的。
  • 4.客户端的ident服务器返回打开第一个连接的进程的用户名。
  • 5.squid记录下用户名用于访问控制目的,并且记录到access.log。

当squid遇到对特殊请求的ident ACL时,该请求被延时,直到ident查询完成。这样,ident ACL可以对你的用户请求造成延时。

我们推荐仅仅在本地局域网中,并且大部分客户工作站运行ident服务时,才使用ident ACL。假如squid和客户工作站连在一个局域网里,ident ACL工作良好。跨广域网使用ident难以成功。

ident协议并非很安全。恶意的用户能替换他们的正常ident服务为假冒服务,并返回任意的他们选择的用户名。例如,假如我知道从administrator用户的连接总是被允许,那么我可以写个简单的程序,在回答每个ident请求时都返回这个用户名。

你可以使用ident ACL拦截cache(请见第9章)。当squid被配置成拦截cache时,操作系统假设它自己是原始服务器。这意味着用于拦截TCP连接的本地socket地址有原始服务器的IP地址。假如你在squid上运行netstat -n时,你可以看到大量的外部IP地址出现在本地地址栏里。当squid发起一个ident查询时,它创建一个新的TCP套接字,并绑定本地终点到同一个IP地址上,作为客户TCP连接的本地终点。既然本地地址并非真正是本地的(它可能与原始服务器IP地址相距遥远),bind()系统调用失败。squid将这个作为失败的ident查询来处理。

注意squid也有个特性,对客户端执行懒惰ident查询。在该情形下,在等待ident查询时,请求不会延时。在HTTP请求完成时,squid记录ident信息,假如它可用。你能使用ident_lookup_access指令来激活该特性,我将在本章后面讨论。

6.1.2.12 proxy_auth

squid有一套有力的,在某种程度上有点混乱的特性,用以支持HTTP代理验证功能。使用代理验证,客户的包括头部的http请求包含了验证信用选项。通常,这简单的是用户名和密码。squid解密信用选项,并调用外部验证程序以发现该信用选项是否有效。

squid当前支持三种技术以接受用户验证:HTTP基本协议,数字认证协议,和NTLM。基本认证已经发展了相当长时间。按今天的标准,它是非常不安全的技术。用户名和密码以明文同时发送。数字认证更安全,但也更复杂。基本和数字认证在RFC 2617文档里被描述。NTLM也比基本认证更安全。然而,它是Microsoft发展的专有协议。少数squid开发者已经基本完成了对它的反向工程。

为了使用代理验证,你必须配置squid使用大量的外部辅助程序。squid源代码里包含了一些程序,用于对许多标准数据库包括LDAP,NTLM,NCSA类型的密码文件,和标准Unix密码数据库进行认证。auth_param指令控制对所有辅助程序的配置。我将在12章里讨论这些细节。

auth_param指令和proxy_auth ACL是少数在配置文件里顺序重要的实例。你必须在proxy_auth ACL之前定义至少一个验证辅助程序(使用auth_param)。假如你没有这样做,squid打印出错误消息,并且忽略proxy_auth ACL。这并非致命错误,所以squid可以启动,但所有你的用户的请求可能被拒绝。

proxy_auth ACL取用户名作为值。然而,大部分安装里简单的使用特殊值REQUIRED:

auth_param ...

acl Auth1 proxy_auth REQUIRED

在该情况中,任何具有有效信用选项的请求会匹配该ACL。假如你需要细化控制,你可以指定独立的用户名:

auth_param ...

acl Auth1 proxy_auth allan bob charlie

acl Auth2 proxy_auth dave eric frank

代理验证不支持HTTP拦截,因为用户代理不知道它在与代理服务器,而非原始服务器通信。用户代理不知道在请求里发送Proxy-Authorization头部。见9.2章更多细节。

6.1.2.13 src_as

该类型检查客户源IP地址所属的具体AS号(见6.1.1.6关于squid如何将AS号映射到IP地址的信息)。作为示例,我们虚构某ISP使用AS 64222并且通告使用10.0.0.0/8,172.16.0.0/12,192.168.0.0/16网络。你可以编写这样的ACL,它允许来自该ISP地址空间的任何主机请求:

acl TheISP src 10.0.0.0/8

acl TheISP src 172.16.0.0/12

acl TheISP src 192.168.0.0/16

http_access allow TheISP

当然,你还可以这样写:

acl TheISP src_as 64222

http_access allow TheISP

第二种写法不但更短,而且假如ISP增加了新的网络,你不必更新ACL配置。

6.1.2.14 dst_as

dst_as ACL经常与cache_peer_access指令一起使用。在该方法中,squid使用与IP路由一致的方式转发cache丢失。考虑某ISP,它比其他ISP更频繁的更换路由。每个ISP处理他们自己的cache代理,这些代理能转发请求到其他代理。理论上,ISP A将ISP B网络里主机的cache丢失转发到ISP B的cache代理。使用AS ACL和cache_peer_access指令容易做到这点:

acl ISP-B-AS dst_as 64222

acl ISP-C-AS dst_as 64333

cache_peer proxy.isp-b.net parent 3128 3130

cache_peer proxy.isp-c.net parent 3128 3130

cache_peer_access proxy.isb-b.net allow ISP-B-AS

cache_peer_access proxy.isb-c.net allow ISP-C-AS

我将在第10章里讨论更多关于cache协作。

6.1.2.15 snmp_community

snmp_community ACL对SNMP查询才有意义,后者被snmp_access指令控制。例如,你可以这样写:

acl OurCommunityName snmp_community hIgHsEcUrItY

acl All src 0/0

snmp_access allow OurCommunityName

snmp_access deny All

在该情况中,假如community名字设置为hIgHsEcUrItY,SNMP查询才被允许。

6.1.2.16 maxconn

maxconn ACL指来自客户IP地址的大量同时连接。某些squid管理员发现这是个有用的方法,用以阻止用户滥用代理或者消耗过多资源。

maxconn ACL在请求超过指定的数量时,会匹配这个请求。因为这个理由,你应该仅仅在deny规则里使用maxconn。考虑如下例子:

acl OverConnLimit maxconn 4

http_access deny OverConnLimit

在该情况中,squid允许来自每个IP地址的同时连接数最大为4个。当某个客户发起第五个连接时,OverConnLimit ACL被匹配,http_access规则拒绝该请求。

6.1.2.17 arp

arp ACL用于检测cache客户端的MAC地址(以太网卡的物理地址)。地址解析协议(ARP)是主机查找对应于IP地址的MAC地址的方法。某些大学学生发现,在Microsoft Windows下,他们可以改变系统的IP地址到任意值,然后欺骗squid的基于地址的控制。这时arp功能就派上用场了,聪明的系统管理员会配置squid检查客户的以太网地址。

不幸的是,该特性使用非移植性代码。假如你运行Solaris或Linux,你能使用arp ACL。其他系统不行。当你运行./configure时增加–enable-arp-acl选项,就可以激活该功能。

arp ACL有另一个重要限制。ARP是数据链路层协议,假如客户主机和squid在同一子网,它才能工作。你不容易发现不同子网主机的MAC地址。假如在squid和你的用户之间有路由器存在,你可能不能使用arp ACL。

现在你知道何时去使用它们,让我们看看arp ACL实际上是怎样的。它的值是以太网地址,当使用ifconfig和arp时你能看到以太网地址。例如:

acl WinBoxes arp 00:00:21:55:ed:22

acl WinBoxes arp 00:00:21:ff:55:38
6.1.2.18 srcdom_regex

srcdom_regex ACL允许你使用正则表达式匹配客户域名。这与srcdomain ACL相似,它使用改进的的子串匹配。相同的限制是:某些客户地址不能反向解析到域名。作为示例,下面的ACL匹配以dhcp开头的主机名:

acl DHCPUser srcdom_regex -i ^dhcp

因为领头的^符号,该ACL匹配主机名dhcp12.example.com,但不匹配host12.dhcp.example.com。

6.1.2.19 dstdom_regex

dstdom_regex ACL也与dstdomain相似。下面的例子匹配以www开头的主机名:

acl WebSite dstdom_regex -i ^www\.

如下是另一个有用的正则表达式,用以匹配在URL主机名里出现的IP地址:

acl IPaddr dstdom_regex [0-9]$

这样可以工作,因为squid要求URL主机名完全可验证。既然全局顶级域名中没有以数字结尾的,该ACL仅仅匹配IP地址,它以数字结尾。

6.1.2.20 url_regex

url_regex ACL用于匹配请求URL的任何部分,包括传输协议和原始服务器主机名。例如,如下ACL匹配从FTP服务器的MP3文件请求:

acl FTPMP3 url_regex -i ^ftp://.*\.mp3$
6.1.2.21 urlpath_regex

urlpath_regex与url_regex非常相似,不过传输协议和主机名不包含在匹配条件里。这让某些类型的检测非常容易。例如,假设你必须拒绝URL里的”sex”,但仍允许在主机名里含有”sex”的请求,那么这样做:

acl Sex urlpath_regex sex

另一个例子,假如你想特殊处理cgi-bin请求,你能这样捕获它们:

acl CGI1 urlpath_regex ^/cgi-bin

当然,CGI程序并非总在/cgi-bin/目录下,这样你应该编写其他的ACL来捕获它们。

6.1.2.22 browser

大部分HTTP请求包含了User-Agent头部。该头部的值典型如下:

Mozilla/4.51 [en] (X11; I; Linux 2.2.5-15 i686)

browser ACL对user-agent头执行正则表达式匹配。例如,拒绝不是来自Mozilla浏览器的请求,可以这样写:

acl Mozilla browser Mozilla

http_access deny !Mozilla

在使用browser ACL之前,请确认你完全理解cache接受到的User-Agent字符串。某些user-agent与它们的来源相关。甚至squid可以重写它转发的请求的User-Agent头部。某些浏览器例如Opera和KDE的Konqueror,用户可以对不同的原始服务器发送不同的user-agent字串,或者干脆忽略它们。

6.1.2.23 req_mime_type

req_mime_type ACL指客户HTTP请求里的Content-Type头部。该类型头部通常仅仅出现在请求消息主体里。POST和PUT请求可能包含该头部,但GET从不。你能使用该类型ACL来检测某些文件上传,和某些类型的HTTP隧道请求。req_mime_type ACL值是正则表达式。你可以这样编写ACL去捕获音频文件类型:

acl AuidoFileUploads req_mime_type -i ^audio/
6.1.2.24 rep_mime_type

该类型ACL指原始服务器的HTTP响应里的Content-Type头部。它仅在使用http_reply_access规则时才有用。所有的其他访问控制形式是基于客户端请求的。该ACL基于服务器响应。假如你想使用squid阻塞Java代码,你可以这样写:

acl JavaDownload rep_mime_type application/x-java

http_reply_access deny JavaDownload
6.1.2.25 ident_regex

在本节早些时讲过ident ACL。ident_regex允许你使用正则表达式,代替严格的字符串匹配,这些匹配是对ident协议返回的用户名进行。例如,如下ACL匹配包含数字的用户名:

acl NumberInName ident_regex [0-9]
6.1.2.26 proxy_auth_regex

该ACL允许对代理认证用户名使用正则表达式。例如,如下ACL匹配admin,administrator和administrators:

acl Admins proxy_auth_regex -i ^admin
6.1.3 外部ACL

Squid 2.5版本介绍了一个新特性:外部ACL。你可以指示squid发送某些信息片断到外部进程,然后外部的辅助程序告诉squid,数据匹配或不匹配。

squid附带着大量的外部ACL辅助程序;大部分用于确定命名用户是不是某个特殊组的成员。请见12.5章关于这些程序的描述,以及关于如何编写你自己的程序的信息。现在,我解释如何定义和使用外部ACL类型。

external_acl_type指令定义新的外部ACL类型。如下是通用语法:

external_acl_type type-name [options] format helper-command

type-name是用户定义的字串。你也可以在acl行里引用它。Squid当前支持如下选项(options):

ttl=n

时间数量,单位是秒,用以缓存匹配值的时间长短。默认是3600秒,或1小时。

negative_ttl=n

时间数量,单位是秒,用以缓存不匹配值的时间长短。默认是3600秒,或1小时。

concurrency=n

衍生的辅助程序的数量,默认是5。

cache=n

缓存结果的最大数量。默认是0,即不限制cache大小。格式是以%字符开始的一个或多个关键字。squid当前支持如下格式:

%LOGIN

从代理验证信用选项里获取的用户名。

%IDENT

从RFC 1413 ident获取的用户名。

%SRC

客户端IP地址。

%DST

原始服务器IP地址。

%PROTO

传输协议(例如HTTP,FTP等)

%PORT

原始服务器的TCP端口。

%METHOD

HTTP请求方法。

%{Header}

HTTP请求头部的值;例如,%{User-Agent}导致squid发送这样的字串到验证器:

"Mozilla/4.0 (compatible; MSIE 6.0; Win32)"
%{Hdr:member}

选择某些数量的基于列表的HTTP头部,例如Caceh-Control;例如,给出如下HTTP头部:

X-Some-Header: foo=xyzzy, bar=plugh, foo=zoinks

对%{X-Some-Header:foo}的取值,squid发送这样的字串到外部ACL进程:

foo=xyzzy, foo=zoinks
%{Hdr:;member}

与%{Hdr:member}相同,除了”;”是列表分隔符外。你能使用任何非字母数字的字符作为分隔符。辅助命令是squid为辅助程序衍生的命令。你也可以在这里包含命令参数。例如,整条命令可能类似如此:

/usr/local/squid/libexec/my-acl-prog.pl -X -5 /usr/local/squid/etc/datafile

将这些放在一个长行里。squid不支持如下通过反斜杠分隔长行的技术,所以请记住所有这些必须放在单行里:

external_acl_type MyAclType cache=100 %LOGIN %{User-Agent} \

    /usr/local/squid/libexec/my-acl-prog.pl -X -5 \

    /usr/local/squid/share/usernames \

    /usr/local/squid/share/useragents

现在你知道如何定义外部ACL,下一步是编写引用它的acl行。这相对容易,语法如下:

acl acl-name external type-name [args ...]

如下是个简单示例:

acl MyAcl external MyAclType

squid接受在type-name后面的任意数量的参数。这些在每个请求里被发送到辅助程序。请见12.5.3章,我描述了unix_group辅助程序,作为该功能的示例。

6.1.4 处理长ACL列表

ACL列表某些时候非常长。这样的列表在squid.conf文件里难以维护。你也可能想从其他资源里自动产生squid ACL列表。在如此情况下,你可以从外部文件里包含ACL列表。语法如下:

acl name "filename"

这里的双引号指示squid打开filename,并且将它里面的内容分配给ACL。例如,如下的ACL太长了:

acl Foo BadClients 1.2.3.4 1.2.3.5 1.2.3.6 1.2.3.7 1.2.3.9 ...

你可以这样做:

acl Foo BadClients "/usr/local/squid/etc/BadClients"

将IP地址放在BadClients文件里:

1.2.3.4

1.2.3.5

1.2.3.6

1.2.3.7

1.2.3.9

...

文件可以包含以#开头的注释。注意在该文件里的每个IP地址必须是一个单独的行。acl行里的任何地方,以空格来分隔值,新行是包含ACL值的文件的分界。

6.1.5 Squid如何匹配访问控制元素

理解squid如何搜索ACL元素去匹配是很重要的。当ACL元素有多个值时,任何单个值能导致匹配。换句话说,squid在检查ACL元素值时使用OR逻辑。当squid找到第一个值匹配时,它停止搜索。这意味着把最可能匹配的值放在列表开头处,能减少延时。

让我们看一个特殊的例子,考虑如下ACL定义:

acl Simpsons ident Maggie Lisa Bart Marge Homer

当squid在访问列表里遇到Simpsons ACL时,它执行ident查询。让我们看一下,当用户ident服务返回Marge时,会发生什么呢?squid的ACL代码在成功匹配Marge前,会先后将这个值与Maggie,Lisa,和Bart对比。当搜索完成时,我们认为Simpsons ACL匹配了这个请求。

实际上,这有点欺骗。ident ACL值并非存储在无序列表里。它们存储在splay tree中。这意味着,在非匹配事件中,squid不会搜索完所有的名字。对一个splay tree搜索N个条目需要记录N个比较。许多其他的ACL类型也使用splay tree。然而,基于正则表达式的类型不使用。

既然正则表达式不能这样存储,它们以链表形式存储。这使得在大链表里它们特别低效,特别是不匹配链表里任何正则表达式的请求。为了改进这个形式,当匹配发生时,squid将正则表达式移到列表的顶部。实际上,因为ACL匹配代码的天然特性,squid将匹配的条目移到列表的第二个位置。这样,普通的匹配值自然移到ACL列表的顶部,这样会减少比较数量。

让我们看另一个简单示例:

acl Schmever port 80-90 101 103 107 1 2 3 9999

该ACL匹配到原始服务器80-90端口,和其他独立端口的请求。对80端口的请求,squid通过查看第一个值就匹配了该ACL。对9999端口,其他每个值都先被检查。对某个不在列表里的端口,squid要检查所有值才宣布它不匹配。就像我已经讲过的,将最常用的值放在第一位能优化ACL匹配。

6.2 访问控制规则

前面提过,ACL元素是建立访问控制的第一步。第二步是访问控制规则,用来允许或拒绝某些动作。在早先的例子里,你已见过http_access规则。squid有大量其他的访问控制列表:

http_access

这是最重要的访问控制列表。它决定哪些客户HTTP请求被允许,和哪些被拒绝。假如http_access配置错误,squid cache容易遭受攻击或被不当利用。

http_reply_access

http_reply_access与http_access类似。不同之处是前者在squid接受到来自原始服务器或上级代理的响应时,才会被检测。大部分访问控制基于客户请求的方式,对这些使用http_access就够了。然而,某些人喜欢基于响应内容类型来允许或拒绝请求。更多信息请见6.3.9章。

icp_access

假如你的squid被配置来服务ICP响应(见10.6章),那么该使用icp_access列表。大部分情况下,你该仅仅允许来自邻居cache的ICP请求。

no_cache

你能使用no_cache访问列表来指示squid,它不必存储某些响应(在磁盘或内存里)。该列表典型的与dst,dstdomain,url_regex ACL结合使用。

对no_cache使用”否”条件,这样的双重否定会导致某些混乱。被no_cache列表拒绝的请求不被缓存。换句话说,no_cache deny…是让目标不被缓存。见6.3.10章的示例。

miss_access

miss_access列表主要用于squid的邻居cache。它决定squid怎样处理cache丢失的请求。如果squid使用集群技术,那么该功能必需。见6.3.7的示例。

redirector_access

该访问列表决定哪个请求被发送到重定向进程(见11章)。默认情况下,假如你使用重定向器,那么所有的请求都通过重定向器。你可以使用redirector_access列表来阻止某些请求被重写。这点特别有用,因为这样的访问列表,使重定向器相对于访问控制系统,接受的请求信息要少一些。

ident_lookup_access

ident_lookup_access列表与redirector_access类似。它允许你对某些请求执行懒惰ident查询。squid默认不发布ident查询。假如请求被ident_lookup_access规则(或ident ACL)允许,那么squid才会进行ident查询。

always_direct

该访问列表影响squid怎样处理与邻居cache转发cache丢失。通常squid试图转发cache丢失到父cache,和/或squid使用ICP来查找临近cache响应。然而,当请求匹配always_direct规则时,squid直接转发请求到原始服务器。

使用该规则,对”allow”规则的匹配导致squid直接转发请求,见10.4.4章的更多细节和示例。

never_direct

never_direct与always_direct相反。匹配该列表的cache丢失请求必须发送到邻居cache。这点对在防火墙之后的代理特别有用。使用该列表,对”allow”规则的匹配导致squid转发请求到邻居cache。见10.4.3章的更多细节和示例。

snmp_access

该访问列表应用到发送给squid的SNMP端口的查询。你能配合该列表使用的ACL是snmp_community和src。假如你确实想使用它,那也能使用srcdomain,srcdom_regex,和src_as。见14.3章的示例。

broken_posts

该访问列表影响squid处理某些POST请求的方法。某些老的用户代理在请求主体的结尾处发送一个特别的回车换行符。那就是说,消息主体比content-length头部指示的长度要多2个字节。更糟糕的是,某些老的HTTP服务器实际上依赖于这种不正确的行为。当请求匹配该访问列表时,squid模拟这种客户端并且发送特殊的回车换行符。Squid有大量的使用ACL元素的其他配置指令。它们中的某些过去是全局配置,后被修改来使用ACL以提供更灵活的控制。

cache_peer_access

该访问列表控制发送到邻居cache的HTTP请求和ICP/HTCP查询。见10.4.1章的更多信息和示例。

reply_body_max_size

该访问列表限制对HTTP响应主体的最大可接受size。见附录A的更多信息。

delay_access

该访问规则列表控制是否延时池被应用到某个请求的cache丢失响应。见附录C。

tcp_outgoing_address

该访问列表绑定服务端TCP连接到指定的本地IP地址。见附录A。

tcp_outgoing_tos

该访问列表能设置到原始服务器和邻居cache的TCP连接的不同TOS/Diffserv值,见附录A。

header_access

使用该指令,你能配置squid从它转发的请求里删除某些HTTP头部。例如,你也许想过滤掉发送到某些原始服务器的请求里的Cookie头部。见附录A。

header_replace

该指令允许你替换,而不是删除,HTTP头部的内容。例如,你能设置user-agent头部为假值,满足某些原始服务器的要求,但仍保护你的隐私。见附录A。

6.2.1 访问规则语法

访问控制规则的语法如下:

access_list allow|deny [!]ACLname ...

例如:

http_access allow MyClients

http_access deny !Safe_Ports

http_access allow GameSites AfterHours

当读取配置文件时,squid仅仅扫描一遍访问控制行。这样,在访问列表里引用ACL元素之前,你必须在acl行里定义它们。甚至,访问列表规则的顺序也非常重要。你以怎样的顺序编写访问列表,那么squid就按怎样的顺序来检查它们。将最常用的ACL放在列表的开始位置,可以减少squid的CPU负载。

对大部分访问列表,deny和allow的意义明显。然而,它们中的某些,却并非如此含义清楚。请谨慎的编写always_direct,never_direct,和no_cache规则。在always_direct中,allow规则意味着匹配的请求直接转发到原始服务器。always_direct deny规则意味着匹配的请求不强迫发送到原始服务器,但假如邻居cache不可到达,那可能还是会这么做。no_cache规则也有点麻烦。这里,你必须对不必被cache的请求使用deny。

6.2.2 Squid如何匹配访问规则

回想一下squid在搜索ACL元素时使用的“或”逻辑。在acl里的任何单值都可以导致匹配。

然而,访问规则恰好相反。对http_access和其他规则设置,squid使用“与”逻辑。考虑如下示例:

access_list allow ACL1 ACL2 ACL3

对该匹配规则来说,请求必须匹配ACL1,ACL2,ACL3中的任何一个。假如这些ACL中的任何一个不匹配请求,squid停止搜索该规则,并继续处理下一条。对某个规则来说,将最少匹配的ACL放在首位,能使效率最佳。考虑如下示例:

acl A method http

acl B port 8080

http_access deny A B

该http_access规则有点低效,因为A ACL看起来比B ACL更容易匹配。反转顺序应该更好,以便squid仅仅检查一个ACL,而不是两个:

http_access deny B A

人们易犯的典型错误是编写永不正确的规则。例如:

acl A src 1.2.3.4

acl B src 5.6.7.8

http_access allow A B

该规则永不正确,因为某个源IP地址不可能同时等同于1.2.3.4和5.6.7.8。这条规则的真正意图是:

acl A src 1.2.3.4 5.6.7.8
http_access allow A

对某个ACL值的匹配算法是,squid在访问列表里找到匹配规则时,搜索终止。假如没有访问规则导致匹配,默认动作是列表里最后一条规则的取反。例如,考虑如下简单访问配置:

acl Bob ident bob

http_access allow Bob

假如用户Mary发起请求,她会被拒绝。列表里最后的(唯一的)规则是allow规则,它不匹配用户名mary。这样,默认的动作是allow的取反,故请求被拒绝。类似的,假如最后的规则是deny规则,默认动作是允许请求。在访问列表的最后加上一条,明确允许或拒绝所有请求,是好的实际做法。为清楚起见,以前的示例应该如此写:

acl All src 0/0

acl Bob ident bob

http_access allow Bob

http_access deny All

src 0/0 ACL表示匹配每一个和任意类型的请求。

6.2.3 访问列表风格

squid的访问控制语法非常强大。大多数情况下,你可以使用两种或多种方法来完成同样的事。通常,你该将更具体的和受限制的访问列表放在首位。例如,如下语句并非很好:

acl All src 0/0

acl Net1 src 1.2.3.0/24

acl Net2 src 1.2.4.0/24

acl Net3 src 1.2.5.0/24

acl Net4 src 1.2.6.0/24

acl WorkingHours time 08:00-17:00

http_access allow Net1 WorkingHours

http_access allow Net2 WorkingHours

http_access allow Net3 WorkingHours

http_access allow Net4

http_access deny All

假如你这样写,访问控制列表会更容易维护和理解:

http_access allow Net4

http_access deny !WorkingHours

http_access allow Net1

http_access allow Net2

http_access allow Net3

http_access deny All

无论何时,你编写了一个带两个或更多ACL元素的规则,建议你在其后紧跟一条相反的,更广泛的规则。例如,默认的squid配置拒绝非来自本机IP地址的cache管理请求,你也许试图这样写:

acl CacheManager proto cache_object

acl Localhost src 127.0.0.1

http_access deny CacheManager !Localhost

然而,这里的问题是,你没有允许确实来自本机的cache管理请求。随后的规则可能导致请求被拒绝。如下规则就产生了问题:

acl CacheManager proto cache_object

acl Localhost src 127.0.0.1

acl MyNet 10.0.0.0/24

acl All src 0/0

http_access deny CacheManager !Localhost

http_access allow MyNet

http_access deny All

既然来自本机的请求不匹配MyNet,它被拒绝。编写本规则的更好方法是:

http_access allow CacheManager localhost

http_access deny CacheManager

http_access allow MyNet

        http_access deny All
6.2.4 延时检查

某些ACL不能在一个过程里被检查,因为必要的信息不可用。ident,dst,srcdomain和proxy_auth类型属于该范畴。当squid遇到某个ACL不能被检查时,它延迟决定并且发布对必要信息的查询(IP地址,域名,用户名等)。当信息可用时,squid再次在列表的开头位置检查这些规则。它不会从前次检查剩下的位置继续。假如可能,你应该将这些最可能被延时的ACL放在规则的顶部,以避免不必要的,重复的检查。

因为延时的代价太大,squid会尽可能缓存查询获取的信息。ident查询在每个连接里发生,而不是在每个请求里。这意味着,当你使用ident查询时,持续HTTP连接切实对你有利。DNS响应的主机名和IP地址也被缓存,除非你使用早期的外部dnsserver进程。代理验证信息被缓存,请见6.1.2.12章节的描述。

6.2.5 减缓和加速规则检查

Squid内部考虑某些访问规则被快速检查,其他的被减缓检查。区别是squid是否延迟它的决定,以等待附加信息。换句话说,在squid查询附加信息时,某个减缓检查会被延时,例如:

  • 反向DNS查询:客户IP地址的主机名
  • RFC 1413 ident查询:客户TCP连接的用户名
  • 验证器:验证用户信用
  • DNS转发查询:原始服务器的IP地址
  • 用户定义的外部ACL

某些访问规则使用快速检查。例如,icp_access规则被快速检查。为了快速响应ICP查询,它必须被快速检查。甚至,某些ACL类型例如proxy_auth,对ICP查询来说无意义。下列访问规则被快速检查:

header_access

reply_body_max_size

reply_access

ident_lookup

delay_access

miss_access

broken_posts

icp_access

cache_peer_access

redirector_access

snmp_access

下列ACL类型可能需要来自外部数据源(DNS,验证器等)的信息,这样与快速的访问规则不兼容:

srcdomain, dstdomain, srcdom_regex, dstdom_regex

dst, dst_as

proxy_auth

ident

external_acl_type

这意味着,例如,不能在header_access规则里使用ident ACL。

6.3 常见用法

因为访问控制可能很复杂,本节包含一些示例。它们描述了一些访问控制的普通用法。你可以在实际中调整它们。

6.3.1 仅仅允许本地客户

几乎每个squid安装后,都限制基于客户IP地址的访问。这是保护你的系统不被滥用的最好的方法之一。做到这点最容易的方法是,编写包含IP地址空间的ACL,然后允许该ACL的HTTP请求,并拒绝其他的。

acl All src 0/0

acl MyNetwork src 172.16.5.0/24 172.16.6.0/24

http_access allow MyNetwork

http_access deny All

也许该访问控制配置过于简单,所以你要增加更多行。记住http_access的顺序至关重要。不要在deny all后面增加任何语句。假如必要,应该在allow MyNetwork之前或之后增加新规则。

6.3.2 阻止恶意客户

因为某种理由,你也许有必要拒绝特定客户IP地址的访问。这种情况可能发生,例如,假如某个雇员或学生发起一个异常耗费网络带宽或其他资源的web连接,在根本解决这个问题前,你可以配置squid来阻止这个请求:

acl All src 0/0

acl MyNetwork src 172.16.5.0/24 172.16.6.0/24

acl ProblemHost src 172.16.5.9

http_access deny ProblemHost

http_access allow MyNetwork

        http_access deny All
6.3.3 内容过滤

阻塞对特定内容的访问是棘手的问题。通常,使用squid进行内容过滤最难的部分,是被阻塞的站点列表。你也许想自己维护一个这样的列表,或从其他地方获取一个。squid FAQ的“访问控制”章节有链接指向免费的可用列表。

使用这样的列表的ACL语法依赖于它的内容。假如列表包含正则表达式,你可能要这样写:

acl PornSites url_regex "/usr/local/squid/etc/pornlist"

http_access deny PornSites

另一方面,假如列表包含原始服务器主机名,那么简单的更改url_regex为dstdomain。

6.3.4 在工作时间的受限使用

某些公司喜欢在工作时间限制web使用,为了节省带宽,或者是公司政策禁止员工在工作时做某些事情。关于这个最难的部分是,所谓合适的和不合适的internet使用之间的区别是什么。不幸的是,我不能对这个问题作出回答。在该例子里,假设你已收集了一份web站点域名列表,它包含已知的不适合于你的站点名,那么这样配置squid:

acl NotWorkRelated dstdomain "/usr/local/squid/etc/not-work-related-sites"

acl WorkingHours time D 08:00-17:30

http_access deny !WorkingHours NotWorkRelated

请注意在该规则里首先放置!WorkingHours ACL。相对于字符串或列表,dstdomain ACL产生的性能代价较大,但time ACL检查却很简单。下面的例子,进一步理解如何结合如下方法和前面描述的源地址控制,来控制访问。

acl All src 0/0

acl MyNetwork src 172.16.5.0/24 172.16.6.0/24

acl NotWorkRelated dstdomain "/usr/local/squid/etc/not-work-related-sites"

acl WorkingHours time D 08:00-17:30

http_access deny !WorkingHours NotWorkRelated

http_access allow MyNetwork

http_access deny All

上面的方法可行,因为它实现了我们的目标,在工作时间内拒绝某些请求,并允许来自你自己网络的请求。然而,它也许有点低效。注意NotWorkRelated ACL在所有请求里被搜索,而不管源IP地址。假如那个列表非常长,在列表里对外部网络请求的搜索,纯粹是浪费CPU资源。所以,你该这样改变规则:

http_access deny !MyNetwork

http_access deny !WorkingHours NotWorkRelated

http_access Allow All

这里,将代价较大的检查放在最后。试图滥用squid的外部用户不会再浪费你的CPU资源。

6.3.5 阻止squid与非HTTP服务器会话

你必须尽可能不让squid与某些类型的TCP/IP服务器通信。例如,永不能够使用squid缓存来转发SMTP传输。我在前面介绍port ACL时提到过这点。然而,它是至关重要的,所以再强调一下。

首先,你必须关注CONNECT请求方法。使用该方法的用户代理,通过HTTP代理来封装TCP连接。它被创造用于HTTP/TLS请求,这是CONNECT方法的主要用途。某些用户代理也可以通过防火墙代理来封装NNTP/TLS传输。所有其他的用法应该被拒绝。所以,你的访问列表,应该仅仅允许到HTTP/TLS和NNTP/TLS端口的CONNECT请求。

第二,你应该阻止squid连接到某些服务,例如SMTP。你也可以开放安全端口和拒绝危险端口。我对这两种技术给出示例。让我们看看默认的squid.conf文件提供的规则:

acl Safe_ports port 80          # http

acl Safe_ports port 21          # ftp

acl Safe_ports port 443 563     # https, snews

acl Safe_ports port 70          # gopher

acl Safe_ports port 210         # wais

acl Safe_ports port 280         # http-mgmt

acl Safe_ports port 488         # gss-http

acl Safe_ports port 591         # filemaker

acl Safe_ports port 777         # multiling http

acl Safe_ports port 1025-65535  # unregistered ports

acl SSL_ports port 443 563

acl CONNECT method CONNECT

http_access deny !Safe_ports

http_access deny CONNECT !SSL_ports

<additional http_access lines as necessary...>

Safe_ports ACL列举了所有的squid有合法响应的特权端口(小于1024)。它也列举了所有非特权端口范围。注意Safe_ports ACL也包括了安全HTTP和NNTP端口(443和563),即使它们也出现在SSL_ports ACL里。这是因为Safe_ports在规则里首先被检查。假如你交换了两个http_access行的顺序,你也许能从Safe_ports列表里删除443和563,但没必要这么麻烦。与此相似的其他方法是,列举已知不安全的特权端口:

acl Dangerous_ports 7 9 19 22 23 25 53 109 110 119

acl SSL_ports port 443 563

acl CONNECT method CONNECT

http_access deny Dangerous_ports

http_access deny CONNECT !SSL_ports

<additional http_access lines as necessary...>

假如你不熟悉这些奇特的端口号,也不要担心。你可以阅读unix系统的/etc/services文件,或者阅读IANA的注册TCP/UDP端口号列表:

http://www.iana.org/assignments/port-numbers

6.3.6 授予某些用户特殊的访问

使用基于用户名进行访问控制的组织,通常需要授予某些用户特殊的权限。在该简单示例里,有三个元素:所有授权用户,管理员用户名,限制访问的web站点列表。正常的用户不允许访问受限站点,但管理员有维护这个列表的任务。他们必须连接到所有服务器,去验证某个特殊站点是否该放到受限站点列表里。如下显示如何完成这个任务:

auth_param basic program /usr/local/squid/libexec/ncsa_auth

    /usr/local/squid/etc/passwd

acl Authenticated proxy_auth REQUIRED

acl Admins proxy_auth Pat Jean Chris

acl Porn dstdomain "/usr/local/squid/etc/porn.domains"

acl All src 0/0

http_access allow Admins

http_access deny Porn

http_access allow Authenticated

http_access deny All

首先,有三个ACL定义。Authenticated ACL匹配任何有效的代理验证信用。Admins ACL匹配来自用户Pat,Jean,和Chris的有效信用。Porn ACL匹配某些原始服务器主机名,它们在porn.domains文件里找到。

该示例有四个访问控制规则。第一个仅仅检查Admins ACL,允许所有来自Pat,Jean,和Chris的请求。对其他用户,squid转移到下一条规则。对第二条规则,假如原始主机名位于porn.domains文件,那么该请求被拒绝。对不匹配Porn ACL的请求,squid转移到第三条规则。第三条规则里,假如请求包含有效的验证信用,那么该请求被允许。外部验证器(这里的ncsa_auth)决定是否信用有效。假如它们无效,最后的规则出现,该请求被拒绝。注意ncsa_auth验证器并非必需。你可以使用12章里描述的任何验证辅助程序。

6.3.7 阻止邻近cache的滥用

假如你使用了cache集群,你必须付出多余的小心。cache通常使用ICP来发现哪些对象被缓存在它们的邻居机器上。你仅该接受来自已知授权的邻居cache的ICP查询。

更进一步,通过使用miss_access规则列表,你能配置squid强制限制邻近关系。squid仅仅在cache丢失,没有cache命中时才检查这些规则。这样,在miss_access列表生效前,所有请求必须首先通过http_access规则。

在本示例里,有三个独立的ACL。一个是直接连接到cache的本地用户;另一个是子cache,它被允许来转发cache丢失的请求;第三个是邻近cache,它必须从不转发导致cache丢失的请求。如下是它们如何工作:

alc All src 0/0

acl OurUsers src 172.16.5.0/24

acl ChildCache src 192.168.1.1

acl SiblingCache src 192.168.3.3

http_access allow OurUsers

http_access allow ChildCache

http_access allow SiblingCache

http_access deny All

miss_access deny SiblingCache

icp_access allow ChildCache

icp_access allow SiblingCache

        icp_access deny All
6.3.8 使用IP地址拒绝请求

我在6.1.2.4章节里提过,dstdomain类型是阻塞对指定原始主机访问的好选择。然而,聪明的用户通过替换URL主机名成IP地址,能够绕过这样的规则。假如你想彻底阻止这样的请求,你可能得阻塞所有包含IP地址的请求。你可以使用重定向器,或者使用dstdom_regex ACL来完成。例如:

acl IPForHostname dstdom_regex ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$

        http_access deny IPForHostname
6.3.9 http_reply_access示例

回想一下,当squid检查http_reply_access规则时,响应的内容类型是唯一的可用新信息。这样,你能保持http_reply_access规则简单化。你只需检查rep_mime_type ACL。例如,如下示例告诉你如何拒绝某些内容类型的响应:

acl All src 0/0

acl Movies rep_mime_type video/mpeg

acl MP3s rep_mime_type audio/mpeg

http_reply_access deny Movies

http_reply_access deny MP3s

http_reply_access allow All

你不必在http_reply_access列表里重复http_access规则。这里的allow ALL规则不意味着所有对squid的请求被允许。任何被http_access拒绝的请求,从来不会再被http_reply_access检查。

6.3.10 阻止对本地站点的cache命中

假如你有许多原始服务器在本地网络中,你也许想配置squid,以便它们的响应永不被缓存。因为服务器就在附近,它们不会从cache命中里获益很多。另外,它释放存储空间给其他远程原始主机。

第一步是定义本地服务器的ACL。你可能使用基于地址的ACL,例如:

acl LocalServers dst 172.17.1.0/24

假如服务器不位于单一的子网,你也许该创建dstdomain ACL:

acl LocalServers dstdomain .example.com

接下来,你简单的使用no_cache access规则,拒绝这些服务器的cache:

no_cache deny LocalServers

no_cache规则不会阻止客户发送请求到squid。没有办法配置squid阻止这样的请求进来。代替的,你必须配置用户代理自身。

假如你在squid运行一段时间后增加no_cache规则,cache可能包含一些匹配新规则的对象。在squid2.5之前的版本,这些以前缓存的对象可能以cache命中返回。然而现在,squid清除掉所有匹配no_cache规则的缓存响应。

6.4 测试访问控制

访问控制配置越长,它就越复杂。强烈建议你在将它们用于产品环境之前,先测试访问控制。当然,首先做的事是确认squid能正确的解析配置文件。使用-k parse功能:

% squid -k parse

为了进一步测试访问控制,你需要安装一个用于测试的squid。容易做到的方法是,编译另一份squid到其他$prefix位置。例如:

% tar xzvf squid-2.5.STABLE4.tar.gz
    % cd squid-2.5.STABLE4
    % ./configure --prefix=/tmp/squid ...
    % make && make install

在安装完后,你必须编辑新的squid.conf文件,更改一些指令。假如squid已经运行在默认端口,那么请改变http_port。为了执行简单的测试,创建单一的小目录:

cache_dir ufs /tmp/squid/cache 100 4 4

假如你不想重编译squid,你也能创建一份新的配置文件。该方法的弊端是你必须设置所有的日志文件路径为临时目录,以便不会覆盖真正的文件。

你可以使用squidclient程序来轻松的测试某些访问控制。例如,假如你有一条规则,它依赖于原始服务器主机名(dstdomain ACL),或者某些URL部分(url_regex或urlpath_regex),简单的输入你期望被允许或拒绝的URI:

% squidclient -p 4128 http://blocked.host.name/blah/blah

or:

% squidclient -p 4128 http://some.host.name/blocked.ext

某些类型的请求难以控制。假如你有src ACL,它们阻止来自外部网络的请求,你也许需要从外部主机测试它们。测试time ACL也很困难,除非你能改变系统时钟,或者等待足够长时间。

你能使用squidclient的-H选项来设置任意请求头。例如,假如你需要测试browser ACL,那么这样做:

% squidclient -p 4128 http://www.host.name/blah  \

      -H 'User-Agent: Mozilla/5.0 (compatible; Konqueror/3)\r\n'

更多的复杂请求,包括多个头部,请参考16.4章中描述的技术。

你也许考虑制订一项cron,定期检查ACL,以发现期望的行为,并报告任何异常。如下是可以起步的示例shell脚本:

#!/bin/sh

set -e

TESTHOST="www.squid-cache.org"

# make sure Squid is not proxying dangerous ports

#

ST=`squidclient 'http://$TESTHOST:25/' | head -1 | awk '{print $2}'`

if test "$ST" != 403 ; then

        echo "Squid did not block HTTP request to port 25"

fi

# make sure Squid requires user authentication

#

ST=`squidclient 'http://$TESTHOST/' | head -1 | awk '{print $2}'`

if test "$ST" != 407 ; then

        echo "Squid allowed request without proxy authentication"

fi

# make sure Squid denies requests from foreign IP addresses

# elsewhere we already created an alias 192.168.1.1 on one of

# the system interfaces

#

EXT_ADDR=192.168.1.1

ST=`squidclient -l $EXT_ADDR 'http://$TESTHOST/' | head -1 | awk '{print $2}'`

if test "$ST" != 403 ; then

        echo "Squid allowed request from external address $EXT_ADDR"

fi

exit 0

7.磁盘缓存基础

7.1 cache_dir指令

cache_dir指令是squid.conf配置文件里最重要的指令之一。它告诉squid以何种方式存储cache文件到磁盘的什么位置。cache_dir指令取如下参数:

cache_dir scheme directory size L1 L2 [options]
7.1.1 参数:Scheme

Squid支持许多不同的存储机制。默认的(原始的)是ufs。依赖于操作系统的不同,你可以选择不同的存储机制。在./configure时,你必须使用–enable-storeio=LIST选项来编译其他存储机制的附加代码。我将在8.7章讨论aufs,diskd,coss和null。现在,我仅仅讨论ufs机制,它与aufs和diskd一致。

7.1.2 参数:Directory

该参数是文件系统目录,squid将cache对象文件存放在这个目录下。正常的,cache_dir使用整个文件系统或磁盘分区。它通常不介意是否在单个文件系统分区里放置了多个cache目录。然而,我推荐在每个物理磁盘中,仅仅设置一个cache目录。例如,假如你有2个无用磁盘,你可以这样做:

# newfs /dev/da1d

# newfs /dev/da2d

# mount /dev/da1d /cache0

# mount /dev/da2d /cache1

然后在squid.conf里增加如下行:

cache_dir ufs /cache0 7000 16 256

cache_dir ufs /cache1 7000 16 256

假如你没有空闲硬盘,当然你也能使用已经存在的文件系统分区。选择有大量空闲空间的分区,例如/usr或/var,然后在下面创建一个新目录。例如:

# mkdir /var/squidcache

然后在squid.conf里增加如下一行:

cache_dir ufs /var/squidcache 7000 16 256
7.1.3 参数:Size

该参数指定了cache目录的大小。这是squid能使用的cache_dir目录的空间上限。计算出合理的值也许有点难。你必须给临时文件和swap.state日志,留出足够的自由空间(见13.6章)。我推荐挂载空文件系统,可以运行df:

% df -k

Filesystem  1K-blocks     Used    Avail Capacity  Mounted on

/dev/da1d     3037766        8  2794737     0%    /cache0

/dev/da2d     3037766        8  2794737     0%    /cache1

这里你可以看到文件系统有大约2790M的可用空间。记住,UFS保留了部分最小自由空间,这里约是8%,这就是squid为什么不能使用全部3040M空间的原因。

你也许试图分配2790M给cache_dir。如果cache不很繁忙,并且你经常轮转日志,那么这样做也许可行。然而,为安全起见,我推荐保留10%的空间。这些额外的空间用于存放squid的swap.state文件和临时文件。

注意cache_swap_low指令也影响了squid使用多少空间。我将在7.2章里讨论它的上限和下限。

底线是,你在初始时应保守的估计cache_dir的大小。将cache_dir设为较小的值,并允许写满cache。在squid运行一段时间后,cache目录会填满,这样你可以重新评估cache_dir的大小设置。假如你有大量的自由空间,就可以轻松的增加cache目录的大小了。

7.1.3.1 Inodes

Inodes(i节点)是unix文件系统的基本结构。它们包含磁盘文件的信息,例如许可,属主,大小,和时间戳。假如你的文件系统运行超出了i节点限制,就不能创造新文件,即使还有空间可用。超出i节点的系统运行非常糟糕,所以在运行squid之前,你应该确认有足够的i节点。

创建新文件系统的程序(例如,newfs或mkfs)基于总空间的大小,保留了一定数量的i节点。这些程序通常允许你设置磁盘空间的i节点比率。例如,请阅读newfs和mkfs手册的-i选项。磁盘空间对i节点的比率,决定了文件系统能实际支持的文件大小。大部分unix系统每4KB创建一个i节点,这对squid通常是足够的。研究显示,对大部分cache代理,实际文件大小大约是10KB。你也许能以每i节点8KB开始,但这有风险。

你能使用df -i命令来监视系统的i节点,例如:

% df -ik

Filesystem  1K-blocks     Used    Avail Capacity iused   ifree  %iused  Mounted on

/dev/ad0s1a    197951    57114   125001    31%    1413   52345     3%   /

/dev/ad0s1f   5004533  2352120  2252051    51%  129175 1084263    11%   /usr

/dev/ad0s1e    396895     6786   358358     2%     205   99633     0%   /var

/dev/da0d     8533292  7222148   628481    92%  430894  539184    44%   /cache1

/dev/da1d     8533292  7181645   668984    91%  430272  539806    44%   /cache2

/dev/da2d     8533292  7198600   652029    92%  434726  535352    45%   /cache3

/dev/da3d     8533292  7208948   641681    92%  427866  542212    44%   /cache4

如果i节点的使用(%iused)少于空间使用(Capacity),那就很好。不幸的是,你不能对已经存在的文件系统增加更多i节点。假如你发现运行超出了i节点,那就必须停止squid,并且重新创建文件系统。假如你不愿意这样做,那么请削减cache_dir的大小。

7.1.3.2 在磁盘空间和进程大小之间的联系

Squid的磁盘空间使用也直接影响了它的内存使用。每个在磁盘中存在的对象,要求少量的内存。squid使用内存来索引磁盘数据。假如你增加了新的cache目录,或者增加了磁盘cache大小,请确认你已有足够的自由内存。假如squid的进程大小达到或超过了系统的物理内存容量,squid的性能下降得非常块。

Squid的cache目录里的每个对象消耗76或112字节的内存,这依赖于你的系统。内存以StoreEntry, MD5 Digest, 和LRU policy node结构来分配。小指令(例如,32位)系统,象那些基于Intel Pentium的,取76字节。使用64位指令CPU的系统,每个目标取112字节。通过阅读cache管理的内存管理文档,你能发现这些结构在你的系统中耗费多少内存(请见14.2.1.2章)。

不幸的是,难以精确预测对于给定数量的磁盘空间,需要使用多少附加内存。它依赖于实际响应大小,而这个大小基于时间波动。另外,Squid还为其他数据结构和目的分配内存。不要假设你的估计正确。你该经常监视squid的进程大小,假如必要,考虑削减cache大小。

7.1.4 参数:L1和L2

对ufs,aufs,和diskd机制,squid在cache目录下创建二级目录树。L1和L2参数指定了第一级和第二级目录的数量。默认的是16和256。图7-1显示文件系统结构。

Figure 7-1. 基于ufs存储机制的cache目录结构

::__IHACKLOG_REMOTE_IMAGE_AUTODOWN_BLOCK__::1

某些人认为squid依赖于L1和L2的特殊值,会执行得更好或更差。这点听起来有关系,即小目录比大目录被检索得更快。这样,L1和L2也许该足够大,以便L2目录的文件更少。例如,假设你的cache目录存储了7000M,假设实际文件大小是10KB,你能在这个cache_dir里存储700,000个文件。使用16个L1和256个L2目录,总共有4096个二级目录。700,000/4096的结果是,每个二级目录大约有170个文件。

如果L1和L2的值比较小,那么使用squid -z创建交换目录的过程,会执行更快。这样,假如你的cache文件确实小,你也许该减少L1和L2目录的数量。

Squid给每个cache目标分配一个唯一的文件号。这是个32位的整数,它唯一标明磁盘中的文件。squid使用相对简单的算法,将文件号转换位路径名。该算法使用L1和L2作为参数。这样,假如你改变了L1和L2,你改变了从文件号到路径名的映射关系。对非空的cache_dir改变这些参数,导致存在的文件不可访问。在cache目录激活后,你永不要改变L1和L2值。

Squid在cache目录顺序中分配文件号。文件号到路径名的算法(例如,storeUfsDirFullPath( )),用以将每组L2文件映射到同样的二级目录。Squid使用了参考位置来做到这点。该算法让HTML文件和它内嵌的图片更可能的保存在同一个二级目录中。某些人希望squid均匀的将cache文件放在每个二级目录中。然而,当cache初始写入时,你可以发现仅仅开头的少数目录包含了一些文件,例如:

% cd /cache0; du -k

2164    ./00/00

2146    ./00/01

2689    ./00/02

1974    ./00/03

2201    ./00/04

2463    ./00/05

2724    ./00/06

3174    ./00/07

1144    ./00/08

1       ./00/09

1       ./00/0A

1       ./00/0B

这是完全正常的,不必担心。

7.1.5 参数:Options

Squid有2个依赖于不同存储机制的cache_dir选项:read-only标签和max-size值。

7.1.5.1 read-only

read-only选项指示Squid继续从cache_dir读取文件,但不往里面写新目标。它在squid.conf文件里看起来如下:

cache_dir ufs /cache0 7000 16 256 read-only

假如你想把cache文件从一个磁盘迁移到另一个磁盘,那么可使用该选项。如果你简单的增加一个cache_dir,并且删除另一个,squid的命中率会显著下降。在旧目录是read-only时,你仍能从那里获取cache命中。在一段时间后,就可以从配置文件里删除read-only缓存目录。

7.1.5.2 max-size

使用该选项,你可以指定存储在cache目录里的最大目标大小。例如:

cache_dir ufs /cache0 7000 16 256 max-size=1048576

注意值是以字节为单位的。在大多数情况下,你不必增加该选项。假如你做了,请尽力将所有cache_dir行以max-size大小顺序来存放(从小到大)。

7.2 磁盘空间基准

cache_swap_low和cache_swap_high指令控制了存储在磁盘上的对象的置换。它们的值是最大cache体积的百分比,这个最大cache体积来自于所有cache_dir大小的总和。例如:

cache_swap_low 90

cache_swap_high 95

如果总共磁盘使用低于cache_swap_low,squid不会删除cache目标。如果cache体积增加,squid会逐渐删除目标。在稳定状态下,你发现磁盘使用总是相对接近cache_swap_low值。你可以通过请求cache管理器的storedir页面来查看当前磁盘使用状况(见14.2.1.39章)。

请注意,改变cache_swap_high也许不会对squid的磁盘使用有太大效果。在squid的早期版本里,该参数有重要作用;然而现在,它不是这样了。

7.3 对象大小限制

你可以控制缓存对象的最大和最小体积。比maximum_object_size更大的响应不会被缓存在磁盘。然而,它们仍然是代理方式的。在该指令后的逻辑是,你不想某个非常大的响应来浪费空间,这些空间能被许多小响应更好的利用。该语法如下:

maximum_object_size size-specification

如下是一些示例:

maximum_object_size 100 KB

maximum_object_size 1 MB

maximum_object_size 12382 bytes

maximum_object_size 2 GB

Squid以两个不同的方法来检查响应大小。假如响应包含了Content-Length头部,squid将这个值与maximum_object_size值进行比较。假如前者大于后者,该对象立刻不可缓存,并且不会消耗任何磁盘空间。

不幸的是,并非每个响应都有Content-Length头部。在这样的情形下,squid将响应写往磁盘,把它当作来自原始服务器的数据。在响应完成后,squid再检查对象大小。这样,假如对象的大小达到 maximum_object_size限制,它继续消耗磁盘空间。仅仅当squid在做读取响应的动作时,总共cache大小才会增大。

换句话说,活动的,或者传输中的目标,不会对squid内在的cache大小值有影响。这点有好处,因为它意味着squid不会删除cache里的其他目标,除非目标不可缓存,并对总共cache大小有影响。然而,这点也有坏处,假如响应非常大,squid可能运行超出了磁盘自由空间。为了减少发生这种情况的机会,你应该使用reply_body_max_size指令。某个达到reply_body_max_size限制的响应立即被删除。

Squid也有一个minimum_object_size指令。它允许你对缓存对象的大小设置最低限制。比这个值更小的响应不会被缓存在磁盘或内存里。注意这个大小是与响应的内容长度(例如,响应body大小)进行比较,后者包含在HTTP头部里。

7.4 分配对象到缓存目录

当squid想将某个可缓存的响应存储到磁盘时,它调用一个函数,用以选择cache目录。然后它在选择的目录里打开一个磁盘文件用于写。假如因为某些理由,open()调用失败,响应不会被存储。在这样的情况下,squid不会试图在其他cache目录里打开另一个磁盘文件。

Squid有2个cache_dir选择算法。默认的算法叫做lease-load;替代的算法是round-robin。

least-load算法,就如其名字的意义一样,它选择当前工作负载最小的cache目录。负载概念依赖于存储机制。对aufs,coss和diskd机制来说,负载与挂起操作的数量有关。对ufs来说,负载是不变的。在cache_dir负载相等的情况下,该算法使用自由空间和最大目标大小作为附加选择条件。

该选择算法也取决于max-size和read-only选项。假如squid知道目标大小超出了限制,它会跳过这个cache目录。它也会跳过任何只读目录。

round-robin算法也使用负载作为衡量标准。它选择某个负载小于100%的cache目录,当然,该目录里的存储目标没有超出大小限制,并且不是只读的。

在某些情况下,squid可能选择cache目录失败。假如所有的cache_dir是满负载,或者所有目录的实际目标大小超出了max-size限制,那么这种情况可能发生。这时,squid不会将目标写往磁盘。你可以使用cache管理器来跟踪squid选择cache目录失败的次数。请见store_io页(14.2.1.41章),找到create.select_fail行。

7.5 置换策略

cache_replacement_policy指令控制了squid的磁盘cache的置换策略。Squid2.5版本提供了三种不同的置换策略:最少近来使用(LRU),贪婪对偶大小次数(GDSF),和动态衰老最少经常使用(LFUDA)。

LRU是默认的策略,并非对squid,对其他大部分cache产品都是这样。LRU是流行的选择,因为它容易执行,并提供了非常好的性能。在32位系统上,LRU相对于其他使用更少的内存(每目标12对16字节)。在64位系统上,所有的策略每目标使用24字节。

在过去,许多研究者已经提议选择LRU。其他策略典型的被设计来改善cache的其他特性,例如响应时间,命中率,或字节命中率。然而研究者的改进结果也可能在误导人。某些研究使用并不现实的小cache目标;其他研究显示当cache大小增加时,置换策略的选择变得不那么重要。

假如你想使用GDSF或LFUDA策略,你必须在./configure时使用–enable-removal-policies选项(见3.4.1章)。Martin Arlitt 和HP实验室的John Dilley为squid写了GDSF和LFUDA算法。你可以在线阅读他们的文档:

http://www.hpl.hp.com/techreports/1999/HPL-1999-69.html

我在O’Reilly出版的书”Web Caching”,也讨论了这些算法。

cache_replacement_policy指令的值是唯一的,这点很重要。不象squid.conf里的大部分其他指令,这个指令的位置很重要。cache_replacement_policy指令的值在squid解析cache_dir指令时,被实际用到。通过预先设置替换策略,你可以改变cache_dir的替换策略。例如:

cache_replacement_policy lru

cache_dir ufs /cache0 2000 16 32

cache_dir ufs /cache1 2000 16 32

cache_replacement_policy heap GDSF

cache_dir ufs /cache2 2000 16 32

cache_dir ufs /cache3 2000 16 32

在该情形中,头2个cache目录使用LRU置换策略,接下来2个cache目录使用GDSF。请记住,假如你已决定使用cache管理器的config选项(见14.2.1.7章),这个置换策略指令的特性就非常重要。cache管理器仅仅输出最后一个置换策略的值,将它置于所有的cache目录之前。例如,你可能在squid.conf里有如下行:

cache_replacement_policy heap GDSF

cache_dir ufs /tmp/cache1 10 4 4

cache_replacement_policy lru

cache_dir ufs /tmp/cache2 10 4 4

但当你从cache管理器选择config时,你得到:

cache_replacement_policy lru

cache_dir ufs /tmp/cache1 10 4 4

cache_dir ufs /tmp/cache2 10 4 4

就象你看到的一样,对头2个cache目录的heap GDSF设置被丢失了。

7.6 删除缓存对象

在某些情况下,你必须从squid的cache里手工删除一个或多个对象。这些情况可能包括:

  • 你的用户抱怨总接收到过时的数据;
  • 你的cache因为某个响应而“中毒”;
  • Squid的cache索引在经历磁盘I/O错误或频繁的crash和重启后,变得有问题;
  • 你想删除一些大目标来释放空间给新的数据;
  • Squid总从本地服务器中cache响应,现在你不想它这样做。

上述问题中的一些可以通过强迫web浏览器reload来解决。然而,这并非总是可靠。例如,一些浏览器通过载入另外的程序,从而显示某些类容类型;那个程序可能没有reload按钮,或甚至它了解cache的情况。

假如必要,你总可以使用squidclient程序来reload缓存目标。简单的在uri前面使用-r选项:

% squidclient -r http://www.lrrr.org/junk >/tmp/foo

假如你碰巧在refresh_pattern指令里设置了ignore-reload选项,你和你的用户将不能强迫缓存响应更新。在这样的情形下,你最好清除这些有错误的缓存对象。

7.6.1 删除个别对象

Squid接受一种客户请求方式,用于删除cache对象。PURGE方式并非官方HTTP请求方式之一。它与DELETE不同,对后者,squid将其转发到原始服务器。PURGE请求要求squid删除在uri里提交的目标。squid返回200(OK)或404(Not Found)。

PURGE方式某种程度上有点危险,因为它删除了cache目标。除非你定义了相应的ACL,否则squid禁止PURGE方式。正常的,你仅仅允许来自本机和少数可信任主机的PURGE请求。配置看起来如下:

acl AdminBoxes src 127.0.0.1 172.16.0.1 192.168.0.1

acl Purge method PURGE

http_access allow AdminBoxes Purge

http_access deny Purge

squidclient程序提供了产生PURGE请求的容易方法,如下:

% squidclient -m PURGE http://www.lrrr.org/junk

代替的,你可以使用其他工具(例如perl脚本)来产生你自己的HTTP请求。它非常简单:

PURGE http://www.lrrr.org/junk HTTP/1.0

Accept: */*

注意某个单独的URI不唯一标明一个缓存响应。Squid也在cache关键字里使用原始请求方式。假如响应包含了不同的头部,它也可以使用其他请求头。当你发布PURGE请求时,Squid使用GET和HEAD的原始请求方式来查找缓存目标。而且,Squid会删除响应里的所有variants,除非你在PURGE请求的相应头部里指定了要删除的variants。Squid仅仅删除GET和HEAD请求的variants。

7.6.2 删除一组对象

不幸的是,Squid没有提供一个好的机制,用以立刻删除一组对象。这种要求通常出现在某人想删除所有属于同一台原始服务器的对象时。

因为很多理由,squid不提供这种功能。首先,squid必须遍历所有缓存对象,执行线性搜索,这很耗费CPU,并且耗时较长。当squid在搜索时,用户会面临性能下降问题。第二,squid在内存里对URI保持MD5算法,MD5是单向哈希,这意味着,例如,你不能确认是否某个给定的MD5哈希是由包含”www.example.com”字符串的URI产生而来。唯一的方法是从原始URI重新计算MD5值,并且看它们是否匹配。因为squid没有保持原始的URI,它不能执行这个重计算。

那么该怎么办呢?

你可以使用access.log里的数据来获取URI列表,它们可能位于cache里。然后,将它们用于squidclient或其他工具来产生PURGE请求,例如:

% awk '{print $7}' /usr/local/squid/var/logs/access.log \
        | grep www.example.com \
        | xargs -n 1 squidclient -m PURGE
7.6.3 删除所有对象

在极度情形下,你可能需要删除整个cache,或至少某个cache目录。首先,你必须确认squid没有在运行。

让squid忘记所有缓存对象的最容易的方法之一,是覆盖swap.state文件。注意你不能简单的删除swap.state文件,因为squid接着要扫描cache目录和打开所有的目标文件。你也不能简单的截断swap.state为0大小。代替的,你该放置一个单字节在里面,例如:

# echo '' > /usr/local/squid/var/cache/swap.state

当squid读取swap.state文件时,它获取到了错误,因为在这里的记录太短了。下一行读取就到了文件结尾,squid完成重建过程,没有装载任何目标元数据。

注意该技术不会从磁盘里删除cache文件。你仅仅使squid认为它的cache是空的。当squid运行时,它增加新文件到cache里,并且可能覆盖旧文件。在某些情形下,这可能导致你的磁盘使用超出了自由空间。假如这样的事发生,你必须在再次重启squid前删除旧文件。

删除cache文件的方法之一是使用rm。然而,它通常花费很长的时间来删除所有被squid创建的文件。为了让squid快速启动,你可以重命名旧cache目录,创建一个新目录,启动squid,然后同时删除旧目录。例如:

# squid -k shutdown

# cd /usr/local/squid/var

# mv cache oldcache

# mkdir cache

# chown nobody:nobody cache

# squid -z

# squid -s

# rm -rf oldcache &

另一种技术是简单的在cache文件系统上运行newfs(或mkfs)。这点仅在你的cache_dir使用整个磁盘分区时才可以运行。

7.7 refresh_pattern

refresh_pattern指令间接的控制磁盘缓存。它帮助squid决定,是否某个给定请求是cache命中,或作为cache丢失对待。宽松的设置增加了你的cache命中率,但也增加了用户接收过时响应的机会。另一方面,保守的设置,降低了cache命中率和过时响应。

refresh_pattern规则仅仅应用到没有明确过时期限的响应。原始服务器能使用Expires头部,或者Cache-Control:max-age指令来指定过时期限。

你可以在配置文件里放置任意数量的refresh_pattern行。squid按顺序查找它们以匹配正则表达式。当squid找到一个匹配时,它使用相应的值来决定,某个缓存响应是存活还是过期。refresh_pattern语法如下:

refresh_pattern [-i] regexp min percent max [options]

例如:

refresh_pattern -i \.jpg$ 30 50% 4320 reload-into-ims

refresh_pattern -i \.png$ 30 50% 4320 reload-into-ims

refresh_pattern -i \.htm$ 0 20% 1440

refresh_pattern -i \.html$ 0 20% 1440

refresh_pattern -i . 5 25% 2880

regexp参数是大小写敏感的正则表达式。你可以使用-i选项来使它们大小写不敏感。squid按顺序来检查refresh_pattern行;当正则表达式之一匹配URI时,它停止搜索。

min参数是分钟数量。它是过时响应的最低时间限制。如果某个响应驻留在cache里的时间没有超过这个最低限制,那么它不会过期。类似的,max参数是存活响应的最高时间限制。如果某个响应驻留在cache里的时间高于这个最高限制,那么它必须被刷新。

在最低和最高时间限制之间的响应,会面对squid的最后修改系数 (LM-factor)算法。对这样的响应,squid计算响应的年龄和最后修改系数,然后将它作为百分比值进行比较。响应年龄简单的就是从原始服务器产生,或最后一次验证响应后,经历的时间数量。源年龄在Last-Modified和Date头部之间是不同的。LM-factor是响应年龄与源年龄的比率。

图7-2论证了LM-factor算法。squid缓存了某个目标3个小时(基于Date和Last-Modified头部)。LM-factor的值是50%,响应在接下来的1.5个小时里是存活的,在这之后,目标会过期并被当作过时处理。假如用户在存活期间请求cache目标,squid返回没有确认的cache命中。若在过时期间发生请求,squid转发确认请求到原始服务器。

图7-2 基于LM-factor计算过期时间

::__IHACKLOG_REMOTE_IMAGE_AUTODOWN_BLOCK__::2

理解squid检查不同值的顺序非常重要。如下是squid的refresh_pattern算法的简单描述:

  • 假如响应年龄超过refresh_pattern的max值,该响应过期;
  • 假如LM-factor少于refresh_pattern百分比值,该响应存活;
  • 假如响应年龄少于refresh_pattern的min值,该响应存活;
  • 其他情况下,响应过期。

refresh_pattern指令也有少数选项导致squid违背HTTP协议规范。它们如下:

override-expire

该选项导致squid在检查Expires头部之前,先检查min值。这样,一个非零的min时间让squid返回一个未确认的cache命中,即使该响应准备过期。

override-lastmod

改选项导致squid在检查LM-factor百分比之前先检查min值。

reload-into-ims

该选项让squid在确认请求里,以no-cache指令传送一个请求。换句话说,squid在转发请求之前,对该请求增加一个If-Modified-Since头部。注意这点仅仅在目标有Last-Modified时间戳时才能工作。外面进来的请求保留no-cache指令,以便它到达原始服务器。

ignore-reload

该选项导致squid忽略请求里的任何no-cache指令。

第8章 高级磁盘缓存主题

8.1 是否存在磁盘I/O瓶颈?

Web缓存器例如squid,通常在磁盘I/O变成瓶颈时,不会正确的体现和告知你。代替的是,随着负载的增加,响应时间和/或命中率会更低效。当然,响应时间和命中率可能因为其他原因而改变,例如网络延时和客户请求方式的改变。

也许探测cache性能瓶颈的最好方式是做压力测试,例如Web Polygraph。压力测试的前提是你能完全控制环境,消除未知因素。你可以用不同的cache配置来重复相同的测试。不幸的是,压力测试通常需要大量的时间,并要求有空闲的系统(也许它们正在使用中)。

假如你有资源执行squid压力测试,请以标准的cache工作负载开始。当你增加负载时,在某些点上你能看到明显的响应延时和/或命中率下降。一旦你观察到这样的性能降低,就禁止掉磁盘缓存,再测试一次。你可以配置squid从来不缓存任何响应(使用null存储机制,见8.7章)。代替的,你能配置工作负载到100%不可cache响应。假如不使用cache时,平均响应时间明显更好,那么可以确认磁盘I/O是该水平吞吐量的瓶颈。

假如你没有时间或没有资源来执行squid压力测试,那么可检查squid的运行时统计来查找磁盘I/O瓶颈。cache管理器的General Runtime Information 页面(见14章)会显示出cache命中和cache丢失的中值响应时间。

Median Service Times (seconds)  5 min    60 min:

        HTTP Requests (All):   0.39928  0.35832

        Cache Misses:          0.42149  0.39928

        Cache Hits:            0.12783  0.11465

        Near Hits:             0.37825  0.39928

        Not-Modified Replies:  0.07825  0.07409

对健壮的squid缓存来说,命中显然快于丢失。中值命中响应时间典型的少于0.5秒或更少。我强烈建议你使用SNMP或其他的网络监视工具来从squid缓存采集定期测量值。如果平均命中响应时间增加得太明显,意味着系统有磁盘I/0瓶颈。

假如你认为产品cache面临此类问题,可以用前面提到的同样的技术来验证你的推测。配置squid不cache任何响应,这样就避开了所有磁盘I/O。然后仔细观察cache丢失响应时间。假如它降下去,那么你的推测该是正确的。

一旦你确认了磁盘吞吐能力是squid的性能瓶颈,那么可做许多事来改进它。其中一些方法要求重编译squid,然而另一些相对较简单,只需调整Unix文件系统。

8.2 文件系统调整选项

首先,从来不在squid的缓存目录中使用RAID。以我的经验看,RAID总是降低squid使用的文件系统的性能。最好有许多独立的文件系统,每个文件系统使用单独的磁盘驱动器。

我发现4个简单的方法来改进squid的UFS性能。其中某些特指某种类型的操作系统例如BSD和Linux,也许对你的平台不太合适:

  • 1.某些UFS支持一个noatime的mount选项。使用noatime选项来mount的文件系统,不会在读取时,更新相应的i节点访问时间。使用该选项的最容易的方法是在/etc/fstab里增加如下行:
    # Device            Mountpoint    FStype  Options        Dump    Pass#
    
    /dev/ad1s1c         /cache0       ufs     rw,noatime     0       0
  • 2.检查mount(8)的manpage里的async选项。设置了该选项,特定的I/O操作(例如更新目录)会异步执行。某些系统的文档会标明这是个危险的标签。某天你的系统崩溃,你也许会丢失整个文件系统。对许多squid安装来说,执行性能的提高值得冒此风险。假如你不介意丢失整个cache内容,那么可以使用该选项。假如cache数据非常有价值,async选项也许不适合你。
  • 3.BSD有一个功能叫做软更新。软更新是BSD用于Journaling文件系统的代替品。在FreeBSD上,你可以在没有mount的文件系统中,使用tunefs命令来激活该选项:
    # umount /cache0
    
    # tunefs -n enable /cache0
    
    # mount /cache0
  • 4.你对每个文件系统运行一次tunefs命令就可以了。在系统重启时,软更新自动在文件系统中激活了。 在OpenBSD和NetBSD中,可使用softdep mount选项:
    # Device            Mountpoint    FStype  Options        Dump    Pass#
    
    /dev/sd0f           /usr          ffs     rw,softdep     1       2

假如你象我一样,你可能想知道在async选项和软更新选项之间有何不同。一个重要的区别是,软更新代码被设计成在系统崩溃事件中,保持文件系统的一致性,而async选项不是这样的。这也许让你推断async执行性能好于软更新。然而,如我在附录D中指出的,事实相反。

以前我提到过,UFS性能特别是写性能,依赖于空闲磁盘的数量。对空文件系统的磁盘写操作,要比满文件系统快得多。这是UFS的最小自由空间参数,和空间/时间优化权衡参数背后的理由之一。假如cache磁盘满了,squid执行性能看起来很糟,那么试着减少cache_dir的容量值,以便更多的自由空间可用。当然,减少cache大小也会降低命中率,但响应时间的改进也许值得这么做。假如你给squid缓存配置新的设备,请考虑使用超过你需要的更大磁盘,并且仅仅使用空间的一半。

8.3 可选择的文件系统

某些操作系统支持不同于UFS(或ext2fs)的文件系统。Journaling文件系统是较普遍的选择。在UFS和Journaling文件系统之间的主要不同在于它们处理更新的方式。在UFS下,更新是实时的。例如,当你改变了某个文件并且将它存储到磁盘,新数据就替换了旧数据。当你删除文件时,UFS直接更新了目录。

Journaling文件系统与之相反,它将更新写往独立的记帐系统,或日志文件。典型的你能选择是否记录文件改变或元数据改变,或两者兼备。某个后台进程在空闲时刻读取记帐,并且执行实际的改变操作。Journaling文件系统典型的在系统崩溃后比UFS恢复更快。在系统崩溃后,Journaling文件系统简单的读取记帐,并且提交所有显著的改变。

Journaling文件系统的主要弊端在于它们需要额外的磁盘写操作。改变首先写往日志文件,然后才写往实际的文件或目录。这对web缓存影响尤其明显,因为首先web缓存倾向于更多的磁盘写操作。

Journaling文件系统对许多操作系统可用。在Linux上,你能选择ext3fs,reiserfs, XFS,和其他的。XFS也可用在SGI/IRIX,它原始是在这里开发的。Solaris用户能使用Veritas文件系统产品。TRU64(以前的Digital Unix)高级文件系统(advfs)支持Journaling。

你可以不改变squid的任何配置而使用Journaling文件系统。简单的创建和挂载在操作系统文档里描述的文件系统,而不必改变squid.cf配置文件里的cache_dir行。

用类似如下命令在Linux中制作reiserfs文件系统:

# /sbin/mkreiserfs /dev/sda2

对XFS,使用:

# mkfs -t xfs -f /dev/sda2

注意ext3fs其实简单的就是激活了记帐的ext2fs。当创建该文件系统时,对mke2fs使用-j选项:

# /sbin/mke2fs -j /dev/sda2

请参考其他操作系统的相关文档。

8.4 aufs存储机制

aufs存储机制已经发展到超出了改进squid磁盘I/O响应时间的最初尝试。”a”代表着异步I/O。默认的ufs和aufs之间的唯一区别,在于I/O是否被squid主进程执行。数据格式都是一样的,所以你能在两者之间轻松选择,而不用丢失任何cache数据。

aufs使用大量线程进行磁盘I/O操作。每次squid需要读写,打开关闭,或删除cache文件时,I/O请求被分派到这些线程之一。当线程完成了I/O后,它给squid主进程发送信号,并且返回一个状态码。实际上在squid2.5中,某些文件操作默认不是异步执行的。最明显的,磁盘写总是同步执行。你可以修改src/fs/aufs/store_asyncufs.h文件,将ASYNC_WRITE设为1,并且重编译squid。

aufs代码需要pthreads库。这是POSIX定义的标准线程接口。尽管许多Unix系统支持pthreads库,但我经常遇到兼容性问题。aufs存储系统看起来仅仅在Linux和Solaris上运行良好。在其他操作系统上,尽管代码能编译,但也许会面临严重的问题。

为了使用aufs,可以在./configure时增加一个选项:

% ./configure --enable-storeio=aufs,ufs

严格讲,你不必在storeio模块列表中指定ufs。然而,假如你以后不喜欢aufs,那么就需要指定ufs,以便能重新使用稳定的ufs存储机制。

假如愿意,你也能使用—with-aio-threads=N选项。假如你忽略它,squid基于aufs cache_dir的数量,自动计算可使用的线程数量。表8-1显示了1-6个cache目录的默认线程数量。

Table 8-1. Default number of threads for up to six cache directories

cache_dirs
Threads

1
16

2
26

3
32

4
36

5
40

6
44

将aufs支持编译进squid后,你能在squid.conf文件里的cache_dir行后指定它:

cache_dir aufs /cache0 4096 16 256

在激活了aufs并启动squid后,请确认每件事仍能工作正常。可以运行tail -f store.log一会儿,以确认缓存目标被交换到磁盘。也可以运行tail -f cache.log并且观察任何新的错误或警告。

8.4.1 aufs如何工作

Squid通过调用pthread_create()来创建大量的线程。所有线程在任何磁盘活动之上创建。这样,即使squid空闲,你也能见到所有的线程。

无论何时,squid想执行某些磁盘I/O操作(例如打开文件读),它分配一对数据结构,并将I/O请求放进队列中。线程循环读取队列,取得I/O请求并执行它们。因为请求队列共享给所有线程,squid使用独享锁来保证仅仅一个线程能在给定时间内更新队列。

I/O操作阻塞线程直到它们被完成。然后,将操作状态放进一个完成队列里。作为完整的操作,squid主进程周期性的检查完成队列。请求磁盘I/O的模块被通知操作已完成,并获取结果。

你可能已猜想到,aufs在多CPU系统上优势更明显。唯一的锁操作发生在请求和结果队列。然而,所有其他的函数执行都是独立的。当主进程在一个CPU上执行时,其他的CPU处理实际的I/O系统调用。

8.4.2 aufs发行

线程的有趣特性是所有线程共享相同的资源,包括内存和文件描述符。例如,某个线程打开一个文件,文件描述符为27,所有其他线程能以相同的文件描述符来访问该文件。可能你已知道,在初次管理squid时,文件描述符短缺是较普遍问题。Unix内核典型的有两种文件描述符限制:

进程级的限制和系统级的限制。你也许认为每个进程拥有256个文件描述符足够了(因为使用线程),然而并非如此。在这样的情况下,所有线程共享少量的文件描述符。请确认增加系统的进程文件描述符限制到4096或更高,特别在使用aufs时。

调整线程数量有点棘手。在某些情况下,可在cache.log里见到如下警告:

2003/09/29 13:42:47| squidaio_queue_request: WARNING - Disk I/O overloading

这意味着squid有大量的I/O操作请求充满队列,等待着可用的线程。你首先会想到增加线程数量,然而我建议,你该减少线程数量。

增加线程数量也会增加队列的大小。超过一定数量,它不会改进aufs的负载能力。它仅仅意味着更多的操作变成队列。太长的队列导致响应时间变长,这绝不是你想要的。

减少线程数量和队列大小,意味着squid检测负载条件更快。当某个cache_dir超载,它会从选择算法里移除掉(见7.4章)。然后,squid选择其他的cache_dir或简单的不存储响应到磁盘。这可能是较好的解决方法。尽管命中率下降,响应时间却保持相对较低。

8.4.3 监视aufs操作

Cache管理器菜单里的Async IO Counters选项,可以显示涉及到aufs的统计信息。它显示打开,关闭,读写,stat,和删除接受到的请求的数量。例如:

% squidclient mgr:squidaio_counts

...

ASYNC IO Counters:

Operation       # Requests

open             15318822

close            15318813

cancel           15318813

write                   0

read             19237139

stat                    0

unlink            2484325

check_callback  311678364

queue                   0

取消(cancel)计数器正常情况下等同于关闭(close)计数器。这是因为close函数总是调用cancel函数,以确认任何未决的I/O操作被忽略。

写(write)计数器为0,因为该版本的squid执行同步写操作,即使是aufs。

check_callbak计数器显示squid主进程对完成队列检查了多少次。

queue值显示当前请求队列的长度。正常情况下,队列长度少于线程数量的5倍。假如你持续观察到队列长度大于这个值,说明squid配得有问题。增加更多的线程也许有帮助,但仅仅在特定范围内。

8.5 diskd存储机制

diskd(disk守护进程的短称)类似于aufs,磁盘I/O被外部进程来执行。不同于aufs的是,diskd不使用线程。代替的,它通过消息队列和共享内存来实现内部进程间通信。

消息队列是现代Unix操作系统的标准功能。许多年以前在AT&T的Unix System V的版本1上实现了它们。进程间的队列消息以较少的字节传递:32-40字节。每个diskd进程使用一个队列来接受来自squid的请求,并使用另一个队列来传回请求。

8.5.1 diskd如何工作

Squid对每个cache_dir创建一个diskd进程。这不同于aufs,aufs对所有的cache_dir使用一个大的线程池。对每个I/O操作,squid发送消息到相应的diskd进程。当该操作完成后,diskd进程返回一个状态消息给squid。squid和diskd进程维护队列里的消息的顺序。这样,不必担心I/O会无序执行。

对读和写操作,squid和diskd进程使用共享内存区域。两个进程能对同一内存区域进行读和写。例如,当squid产生读请求时,它告诉diskd进程在内存中何处放置数据。diskd将内存位置传递给read()系统调用,并且通过发送队列消息,通知squid该过程完成了。然后squid从共享内存区域访问最近的可读数据。

diskd与aufs本质上都支持squid的无阻塞磁盘I/O。当diskd进程在I/O操作上阻塞时,squid有空去处理其他任务。在diskd进程能跟上负载情况下,这点确实工作良好。因为squid主进程现在能够去做更多工作,当然它有可能会加大diskd的负载。diskd有两个功能来帮助解决这个问题。

首先,squid等待diskd进程捕获是否队列超出了某种极限。默认值是64个排队消息。假如diskd进程获取的数值远大于此,squid会休眠片刻,并等待diskd完成一些未决操作。这本质上让squid进入阻塞I/O模式。它也让更多的CPU时间对diskd进程可用。通过指定cache_dir行的Q2参数的值,你可以配置这个极限值:

cache_dir diskd /cache0 7000 16 256 Q2=50

第二,假如排队操作的数量抵达了另一个极限,squid会停止要求diskd进程打开文件。这里的默认值是72个消息。假如squid想打开一个磁盘文件读或写,但选中的cache_dir有太多的未完成操作,那么打开请求会失败。当打开文件读时,会导致cache丢失。当打开文件写时,会阻碍squid存储cache响应。这两种情况下用户仍能接受到有效响应。唯一实际的影响是squid的命中率下降。这个极限用Q1参数来配置:

cache_dir diskd /cache0 7000 16 256 Q1=60 Q2=50

注意在某些版本的squid中,Q1和Q2参数混杂在默认的配置文件里。最佳选择是,Q1应该大于Q2。

8.5.2 编译和配置diskd

为了使用diskd,你必须在运行./configure时,在–enable-storeio列表后增加一项:

% ./configure --enable-storeio=ufs,diskd

diskd看起来是可移植的,既然共享内存和消息队列在现代Unix系统上被广泛支持。然而,你可能需要调整与这两者相关的内核限制。内核典型的有如下可用参数:

MSGMNB

每个消息队列的最大字节限制。对diskd的实际限制是每个队列大约100个排队消息。squid传送的消息是32-40字节,依赖于你的CPU体系。这样,MSGMNB应该是4000或更多。为安全起见,我推荐设置到8192。

MSGMNI

整个系统的最大数量的消息队列。squid对每个cache_dir使用两个队列。假如你有10个磁盘,那就有20个队列。你也许该增加更多,因为其他应用程序也要使用消息队列。我推荐的值是40。

MSGGSZ

消息片断的大小(字节)。大于该值的消息被分割成多个片断。我通常将这个值设为64,以使diskd消息不被分割成多个片断。

MSGSEG

在单个队列里能存在的最大数量的消息片断。squid正常情况下,限制队列的长度为100个排队消息。记住,在64位系统中,假如你没有增加MSGSSZ的值到64,那么每个消息就会被分割成不止1个片断。为了安全起见,我推荐设置该值到512。

MSGTQL

整个系统的最大数量的消息。至少是cache_dir数量的100倍。在10个cache目录情况下,我推荐设置到2048。

MSGMAX

单个消息的最大size。对Squid来说,64字节足够了。然而,你系统中的其他应用程序可能要用到更大的消息。在某些操作系统例如BSD中,你不必设置这个。BSD自动设置它为MSGSSZ * MSGSEG。其他操作系统中,你也许需要改变这个参数的默认值,你可以设置它与MSGMNB相同。

SHMSEG

每个进程的最大数量的共享内存片断。squid对每个cache_dir使用1个共享内存标签。我推荐设置到16或更高。

SHMMNI

共享内存片断数量的系统级的限制。大多数情况下,值为40足够了。

SHMMAX

单个共享内存片断的最大size。默认的,squid对每个片断使用大约409600字节。 为安全起见,我推荐设置到2MB,或2097152。

SHMALL

可分配的共享内存数量的系统级限制。在某些系统上,SHMALL可能表示成页数量,而不是字节数量。在10个cache_dir的系统上,设置该值到16MB(4096页)足够了,并有足够的保留给其他应用程序。

在BSD上配置消息队列,增加下列选项到内核配置文件里:

# System V message queues and tunable parameters

options         SYSVMSG         # include support for message queues

options         MSGMNB=8192     # max characters per message queue

options         MSGMNI=40       # max number of message queue identifiers

options         MSGSEG=512      # max number of message segments per queue

options         MSGSSZ=64       # size of a message segment MUST be power of 2

options         MSGTQL=2048     # max number of messages in the system

options         SYSVSHM

options         SHMSEG=16       # max shared mem segments per process

options         SHMMNI=32       # max shared mem segments in the system

options         SHMMAX=2097152  # max size of a shared mem segment

options         SHMALL=4096     # max size of all shared memory (pages)

在Linux上配置消息队列,增加下列行到/etc/sysctl.conf:

kernel.msgmnb=8192

kernel.msgmni=40

kernel.msgmax=8192

kernel.shmall=2097152

kernel.shmmni=32

kernel.shmmax=16777216

另外,假如你需要更多的控制,可以手工编辑内核资源文件中的include/linux/msg.h和include/linux/shm.h。

在Solaris上,增加下列行到/etc/system,然后重启:

set msgsys:msginfo_msgmax=8192

set msgsys:msginfo_msgmnb=8192

set msgsys:msginfo_msgmni=40

set msgsys:msginfo_msgssz=64

set msgsys:msginfo_msgtql=2048

set shmsys:shminfo_shmmax=2097152

set shmsys:shminfo_shmmni=32

set shmsys:shminfo_shmseg=16

在Digital Unix(TRU64)上,可以增加相应行到BSD风格的内核配置文件中,见前面所叙。另外,你可使用sysconfig命令。首先,创建如下的ipc.stanza文件:

ipc:

msg-max = 2048

msg-mni = 40

msg-tql = 2048

msg-mnb = 8192

shm-seg = 16

shm-mni = 32

shm-max = 2097152

shm-max = 4096

然后,运行这个命令并重启:

# sysconfigdb -a -f ipc.stanza

一旦你在操作系统中配置了消息队列和共享内存,就可以在squid.conf里增加如下的cache_dir行:

cache_dir diskd /cache0 7000 16 256 Q1=72 Q2=64

cache_dir diskd /cache1 7000 16 256 Q1=72 Q2=64

...
8.5.3 监视diskd

监视diskd运行的最好方法是使用cache管理器。请求diskd页面,例如:

% squidclient mgr:diskd

...

sent_count: 755627

recv_count: 755627

max_away: 14

max_shmuse: 14

open_fail_queue_len: 0

block_queue_len: 0

             OPS SUCCESS    FAIL

   open   51534   51530       4

 create   67232   67232       0

  close  118762  118762       0

 unlink   56527   56526       1

   read   98157   98153       0

  write  363415  363415       0

请见14.2.1.6章关于该输出的详细描述。

8.6 coss存储机制

循环目标存储机制(Cyclic Object Storage Scheme,coss)尝试为squid定制一个新的文件系统。在ufs基础的机制下,主要的性能瓶颈来自频繁的open()和unlink()系统调用。因为每个cache响应都存储在独立的磁盘文件里,squid总是在打开,关闭,和删除文件。

与之相反的是,coss使用1个大文件来存储所有响应。在这种情形下,它是特定供squid使用的,小的定制文件系统。coss实现许多底层文件系统的正常功能,例如给新数据分配空间,记忆何处有自由空间等。不幸的是,coss仍没开发完善。coss的开发在过去数年里进展缓慢。虽然如此,基于有人喜欢冒险的事实,我还是在这里描述它。

8.6.1 coss如何工作

在磁盘上,每个coss cache_dir是一个大文件。文件大小一直增加,直到抵达它的大小上限。这样,squid从文件的开头处开始,覆盖掉任何存储在这里的数据。然后,新的目标总是存储在该文件的末尾处。

squid实际上并不立刻写新的目标数据到磁盘上。代替的,数据被拷贝进1MB的内存缓冲区,叫做stripe。在stripe变满后,它被写往磁盘。coss使用异步写操作,以便squid主进程不会在磁盘I/O上阻塞。

象其他文件系统一样,coss也使用块大小概念。在7.1.4章里,我谈到了文件号码。每个cache目标有一个文件号码,以便squid用于定位磁盘中的数据。对coss来说,文件号码与块号码一样。例如,某个cache目标,其交换文件号码等于112,那它在coss文件系统中就从第112块开始。因此coss不分配文件号码。某些文件号码不可用,因为cache目标通常在coss文件里占用了不止一个块。

coss块大小在cache_dir选项中配置。因为squid的文件号码仅仅24位,块大小决定了coss缓存目录的最大size:size = 块大小 x (2的24次方)。例如,对512字节的块大小,你能在coss cache_dir中存储8GB数据。

coss不执行任何squid正常的cache置换算法(见7.5章)。代替的,cache命中被”移动”到循环文件的末尾。这本质上是LRU算法。不幸的是,它确实意味着cache命中导致磁盘写操作,虽然是间接的。

在coss中,没必要去删除cache目标。squid简单的忘记无用目标所分配的空间。当循环文件的终点再次抵达该空间时,它就被重新利用。

8.6.2 编译和配置coss

为了使用coss,你必须在运行./configure时,在–enable-storeio列表里增加它:

% ./configure --enable-storeio=ufs,coss ...

coss缓存目录要求max-size选项。它的值必须少于stripe大小(默认1MB,但可以用–enable-coss-membuf-size选项来配置)。也请注意你必须忽略L1和L2的值,它们被ufs基础的文件系统使用。如下是示例:

cache_dir coss /cache0/coss 7000 max-size=1000000

cache_dir coss /cache1/coss 7000 max-size=1000000

cache_dir coss /cache2/coss 7000 max-size=1000000

cache_dir coss /cache3/coss 7000 max-size=1000000

cache_dir coss /cache4/coss 7000 max-size=1000000

甚至,你可以使用block-size选项来改变默认的coss块大小。

cache_dir coss /cache0/coss 30000 max-size=1000000 block-size=2048

关于coss的棘手的事情是,cache_dir目录参数(例如/cache0/coss)实际上不是目录,它是squid打开或创建的常规文件。所以你可以用裸设备作为coss文件。假如你错误的创建coss文件作为目录,你可以在squid启动时见到如下错误:

2003/09/29 18:51:42|  /usr/local/squid/var/cache: (21) Is a directory

FATAL: storeCossDirInit: Failed to open a coss file.

因为cache_dir参数不是目录,你必须使用cache_swap_log指令(见13.6章)。否则squid试图在cache_dir目录中创建swap.state文件。在该情形下,你可以见到这样的错误:

2003/09/29 18:53:38| /usr/local/squid/var/cache/coss/swap.state:

        (2) No such file or directory

FATAL: storeCossDirOpenSwapLog: Failed to open swap log.

coss使用异步I/O以实现更好的性能。实际上,它使用aio_read()和aio_write()系统调用。这点也许并非在所有操作系统中可用。当前它们可用在FreeBSD,Solaris,和Linux中。假如coss代码看起来编译正常,但你得到”Function not implemented”错误消息,那就必须在内核里激活这些系统调用。在FreeBSD上,必须在内核配置文件中有如下选项:

options         VFS_AIO
8.6.3 coss发行

coss还是实验性的功能。没有充分证实源代码在日常使用中的稳定性。假如你想试验一下,请做好存储在coss cache_dir中的资料丢失的准备。

从另一面说,coss的初步性能测试表现非常优秀。示例请见附录D。

coss没有很好的支持从磁盘重建cache数据。当你重启squid时,你也许会发现从swap.state文件读取数据失败,这样就丢失了所有的缓存数据。甚至,squid在重启后,不能记忆它在循环文件里的位置。它总是从头开始。

coss对目标置换采用非标准的方式。相对其他存储机制来说,这可能导致命中率更低。某些操作系统在单个文件大于2GB时,会有问题。假如这样的事发生,你可以创建更多小的coss区域。例如:

cache_dir coss /cache0/coss0 1900 max-size=1000000 block-size=128

cache_dir coss /cache0/coss1 1900 max-size=1000000 block-size=128

cache_dir coss /cache0/coss2 1900 max-size=1000000 block-size=128

cache_dir coss /cache0/coss3 1900 max-size=1000000 block-size=128

使用裸磁盘设备(例如/dev/da0s1c)也不会工作得很好。理由之一是磁盘设备通常要求I/O发生在512个字节的块边界(译者注:也就是块设备访问)。另外直接的磁盘访问绕过了系统高速缓存,可能会降低性能。然而,今天的许多磁盘驱动器,已经内建了高速缓存。

8.7 null存储机制

Squid有第5种存储机制叫做null。就像名字暗示的一样,这是最不健壮的机制。写往null cache_dir的文件实际上不被写往磁盘。

大多数人没有任何理由要使用null存储系统。当你想完全禁止squid的磁盘缓存时,null才有用。你不能简单的从squid.conf文件里删除所有cache_dir行,因为这样的话squid会增加默认的ufs cache_dir。null存储系统有些时候在测试squid,和压力测试时有用。既然文件系统是典型的性能瓶颈,使用null存储机制能获取基于当前硬件的squid的性能上限。

为了使用该机制,在运行./configure时,你必须首先在–enable-storeio列表里指定它:

% ./configure --enable-storeio=ufs,null ...

然后在squid.conf里创建cache_dir类型为null:

cache_dir /tmp null

也许看起来有点奇怪,你必须指定目录给null存储机制。squid使用目录名字作为cache_dir标识符。例如,你能在cache管理器的输出里见到它。

8.8 哪种最适合我?

Squid的存储机制选择看起来有点混乱和迷惑。aufs比diskd更好?我的系统支持aufs或coss吗?假如我使用新的机制,会丢失数据吗?可否混合使用存储机制?

首先,假如Squid轻度使用(就是说每秒的请求数少于5个),默认的ufs存储机制足够了。在这样的低请求率中,使用其他存储机制,你不会观察到明显的性能改进。

假如你想决定何种机制值得一试,那你的操作系统可能是个决定因素。例如,aufs在Linux和Solaris上运行良好,但看起来在其他系统中有问题。另外,coss代码所用到的函数,当前不可用在某些操作系统中(例如NetBSD)。

从我的观点看来,高性能的存储机制在系统崩溃事件中,更易受数据丢失的影响。这就是追求最好性能的权衡点。然而对大多数人来说,cache数据相对价值较低。假如squid的缓存因为系统崩溃而破坏掉,你会发现这很容易,只需简单的newfs磁盘分区,让cache重新填满即可。如果你觉得替换Squid的缓存内容较困难或代价很大,你就应该使用低速的,但可信的文件系统和存储机制。

近期的Squid允许你对每个cache_dir使用不同的文件系统和存储机制。然而实际上,这种做法是少见的。假如所有的cache_dir使用相同的size和相同的存储机制,可能冲突更少。

第9章 Cache拦截

Cache拦截是让传输流向Squid的流行技术,它不用配置任何客户端。你可以配置路由器或交换机将HTTP连接转发到squid运行的主机。squid运行的操作系统被配置成接受外部数据包,并将其递交给squid进程。为了让HTTP拦截生效,你必须配置3个独立的因素:网络设备,squid运行的操作系统,和squid自身。

(译者注:Cache拦截实际上指的是Squid的透明代理)

9.1它如何工作?

Cache拦截包含了某些网络欺骗,它对理解在客户端和Squid之间的会话有用。我使用图9-1和如下的tcpdump示例输出,来解释当数据包通过网络时,如何被拦截。

Figure 9-1. How HTTP interception works

::__IHACKLOG_REMOTE_IMAGE_AUTODOWN_BLOCK__::3

1.用户代理(user-agent)想请求某个资源,它对原始服务器发起index.html请求,例如:www.oreilly.com。它需要原始服务器的IP地址,所以先发起一个DNS请求:

Packet 1

TIME:   19:54:41.317310

UDP:    206.168.0.3.2459 -> 206.168.0.2.53

DATA:   .d...........www.oreilly.com.....

---------------------------------------------------------------------------

Packet 2

TIME:   19:54:41.317707 (0.000397)

UDP:    206.168.0.2.53 -> 206.168.0.3.2459

DATA:   .d...........www.oreilly.com.............PR.....%........PR.

....$........PR...ns1.sonic.net.........PR...ns2.Q........PR

...ns...M...............h.............!.z.......b......

2.现在有了IP地址,用户代理初始化到原始服务器80端口的TCP连接:

Packet 3
    TIME:   19:54:41.320652 (0.002945)
    TCP:    206.168.0.3.3897 -> 208.201.239.37.80 Syn
    DATA:   <No data>

3.路由器或交换机注意到目的地址是80端口的TCP SYN包。下一步会发生什么依赖于特定的拦截技术。在4层交换和路由策略上,网络设备简单的将TCP包转发到Squid的数据链路地址。当squid直接挂在网络设备上时,就这样工作。对WCCP来说,路由器封装TCP包为GRE包。因为GRE包有它自己的IP地址,它可能被通过多个子网进行路由。换句话说,WCCP不要求squid直接挂在路由器上。

4.Squid主机的操作系统接受到拦截包。对4层交换来说,TCP/IP包并没有改变。假如包使用了GRE封装,主机会剥离外部的IP和GRE头部,并将原始的TCP/IP包放在输入队列里。注意squid主机接受到的包是针对外部地址的(原始服务器的)。正常情况下,这个包不匹配任何本地地址,它会被丢弃。为了让主机接受外部数据包,你必须在大多数操作系统上激活IP转发。

5.客户端的TCP/IP包被包过滤代码处理。数据包必须匹配某个规则,该规则指示内核转交这个包给squid。如果没有这样的规则,内核简单的将包按照它自己的方式转发给原始服务器,这不是你想要的。

注意SYN包的目的端口是80,但squid可能侦听在不同的端口,例如3128。包过滤规则允许你改变端口号。你不必让squid侦听在80端口。通过tcpdump,你能见到这步,因为转发的包不会再次通过网络接口代码。

即使squid侦听在80端口,包过滤器的重定向规则仍是必要的。可以让squid不在这些端口上接受拦截包。重定向规则有点神奇,它转交外部数据包给squid。

6.Squid接受到新连接的通知,它接受这个连接。内核发送SYN/ACK包返回给客户端:

Packet 4

TIME:   19:54:41.320735 (0.000083)

TCP:    208.201.239.37.80 -> 206.168.0.3.3897 SynAck

DATA:   <No data>

就象你见到的一样,源地址是原始服务器,尽管这个包不会抵达原始服务器。操作系统只是简单的将源地址和目的地址交换一下,并将它放进响应数据包里。

7.用户代理接受到SYN/ACK包,建立起完整的TCP连接。用户代理现在相信它是连接到原始服务器,所以它发送HTTP请求:

Packet 5

TIME:   19:54:41.323080 (0.002345)

TCP:    206.168.0.3.3897 -> 208.201.239.37.80 Ack

DATA:   <No data>

---------------------------------------------------------------------------

Packet 6

TIME:   19:54:41.323482 (0.000402)

TCP:    206.168.0.3.3897 -> 208.201.239.37.80 AckPsh

DATA:   GET / HTTP/1.0

        User-Agent: Wget/1.8.2

        Host: www.oreilly.com

        Accept: */*

        Connection: Keep-Alive

8.Squid接受HTTP请求。它使用HTTP Host头部来转换局部URL为完整的URL。在这种情形下,可在access.log文件里见到http://www.oreilly.com。

9.从这点开始,squid正常的处理请求。一般cache命中会立刻返回。cache丢失会转发到原始服务器。

10.最后,是squid从原始服务器接受到的响应:

Packet 8

TIME:   19:54:41.448391 (0.030030)

TCP:    208.201.239.37.80 -> 206.168.0.3.3897 AckPsh

DATA:   HTTP/1.0 200 OK

        Date: Mon, 29 Sep 2003 01:54:41 GMT

        Server: Apache/1.3.26 (Unix) PHP/4.2.1 mod_gzip/1.3.19.1a mo

        d_perl/1.27

        P3P: policyref="http://www.oreillynet.com/w3c/p3p.xml",CP="C

        AO DSP COR CURa ADMa DEVa TAIa PSAa PSDa IVAa IVDa CONo OUR

        DELa PUBi OTRa IND PHY ONL UNI PUR COM NAV INT DEM CNT STA P

        RE"

        Last-Modified: Sun, 28 Sep 2003 23:54:44 GMT

        ETag: "1b76bf-b910-3ede86c4"

        Accept-Ranges: bytes

        Content-Length: 47376

        Content-Type: text/html

        X-Cache: MISS from www.oreilly.com

        X-Cache: MISS from 10.0.0.1

        Connection: keep-alive

不应该让交换机或路由器来拦截squid到原始服务器的连接。假如这种情况发生,squid结束与自己的会话,并且不能满足任何cache丢失。防止这类转发死循环的最好方法是,确认用户和squid连接到交换机或路由器的独立接口。无论何时,应该在指定接口上应用拦截规则。最明显的,不该在squid使用的接口上激活拦截。

9.2为何要(或不要)拦截?

许多单位发现,cache拦截很有用,因为他们不能,或不愿意配置所有用户的web浏览器。相对于配置成百上千台工作站来说,在单个交换机或路由器上做一点网络欺骗更容易。从我们面临的许多选择来看,cache拦截确实有好也有坏。它可能让你的生活更容易,但也许会更难。

Cache拦截的最明显的贡献是,所有HTTP请求通过squid自动离开你的网络。你不必担心配置任何浏览器,用户可能在浏览器上禁止他们的代理设置。cache拦截让网络管理员完全控制HTTP会话。你可以改变,增加,或删除squid的缓存,而不会显著影响你的用户上网冲浪。

关于HTTP拦截的主要不利点就是该技术违背了TCP/IP的标准。这些协议要求路由器或交换机转发TCP/IP包到目的IP地址里指定的主机。然而转发包到cache代理破坏了这些规则。代理伪装身份接受转交过来的连接。用户代理被欺骗了,以为它们在与真正的web服务器会话。

这样的混乱导致在老版本的Microsoft IE浏览器中产生严重问题。浏览器的Reload按钮是刷新HTML页面的最容易的方法。当浏览器被配置成使用cache代理时,reload请求包含了一个Cache-Control:no-cache头部,它强迫产生cache丢失(或cache确认),并确保响应是最近更新的。假如没有明确配置使用代理,浏览器会忽略该头部。当使用cache拦截时,浏览器认为它在连接到原始服务器,因此没必要发送该头部。在这种情形下,squid不会告知用户的Reload按钮,也许不会验证cache响应。squid的ie_refresh提供了解决此bug的局部解决方法(见附录A)。Microsoft已经在其IE 5.5 SP1中解决了这个问题。

因为类似的理由,你不能结合cache拦截使用HTTP代理验证。因为客户端不知道这个代理,它不会发送必要的Proxy-Authorization头部。另外,407(代理验证请求)响应代码也不恰当,因为响应看起来象来自原始服务器,原始服务器从来不会发送如此响应。

也不能在cache拦截中使用RFC 1413 ident查询(见6.1.2.11章节)。Squid不能对必要的IP地址建立新的TCP Socket连接。操作系统在转发拦截连接到squid时,它执行欺骗。然后,当squid希望bind新的TCP Socket到外部IP地址时,它不能执行欺骗。它想bind的地址实际上并非真正本地的,所以bind系统调用失败。

cache拦截也与设计成阻止地址欺骗的IP过滤冲突(见RFC 2267:Network Ingress Filtering: Defeating Denial of Service Attacks Which Employ IP Source AddressSpoofing)。考虑如图9-2显示的网络。路由器有2个LAN接口:lan0和lan1。网络管理员在路由器上使用包过滤器,以确保没有内部主机传送假冒源地址的数据包。路由器只会转发源地址对应相连网络的数据包。包过滤规则也许看起来如下:

# lan0

allow ip from 172.16.1.0/24 to any via lan0

deny ip from any to any via lan0

# lan1

allow ip from 10.0.0.0/16 to any via lan1

deny ip from any to any via lan1

Figure 9-2. Interception caching breaks address spoofing filters

::__IHACKLOG_REMOTE_IMAGE_AUTODOWN_BLOCK__::4

现在看看,当路由器和lan1中的squid主机配置成拦截来自lan0中的HTTP连接后,会发生什么。Squid装扮成原始服务器,这意味着从squid到用户的响应TCP包欺骗了源地址。lan0过滤规则导致路由器拒绝这些包。为了让cache拦截生效,网络管理员须移除lan0规则。这样就让网络有漏洞,从而易遭拒绝服务攻击。

我在先前的章节里描述过,客户端在打开连接之前必须先进行DNS查询。在某些防火墙环境中,这样做可能有问题。你想进行HTTP拦截的主机必须能够查询DNS。如果客户端了解自己正使用代理(因为手工配置或代理自动配置),它通常就不去解析主机名。代替的,它简单的将完整URL转发给squid,由squid来查询原始服务器的IP地址。

另一个小问题是,squid接受任意目的IP地址的连接。例如,某个web站点当机了,但它仍然有DNS记录存在。squid伪装这个站点接受TCP连接。客户端会认为该站点仍然在运行,因为连接有效。当squid连接到原始服务器失败时,它强迫返回错误消息。

万一形势不清,HTTP拦截在初次使用时有些棘手或困难。许多不同的组件必须组合工作,并且要配置正确。甚至,从内存中恢复整个配置也很困难。我强烈建议你在将其应用于生产环境之前,先建立测试环境。一旦你让它正常运行,请记录每一步细节。

9.3 网络设备

现在你了解了cache拦截的相关细节,让我们看看如何实际让它工作。我们先配置网络设备,它们用来拦截HTTP连接。

9.3.1 内置Squid

在该配置中,你无需交换或网络路由设备来拦截HTTP连接。代替的,squid运行的Unix系统,也就是路由器(或网桥),请见图9-2。

Figure 9-3. A system that combines routing and caching can easily intercept HTTP traffic

::__IHACKLOG_REMOTE_IMAGE_AUTODOWN_BLOCK__::5

该配置本质上跳过了9.1章的头三步。squid主机充当网络路由器,它接受HTTP连接包。假如你采用此方法,请直接跳到9.4章。

9.3.2 四层交换

许多单位使用四层交换机来支持HTTP拦截。这些产品提供更多的功能,例如健壮性检测和负载均衡。我在这里仅仅讲讲拦截。关于健壮性检测和负载均衡的信息,请见O’Reilly’s Server Load Balancing and Load Balancing Servers, Firewalls, and Caches (John Wiley & Sons). 下面的章节包含了许多产品和技术的示例配置。

9.3.2.1 Alteon/Nortel

下面的配置来自ACEswitch 180和Alteon’s WebOS 8.0.21。网络设置请见图9-4。

Figure 9-4. Sample network for layer four switch interception, for Alteon and Foundry examples

::__IHACKLOG_REMOTE_IMAGE_AUTODOWN_BLOCK__::6

客户端连接到端口1,通过端口2连接到因特网,squid运行在端口3。下面的行是交换机的/cfg/dump命令的输出。你无须敲入所有这些行。甚至,在Alteon的新版软件里,某些命令可能改变了。注意Alteon把这个功能叫做Web Cache重定向(WCR)。如下是处理步骤:

  • 1. 首先,你必须分配给Alteon交换机一个IP地址。这是必要的,以便交换机能检查squid的存活状态。
    /cfg/ip/if 1
            ena
    
            addr 172.16.102.1
    
            mask 255.255.255.0
    
            broad 172.16.102.255
  • 2. Alteon的WCR属于服务负载均衡(SLB)配置。所以,必须使用如下命令在交换机上激活SLB功能:
    /cfg/slb
    
            on
  • 3. 现在,用squid的IP地址定义real server:
    /cfg/slb/real 1
    
            ena
    
            rip 172.16.102.66
  • 4. 必须定义一个组,并分配给real server一个组号:
    /cfg/slb/group 1
    
            health tcp
    
            add 1
  • 5. 下一步定义2个过滤规则。第1条规则匹配HTTP连接(目的端口是80的TCP包),并重定向它们到组1里的server。第2条规则匹配所有其他数据包,并正常转发它们。
    /cfg/slb/filt 1
    
            ena
    
            action redir
    
            sip any
    
            smask 0.0.0.0
    
            dip any
    
            dmask 0.0.0.0
    
            proto tcp
    
            sport any
    
            dport http
    
            group 1
    
            rport 0
    
    /cfg/slb/filt 224
    
            ena
    
            action allow
    
            sip any
    
            smask 0.0.0.0
    
            dip any
    
            dmask 0.0.0.0
    
            proto any
  • 6. 最后一步是给SLB配置指定的交换端口。在端口1上,处理客户端连接(这也是客户端连接的端口),并增加2条过滤规则。在端口2上,仅须配置它正常服务(例如,向上连接到Internet):
    cfg/slb/port 1
    
            client ena
    
            filt ena
    
            add 1
    
            add 224
    
    /cfg/slb/port 2
    
            server ena

为了验证HTTP拦截配置正确并工作良好,你可以使用/stats/slb和/info/slb菜单里的命令。/info/slb/dump是快速有效的查看整个SLB配置的方法:

>> Main# /info/slb/dump

Real server state:

  1: 172.16.102.66, 00:c0:4f:23:d7:05, vlan 1, port 3, health 3, up

Virtual server state:

Redirect filter state:

  1: dport http, rport 0, group 1, health tcp, backup none

    real servers:

      1: 172.16.102.66, backup none, up

Port state:

  1: 0.0.0.0, client

     filt  enabled, filters: 1 224

  2: 0.0.0.0, server

     filt disabled, filters: empty

  3: 0.0.0.0

        filt disabled, filters: empty

在该输出里,注意到交换机显示Squid在端口3上可到达,并且运行正常。你也能见到过滤规则1应用到端口1。在端口状态节里,端口1定义为客户端连接端口,端口2简单的标记为服务端口。

/stats/slb/real命令显示real server(squid)的有用统计:

>> Main# /stats/slb/real 1

------------------------------------------------------------------

Real server 1 stats:

Health check failures:                0

Current sessions:                    41

Total sessions:                     760

Highest sessions:                    55

Octets:                               0

大部分统计与任务(例如TCP连接)数量相关。假如再次运行该命令,总共的任务计数会增加。最后,/stats/slb/group命令显示几乎同样的信息:

>> Main# /stats/slb/group 1

------------------------------------------------------------------

Real server group 1 stats:

                      Current      Total  Highest

Real IP address      Sessions   Sessions Sessions           Octets

---- --------------- -------- ---------- --------  ---------------

   1 172.16.102.66         65       2004       90                0

---- --------------- -------- ---------- --------  ---------------

                           65       2004       90                0

假如不止1个real server在组里,该输出会更有趣。

9.3.2.2 Foundry

下面的配置示例来自ServerIron XL,运行的软件版本是07.0.07T12。跟前面一样,客户端在端口1,Internet连接在端口2,squid运行在端口3。然而,这样的配置少了点东西,因为这里可以激活HTTP全局拦截。Foundry的cache拦截的名字叫做Transparent Cache Switching(TCS)。请参考图9-4。首先请给交换机分配1个IP地址,以便执行健壮性检测:

ip address 172.16.102.1 255.255.255.0

Foundry允许你在特定端口上激活或禁用TCS。然而简单起见,这里全局激活它:

ip policy 1 cache tcp http global

在该行里,cache是针对TCS功能的关键字。下1行定义web cache,我定义其名字为squid1,并且告诉交换机它的IP地址:

server cache-name squid1 172.16.102.66

最后的步骤是将web cache加进cache组里:

server cache-group 1

 cache-name squid1

假如在转发连接时有问题,请参阅show cache-group命令的输出:

ServerIron#show cache-group

Cache-group 1 has 1 members Admin-status = Enabled Active = 0

Hash_info: Dest_mask = 255.255.255.0 Src_mask = 0.0.0.0

Cache Server Name                Admin-status Hash-distribution

squid1                           6            3

HTTP Traffic  From <-> to  Web-Caches

Name: squid1          IP: 172.16.102.66    State: 6   Groups =   1

                         Host->Web-cache       Web-cache->Host

           State   CurConn TotConn Packets    Octets     Packets    Octets

Client     active  441     12390   188871     15976623   156962     154750098

Web-Server active  193     11664   150722     151828731  175796     15853612

Total              634     24054   339593     167805354  332758     170603710

某些输出有些模糊,但通过重复该命令,并且观察计数器的增长,你能了解拦截是否在进行。

show server real提供几乎同样的信息:

ServerIron#show server real squid1

Real Servers Info

Name : squid1                                       Mac-addr: 00c0.4f23.d705

IP:172.16.102.66   Range:1    State:Active          Wt:1     Max-conn:1000000

Src-nat (cfg:op):(off:off)    Dest-nat (cfg:op):(off:off)

squid1 is a TRANSPARENT CACHE in groups   1

Remote server   : No          Dynamic : No      Server-resets:0

Mem:server: 02009eae Mem:mac: 045a3714

Port    State    Ms CurConn TotConn Rx-pkts  Tx-pkts  Rx-octet   Tx-octet   Reas

----    -----    -- ------- ------- -------  -------  --------   --------   ----
http    active   0  855     29557   379793   471713   373508204  39425322   0

default active   0  627     28335   425106   366016   38408994   368496301  0

Server  Total       1482    57892   804899   837729   411917198  407921623  0

最后,使用show logging命令来观察交换机是否显示squid正常或异常:

ServerIron#show logging

...

00d00h11m51s:N:L4 server 172.16.102.66 squid1 port 80 is up

00d00h11m49s:N:L4 server 172.16.102.66 squid1 port 80 is down

00d00h10m21s:N:L4 server 172.16.102.66 squid1 port 80 is up

00d00h10m21s:N:L4 server 172.16.102.66 squid1 is up

注意ServerIron认为服务运行在80端口。以后你会见到squid运行在3128端口的示例。包过滤规则实际上将包的目的地址从80改变为3128。这导致一些与状态检测有关的有趣结果,我在9.3.2.5节里会讲到。

9.3.2.3 Extreme Networks

在该示例里,硬件是Summit1i,软件版本是6.1.3b11。再次将客户端分配在端口1,Internet在端口2,squid在端口3。网络配置见图9-5。

Figure 9-5. Sample network for intercepting with a router, for the Extreme and Cisco policy routing examples

::__IHACKLOG_REMOTE_IMAGE_AUTODOWN_BLOCK__::7

Extreme交换机仅仅对在不同子网间进行路由的数据包进行HTTP连接的拦截。换句话说,如果你配置Extreme交换机使用二层模式(单一VLAN里),就不能将包转发给squid。为了让HTTP拦截正常工作,必须给用户,Squid,和Internet配置不同的VLAN。

configure Default delete port 1-8

create vlan Users

configure Users ip 172.16.102.1 255.255.255.192

configure Users add port 1

create vlan Internet

configure Internet ip 172.16.102.129 255.255.255.192

configure Internet add port 2

create vlan Squid

configure Squid ip 172.16.102.65 255.255.255.192

configure Squid add port 3

下一步是激活和配置交换机的路由:

enable ipforwarding
configure iproute add default 172.16.102.130

最后,配置交换机重定向HTTP连接到Squid:

create flow-redirect http tcp destination any ip-port 80 source any
configure http add next-hop 172.16.102.66
9.3.2.4 Cisco Arrowpoint

下类配置基于我以前的测试笔记。然而,最近我没有使用这类型的交换机,不能确保如下命令仍然正确:

circuit VLAN1

  ip address 172.16.102.1 255.255.255.0

service pxy1

  type transparent-cache

  ip address 172.16.102.66

  port 80

  protocol tcp

  active

owner foo

  content bar

    add service pxy1

    protocol tcp

    port 80

    active
9.3.2.5 关于HTTP服务和健壮性检测的评论

在上面的示例里,路由器/交换机都直接转发包,不会改变目的TCP端口。在9.4章里用到的包过滤规则改变了目的端口。如果试图在同一主机上运行HTTP服务和squid,那么就产生了一个有趣的问题。

为了在3128端口运行Squid的同时,还要在80端口运行HTTP,包过滤配置必须有1条特殊的规则,它接受到本机HTTP服务的TCP连接。否则,连接会直接转交给Squid。该规则易于建立。假如目的端口是80,并且目的地址是服务器的,那么主机正常接受这个包。然而所有的拦截包有外部目的地址,所以它们不会匹配该规则。

然而,当路由器/交换机进行HTTP健壮性检测时,它连接到服务器的IP地址。这样,健壮性检测的数据包匹配了上述规则,它不会转交给Squid。路由器/交换机检测了错误的服务。假如HTTP服务down掉了,而squid还在运行,那健壮性检测就产生错误结果。

解决这个问题的一些选择是:

  • 1.不要在Squid主机上运行HTTP服务;
  • 2.增加1条特殊的包过滤规则,将来自路由器/交换机的状态检测的包转交给squid;
  • 3.配置路由器/交换机,改变目的端口为3128;
  • 4.禁止4层状态检测。
9.3.3 Cisco策略路由

策略路由与4层交换并非不同。它在Cisco和其他公司的路由产品上执行。主要的区别是策略路由不包括任何健壮性检测。这样,假如squid超载或完全不可响应,路由器还会继续转发包到squid,而不是将包路由到原始服务器。策略路由要求squid位于路由器直接相连的子网中。

在本示例里,使用了Cisco 7204路由器,运行IOS Version 12.0(5)T。网络配置与前面的一样,见图9-5。

首先的配置步骤是定义访问列表,匹配来自客户端的到80端口的数据包。必须确保Squid发起的到80端口的数据包不会被再次拦截。做到这点的方法之一是,定义1个特殊规则,拒绝来自squid的数据包,紧跟1条规则允许所有其他的数据包:

access-list 110 deny tcp host 172.16.102.66 any eq www

access-list 110 permit tcp any any eq www

另外,如果Squid和用户位于不同的子网,你可以仅仅允许来自用户所在网络的数据包:

access-list 110 permit tcp 10.102.0.0 0.0.255.255 any eq www

下一步是定义路由映射。在这里你告诉路由器转发拦截包到何处去:

route-map proxy-redirect permit 10

 match ip address 110

 set ip next-hop 172.16.102.66

这些命令表明,“假如IP地址匹配访问列表110,就转发该包到172.16.102.66”。在route-map行的数字10是一个序列号,假如你有多个路由映射的话。最后一步是应用路由映射到客户端连接的接口:

interface Ethernet0/0

ip policy route-map proxy-redirect

IOS不对策略路由提供很多调试方法。然而,show route-map命令足够可用:

router#show route-map proxy-redirect

route-map proxy-redirect, permit, sequence 10

  Match clauses:

    ip address (access-lists): 110

  Set clauses:

    ip next-hop 172.16.102.66

                                Policy routing matches: 730 packets, 64649 bytes
9.3.4 Web Cache Coordination协议

Cisco对4层交换技术的响应叫做Web Cache Coordination Protocol(WCCP).WCCP在许多方面与典型的4层拦截不同。

首先,拦截包被封装在GRE(路由封装类)里。这点允许数据包跨子网传输,也就意味着squid不必直接连在路由器上。因为数据包是封装的,squid主机必须对其进行解包。并非所有的Unix系统有解开GRE包的代码。

第二个不同是,路由器如何决定将负载分摊到多个cache上。事实上,路由器不做这个决定,由cache来做。当路由器有一组支持WCCP的cache时,其中一个cache主动成为组的领导。由领导cache来决定如何分摊负载和通知路由器。在路由器重定向然任何连接之前,这是一个额外的必要步骤。

因为WCCP使用GRE,路由器可能强迫要求将来自HTTP请求的大TCP包分割成片断。幸运的是,这点不会经常发生,因为大部分HTTP请求比以太网MTU size(1500字节)要小。默认的TCP和IP包头部是20字节,这意味着以太网帧能实际携带1460字节的数据。GRE封装在GRE头部增加了20字节,另外在第二个IP头部增加了20字节。这样来自客户端的正常的1500字节的TCP/IP包,在封装后变成了1540字节。这样数据包就太大而不能在单个以太网帧里传输,所以路由器将原始包分割成两个片断。

9.3.4.1 WCCPv1

该节的配置示例在运行IOS Version 12.0(5)T的Cisco 7204路由器上测试。网络配置跟图9-5同。

首先,在IOS配置中输入如下两行,激活路由器的WCCP:

ip wccp version 1

ip wccp web-cache

接着,必须在单独的路由器接口上激活WCCP。在HTTP包离开路由器的接口上激活WCCP,也就是路由器连接到外部原始服务器或Internet网关的接口:

interface Ethernet0/1

 ip address 172.16.102.129 255.255.255.192

 ip wccp web-cache redirect out

请确认保存了配置改变。

你也许想使用访问列表来阻止某些web站点的拦截。可以使用访问列表来防止循环转发。例如:

! don't re-intercept connections coming from Squid:

access-list 112 deny   tcp host 172.16.102.66 any eq www

! don't intercept this broken web site

access-list 112 deny   tcp any 192.16.8.7 255.255.255.255 eq www

! allow other HTTP traffic

access-list 110 permit tcp any any eq www

ip wccp web-cache redirect-list 112

路由器不发送任何数据到squid,直到squid宣称它自己是路由器。我在9.5.1节里解释如何配置squid的WCCP。

9.3.4.2 WCCPv2

当前标准的Squid发布仅支持WCCPv1。然而,可在http://devel.squid-cache.org/找到WCCPv2的补丁。该代码仍是实验性的。注意从路由器发送到Squid的GRE包,包含了额外的4个字节。WCCPv2在GRE头部和封装的IP包之间,插入了一个重定向头部。也许需要修改内核代码来计算这个额外的头部。

9.3.4.3 调试

IOS提供许多命令来监视和调试WCCP。show ip wccp web-cache命令提供一些基本的信息:

router#show ip wccp web-cache

Global WCCP information:

    Router information:

        Router Identifier:                   172.16.102.129

        Protocol Version:                    1.0

    Service Identifier: web-cache

        Number of Cache Engines:             1

        Number of routers:                   1

        Total Packets Redirected:            1424

        Redirect access-list:                -none-

        Total Packets Denied Redirect:       0

        Total Packets Unassigned:            0

        Group access-list:                   -none-

        Total Messages Denied to Group:      0

        Total Authentication failures:       0

欲了解更多细节,在前叙命令后加一个detail单词:

router#show ip wccp web-cache detail

WCCP Cache-Engine information:

        IP Address:            172.16.102.66

        Protocol Version:      0.4

        State:                 Usable

        Initial Hash Info:     00000000000000000000000000000000

                               00000000000000000000000000000000

        Assigned Hash Info:    FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

                               FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
        Hash Allotment:        256 (100.00%)

        Packets Redirected:    1424

        Connect Time:          00:17:40

这里可以看到squid的IP地址和状态。假如不止一个cache与路由器会话,那么hash分配信息看起来不同。大多数情况下,每个cache接受到相等的hash值。

注意第二条命令输出的协议版本值,与第一条命令的不一样。不幸的是,赋予了版本号太多的意义。show ip wccp web-cache命令看起来报告WCCP协议的主版本号(例如1或2),然而show ip wccp web-cache detail的版本号看起来匹配Squid的wccp_version指令的值。

9.4 操作系统配置

为了让cache拦截正常工作,必须在操作系统中激活某些网络功能。首先,必须激活IP包转发。这就允许操作系统接受目的地址是外部IP的数据包。接着,必须激活和配置内核中的相关代码,以重定向外部包到Squid。

9.4.1 Linux

本节的指导适合2.4系列Linux内核。我使用RedHat Linux7.2(内核是2.4.7-10)。假如你使用的版本不同,那可能不能运行。建议搜索下Squid的FAQ或其他地方,找到关于内核的更新的或历史的信息。

在我测试iptables过程中,不必激活IP转发。然而,你也可以试试在一开始就激活它,并在一切运行良好后,看看能否禁掉它。激活包转发的最好的方法是在/etc/sysctl.conf文件里增加如下行:

net.ipv4.ip_forward = 1

一般来说,在HTTP拦截生效前,不必编译新内核。假如你不知道如何配置和创建新Linux内核,请参阅O’Reilly’s Running Linux by Matt Welsh, Matthias Kalle Dalheimer, and Lar Kaufman。当你配置内核时,请确认如下选项被激活:

o  General setup

     Networking support (CONFIG_NET=y)

     Sysctl support (CONFIG_SYSCTL=y)

o  Networking options

     Network packet filtering (CONFIG_NETFILTER=y)

     TCP/IP networking (CONFIG_INET=y)

     Netfilter Configuration

       Connection tracking (CONFIG_IP_NF_CONNTRACK=y)

       IP tables support (CONFIG_IP_NF_IPTABLES=y)

       Full NAT (CONFIG_IP_NF_NAT=y)

       REDIRECT target support (CONFIG_IP_NF_TARGET_REDIRECT=y)

o  File systems

     /proc filesystem support (CONFIG_PROC_FS=y)

另外,请确认该选项没被激活:

o Networking options

     Fast switching (CONFIG_NET_FASTROUTE=n)

重定向外部数据包到squid的代码是Netfilter软件的一部分。如下是发送HTTP拦截连接到squid的规则:

iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 3128

Linux内核维护许多不同的tables。-t nat选项表明我们正在修改NAT(网络地址转换)表。本质上,我们使用iptables将原始服务器的TCP/IP地址转换为squid的本地TCP/IP地址。

每个iptables表有许多链。-A PREROUTING表明我们增加了一条规则到内建的链叫做PREROUTING。PREROUTING链仅对从外部网络进入系统的数据包有效。

接下来的三个选项决定哪个包匹配该规则。-i eth0选项限制规则仅对eth0接口上接受的数据包有效。-p tcp选项指定TCP包,–dport 80指定包的目的端口是80。假如这三个条件都是true,那么包匹配该规则。

-j REDIRECT选项表明对匹配规则的包,采取何种动作。REDIRECT是内建的动作名,它导致iptables改变包的目的地址为127.0.0.1。–to-port 3128选项也指示iptables改变目的TCP端口为3128。

假如你在squid主机上运行HTTP服务(例如Apache),就必须增加另外的iptables规则。该必要规则允许连接到HTTP服务。否则,REDIRECT规则导致iptables转发连接到squid的3128端口。可以使用-I选项在列表顶部插入一条新规则:

iptables -t nat -I PREROUTING -i eth0 -p tcp -d 172.16.102.66 --dport 80 -j ACCEPT

一旦确认所有iptables规则工作正确,记得运行如下命令来保存配置:

/sbin/service iptables save

将当前规则保存到/etc/sysconfig/iptables,当系统重启时,这些规则自动载入。

9.4.1.1 Linux和WCCP

2.4版本的Linux内核自带1个GRE伪装接口。然而,它不能解码WCCP任务里封装的GRE包。问题看起来在于路由器设置了WCCP/GRE包的协议类型域为0x883E。Linux的GRE驱动不知道如何处理这类型包,因为它不了解0x883E类型的协议。

可以试试给Linux打GRE模块的补丁,以便它能在WCCP下工作。Squid FAQ包含了对这个补丁的链接。然而,假如使用WCCP指定的Linux模块,事情会容易些。可以在这里找到它:http://www.squid-cache.org/WCCP-support/Linux/ip_wccp.c

必须编译ip_wccp.c文件为可装载内核模块。有点棘手的是,依赖于内核版本的不同,编译选项可能不同。你可以进入内核源代码目录,敲入make modules并观察编译器命令的滚动。然后拷贝这些命令中的一个,然后改变最后一个参数为ip_wccp.c。如下是我在2.4.7-10 Linux内核中使用的命令:

% gcc -Wall -D_ _KERNEL_ _ -I/usr/src/linux-2.4.7-10/include  \

  -DMODULE -DMODVERSIONS -DEXPORT_SYMBAB \

  -include /usr/src/linux-2.4.7-10/include/linux/modversions.h \

  -O2 -c ip_wccp.c

gcc命令在当前目录会生成ip_wccp.o文件。下一步使用insmod命令,装载模块到内核中:

# insmod ip_wccp.o

注意ip_wccp模块接受来自任何源地址的GRE/WCCP包。换句话说,恶意用户可能发送数据到squid cache。假如使用该模块,应该安装一条iptables规则,拒绝外部的GRE包。例如:

# iptables -A INPUT -p gre -s 172.16.102.65 -j ACCEPT

# iptables -A INPUT -p gre -j DROP

不要忘记敲入/sbin/service iptables save命令来保存配置。

9.4.2 FreeBSD

本节的例子基于FreeBSD-4.8,并可以在任何FreeBSD-4和5系列的后续版本上运行。要激活IP包转发,在/etc/sysctl.conf中增加如下行:

net.inet.ip.forwarding=1

需要在内核中激活2个特殊选项。假如你不知道如何编译内核,参见FreeBSD Handbook第9章(http://www.freebsd.org/handbook/index.html). 编辑内核配置文件,确保有如下行:

options          IPFIREWALL

options          IPFIREWALL_FORWARD

假如squid主机位于无人照看的机房中,我也推荐使用IPFIREWALL_DEFAULT_TO_ACCEPT选项。假如你被防火墙的规则困扰,仍然可以登陆系统中。ipfw命令告诉内核重定向拦截连接到squid:

/sbin/ipfw add allow tcp from 172.16.102.66 to any out

/sbin/ipfw add allow tcp from any 80 to any out

/sbin/ipfw add fwd 127.0.0.1,3128 tcp from any to any 80 in

/sbin/ipfw add allow tcp from any 80 to 172.16.102.66 in

第一条规则匹配squid主机发起的数据包。它确保外出的TCP连接不会被重新定向回squid。第二条规则匹配squid响应客户端的数据包。我在这里列出它,因为随后会有另外的ipfw规则,这些规则会拒绝这些包。第三条规则实际重定向进来的连接到squid。第四条规则匹配从原始服务器返回squid的数据包。这里又一次假设随后会有相应的拒绝规则。

假如在squid主机上运行HTTP服务,就必须增加另外的规则,它放过,而不是重定向,目的地址是原始服务器的TCP包。下列规则在fwd规则之前:

/sbin/ipfw add allow tcp from any to 172.16.102.66 80 in

FreeBSD典型的将ipfw规则存储在/etc/rc.firewall里。一旦你确认规则设置正确,记得保存它们。将如下行加入/etc/rc.local文件,让FreeBSD在启动时自动运行/etc/rc.firewall脚本。

firewall_enable="YES"
9.4.2.1 FreeBSD和WCCP

FreeBSD版本4.8和后续版本内建了对GRE和WCCP的支持。早期的版本需要补丁,你可以在这里找到: http://www.squid-cache.org/WCCP-support/FreeBSD/ . 内建代码的性能非常好,它是真正的内核组织编写的。可能也需要编译支持GRE的新内核。将如下行加入内核配置文件里:

pseudo-device   gre

对Freebsd-5,使用device代替了pseudo-device。当然,你也需要前面章节里提到的FIREWALL选项。

在安装和重启了新内核后,必须配置GRE通道来接受来自路由器的GRE包。例如:

# ifconfig gre0 create

# ifconfig gre0 172.16.102.66 172.16.102.65 netmask 255.255.255.255 up

# ifconfig gre0 tunnel 172.16.102.66 172.16.102.65

# route delete 172.16.102.65

ifconfig命令在gre0接口上,增加了一个到路由器(172.16.102.65)的路由表入口。我发现必须删除该路由,以便squid能与其他路由器会话。

你也许想或必须对来自路由器的GRE包,增加一条ipfw规则:

/sbin/ipfw add allow gre from 172.16.102.65 to 172.16.102.66
9.4.3 OpenBSD

本节的示例基于OpenBSD 3.3。

为了激活包转发,在/etc/sysctl.conf文件里增加该行:

net.inet.ip.forwarding=1

现在,在/etc/pf.conf文件里增加如下类似行,配置包过滤规则:

rdr inet proto tcp from any to any port = www -> 127.0.0.1 port 3128

pass out proto tcp from 172.16.102.66 to any

pass out proto tcp from any port = 80 to any

pass in proto tcp from any port = 80 to 172.16.102.66

假如你没有使用OpenBSD的包过滤器,需要在/etc/rc.conf.local文件里增加一行来激活它:

pf=YES
9.4.3.1 OpenBSD和WCCP

首先,增加如下行到/etc/sysctl.conf文件,告诉系统接受和处理GRE和WCCP包:

net.inet.gre.allow=1

net.inet.gre.wccp=1

然后,用如下命令配置GRE接口:

# ifconfig gre0 172.16.102.66 172.16.102.65 netmask 255.255.255.255 up

# ifconfig gre0 tunnel 172.16.102.66 172.16.102.65

# route delete 172.16.102.65

跟Freebsd一样,我发现必须删除ifconfig自动产生的路由。最后,依赖于包过滤器的配置,必须增加一条规则以允许GRE包:

pass in proto gre from 172.16.102.65 to 172.16.102.66
9.4.4 在NetBSD和其他系统上的IPFilter

本节的示例基于NetBSD 1.6.1。它们也能运行在Solaris,HP-UX,IRIX,和Tru64上,既然这些系统本身就配备了IPFilter.

激活NetBSD的包转发,将如下行加进/etc/sysctl.conf:

net.inet.ip.forwarding=1

然后,将如下行插入NAT配置文件/etc/ipnat.conf中:

rdr fxp0 0/0 port 80 -> 172.16.102.66 port 3128 tcp

你的接口名可能与本例的fxp0不同。

9.4.4.1 NetBSD和WCCP

我不能在NetBSD上运行WCCP,即使打了GRE补丁来接受WCCP包。该问题看起来根源在IPFilter rdr规则阻塞了特定的端口。来自路由器的包通过NetBSD的gre0接口(在这里它们被解包)。然而,包回到路由器时,走另一条通道,未被封装并且不走同一网络接口。这样,IPFilter代码没有将squid的本地IP地址转换回原始服务器的地址。

9.5 配置Squid

假如你使用Linux 2.4和iptables,在运行./configure时,可使用–enable-linux-netfilter选项。它激活某些Linux的特殊代码,以发现发起请求的原始服务器的IP地址。Squid正常情况下从Host头部,得到原始服务器的名字(和/或地址)。–enable-linux-netfilter功能仅对没有Host头部的请求来说是必要的。统计显示几乎所有的请求有Host头部,所以实际中可以不使用–enable-linux-netfilter选项。

假如正在使用IPFilter包(NetBSD,Solaris,或其他),因为同样的理由,你应该使用–enable-ipf-transparent选项。在OpenBSD上,请使用–enable-pf-transparent选项。每次运行./configure时,必须重编译squid,见3.8章的描述。

在运行了./configure和重编译了squid后,可以编辑squid.conf文件。作为起点,请确认下列指令定义了给定的值:

httpd_accel_host virtual

httpd_accel_port 80

httpd_accel_uses_host_header on

httpd_accel_with_proxy on

httpd_accel_single_host off

http_accel_host指令是关键。它指示squid接受包含局部URI的HTTP请求。httpd_accel_uses_host_header被激活,允许squid使用Host头部来重新构建完整URI。virtual关键字指示squid在缺乏Host头部时,将原始服务器的IP地址放进URL里。

httpd_accel_with_proxy指令控制squid是否既接受HTTP服务(部分URI)请求,又接受代理(完整URI)请求。在cache拦截里,它应该被激活。如果没有用户明确的配置使用squid做代理,那即使httpd_accel_with_proxy没被激活,squid也能工作。

httpd_acces_single_host指令正常情况下被禁止,在早期版本的squid里,它可能被默认激活。在cache拦截里,它应明确被禁止。

假如拦截不止针对80端口,你也许该将httpd_accel_port设为0。见附录A的更多信息。

假如你没有使用WCCP,就该准备开始发起拦截会话到squid了。通过使用浏览器来上网冲浪,或者使用squidclient发起测试请求,就可以放手一试。假如你使用WCCP,那么还有许多步骤要完成。

9.5.1 配置WCCPv1

路由器不发送任何会话到squid,直到squid宣称它自己是路由器。为了让squid那样做,在squid.conf中增加如下行:

wccp_router 172.16.102.65

wccp_version 4

路由器有多个接口。请确认使用与squid相连的接口的IP地址。这点是必要的,因为来自路由器的WCCP消息,将源IP地址设置为外出接口的地址。假如源地址不匹配wccp_router值,squid会拒绝WCCP消息。

WCCPv1文档规定4作为协议版本号。然而,某些用户报告,Cisco IOS 11.2仅支持协议版本3。假如你使用该版本的IOS,请在squid.conf里改变版本号:

wccp_version 3
9.6 调试问题

HTTP拦截比较复杂,因为许多不同设备必须组合正确工作。为了帮助你跟踪问题,如下是一个问题解答检查列表:

  • * 客户端数据包正在通过路由器/交换机吗?
  • 在简单网络里,这点显而易见。你可以trace线缆并观察指示灯的活动闪烁。然而在大而复杂的网络,数据包可能走不同的路线。假如你的组织够大,并有网络sniffer设备,就可以观察线路中web客户端的请求数据包。低技术的方法是,断开有问题的线路,并观察是否影响客户端的web浏览。
  • * 路由器/交换机配置是否正确?
  • 你也许要再次检查路由器/交换机配置。假如你已配置了某个接口,那能否确保它正确呢?是否新的配置真正在设备上运行?也许在你保存配置之前,路由器/交换机已重启了。在改变生效前,你或许需要reboot设备。
  • * 交换机/路由器能与squid主机会话吗?
  • 能从路由器/交换机上ping通squid吗?大部分4层拦截配置要求网络设备和squid在同一子网里。登陆路由器/交换机,确认能ping通squid的IP地址。
  • * 交换机/路由器相信squid在运行吗?
  • 许多传输拦截设备不会发送会话到squid,除非它们知道squid是健壮的。使用调试命令来预览squid的健壮性状态。也许会发现三层健壮性检测(例如ICMP ping)比四层检测(例如HTTP)更容易,它使网络设备更容易将squid标记为存活状态。
  • * Squid实际在运行吗?
  • 请再次确认squid真正在运行,特别是在系统近期重启过的情况下。
  • * 数据包正在抵达squid主机吗?
  • 使用tcpdump能见到拦截的TCP连接。如下是示例:
    # tcpdump -n -i eth0 port 80

    假如使用WCCP,请检查来自路由器的GRE包:

    # tcpdump -n -i eth0 ip proto gre

    假如没有看到tcpdump的任何输出,则路由器/交换机可能没有发送任何数据。在这种情况下,返回到以前的建议。 注意,假如设备正使用四层健壮性检测,你可以在tcpdump的输出里见到这些。健壮性检测来自路由器/交换机的IP地址,所以它们容易被认出。假如你见到健壮性检测,但没有其他数据,那可能意味着路由器/交换机正把squid的响应理解为不健壮。例如,设备可能想见到200(OK)响应,但squid返回一个错误,例如401(未授权)或404(未发现)。请对access.log运行tail -f命令。

     

  • * 激活了IP转发吗?
  • 请再次确认squid运行的操作系统配置了IP包转发。假如没有,主机可能会丢弃拦截数据包,因为目的IP地址并非本地。
  • * 配置包过滤了吗?
  • 请确认包过滤器(例如ipfw,iptables,pf等)配置正确。当每件事都运行良好时,你能定期运行命令来显示过滤规则,并观察计数器增长。例如:
    # ipfw show 300 ; sleep 3; ipfw show 300
    
    00300  86216 8480458 fwd 127.0.0.1,3128 tcp from any to any 80 in
    
    00300  86241 8482240 fwd 127.0.0.1,3128 tcp from any to any 80 in

    注意该示例在FreeBSD上,包和字节计数器(第二和第三列)正在增长。

  • * 环路接口起来和配置了吗?
  • 假如有一条规则转发/重定向包到127.0.0.1,请确认环路接口(例如lo0,lo等)起来了,并配置过它。假如没有,内核简单的跳过这条转发/重定向规则。
  • * WCCP/GRE包被正确解开了吗?
  • 假如使用WCCP,请确认GRE包被正确解开。假如因为某些理由,系统不知道该如何处理GRE包,那就在netstat -s的输出里会见到”unknown/unsupported protocol”计数器在增长。
    # netstat -s | grep unknown
    
                46 packets for unknown/unsupported protocol

    假如OS有GRE接口,请频繁运行netstat -i命令,观察不断增长的包数量:

    # netstat -in | grep ^gre0
    
    Name    Mtu Network       Address              Ipkts Ierrs    Opkts Oerrs  Coll
    
    gre0   1476 <Link#4>
    
    304452     0        0     4     0

    另外,在GRE接口上运行tcpdump:

    # tcpdump -n -i gre0

     

  • * Squid能响应客户端吗?
  • 有可能路由器/交换机能发送包到squid,但squid不能将包发送回客户端。这种情况可能发生在:防火墙过滤规则拒绝外出数据包,或squid没有到客户端IP地址的路由。为了检查这种情况,请运行netstat -n并观察SYN_RCVD状态的sockets:
    % netstat -n
    
    Active Internet connections
    
    Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
    
    tcp4       0      0  10.102.129.246.80      10.102.0.1.36260       SYN_RCVD
    
    tcp4       0      0  10.102.129.226.80      10.102.0.1.36259       SYN_RCVD
    
    tcp4       0      0  10.102.128.147.80      10.102.0.1.36258       SYN_RCVD
    
    tcp4       0      0  10.102.129.26.80       10.102.0.2.36257       SYN_RCVD
    
    tcp4       0      0  10.102.129.29.80       10.102.0.2.36255       SYN_RCVD
    
    tcp4       0      0  10.102.129.226.80      10.102.0.1.36254       SYN_RCVD
    
    tcp4       0      0  10.102.128.117.80      10.102.0.1.36253       SYN_RCVD
    
    tcp4       0      0  10.102.128.149.80      10.102.0.1.36252       SYN_RCVD

    假如你看到这些,请使用ping和traceroute来确认squid能与客户端双向通信。

  • * Squid能与原始服务器会话吗?
  • 假如squid不能连接到原始服务器,拦截HTTP连接会无法进行。如果这点发生,netstat会显示许多连接在SYN_SENT状态:
    % netstat -n
    
    Active Internet connections
    
    Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
    
    tcp4       0      0  172.16.102.66.5217     10.102.129.145.80      SYN_SENT
    
    tcp4       0      0  172.16.102.66.5216     10.102.129.224.80      SYN_SENT
    
    tcp4       0      0  172.16.102.66.5215     10.102.128.71.80       SYN_SENT
    
    tcp4       0      0  172.16.102.66.5214     10.102.129.209.80      SYN_SENT
    
    tcp4       0      0  172.16.102.66.5213     10.102.129.62.80       SYN_SENT
    
    tcp4       0      0  172.16.102.66.5212     10.102.129.160.80      SYN_SENT
    
    tcp4       0      0  172.16.102.66.5211     10.102.128.129.80      SYN_SENT
    
    tcp4       0      0  172.16.102.66.5210     10.102.129.44.80       SYN_SENT
    
    tcp4       0      0  172.16.102.66.5209     10.102.128.73.80       SYN_SENT
    
    tcp4       0      0  172.16.102.66.5208     10.102.128.43.80       SYN_SENT

    再次用ping和traceroute来确认squid能与原始服务器会话。

  • * 外出连接正被拦截吗?
  • 假如squid能ping通原始服务器,并且仍然见到大量的连接在SYN_SENT状态,那么路由器/交换机可能正在拦截squid的外出TCP连接。在某些情况下,squid能检测到这种转发循环,并写警告日志到cache.log。如此的转发死循环能迅速耗光squid的所有文件描述符,这样也会在cache.log里产生警告。

假如你怀疑这个问题,请使用squidclient程序来发起一些简单的HTTP请求。例如,该命令发起一条直接到原始服务器的HTTP请求:

% /usr/local/squid/bin/squidclient -p 80 -h slashdot.org /

假如该命令成功,你可以在屏幕上见到来自Slashdot站点的许多难看的HTML。然后通过squid来试试同样的请求:

% /usr/local/squid/bin/squidclient -r -p 3128 -h 127.0.0.1 http://slashdot.org/

假如没有在cache.log里看到错误消息,就可以再次在屏幕上看到一些HTML。假如看到转发循环错误,就必须重新配置交换机/路由器,以便它允许squid的外出连接正常通过,而不被拦截。

第10章 与其他Squid会话

10.1 某些术语

通常把一组互相转发请求的cache(或代理)叫做cache堆叠。把cache堆叠的成员叫做邻居或对等伙伴

。邻居cache有2种关系:父子或姐妹。从拓扑上看,父cache在堆叠里位于顶层,而姐妹cache位于同一层。两者真正的不同在于,父cache能为子cache转发cache丢失,然而姐妹cache之间不允许转发cache丢失。这意味着,在发送请求到姐妹cache前,发起者应该知道这是个cache命中。类似于ICP,HTCP和Cache Digests之类的堆叠协议,能预知邻居的cache命中。然而CARP不能这样。

某些时候,cache堆叠并非真正层次性的。例如,考虑1组5个姐妹cache。因为它们没有父子关系,故而不存在上或下。在这种情况下,你可以叫它cache结网,或甚至cache编队,而不是堆叠。

10.2 为何要(或不要)使用堆叠?

邻居cache通过提供某些额外的cache命中而改进了执行性能。换句话说,某些请求的cache丢失,在邻居cache里可能会命中。假如你的cache从邻居cache里下载这些命中,比从原始服务器快,那cache堆叠可全面改善性能。底线是邻居cache能提供一小部分的请求命中。大约5%或10%(假如幸运的话)的cache丢失请求,会在邻居cache中命中。在某些情况下,这点小作用价值不高。然而有的情形下,例如网络连接不尽人意时,cache堆叠非常明显的改进了终端用户的访问性能。

假如在有防火墙的网络中使用squid,就有必要配置防火墙作为父代理。在该情形中,squid转发每个请求到防火墙,因为它不直接连接到外部原始服务器。假如有某些原始服务器在防火墙里面,就可以配置squid直接连接到它们。

也可以使用堆叠来在不同方向上传送web数据。这点有时候叫做应用层路由,或更近的叫法:内容路由。例如,考虑某个大公司,它有2个Internet连接。也许第二个连接比第一个成本更低。该公司可能想利用第二个连接作为次优先性传输,例如下载游戏、音频或视频,或其他形式的大文件传输。或者,也许他们想利用某条链路进行HTTP访问,而在另一条链路上进行非HTTP访问。再或者,也许某些用户的数据通过低优先级链路传输,而付费用户的数据通过更贵的链路传输。可以使用cache代理堆叠来完成上述设想。

信任机制对cache堆叠来说,是最重要的因素。你必须信任邻居cache会服务正确的、未修改的响应。必须在敏感信息上信任它们,例如用户请求的URI。

它们会维护一个安全的,不断更新的系统,将未授权访问和拒绝服务的机会降至最低。关于堆叠的另一个问题,在于它们正常传播错误的途径。当邻居cache经历了某个错误,例如服务不可到达,它产生一个HTML页面来解释该错误及错误源头。假如邻居cache位于公司之外,它返回错误时,用户会觉得迷惑。假如该问题继续,用户将很难找到帮它们解决问题的管理员。

姐妹关系也有一个已知的问题,叫做假命中。假如squid发送某个请求到它的姐妹,它认为这是个cache命中,然而它的姐妹没有联系过原始服务器,因此不能满足该请求,这时就会发生假命中。有很多情况导致假命中,但通常可能性很低。甚至,squid和其他HTTP代理有自动重新请求假命中的功能,不会让用户感觉到已发生的问题。

转发循环有时候是cache堆叠的另一个问题。当squid转发某个请求到某处,但该请求又被转发回squid,这就发生了转发循环。请见图10-1(略图)。

Figure 10-1. A forwarding loop

::__IHACKLOG_REMOTE_IMAGE_AUTODOWN_BLOCK__::8

转发循环典型的发生在2个cache互相把对方当作父cache的情况。假如你遇到这样的问题,请确认使用cache_peer_access指令来阻止这类循环。例如,假如邻居cache的IP地址是192.168.1.1,下面的行让squid不会产生转发循环:

acl FromNeighbor src 192.168.1.1 

cache_peer_access the.neighbor.name deny FromNeighbor

转发循环在HTTP拦截里也能发生,特别是当拦截设备位于squid和原始服务器之间的路径上时。

Squid通过检查Via头部里的主机名,来检测转发循环。假如2个协作cache有相同的主机名,实际上就会得到假转发循环。在该情形下,unique_hostname指令很有用。注意,假如Via头部被过滤掉了(例如使用headers_access指令),squid就不能检测到转发循环。

10.3 配置Squid与邻居通信

cache_peer指令定义邻居cache,并告诉squid如何与它的邻居通信:

cache_peer hostname type http-port icp-port [options]

第1个参数是邻居的主机名,或IP地址。可以安全的在这里使用主机名,因为squid会解析它们。在squid运行期间,主机名对应的IP地址可能会改变,所以实际上squid会周期性的解析主机名。邻居主机名必须唯一:不能在2个邻居cache上使用同样的主机名,即使它们有不同的端口。

第2个参数指定邻居cache的类型。有3个选择:父亲,姐妹,或广播。父亲和姐妹关系容易理解。我在10.6.3节里会详细谈到广播。

第3个参数是邻居HTTP端口号。它应该等同于邻居的http_port设置。总是应该指定1个非零的HTTP端口号。

第4个参数指定ICP或HTCP端口号。squid默认使用ICP来查询其他cache。也就是说,squid发送ICP查询到邻居cache的指定端口。假如你增加了htcp选项,squid就会发送HTCP查询到这个端口。默认的ICP端口是3130,默认的HTCP端口是4827。假如增加了htcp选项,请记得改变它的端口号。将端口号设为0,会禁止ICP和HTCP。然而,应该使用no-query选项来禁止这些协议。

10.3.1 cache_peer选项

cache_peer指令有很多选项。我在这里描述其中的一些,其他的参数在与之相关的协议的章节里有描述。

proxy-only

该选项指示squid不存储它接受自邻居的任何响应。如果你有cache集群,并且不希望资源存储在多个cache上,那么该选项有用。

weight=n

该选项指定给ICP/HTCP。请见10.6.2.1章节。

ttl=n

该选项指定给广播ICP。见10.6.3节。

no-query

该选项指定给ICP/HTCP。见10.6.2.1节。

default

在缺少其他线索时,该选项指定邻居cache作为适当的选择。正常情况下,squid将cache丢失转发给父cache,父cache看起来有特定资源的缓存拷贝。有时候squid没有任何线索(例如使用no-query禁用了ICP/HTCP),这时squid会寻找父cache,并将其作为默认选择。

round-robin

该选项是简单的负载共享技术。仅仅当你指定了2个或多个父cache作为轮转时,它才有用。squid对每个父cache维持一个计数器。当需要转发cache丢失时,squid选择计数器值最低的父cache。

multicast-responder

该选项指定给广播ICP。见10.6.3节。

closest-only

该选项指定给ICP/HTCP。见10.6.2.1节。

no-digest

该选项指定给cache digest,见10.7章。

no-netdb-exchange

该选项告诉squid,不要请求邻居cache的netdb数据库(见10.5节)。

no-delay

该选项告诉squid,忽略任何对邻居cache请求的延迟池设置。见附录C关于延迟池的更多信息。

login= credentials

该选项指示squid发送HTTP验证信息到邻居cache。它有3个不同的格式:

login =user:password

这是最通用的形式。它导致squid在每个到邻居cache的请求里,增加相同的用户名和密码。用户无须填写任何验证信息。

login=PASS

将值设为PASS,导致squid让用户的验证信息直接pass到邻居cache。它仅仅工作在HTTP基础的验证机制下。squid不会增加或修改任何验证信息。

假如squid被配置成需要代理验证(例如使用proxy_auth ACL),邻居cache就必须使用同样的用户名和密码数据库。换句话说,应该只在被同一组织拥有和操作的一组cache中使用PASS形式。该功能是危险的,因为squid不会从转发请求里移除验证信息。

login =* :password

使用该形式,squid改变它转发的请求里的密码,但不改变用户名。它允许邻居cache来验证独立的用户,但不暴露其密码。该形式比使用PASS危险少一些,但确实有一些隐私牵连。

使用该功能需要额外小心。即使你不管隐私条目,该功能也可能对上级代理产生不良的副作用。例如,我知道至少1个其他的cache产品,它仅仅查看持续连接里第一个请求的信用信息。它看起来(不正确的)假设在单个连接里的所有请求,都来自同一用户。

connect-timeout= n

该选项指定,在squid建立TCP连接到邻居cache时,等待的超时时间。若没有该选项,超时值就取自全局connect_timeout指令,它的默认值是120秒。使用低的超时值,squid会迅速放弃到邻居cache的会话,并且试着发送请求到其他邻居cache,或直接请求原始服务器。

digest-url= url

该选项指定给cache digest。见10.7章。

allow-miss

该选项指示squid忽略Cache-Control: only-if-cached指令,该指令是针对发送给姐妹cache的请求的。仅在如下情况下使用它:邻居cache激活了icp_hit_stale指令,并且没有使用miss_access列表。

max-conn= n

该选项对squid打开到邻居cache的同时连接的数量进行限制。当抵达了该限制时,squid从它的选择算法里排除掉该邻居cache。

htcp

该选项指定邻居cache为HTCP服务器。换句话说,squid发送HTCP查询,而不是ICP查询,到邻居cache。注意squid不会在同一端口上接受ICP和HTCP查询。若你增加了该选项,不要忘了改变icp-port的值。见10.8.1节。HTCP支持需要在运行./configure时,使用–enable-htcp选项。

carp-load-factor= f

该选项让邻居cache(它必须是父cache)成为CARP的成员。对所有父cache的f值的总和,必须等于1。我在10.9章里讲到CARP。CARP支持需要在运行./configure时,使用–enable-carp选项。

10.3.2 邻居状态

Squid对其每个邻居cache,维持着多种统计和状态信息。最重要的信息之一,是其邻居cache的存活状态。邻居cache的存活/死亡状态影响了squid选择算法的许多方面。确定邻居cache的存活/死亡状态的机制有点复杂,所以我在这里不讲它。假如你想通过阅读源代码来了解这点,请查看neighborUp()函数。

Squid使用TCP(HTTP)和UDP(ICP/HTCP)通讯来确定邻居cache的状态。TCP状态默认是存活的,但如果连续10个TCP连接失败,状态会改变为死亡。如果发生这种事,squid会发起探查连接,在每个connect_timeout期间内(全局指令,并非cache_peer选项),连接不会超过1次。邻居cache会保持死亡状态,直到探查连接之一成功。

假如no-query选项没有设置(意味着squid正发送ICP/HTCP查询到邻居cache),UDP层通讯也参与到存活/死亡算法里来。UDP状态默认是存活的,但假如squid在超时范围内(dead_peer_timeout指令的值),没有接受到任何ICP/HTCP响应,UDP状态就会改变为死亡。

假如邻居cache的主机名不可解析,squid也会将其标记为死亡。当squid确定其邻居cache死亡时,它在cache.log里增加类似如下的日志记录:

2003/09/29 01:13:46| Detected DEAD Sibling: bo2.us.ircache.net/3128/3130

当与邻居cache的通信重新建立起来时,squid记录类似如下的日志:

2003/09/29 01:13:49| Detected REVIVED Sibling: bo2.us.ircache.net/3128/3130

邻居cache的状态,在下述几个方面影响邻居选择算法:

  • 1)Squid不希望接受来自死亡邻居的ICP/HTCP响应。squid在每个dead_peer_timeout期间内,只发送1次ICP查询到死亡的邻居。见附录A。
  • 2)死亡的父cache从下列算法里排除掉:Digests, round-robin parent, first up parent, default parent, 和closest parent。
  • 3)CARP特殊:任何失败的TCP连接,会从CARP算法里排除掉父cache。

没有办法强迫squid发送HTTP请求到死亡的邻居cache。假如所有的邻居cache都死亡,squid会试图连接到原始服务器。假如你不允许squid与原始服务器会话(使用never_direct指令),squid会返回cannot forward的错误消息:

This request could not be forwarded to the origin server or to any 

parent caches. The most likely cause for this error is that: 

  * The cache administrator does not

  allow this cache to make direct connections to origin servers, and * All configured

                parent caches are currently unreachable
10.3.3 改变关系

neighbor_type_domain指令允许你改变与基于原始服务器主机名的邻居cache的关系。假如邻居cache期望服务任何请求的cache命中,但仅仅服务某些临近域的cache丢失,那么这个指令就很有用。语法是:

neighbor_type_domain neighbor.host.name relationship [!]domain ...

例如:

cache_peer squid.uk.web-cache.net sibling 3128 3130 

neighbor_type_domain squid.uk.web-cache.net parent .uk

当然,该示例里的squid.uk.web-cache.net缓存,会利用相应的miss_access规则来强迫姐妹关系给non-UK请求。注意域名会按照6.1.1.2章节里描述的那样,匹配主机名。

10.4 对邻居的请求限制

许多使用cache堆叠的用户,想控制或限制squid发送到邻居cache的请求。squid有7个不同的指令可以影响请求路由:cache_peer_access, cache_peer_domain, never_direct, always_direct, hierarchy_stoplist,nonhierarchical_direct, 和prefer_direct。

10.4.1 cache_peer_access

cache_peer_access指令定义对邻居cache的访问列表。也就是说,它决定哪个请求允许或不允许发送到邻居cache。

例如,可以使用这点来对FTP和HTTP请求进行分流。你可以发送所有的FTP URI到某个父cache,所有的HTTP URI到另一个父cache:

cache_peer A-parent.my.org parent 3128 3130 

cache_peer B-parent.my.org parent 3128 3130 

acl FTP proto FTP 

acl HTTP proto HTTP 

cache_peer_access A-parent allow FTP 

cache_peer_access B-parent allow HTTP

改配置确保父cache A仅接受FTP URI请求,而父cache B仅接受HTTP URI请求。当然也包括ICP/HTCP请求。

也可以使用cache_peer_access,在一天中的某段时间来激活或禁止邻居cache:

cache_peer A-parent.my.org parent 3128 3130 

acl DayTime time 07:00-18:00 

                cache_peer_access A-parent.my.org deny DayTime
10.4.2 cache_peer_domain

cache_peer_domain指令是cache_peer_access指令的早期形式。相对于使用完整的访问控制特性,它仅使用URI里的域名。它常用于通过域名区分一组父cache。例如,假如你有一个遍布全球的内部网,你也许想发送请求到位于各自大陆的cache:

cache_peer europe-cache.my.org parent 3128 3130 

cache_peer asia-cache.my.org parent 3128 3130 

cache_peer aust-cache.my.org parent 3128 3130 

cache_peer africa-cache.my.org parent 3128 3130 

cache_peer na-cache.my.org parent 3128 313 

cache_peer sa-cache.my.org parent 3128 3130 

cache_peer_domain europe-cache.my.org parent .ch .dk .fr .uk .nl .de .fi ...

cache_peer_domain asia-cache.my.org parent .jp .kr .cn .sg .tw .vn .hk ...

cache_peer_domain aust-cache.my.org parent .nz .au .aq ... 

cache_peer_domain africa-cache.my.org parent .dz .ly .ke .mz .ma .mg ... 

cache_peer_domain na-cache.my.org parent .mx .ca .us ... 

cache_peer_domain sa-cache.my.org parent .br .cl .ar .co .ve ...

当然,该机制不匹配流行的全球顶级域名,例如.com。

10.4.3 never_direct

never_direct指令是对从来不必直接发送到原始服务器的请求的访问列表。当请求匹配该访问列表,它必须被发送到邻居cache(通常是父cache)。

例如,假如squid位于防火墙之后,它也许能够直接与内部服务器会话,但必须通过防火墙代理(父cache)发送所有的请求到外部服务器。你可以告诉squid:“永不要直接连到防火墙之外的站点”。为了做到这点,请告诉squid有什么在防火墙之内:

acl InternalSites dstdomain .my.org 

never_direct allow !InternalSites

语法有点奇怪。never_direct allow foo意味着squid不会直接放过匹配foo的请求。既然内部站点容易指定,我使用否操作符(!)来匹配外部站点,这些站点squid不会直接联系。

注意该示例不会强迫squid直接连接到匹配InternalSites ACL的站点。never_direct访问规则仅仅强迫squid不直接联系某些原始服务器。你必须使用always_direct规则来强迫直接连接到原始服务器。

在结合其他控制请求路由的指令来使用never_direct时,必须谨慎。可能很容易就建立一件不可能做到的事件。如下是个示例:

cache_peer A-parent.my.org parent 3128 3130 

acl COM dstdomain .com 

cache_peer_access

A-parent.my.org deny COM 

never_direct allow COM

该配置自相矛盾,因为任何域名以.com结尾的请求必须通过邻居cache。然而仅仅定义了一个邻居cache,并且不允许.com请求通过它。若发生这种情况,squid会发布“cannot forward”的错误消息。

10.4.4 always_direct

也许你已猜到,always_direct规则列表告诉squid某些请求必须直接转发到原始服务器。例如,许多单位想保持他们的内网通信本地化。容易做到的方法是,定义基于IP地址的ACL,并将它放入always_direct规则列表:

acl OurNetwork src 172.16.3.0/24 always_direct allow OurNetwork
10.4.5 hierarchy_stoplist

Squid内在的将每个客户端请求标记为层叠或不可层叠。不可层叠的请求看起来不会导致cache命中。例如,POST请求的响应几乎从不会被cache。在squid能简单的连接到原始服务器时,转发不可cache目标的请求到邻居cache,纯粹是浪费资源。

某些区分层叠和不可层叠请求的规则,在squid里难于编码。例如,POST和PUT方式总是不可层叠的。然而,hierarchy_stoplist指令允许你定制这种算法。它包含一个字符串列表,当在URI里发现它们时,squid将请求标记为不可层叠。默认的该列表是:

hierarchy_stoplist ? cgi-bin

这样,任何包含问号或cgi-bin字符串的请求匹配该列表,变成不可层叠。

默认的,squid直接发送不可层叠的请求到原始服务器。因为这些请求不会导致cache命中,它们通常是邻居cache的额外负担。然而,never_direct访问控制规则凌驾于hierarchy_stoplist规则之上。具体而言,squid这样做:

  • 1)从不对不可层叠的请求发送ICP/HTCP查询,除非该请求匹配never_direct规则;
  • 2)从不对不可层叠的请求发送ICP/HTCP查询到姐妹cache;
  • 3)从不对不可层叠的请求查询邻居的cache digest。
10.4.6 nonhierarchical_direct

该指令控制squid转发不可层叠的请求的方法。squid默认直接发送不可层叠的请求到原始服务器。这是因为这些请求不会导致cache命中。我觉得直接从原始服务器获取它们总是好的,而不要浪费时间在邻居cache上查询它们。假如因为某些理由,你想路由这些请求通过邻居cache,请禁止该指令:

nonhierarchical_direct off
10.4.7 prefer_direct

该指令控制squid转发层叠请求的方法。默认的,squid首先发送这样的请求到邻居cache,然后再到原始服务器。通过激活该指令,你能反转这种行为:

prefer_direct

on 这样,假如squid与原始服务器的通信失败,邻居cache就变成了备份。

10.5 网络度量数据库(netdb)

Squid的网络度量数据库(netdb)被设计来测量到原始服务器的远近。换句话说,通过查询该数据库,squid知道它离原始服务器有多远。该数据库包含ICMP往返时间(RTT)测量值和路由跳计数。正常情况下,squid仅使用RTT测量值,但在某些情形下也使用路由跳计数。

为了激活netdb,必须使用–enable-icmp选项来配置squid。也必须以超级用户权限来安装pinger程序,请见3.6章的描述。当运行正确后,可以在cache.log里见到类似如下的消息:

2003/09/29 00:01:03| Pinger socket opened on FD 28

当激活了nebdb时,squid发送ICMP ping到原始服务器。ICMP消息实际上由pinger程序来发送和接受,pinger以root运行。squid会小心的不至于频繁发送ping消息,以避免骚扰web站点管理员。默认的,squid在发送另一个ping到同一主机,或同一24位子网中主机时,会至少等待5分钟。可以使用netdb_ping_period指令来调整这个时间间隙。

ICMP ping消息通常非常小(少于100字节)。squid在ICMP消息的有效载荷里包含了原始服务器的主机名,紧跟一个时间戳。

为了减少内存需求,squid以24位子网来合计netdb数据。squid假设所有位于同一24位子网里的主机,有相似的RTT和路由跳计数。若某台新的原始主机所在子网的其他主机已被测量过,该机制也允许squid去估算这台新原始主机的远近。

随同RTT和路由跳计数,squid也维护着一份主机名结合子网的列表。典型的记录看起来类似如下:

Subnet 140.98.193.0 

RTT    76.5 

Hops   20.0

Hosts  services1.ieee.org 

       www.spectrum.ieee.org 

       www.ieee.org

netdb度量值主要被ICP和HTCP使用。当你在squid.conf里激活query_icmp指令时,squid在发送到邻居cache的ICP/HTCP查询里设置一个标记。该标记请求在ICP/HTCP响应里包含远近测量值。假如邻居cache也激活了netdb,它们的响应将包含RTT和路由跳计数(假如可用)。注意squid总是立刻响应ICP。在响应ICP查询前,它不会等待ICMP测量。见10.6.2.2节关于ICP如何使用netdb的细节。

Squid会记住它从ICP/HTCP响应里学到的RTT值。这些值在随后的决定最佳转发途径时,也许会用到。通过叫做netdb交换的机制,squid也支持netdb度量值的批量迁移。squid周期性的通过HTTP请求邻居cache的netdb数据。在cache_peer行里设置no-netdb-exchange选项,就可以禁止这些请求。

netdb_low和netdb_high指令控制度量数据库的大小。当存储子网的数量抵达netdb_high时,squid删除最少近来使用的条目,直到数量低于netdb_low。

minimum_direct_hops和minimum_direct_rtt指令指示squid直接连接到低于一定数量路由跳计数,或少于一定毫秒的原始服务器。匹配该标准的请求在access.log里以CLOSEST_DIRECT形式记载。

cache管理器的netdb页显示完整的网络度量数据库,包括来自邻居cache的值。例如:

Network          recv/sent     RTT  Hops Hostnames

63.241.84.0         1/   1    25.0   9.0 www.xyzzy.com

sd.us.ircache.net             21.5  15.0

bo1.us.ircache.net            27.0  13.0

pb.us.ircache.net             70.0  11.0

206.100.24.0        5/   5    25.0   3.0 wcarchive.cdrom.com ftp.cdrom.com

uc.us.ircache.net             23.5  11.0

bo1.us.ircache.net            27.7   7.0

pb.us.ircache.net             35.7  10.0

sd.us.ircache.net             72.9  10.0

146.6.135.0         1/   1    25.0  13.0 www.cm.utexas.edu

bo1.us.ircache.net            32.0  11.0

sd.us.ircache.net             55.0   8.0

216.234.248.0       2/   2    25.0   8.0 postfuture.com www1.123india.com

pb.us.ircache.net             44.0  14.0

216.148.242.0       1/   1    25.0   9.0 images.worldres.com

sd.us.ircache.net             25.2  15.0

bo1.us.ircache.net            27.0  13.0

pb.us.ircache.net             69.5  11.0

这里,你可以见到www.xyzzy.com服务器有一个IP地址位于63.241.84.0/24子网。从cache到这台原始服务器的RTT值是25毫秒。邻居cache: sd.us.ircache.net更近一点,在21.5毫秒。

10.6 Internet Cache协议(ICP)

ICP是轻量级的目标定位协议,它作为Harvest项目的一部分而被发明。ICP客户发送查询消息到一个或多个ICP服务器,询问它们是否缓存了某个URI。每个服务器响应一个ICP_HIT(ICP命中),ICP_MISS(ICP丢失),或其他类型的ICP消息。ICP客户使用ICP响应里的信息来做转发决定。

除了指示cache命中,ICP也用于提供关于squid和邻居cache之间网络条件的线索。从这点看,ICP消息类似于ICMP ping。通过计算查询/响应往返时间,squid能估算网络拥塞情况。在极端情况下,ICP消息可能丢失,这意味着两者之间的链路断掉,或线路拥塞。在这种情况下,squid会避免请求这个邻居cache。

增加延时可能是使用ICP的最显然的弊端。查询/响应交换要花费一些时间。cache代理被设计来减少响应时间,而不是增加延时。如果ICP帮助我们发现在邻居cache上的cache命中,这样它应该全面减少响应时间。见10.10章关于squid的查询算法实现的描述。

ICP也得承受某些设计不足带来的责难:安全性,伸缩性,假命中,和请求方式的缺乏。该协议不包括任何安全机制。通常squid不能确认某个ICP消息是可信的;它依赖于基于地址的访问控制来过滤掉不想要的ICP消息。

ICP的伸缩性也很差。ICP消息的数量(和带宽)增长,与邻居cache的数量成正比。除非使用某种隔离机制,这实际上限制了你能使用的邻居cache的数量。我不推荐拥有超过5或6个邻居cache。

ICP查询仅包含URI,没有另外的请求头部。这让它很难精确的指示cache命中。某个HTTP请求可能包含附加的头部(例如Cache-Control: max-stale=N)将cache命中转换为cache丢失。对姐妹关系的cache来说,这些假命中的弊端特别明显。

从ICP查询消息里丢失的还有请求方式。ICP假设所有的查询采用GET请求。cache代理不能使用非GET请求方式的ICP来查找缓存目标。

通过阅读如下资料,你可以找到其他的关于ICP的信息:

  • 1)我写的书: Web Caching (O’Reilly)
  • 2)RFCs 2186 and 2187
  • 3)我的论文:”ICP and the Squid Web Cache” in the IEEE Journal on Selected Areas in Communication, April 1998
  • 4) http://icp.ircache.net/
10.6.1 成为ICP服务器

当你使用icp_port指令时,squid自动成为ICP服务器。也就是说,它在你指定的端口侦听ICP消息,或默认的3130端口。假如决定使用非标准端口,别忘了告知姐妹和/或子cache。squid默认拒绝所有ICP查询。必须使用icp_access规则列表,允许来自邻居cache的查询。使用src ACL容易做到这点。例如:

acl N1 src 192.168.0.1 

acl N2 src 172.16.0.2 

acl All src 0/0 

icp_access allow N1 

icp_access allow N2 

icp_access deny All

注意仅仅ICP_QUERY消息从属于icp_access规则。ICP客户端的功能,例如发送查询和接受响应,不需要任何特殊访问控制。我也推荐使用操作系统的包过滤功能(例如ipfw,iptables,和pf)。允许来自可信任邻居的UDP消息到ICP端口,并拒绝所有其他的。

如果squid因为icp_access规则而拒绝某个ICP查询,它返回一个ICP_DENIED消息。然而,假如squid检测到超过95%的近来查询被拒绝,它停止响应1个小时。若发生这种情况,squid在cache.log里写如下消息:

WARNING: Probable misconfigured neighbor at foo.web-cache.com 

WARNING: 150 of the last 150 ICP replies are DENIED 

WARNING: No replies will be sent for the next 3600 seconds

假如你看到这样的消息,你该联系错误配置了cache的管理员。

squid被设计为迅速响应ICP查询。也就是说,squid通过检查内存索引,能告知它是否有更新的、缓存的响应。这也就是为什么squid如此耗内存的原因。当ICP查询进来时,squid计算URI的MD5 hash值,并且在索引里查找它。假如没有找到,squid返回ICP_MISS消息。假如找到了,squid检查超时时间。假如目标没有刷新,squid返回ICP_MISS。对刷新的目标,squid返回ICP_HIT。

默认的,squid记录所有的ICP查询(但非响应)到access.log。假如拥有许多繁忙的邻居cache,日志文件可能变得太大而难于管理。使用log_icp_queries指令来阻止记录这些查询。尽管会丢失ICP的详细日志,你仍可以通过cache管理器来获取一些合计信息。

假如有姐妹邻居,你也许想使用miss_access指令来强迫其关系。它指定cache丢失的访问规则。它与http_access相似,但仅检查必须被转发的请求。默认的规则是允许所有的cache丢失。除非增加一些miss_access规则,任何姐妹cache都可能变成子cache,并通过你的网络连接来转发cache丢失,这样就盗用了带宽。

miss_access规则相对简单。不要忘记包含本地客户端(例如web浏览器)。如下是个简单示例:

acl Browsers src 10.9.0.0/16 

acl  Child1 src 172.16.3.4 

acl Child2 src 192.168.2.0/24 

acl All src 0/0 

miss_access allow Browsers 

miss_access allow Child1 

miss_access allow Child2 

miss_access deny All

注意我没有在这里列举任何姐妹cache。子cache允许通过我们请求cache丢失,但姐妹cache不允许。它们的cache丢失请求被deny all规则拒绝。

10.6.1.1 icp_hit_stale指令

ICP的问题之一是,它对cache住的,但陈旧的响应返回ICP_MISS。这点确实存在,即使响应陈旧但有效。考虑一个简单的堆叠,有1个子cache和2个父cache。某个目标缓存在其中1个父cache上。缓存的响应是陈旧的,但未改变,需要确认。子cache的ICP查询导致2条ICP_MISS响应。由于不知道陈旧的响应存在于第1个父cache中,子cache会转发它的请求到第2个父cache。现在目标存储在2个父cache中,浪费资源。

在该情形下,icp_hit_stale指令有用。它告诉squid对任何cache住的目标,即使它是陈旧的,都返回ICP_HIT。这在父子关系的cache中很安全,但对姐妹关系的cache有问题。

回想一下姐妹关系,客户cache仅仅允许发送cache命中的请求。激活了icp_hit_stale指令,增加了假命中的数量,因为squid必须确认陈旧响应。正常情况下,squid这样处理假命中:在发送给姐妹cache的HTTP请求里增加Cache-Control: only-if-cached指令。假如姐妹cache不能满足HTTP请求为cache命中,它返回一个HTTP 504(网关超时)消息。当squid接受到504响应,它再次转发请求到父cache或原始服务器。

假如必须转发所有的假命中,激活icp_hit_stale就会给姐妹关系cache带来麻烦。这时ICP客户端cache_peer的allow-miss选项就变得有用。当设置了allow-miss选项时,squid忽略它发送到姐妹cache的HTTP请求里的only-if-cached指令。

假如激活了icp_hit_stale,必须确保miss_access不会拒绝来自姐妹cache的cache丢失请求。不幸的是,没有办法让squid只允许cache住的,但陈旧的目标的cache丢失。允许姐妹cache的cache丢失请求,也让squid容易被滥用。姐妹cache的管理员可能没经过你的同意,而擅自改变它为父cache。

10.6.1.2 ICP_MISS_NOFETCH功能

命令行-Y选项的作用是:在squid重建内存索引时,它导致squid返回ICP_MISS_NOFETCH,而不是ICP_MISS。接受到ICP_MISS_NOFETCH响应的ICP客户,不会对这些目标发送HTTP请求。这减少了squid的负载,并让重建过程快速完成。

10.6.1.3 test_reachability指令

假如激活了netdb功能(见10.5章),你也许会对test_reachability指令感兴趣。它背后的目的是,squid仅接受某些请求,这些请求的原始服务器squid能访问到。激活了test_reachability指令,在原始服务器不响应ICMP ping时,squid返回ICP_MISS_NOFETCH,而不是ICP_MISS。这点有助于减少假HTTP请求的数量,并增加终端用户迅速接受数据的机会。然而,一定比率的原始服务器站点过滤掉了ICMP传输。对这些站点,即使HTTP连接成功,squid也会返回ICP_MISS_NOFETCH。

激活test_reachability也导致squid在ICP查询的响应里,发起netdb度量。假如squid没有请求中的原始服务器的任何RTT度量值,它会发出一个ICMP ping消息。

10.6.2 成为ICP客户

首先,必须使用cache_peer指令来定义邻居cache。见10.3章。

接着,必须使用icp_port指令,即使squid仅作为ICP客户。这是因为squid使用同样的socket来发送和接受ICP消息。这也许是个不好的设计思路。假如仅作为ICP客户,请使用icp_access来阻塞查询。例如:

acl All src 0/0 

icp_access deny All

Squid默认的对大多数请求发送ICP查询到其邻居。见10.10章关于squid决定何时查询其邻居cache的方法的完整描述。

在发送一个或多个查询后,squid等待一定数量的时间,等候ICP响应抵达。假如squid从某个邻居cache接受到ICP_HIT,它会立刻转发请求到那里。会则,squid会一直等待,直到所有的响应抵达,或超时发生。squid基于下列算法,动态的计算超时。

Squid从最近的ICP传输中,知道从它自己到每个邻居cache之间的平均往返时间。在查询一组邻居时,squid计算所有邻居ICP RTT的平均数,然后将该数值翻倍。换句话说,查询超时值是每个邻居cache查询的RTT的平均值的2倍。在计算超时值时,squid忽略看起来已down掉的邻居。

在某些情形下,该算法不会工作良好,特别在邻居cache的RTT变化很大的情况下。使用maximum_icp_query_timeout指令可以改变超时的上限。另外,也可以通过icp_query_timeout指令,让squid使用一个常量超时值。

10.6.2.1 ICP客户的cache_peer选项

weight=n允许你在使用ICP/HTCP时,手工加权父cache。它仅当所有父cache报告cache丢失时,才变得有用。正常情况下,squid选择响应首先抵达的父cache。实际上,squid会记住查询中哪个父cache有最好的RTT。squid实际上通过权重来区分RTT,所以weight=2的父cache,不管其实际距离如何,都会被对待为离squid很近。

no-query禁止对邻居cache的ICP/HTCP。也就是说,你的cache不会发送任何对cache丢失的查询到邻居。它通常以default选项被使用。

closest-only指squid的netdb功能之一。它指示squid仅基于netdb RTT度量值来选择父cache,而不是响应抵达的顺序。该选项要求netdb在两端都被激活。

10.6.2.2 ICP和netdb

就像10.5章里提到的一样,netdb主要用于ICP查询。在本节里,我们跟随在这个过程中,所有的步骤调用。

  • 1.作为ICP客户的squid cache,准备发送查询到一个或多个邻居cache。假如设置了query_icmp,squid在ICP查询里设置SRC_RTT标记。这通知ICP服务器,squid想在ICP响应里接受RTT度量值。
  • 2.邻居cache接受到带有SRC_RTT标记的查询。假如邻居cache配置了使用netdb度量,它会在数据库里搜索原始服务器的主机名。注意邻居cache不会查询DNS来解析原始服务器的IP地址。这样,假如指定的主机已经被度量过,它才能在netdb里找到相关条目。
  • 3.假如主机存在于netdb数据库里,邻居cache在ICP响应里返回RTT和路由跳数。并在响应里设置SRC_RTT标记,指示度量值已提供。
  • 4.当squid接受到设置了SRC_RTT标记的ICP响应,它剥离出RTT和路由跳数。这些被追加到本地netdb,以便将来squid了解从邻居cache到原始服务器的近似RTT。
  • 5.ICP_HIT响应导致squid立刻转发HTTP请求。然而假如squid仅接受到ICP_MISS应答,它就选择具有到原始服务器的最小(非零)RTT的父cache。该请求以CLOSEST_PARENT_MISS被记录到access.log。
  • 6.假如没有父cache的ICP_MISS响应中包含RTT值,squid选择ICP应答最先到达的父cache。这时,请求以FIRST_PARENT_MISS被记日志。然而,假如父cache设置了losest-only选项,squid就从不会选择它作为第一个父cache。换句话说,仅当该父cache离原始服务器最近时,才会被选中。
10.6.3 广播ICP

你已知道,ICP的伸缩性差。消息的数量与邻居cache的数量成正比。因为squid发送同样的ICP_QUERY消息到每个邻居,你可以使用广播来减少在网络中传输的消息数量。相对于发送N条消息到N个邻居,squid仅发送一个消息到广播地址。广播路由结构确保每个邻居能接受到消息的复制品。请参阅书籍:Interdomain Multicast Routing: Practical Juniper Networks and Cisco Systems Solutions by Brian M. Edwards, Leonard A. Giuliano, and Brian R. Wright (Addison Wesley) 关于内部网络广播的更多信息。

注意ICP应答总是通过单点传播发送。这是因为ICP应答可能不一样,并且单点传播和广播的路由拓扑不同。因为ICP也用于指示网络条件,故ICP应答应走HTTP应答所采取的同样路径。广播仅减少了查询的消息数量。

历史上,我发现广播结构不稳定和不可信。许多ISP以低优先级来对待广播。广播工作一天,某些事情可能在数天或数周后崩溃。在你自己的网络里使用广播可能是安全的,但我不推荐在公共Internet上使用广播ICP。

10.6.3.1 广播ICP服务器

广播ICP服务器加入1个或多个广播组地址来接受消息。mcast_groups指令指定这些组地址。它的值必须是广播IP地址,或可被解析到广播地址的主机名。IPv4广播地址的范围是:224.0.0.0-239.255.255.255。例如:

mcast_groups 224.11.22.45

关于广播有意思的事情是主机,而不是应用,加入一个组。当某台主机加入广播组时,它接受到发送给该组的所有数据包。这意味着,在选择一个广播组用于ICP时,你必须小心一点。不能选择某个已被其他应用所使用的地址。若这种组交迭情况发生,两个组会串起来,并接受彼此的数据传输。

10.6.3.2 广播ICP客户

广播ICP客户发送查询到某个或多个广播组地址。这样,cache_peer行的主机名参数,必须是(或能解析到)一个广播地址。例如:

cache_peer 224.0.14.1 multicast 3128 3130 ttl=32

HTTP端口号(例如3128)在该情形下是无关的,因为squid从来不会发起HTTP连接到广播邻居。

应该认识到,广播组没有任何访问控制。任何主机能加入任何的广播组地址。这意味着,除非足够小心,其他人也能接受到你的squid发送的广播ICP查询。IP广播有2个方法来阻止包传得太远:TTL和管理范围。因为ICP查询可能携带敏感信息(例如用户访问的URI),我推荐使用管理范围地址,并正确的配置路由器。见RFC 2365的更多信息。

ttl=n选项仅针对广播邻居。它是用于ICP查询的广播TTL值。它控制ICP查询能传送多远。有效值范围是0-128。较大的取值允许广播查询传送更远,并有可能被外面的人截获。使用较低的TTL值,可以保证查询不会离源头太远,并位于自己网络中。

广播ICP客户也必须告诉squid关于响应查询的邻居的情况。squid不盲目信任任何发送ICP应答的cache。你必须告诉squid合法的,可信任的邻居。cache_peer指令的multicast-responder选项用以确定这样的邻居。例如,假如知道172.16.2.3是位于广播组里的可信任的邻居,你可以在squid.conf行里增加如下行:

cache_peer 172.16.3.2 parent 3128 3130 multicast-responder

当然也能使用主机名代替IP地址。来自外部邻居的ICP应答被忽略了,但记录在cache.log里。

正常情况下,squid希望对其发送的每个请求都接受到ICP应答。在广播机制下情况会不同,因为每个查询会导致多个响应。为了统计这点,squid周期性的对广播组地址发送探测消息。这些探测告诉squid,多少服务器在侦听。squid统计特定数量时间内抵达的应答数量。该时间数量由mcast_icp_query_timeout指令给定。然后,当squid发送真正的ICP查询到广播组时,它期望返回前述数量的ICP应答。

10.6.3.3 广播ICP示例

既然广播ICP有点棘手,如下是另一个示例。假设你的ISP有3个父cache侦听在广播地址,等待ICP查询。ISP在其配置文件里仅需要一行:

mcast_groups 224.0.14.255

子cache的配置有点复杂。首先,必须列出squid能发送查询的广播邻居。也必须列出3个父cache的单点传送地址,以便squid能接受它们的应答:

cache_peer 224.0.14.225 multicast 3128 3130 ttl=16 

cache_peer parent1.yourisp.net parent 3128 3130 multicast-responder 

cache_peer parent2.yourisp.net parent 3128 3130 multicast-responder 

cache_peer parent3.yourisp.net parent 3128 3130 multicast-responder

mcast_icp_query_timeout 2 sec

请记住,squid从不会发起HTTP请求到广播邻居,也从不发送ICP查询到广播应答邻居。

10.7 Cache摘要(Cache Digest)

关于ICP的最常见的抱怨在于它增加了每个请求的延时。许多情况下,在squid作出转发决定前,它会等待所有的ICP响应抵达。squid的cache摘要提供了类似的功能,但没有每个请求的网络延时。

Cache摘要基于由Pei Cao首先发布的一项技术,叫做摘要缓存。基本思路是用一个Bloom filter来表现cache内容。邻居cache下载其他每个cache的Bloom filter(也即摘要)。然后,通过查询摘要来决定某个URI是否在邻居的cache里。

相对于ICP,cache摘要以空间交换时间。ICP查询浪费时间(延时),cache摘要浪费空间(内存,磁盘)。在squid中,邻居的摘要完全存放在内存里。在一个典型的摘要里,每百万目标需要大约625KB的内存。

Bloom filter是一种有趣的数据结构,它提供对条目集合的有损耗编码。Bloom filter自身简单的是一个大的位数组。给定某个Bloom filter(和用于产生它的参数),你会发现,不能确定是否某个特定条目位于集合中。在squid中,条目就是URI,摘要大小是每个cache目标5位。例如,要呈现1,000,000个cache目标的集合,需要用到5,000,000位或625,000字节的filter。

因为它们的天性,Bloom filter不能完整的呈现条目集合。它们有时候不正确的指示某个条目位于集合中,因为2个或多个条目可能在同一位上打开。换句话说,filter可能显示目标X位于cache中,即使X从来没被缓存或请求过。通过调整filter的参数,你可以在一定程度上控制这种假情况。例如,增加每个目标的位数量,可以减少这种假情况。请见我在O’Reilly出版的书:Web Caching,可以找到关于Caceh摘要的更多细节。

10.7.1 配置squid的cache摘要

首先,必须在编译squid时激活Cache Digest代码。在运行./configure时简单的增加–enable-cache-digests选项。采用这步导致在运行squid时发生2件事:

  • 1)Squid cache产生它自己内容的摘要。邻居cache如果也配置了使用cache摘要,那可能就会请求这个摘要。
  • 2)Squid请求每个邻居的cache摘要。

假如不想请求某个邻居的cache摘要,就在cache_peer行里使用no-digest选项,例如:

cache_peer neighbor.host.name parent 3128 3130 no-digest

Squid在下述URL中保存它自己的摘要:

http://my.host.name:port/squid-internal-periodic/store_digest. 当squid请求邻居的摘要时,它简单请求:http://neighbor.host.name:port/squid-internal-periodic/store_digest. 明显的,这种命名机制特指squid。假如邻居cache支持cache摘要,但它不是squid,你必须告诉squid邻居摘要的不同地址。cache_peer指令的digest-url=url选项允许你配置邻居的cache摘要URL。例如:

cache_peer neighbor.host.name parent 3128 3130 digest-url=http://blah/digest

squid.conf有许多指令控制squid产生它自己的cache摘要的方法。首先,digest_generation指令控制squid是否产生自己的cache摘要。假如你的cache是一个子cache,而不是其他任何cache的父或姐妹cache,那么你也许想禁止产生摘要。其他指令控制产生摘要的低层次细节。只有当你完全理解了cache摘要的执行原理,你才能改变它们。

digest_bits_per_entry决定摘要的大小。默认值是5。增加该值导致更大的摘要(浪费更多内存和带宽)和更低的假命中可能性。降低该值导致更小的摘要和更多的假命中。我觉得默认值非常合理。等于或低于3的设置导致太多的假命中而不可用,等于或高于8的设置简单的浪费带宽。

Squid使用2个步骤来创建cache摘要。首先,它建立cache摘要数据结构。这基本上是1个大的Bloom filter和包含了摘要参数的小头部。一旦写入了数据结构,squid就会对摘要创建缓存的HTTP响应。它预先包含某些HTTP头部,并和其他缓存响应一起,存储该响应到磁盘。

Cache摘要对某时刻的cache内容维护一个快照。digest_rebuild_period控制squid重建摘要数据结构(并非HTTP响应)的频率。默认是每个小时1次。更频繁的重建意味着squid的摘要更新更快,但会浪费更多的CPU时间。重建过程对CPU影响相对较大。在squid重建摘要过程中,用户会感觉到速度降低。

digest_rebuild_chunk_percentage指令控制每次调用重建函数时,多少cache增加到摘要里。默认是10%。在每次调用重建函数过程中,squid增加一定百分比的cache到摘要里。在该函数运行时,squid不处理用户请求。在增加了指定的百分比后,该函数重新安排它自己的时间表并退出,以便squid能正常处理HTTP请求。在处理完等待请求后,squid返回重建函数,并增加另外的cache块到摘要里。减少该值将给予用户更多的响应时间,然而增加了重建摘要所需的总时间。

digest_rewrite_period指令控制squid从摘要数据结构创建HTTP响应的频率。大部分情形下,这个频率应该匹配digest_rebuild_period值。默认是1小时1次。重写过程由对某个函数的数次调用组成,该函数简单的添加一定数量的摘要数据结构到cache条目里(就象squid正在从网络中读取原始服务器的响应)。每次调用该函数,squid添加appends digest_swapout_chunk_size字节的摘要。

10.8 超文本cache协议(HTCP)

HTCP和ICP有许多共同的特征,尽管HTCP范围更广,并且通常更复杂。两者都使用UDP传输,并且两者都是请求执行的协议。然而,HTCP也有很多与ICP不同之处,即:

  • 1)ICP查询仅包括URI,甚至没有请求方式。HTTP查询包括完整的HTTP请求头部。
  • 2)ICP不提供安全保证。HTCP通过共享密钥,提供附加的消息验证,尽管它还没有在squid中实现。两者都不支持加密消息。
  • 3)ICP使用简单的,修正大小的二进制消息格式,难于扩展。

HTCP使用复杂的,可变大小的二进制消息格式。 HTCP支持4种基本的编码:

TST

测试缓存响应是否存在

SET

告诉邻居更新缓存目标头部

CLR

告诉邻居从其cache里删除一个目标

MON

监视邻居cache的活动

在squid里,当前仅实现了TST编码。本书不讨论其他方面。

使用HTCP相对于ICP的主要优势在于更少的假命中。HTCP有更少的假命中,因为查询消息包含了完整的HTTP请求头部,包含了来自客户端的任何Cache-Control要求。使用HTCP的主要不足在于HTCP查询更大,要求更多的CPU来处理产生和解析消息。测量显示,HTCP查询大约是ICP查询的6倍大,这归咎于HTTP请求头部的存在。然而,squid的HTCP响应典型的比ICP响应小。

HTCP的文档在RFC 2756里,它被作为实验性协议。关系消息格式的更多信息,请见RFC:http://www.htcp.org 或者我的O’Reilly的书,Web Cacheing

10.8.1 配置Squid使用HTCP

为了使用HTCP,必须配置squid使用–enable-htcp选项。当该选项激活时,squid默认变成HTCP服务器。htcp_port指定HTCP端口号,默认是4827。将端口号设为0禁止了HTCP服务模式。

要成为HTCP客户端,必须在cache_peer行里增加htcp选项。当你增加该选项时,squid总是发送HTCP消息,而不是ICP,到邻居cache。不能对单一邻居既使用HTCP又使用ICP。放置ICP端口号的那个域实际上变成了HTCP端口号,所以你必须也要改变它。例如,假如你想将某个ICP邻居变成HTCP的,如下是邻居cache的ICP配置:

cache_peer neighbor.host.name parent 3128 3130

为了转换到HTCP,该行变成:

cache_peer neighbor.host.name parent 3128 4827 htcp

有时候人们忘记改变端口号,这样在发送HTCP消息到ICP端口时,就会犯致命错误。若这点发生,squid在cache.log里写警告日志:

2003/09/29 02:28:55| WARNING: Unused ICP version 23 received from 64.216.111.20:4827

与对待ICP查询不同,Squid当前不记录HTCP查询。HTCP查询也不可在client_list页面跟踪到。然而,若为对等cache激活了HTCP,cache管理器的server_list页面就(见14.2.1.50节)会显示命中和丢失的HTCP响应的计数和百分比。

Histogram of PINGS ACKED: 

        Misses 5085 98% 

        Hits 92 2%

注意,当前还没有哪个squid版本支持HTCP验证。

10.9 Cache数组路由协议(CARP)

CARP是一种在一组缓存代理中,区分开URI的算法。换句话说,每个URI被分配到cache中的一个。CARP在一组cache中,最大化了命中率,最小化了目标重复。该协议由2个主要的成分组成:路由函数和代理数组成员表。不象ICP,HTCP,和Cache摘要,CARP不会预示某个请求是否cache命中。这样,你不能在姐妹关系中使用CARP–仅在父cache中有效。

CARP背后的基本想法是,你有一组(或一个数组)的父cache,它们处理所有来自用户或子cache的负载。cache数组是用来处理日益增长的负载的方法之一。无论何时你需要更高的性能,只需增加更多的数组成员。CARP是一个确定性的算法。也就是说,同样的请求总是走向同一数组成员(只要数组大小不改变)。不象ICP和HTCP,CARP不使用查询消息。

关于CARP另一个有趣的事情是,你可以选择用多种不同方法来扩展它。例如,方法之一是让所有用户代理(user-agent)执行CARP算法。使用一个JavaScript写的Proxy Auto-Configuration(PAC)函数(附录F),你也许能做到这点。然而,某些用户代理可能不能执行或支持PAC文件。方法之二是使用二级cache堆叠。子cache接受来自所有用户代理的请求,然后它们执行CARP算法,为每个请求选择父cache。然而,除非你的网络够大,否则太多的cache带来的负担可能大过受益。最后一种方法,你也能在数组自身内执行CARP。也就是说,用户代理连接到cache数组中的随机成员,但每个成员转发cache丢失到基于CARP算法的数组的其他成员。

CARP被设计为比简单的哈希算法更好,后者典型的通过应用某个哈希函数(例如MD5)到URI来工作。然后该算法计算数组成员数量的系数。它可能象如下这段伪代码一样简单:

N = MD5(URI) % num_caches; 

next_hop = Caches[N];

该技术在所有cache中均一的分摊URI。它也提供兼容性映射(最大化cache命中),只要cache的数量保持不变。然而,当增加或删除cache时,该算法改变大部分URI的映射。

CARP的路由函数在该技术中以2种方式得以改进。首先,它允许不均衡共享负载。例如,可以配置一个父cache接受的请求数量是另一个的2倍。第二,增或删数组成员最小化了被再分配的URI的片断。

CARP的负面影响是它相对消耗CPU时间。对每个请求,squid为每个父cache打分。该请求被发送到分数最高的父cache。该算法的复杂性与父cache的数量成正比。换句话说,CPU负载的增加,与CARP父cache的数量成正比。然而,CARP里的算术已被设计成比MD5和其他加密哈希函数更快。

除了负载共享算法,CARP也有个协议成分。成员表有定义良好的结构和语法,以便单一数组里的所有客户端能有相同的配置。假如某些客户端配置得不同,CARP变得没什么用,因为并非所有客户端发送同样的请求到同一父cache。注意squid当前没有实现成员表功能。Squid的CARP实现在另外一方面也有缺陷。协议认为,假如某个请求不能被转发到最高分的父cache,它就被发送到次高分的成员。假如又失败,应用就会放弃。squid当前仅使用最高分的父cache。

CARP的原始文档是一份1998年的Internet草稿,现在已经过期。它由下述2人开发:Vinod Valloppillil of Microsoft and Keith W. Ross of the University of Pennsylvania。查询一下,你还可以在Internet上找到老文档。甚至能在Microsoft的站点上找到一些文档。在我的O’Reilly的书Web Caching里,你能找到更多信息。

10.9.1 配置Squid使用CARP

为了在squid里使用CARP,必须首先在运行./configure脚本时使用–enable-carp选项。接着,必须为属于数组成员的父cache,增加carp-load-factor选项到cache_peer行。如下是示例:

cache_peer neighbor1.host.name parent 3128 0 carp-load-factor=0.3 

cache_peer neighbor2.host.name parent 3128 0 carp-load-factor=0.3 

cache_peer neighbor3.host.name parent 3128 0 carp-load-factor=0.4

注意,所有的carp-load-factor值必须合计为1.0。Squid会检查该条件,假如有差错,它会抱怨。另外,cache_peer行必须以负载因素值的增量来列举。仅仅近来的squid版本会检查该条件是否真。

记住,在关于邻居cache的存活/死亡状态方面,CARP在一定程度上有些特殊。正常情况下,在10次失败连接后,squid宣布邻居死亡(并中止发送请求到它)。然而在CARP情况中,squid会跳过某个有1次或多次失败连接的父cache。一旦squid使用CARP工作,就可以使用cache管理器的carp页面来监视它。请见14.2.1.49的更多信息。

10.10 归纳所有

现在你可能意识到,squid有许多不同的方法,来决定请求如何转发,和转发到哪里。许多情况下,你可能同时使用不止一种的协议或技术。然而,仅仅通过观察配置文件,难以发现squid如何联合使用这些不同技术。在本节我将解释,squid实际上怎样做出转发决定。

显而易见,一切从cache丢失开始。任何作为未确认的cache命中而得到满足的请求,不会在下述节里描述。

选择程序的目的是,创建适当的下一跳位置列表。下一跳位置可能是邻居cache,或原始服务器。依赖于配置不同,squid可能选择3个下一跳。假如请求不能首先满足,squid接着尝试第2个,然后是第3个。

10.10.1 步骤1:直接决定选项

第一步决定某个请求:它可能,必须,或不必直接发送到原始服务器。squid会参考该请求的never_direct和always_direct访问规则列表。目的是为了给下面3个值设置1个标记:DIRECT_YES, DIRECT_MAYBE, 或DIRECT_NO。该标记随后决定squid是否为该请求选择1个邻居cache。squid按顺序检查下述条件。假如任何一个条件为真,它设置direct标记,并跳到步骤2。假如你在跟进源代码,该步相应于peerSelectFoo( )函数的开始:

  • 1.Squid首先查找always_direct列表。假如请求匹配该列表,direct标记设为DIRECT_YES。
  • 2.Squid接着查找never_direct列表。假如请求匹配该列表,direct标记设为DIRECT_NO。
  • 3.对看起来陷入转发死循环的请求,squid有特殊的检查方法。当squid检查到1个转发死循环,它将direct标记设为DIRECT_YES来打破循环。
  • 4.仅在激活了netdb情况下,squid才检查minimum_direct_hops和minimum_direct_rtt的设置。假如测量跳计数,或往返时间低于配置的值,squid设置direct标记为DIRECT_YES。
  • 5.假如前述条件没有真的,squid设置direct标记为DIRECT_MAYBE。
10.10.2 步骤2:邻居选择协议

在这里,squid使用堆叠协议之一来选择邻居cache。象以前一样,一旦squid在本步里选择了邻居,它直接跳到步骤3。该步粗略的相当于peerGetSomeNeighbor( )函数:

  • 1. Squid检查邻居的Cache摘要。假如摘要指示cache命中,该邻居就放到下一跳列表。
  • 2.Squid尝试CARP(假如激活了)。CARP总是成功(例如选择一个父cache),除非cache_peer_access或cache_peer_domain规则,禁止某条特殊请求与任何父cache通信。
  • 3.Squid检查netdb度量(假如激活了),选择最近父cache。假如squid了解到,从1台或多台父cache到原始服务器的往返时间,少于它自己到原始服务器的RTT,squid就选择最少RTT的父cache。这点若发生,必须符合下列条件:
    • 1)Squid和父cache都必须激活netdb功能;
    • 2)query_icmp必须在配置文件里激活;
    • 3)原始服务器必须响应ICMP ping消息;
    • 4)父cache以前必须测量过到原始服务器的RTT,并在ICP/HTCP应答里(或通过netdb交换)返回这些测量值。
  • 4.作为最后的手段,Squid发送ICP/HTCP查询。squid遍历所有的邻居,并检查许多条件。squid在如下情况下不查询邻居:
    • 1)direct标记是DIRECT_MAYBE,请求不可层叠(见10.4.5节)。因为squid能直接到达原始服务器,它不会将这个请求转发到邻居,该请求看起来不可缓存。
    • 2)direct标记是DIRECT_NO,邻居是姐妹关系,并且请求不可层叠。因为squid被强迫要求使用邻居cache,它就仅查询父cache,后者总能处理cache丢失。
    • 3)cache_peer_access或cache_peer_domain规则禁止发送该请求到邻居。
    • 4)邻居设置了no-query标记,或者其ICP/HTCP端口号是0。
    • 5)邻居是广播应答者。
  • 5.Squid计算它发出了多少查询,并估计该收到多少应答。假如它期望至少一个应答,下一跳选择过程会延时,直到应答到达,或超时发生。Squid期望从生存着的邻居cache那里接受到应答,而不是死亡的邻居(见10.3.2节)。
10.10.3 步骤2a:ICP/HTCP应答处理

假如Squid发出任何ICP或HTCP查询,它会等待一定数量的应答。在发送出查询后,Squid知道它期待多少应答,和等待应答的最长时间。Squid期待来自被查询的生存的邻居的1个应答。假如使用广播,squid将当前组大小的估计值增加到期待的应答计数里。在等待应答时,有可能1个或多个应答迟迟不来,这样squid会安排1个超时时间表。

当squid接受到来自邻居cache的ICP/HTCP应答时,它采取如下动作:

  • 1.假如应答是1个命中,squid立即转发请求到那个邻居。在该点后抵达的任何应答都被忽略。
  • 2.假如应答是1个丢失,并且它来自姐妹cache,那么就被忽略。
  • 3.Squid不立刻对来自父cache的ICP/HTCP丢失采取动作。代替的,它记住哪个父cache符合下列标准:
    • closest-parent miss
    • 假如应答包含netdb RTT值,squid会记住到原始服务器的RTT最少的父cache。
    • first-parent miss
    • Squid会记住第一应答的父cache。换句话说,就是到你的cache的RTT最少的父cache。2个cache_peer选项影响这种选择算法:weight=N和closest-only。weight=N选项让父cache比它实际更近。在估算RTT时,squid使用该权重来划分实际RTT。通过增加父cache的权重,可以给予它们更高的优先级。closest-only禁止邻居的第一父cache丢失特性。换句话说,squid仅在父cache离原始服务器最近时,才会选择这个父cache(基于ICP/HTCP丢失应答)。
  • 4.假如squid接受到期待数量的应答(所有丢失),或超时发生,它选择最近父cache(closest-only)丢失邻居(假如设置了)。否则,它选择第一父cache(first-parent)丢失邻居(假如设置了)。

Squid也许不会接受到任何来自父cache的ICP/HTCP应答,因为父cache有可能没被查询到,或因为网络丢包。在该情形下,squid信赖从父cache选择算法,在下面节里描述。

假如在接受到期待数量的应答前,ICP/HTCP查询超时,squid预先考虑将access.log里的字符串TIMEOUT_的值设为结果码。

10.10.4 步骤3:从父cache选择

该步有点棘手。记住假如direct标记是DIRECT_YES,squid就不会执行这步。假如这个标记是DIRECT_NO,并且第2步选择失败,squid会调用getSomeParent( )函数(随后描述)来选择备份父cache。接着,squid将所有它认为是存活的父cache追加到列表。这样,在返回错误消息到用户前,它会尝试所有可能的父cache。

在DIRECT_MAYBE情形下,squid会同时增加父cache和原始服务器到列表。顺序依赖于prefer_direct设置。假如激活了prefer_direct,squid首先将原始服务器插入列表中。接着,假如请求是可层叠的,或假如禁用了nonhierarchical_direct指令,squid会调用getSomeParent()函数。最终,假如禁止了prefer_direct,squid才最后将原始服务器加到列表中。

getSomeParent()函数基于下列标准来选择父cache之一。在每种情形下,父cache必须是存活的,并允许依照cache_peer_access和cache_peer_domain规则来处理请求:

  • 1)第一个父cache,有默认的cache_peer选项。
  • 2)父cache的round-robin cache_peer选项,有最低的请求数量。
  • 3)第一个父cache,已知是存活的。
10.10.5 重试

偶然情况下,因为某些理由,squid试图转发请求到原始服务器或邻居cache可能会失败。这就是为什么squid在邻居选择过程中,要创建一个下一跳位置列表的原因。当下列类型的错误之一发生时,squid重新尝试请求列表里的下一个服务器:

  • 1)网络拥塞或其他错误可能导致“连接超时”。
  • 2)原始服务器或邻居cache可能临时不可到达,导致“连接拒绝”错误。
  • 3)假如请求导致cache丢失,姐妹cache可能返回504(网关超时)错误。
  • 4)假如2个cache在访问控制策略里配错了,邻居cache可能返回“访问拒绝”错误消息。
  • 5)在squid读HTTP消息主体前,可能在已建立的连接上发生读错误。
  • 6)持久连接可能存在紊乱情况。Squid的重试失败请求的算法相对强健。与其让squid对用户返回错误,不如让它再试一次(当然会有延时)。
10.11 该怎么做?

Squid新手经常问同样的或相似的问题,关于如何让squid正确的转发请求。这里我将告诉你,在普通这种情况下,如何配置Squid。

10.11.1 通过另外的代理发送所有请求?

简单的只需定义父cache,并告诉squid它不允许直接连接到原始服务器。例如:

cache_peer parent.host.name parent 3128 0 

acl All src 0/0 

never_direct allow All

该配置的弊端是,假如父cache down掉,squid不能转发cache丢失。假如这种情况发生,用户会接受到“不能转发”的错误消息。

10.11.2 通过另外的代理发送所有请求,除非它down了?

试试这个配置:

nonhierarchical_direct off 

prefer_direct off

cache_peer parent.host.name parent 3128 0 default no-query

或者,假如你喜欢对其他代理使用ICP:

nonhierarchical_direct off 

prefer_direct off 

cache_peer parent.host.name parent 3128 3130 default

在该配置下,只要父cache存活,squid就会将所有cache丢失转发给它。使用ICP可以让squid快速检测到死亡的父cache,但同时在某些情形下,可能不正确的宣称父cache死亡。

10.11.3 确认squid对某些请求,不使用邻居cache吗?

定义1条ACL来匹配特殊的请求:

cache_peer parent.host.name parent 3128 0 

acl Special dstdomain special.server.name always_direct

allow Special

在该情形下,对special.server.name域的请求的cache丢失,总是发送到原始服务器。其他请求也许,或也许不,通过父cache。

10.11.4 通过父cache发送某些请求来绕过本地过滤器?

某些ISP(或其他组织)有上级服务提供者,他们强迫HTTP传输通过包过滤代理(也许使用HTTP拦截)。假如你能在他们的网络之外使用不同的代理,那就能绕过其过滤器。这里显示你怎样仅发送特殊的请求到远端的代理:

cache_peer far-away-parent.host.name parent 3128 0 

acl BlockedSites dstdomain www.censored.com 

cache_peer_access far-away-parent.host.name allow BlockedSites

never_direct allow BlockedSites

第11章 重定向器

重定向器是squid的外部程序,它重写来自客户请求的URI。例如,尽管某个用户请求这个页面:http://www.example.com/page1.html ,重定向器可以将请求改变到别的地方,例如:http://www.example.com/page2.html 。squid自动抓取新的URI,就像是客户端的原始请求一样。假如响应可被缓存,squid将它存储在新的URI下。

重定向功能允许你执行与squid相关的许多有趣事情。许多站点使用它们实现如下目的:访问控制,移除广告,本地镜像,甚至用以绕开浏览器的bug。

关于使用重定向器进行访问控制的好处之一是,你可以将用户的请求重定向到某个页面,这个页面详细解释为何她的请求被拒绝。你也会发现重定向器比squid内建的访问控制提供更多的弹性。然而不久你会看到,重定向器并不能访问包含在客户请求里的完整信息。

许多人使用重定向器来过滤web页面广告。大部分情形下,可以将对GIF或JPEG广告图片的请求,改变为请求位于本地服务器上的,小而空的图片。这样,广告就消失了,然而不会影响页面布局。

所以在本质上,重定向器其实就是一个程序,它从标准输入里读取URI和其他信息,并将新的URI写往标准输出。Per和Python是写重定向器的流行语言,尽管某些作者使用编译性语言(例如C)以求更好的性能。

Squid的源代码没有包含任何重定向程序。作为管理员,你有责任编写自己的重定向器,或者下载别人编写的。该章开头部分描述在squid和重定向进程之间的接口。我也提供几个简单的Perl重定向器示例。假如你志在使用别人的重定向器,而不是自己编写,请跳到11.3章。

11.1 重定向器接口

重定向器在其标准输入里,每次一行的接受来自squid的数据。每行包括下列四个元素,以空格分开:

  • 1)请求URI
  • 2)客户IP地址和完全可验证域名
  • 3)用户名,通过RFC 1413 ident或代理验证
  • 4)HTTP请求方式

例如:

http://www.example.com/page1.html 192.168.2.3/user.host.name jabroni GET

请求URI取自客户请求,包括任何查询条件。然而,分段标记(例如#字符和随后的文本)被移除了。

第二个元素包含客户IP地址,和可选的完整可验证域名(FQDN)。假如激活了log_fqdn指令或使用了srcdomain ACL元素,FQDN才会设置。尽管那样,FQDN也许仍未知,因为客户网络管理员没有在其DNS里正确的设置反向指针区域。假如squid不知道客户的FQDN,它用一个短横线(-)代替。例如:

http://www.example.com/page1.html 192.168.2.3/- jabroni GET

假如squid了解请求背后的用户名,客户ident域才会设置。假如使用了代理验证,ident ACL元素,或激活了ident_lookup_access,这点才会发生。然而请记住,ident_lookup_access指令不会导致squid延缓请求处理。换句话说,假如你激活了该指令,但没有使用访问控制,squid在写往重定向进程时,也许仍不知道用户名。假如squid不知道用户名,它显示一个短横线(-)。例如:

http://www.example.com/page1.html 192.168.2.3/- - GET

Squid从重定向进程里读回一个元素:URI。假如squid读取一个空行,原始URI保留不变。

重定向程序永不退出,除非在标准输入里发生end-of-file。假如重定向进程确实过早退出,squid在cache.log里写一条警告信息:

WARNING: redirector #2 (FD 18) exited

假如50%的重定向进程过早退出,squid会以致命错误消息退出。

11.1.1 处理包含空格的URI

假如请求URI包含空格,并且uri_whitespace指令设置为allow,那么任何在URI里的空格被递交到重定向器。如果重定向器的解析器很简单,那它在这种情况下会很困惑。在使用重定向器时,有2个选项来处理URI里的空格。

一个选项是设置uri_whitespace指令为任何值,除了allow。默认的设置strip,在大多数情况下可能是个好的选择,因为squid在解析HTTP请求时,它简单的从URI里删除空格。该指令的其他值的信息,请见附录A。

假如上述方法不可行,你必须确保重定向器的解析器足够巧妙,以检测额外的元素。例如,假如它发现接受自squid的行里的元素不止4个,它会假设最后3个元素是IP地址,ident,和请求方式。在最后3个元素之前的任何东西,组成请求URI。

11.1.2 产生HTTP重定向消息

当某个重定向器改变客户的URI时,它通常不知道squid决定抓取新的资源。也就是说,这点违背了HTTP RFC。假如你想友好而保留兼容性,有一个小窍门可让squid返回HTTP重定向消息。简单的让重定向器在新的URI前面插入301:, 302:, 303:, 或307:。

例如,假如重定向器在其标准输出里写如下行:

301:http://www.example.com/page2.html

Squid返回类似如下的响应到客户端:

HTTP/1.0 301 Moved Permanently

Server: squid/2.5.STABLE4

Date: Mon, 29 Sep 2003 04:06:23 GMT

Content-Length: 0

Location: http://www.example.com/page2.html

X-Cache: MISS from zoidberg

                Proxy-Connection: close
11.2 重定向器示例

示例11-1是用perl写的非常简单的重定向器。它的目的是,将对squid-cache.org站点的HTTP请求,发送到位于澳洲的本地镜像站点。对看起来是请求www.squid-cache.org或其镜像站点之一的URI,该脚本输出新的URI,将主机名设为 www1.au.squid-cache.org.

重定向程序遇到的通用问题是缓存I/O。注意这里我确保stdout不可缓存。

Example 11-1. A simple redirector in Perl

#!/usr/bin/perl -wl

$|=1;   # don't buffer the output

while (<>) {

        ($uri,$client,$ident,$method) = ( );

        ($uri,$client,$ident,$method) = split;

        next unless ($uri =~ m,^http://.*\.squid-cache\.org(\S*),);

        $uri = "http://www1.au.squid-cache.org$1";

} continue {

        print "$uri";

}

示例11-2是另一个稍微复杂点的脚本。在这里我做了个初步尝试,当URI包含不当词汇时,拒绝该请求。该脚本论证了解析输入域的另一个方法。假如没有得到所有5个请求域,重定向器返回一个空行,请求保留不变。

该示例也优待某些用户。假如ident等于”BigBoss,” ,或来自192.168.4.0子网,请求就直接通过。最后,我使用301:窍门来让squid返回HTTP重定向消息到客户端。注意,本程序既非有效的,又非足够巧妙的,来正确拒绝坏请求。

Example 11-2. A slightly less simple redirector in Perl

#!/usr/bin/perl -wl

$|=1;   # don't buffer the output

$DENIED = "http://www.example.com/denied.html";

&load_word_list( );

while (<>) {

        unless (m,(\S+) (\S+)/(\S+) (\S+) (\S+),) {

                $uri = '';

                next;

        }

        $uri = $1;

        $ipaddr = $2;

        #$fqdn = $3;

        $ident = $4;

        #$method = $5;

        next if ($ident eq 'TheBoss');

        next if ($ipaddr =~ /^192\.168\.4\./);

        $uri = "301:$DENIED" if &word_match($uri);

} continue {

        print "$uri";

}

sub load_word_list {

        @words = qw(sex drugs rock roll);

}

sub word_match {

        my $uri = shift;

        foreach $w (@words) { return 1 if ($uri =~ /$w/); }

        return 0;

}

关于编写自己的重定向器的更多主意,我推荐阅读11.5章里提到的重定向器的源代码。

11.3 重定向器池

重定向器可能经过任意长的时间才返回应答。例如,它可能要查询数据库,搜索正则表达式的长列表,或进行复杂的计算。squid使用重定向进程池以便它们能并行工作。当某个重定向器忙时,squid将请求递交给另一个。

对每个新请求,squid按顺序检查重定向进程池。它将请求提交给第一个空闲进程。假如请求率非常低,第一个重定向器也许自己能处理所有请求。

可以使用 redirect_children指令来控制重定向器池的size。默认值是5个进程。注意squid不会根据负载来动态的增或减进程池的size。这样,建议你适当的放宽size限制。假如所有的重定向器忙碌,squid会将请求排队。假如队列变得太大(大于进程池size的2倍),squid以致命错误消息退出:

FATAL: Too many queued redirector requests

在该情形下,你必须增加重定向器池的size,或改变其他东西以让重定向器能更快的处理请求。你可以使用cache管理器的redirector页面来发现是否有太少,或太多重定向器在运行。例如:

% squidclient mgr:redirector

...

Redirector Statistics:

program: /usr/local/squid/bin/myredir

number running: 5 of 5

requests sent: 147

replies received: 142

queue length: 2

avg service time: 953.83 msec

      #      FD     PID  # Requests     Flags      Time  Offset Request

      1      10   35200          46     AB        0.902       0 http://...

      2      11   35201          29     AB        0.401       0 http://...

      3      12   35202          25     AB        1.009       1 cache_o...

      4      14   35203          25     AB        0.555       0 http://...

      5      15   35204          21     AB        0.222       0 http://...

在该示例里,假如你见到最后一个重定向器的请求数量,几乎和倒数第二个一样多,就应该增加重定向器池的size。另一方面,假如你见到许多重定向器没有请求,就该减少进程池的size。

11.4 配置Squid

下列5个squid.conf指令,控制squid里的重定向器的行为。

11.4.1 redirect_program

redirect_program指令指定重定向程序的命令行。例如:

redirect_program /usr/local/squid/bin/my_redirector -xyz

注意,重定向程序必须能被squid的用户ID执行。假如因为某些理由,squid不能执行重定向器,你将在cache.log里见到错误消息。例如:

ipcCreate: /usr/local/squid/bin/my_redirector: (13) Permission denied

因为squid的工作方式,主squid进程可能不知道执行重定向程序的问题所在。squid不会检测到错误,直到它试图写一个请求和读到一个响应。然后它打印:

WARNING: redirector #1 (FD 6) exited

这样,假如你见到发送给squid的第一个请求的如此错误,请仔细检查cache.log的其他错误,并确保重定向程序可被squid执行。

11.4.2 redirect_children

redirect_children指令指定squid应该开启多少重定向进程。例如:

redirect_children 20

当所有重定向器同时忙碌时,squid会通过cache.log发出警告:

WARNING: All redirector processes are busy.

WARNING: 1 pending requests queued.

假如见到这样的警告,你应该增加子进程的数量,并重启(或reconfigure)Squid。假如队列的size变成重定向器数量的2倍,squid以致命错误退出。

不要试图将redirect_children设为0来禁止squid使用重定向器。简单的从squid.conf里删除redirect_program行就可以了。

11.4.3 redirect_rewrites_host_header

正常情况下,squid在使用重定向器时,会更新请求的Host头部。也就是说,假如重定向器返回的新URI里包含不同的主机名,squid将新的主机名放在Host头部。假如使用squid作为代理人(surrogate,见15章),你也许想将redirect_rewrites_host_header指令设为off来禁止这种行为:

redirect_rewrites_host_header off
11.4.4 redirector_access

正常情况下,squid将每个请求发送往重定向器。然而,可以使用redirector_access规则来有选择的发送某些请求。该语法与http_access相同:

redirector_access allow|deny [!]ACLname ...

例如:

acl Foo src 192.168.1.0/24

acl All src 0/0

redirector_access deny Foo

redirector_access allow All

在该情形里,对任何匹配Foo ACL的请求,Squid跳过重定向器。

11.4.5 redirector_bypass

假如激活了redirector_bypass指令,squid在所有重定向器忙碌时,会绕过它们。正常情况下,squid将未处理请求排队,直到某个重定向进程可用。假如该队列增长得太大,squid以致命错误退出。激活该指令确保squid永不会达到那种状态。

当然,折衷点是当负载高时,某些用户请求可能不会被重定向。假如这样对你没问题,简单的激活该指令即可:

redirector_bypass on
11.5 流行的重定向器

我已经提过,squid的源代码未包含任何重定向器。然而,通过http://www.squid-cache.org的Related Software页面的链接,可以找到许多有用的第三方重定向器。如下是一些流行的重定向器:

11.5.1 Squirm

http://squirm.foote.com.au/

Squirm出自Chris Foote之手。它用C编写,并在GNU公用许可证(GPL)下发布源代码。Squirm的功能包括:

  • 1)非常快速,最少的内存使用
  • 2)完全正则表达式匹配和替换
  • 3)对不同的客户组应用不同的重定向列表
  • 4)命令行的交互式模式的测试
  • 5)防故障模式,假如配置文件包含错误,它不对请求作任何改变
  • 6)将debug信息,错误信息,和其他更多信息写往不同日志文件
11.5.2 Jesred

http://www.linofee.org/~elkner/webtools/jesred/

Jesred出自Jens Elkner之手。它用C编写,基于Squirm而来,也在GNU GPL下发行。其功能包括:

  • 1)比Squirm更快,但内存使用稍多
  • 2)在运行时能重读配置文件
  • 3)完全正则表达式匹配和替换
  • 4)防故障模式,假如配置文件包含错误,它不对请求作任何改变
  • 5)可选择的记录重写请求到日志文件
11.5.3 squidGuard

http://www.squidguard.org/

squidGuard出自Tele Danmark InterNordia的Pal Baltzersen和Lars Erik Haland。它在GNU GPL下发行。作者确保squidGuard在现代Unix系统上能轻松编译。他们的站点包含了许多好文档。如下是squidGuard的一些功能:

  • 1)高度可配置;你能在不同的时间,应用不同的规则到不同的客户组
  • 2)URI置换,而非仅仅替换
  • 3)printf形式的置换,允许递交参数给CGI脚本来定制消息
  • 4)支持重定向器的 301/302/303/307 HTTP重定向状态码功能
  • 5)可选择的重写规则日志记录

在squidGuard的站点,还可以找到超过100,000个站点的黑名单,它们以色情,暴力,毒品,黑客,广告,和其他更多形式来分类。

11.5.4 AdZapper

http://www.adzapper.sourceforge.net

AdZapper是个流行的重定向器,因其明确的目标是从HTML页面里移除广告。它是 Cameron Simpson所写的Perl脚本。AdZapper能阻止横幅图片,弹出式窗口,flash动画,页面计数器,和web bug。该脚本包含正则表达式列表,用以匹配某些已知包含广告,弹窗等的URI。Cameron定期更新该脚本的模式匹配。你也能维护你自己的模式匹配列表。

第12章 验证辅助器

先前我在6.1.2.12章里谈起过代理验证。然而,我仅仅解释了如何编写用于代理验证的访问控制规则。这里,我将告诉你如何选择和配置部分验证辅助器。

回想一下,Squid支持三种方式用于从用户端采集验证信用项:基本,摘要(Digest),和NTLM。这些方式指定squid如何从客户端接受用户名和密码。从安全观点看,基本验证非常脆弱。摘要和NTML验证显然更强壮。对每种方式,squid提供一些验证模块,或辅助进程,用于实际处理认证的过程。

我提到的所有验证辅助器都包含在squid的源代码发布里。你可以在编译时使用./configure选项来指定它们的目录名。例如:

% ls helpers/basic_auth

LDAP                    NCSA                    getpwnam

MSNT                    PAM                     multi-domain-NTLM

Makefile                SASL                    winbind

Makefile.am             SMB

Makefile.in             YP

% ./configure --enable-basic-auth-helpers=LDAP,NCSA ...

辅助器程序正常安装在$prefix/libexec目录。

如同重定向器一样,squid使用一个验证辅助器进程池。某个验证请求会被送往第一个空闲辅助器。当所有验证器进程都忙时,squid将未处理请求放进队列。假如队列变得太大,squid会以致命错误消息退出。大部分情况下,squid缓存验证结果。这样就减少了辅助器进程的负载,并改进了响应时间。

12.1 配置Squid

auth_param指令控制了配置squid的验证辅助器的每个方面。不同的方式(基本,摘要,NTLM)有一些共性,也有一些唯一的参数。紧跟在auth_param后的第一个参数必须是basic, digest, 或ntlm之一。我将在随后章节里,详细讲解每种验证机制的配置细节。

除了auth_param外,squid还有2个指令影响到代理验证。可以使用max_user_ip ACL来阻止用户与其他人共享用户名和密码。假如squid检测到相同的用户名来自太多不同IP地址,该ACL被匹配,就可以拒绝这样的请求。例如:

acl FOO max_user_ip 2

acl BAR proxy_auth REQUIRED

http_access deny FOO

http_access allow BAR

在该情形中,假如用户从3个或更多的不同IP地址提交请求,squid就拒绝该请求。authenticate_ip_ttl指令控制squid记住每个用户的源IP地址多长时间。对于经常改变IP地址的用户,更小的TTL可能好点。在一个用户的IP地址很长时间不变的环境里,可以使用较大的TTL。

12.2 HTTP基本验证

基本验证最简单,然而最不安全。它本质上以明文来传送用户密码,尽管密码被编码成可打印字符。例如,假如用户敲入其用户名Fannie和密码FuRpAnTsClUb,用户代理首先将这2者结合到一个单一串里,以冒号来分割用户名和密码:

Fannie:FuRpAnTsClUb

然后它用base64方法(定义在RFC 2045)来编码这个串。它在HTTP头部里看起来如此:

Authorization: Basic RmFubmllOkZ1UnBBblRzQ2xVYgo=

若有人碰巧捕获到用户的HTTP请求,他能轻易获取到用户名和密码:

% echo RmFubmllOkZ1UnBBblRzQ2xVYgo= | /usr/local/lib/python1.5/base64.py -d

Fannie:FuRpAnTsClUb

遵循HTTP/1.1 RFC的要求,squid不会转发验证信用项到其他服务器。换句话说,假如信用项是用于访问squid的,Authorization头部会从外出请求里移除。

你会注意到,某些基本验证器可被配置来检查系统密码文件。因为基本信用项不被加密,所以在cache访问密码里包含登陆密码是个坏想法。假如选择使用getpwnam验证器,你应该完全理解让用户密码以明文在网络中传送的意义。

HTTP基本验证支持下列auth_param参数:

auth_param basic program command

auth_param basic children number

auth_param basic realm string

auth_param basic credentialsttl time-specification

program参数指定验证辅助程序的命令及其参数。大多数情况下,这里是到某个验证辅助程序的路径名。它们默认安装在/usr/local/squid/libexec下。

children参数告诉squid使用多少辅助器进程。默认值是5,假如你不了解需要多少进程来处理请求,这个值就是个好起点。假如指定得太少,squid会在cache.log里告警。

realm参数是在提示输入用户名和密码时,用户代理显示给用户看的验证域字符串。可以使用一些简单的句子,例如“访问squid的缓存代理”。

credentialsttl参数指定squid内在的缓存验证结果的时间数量。较大的值减少了外部验证器进程的负载,但加长了刷新期,直到squid检测到验证数据库的改变。注意,这仅影响到积极结果(例如成功的验证),消极的结果不会被squid缓存。默认的TTL值是2小时。

如下是个完整的示例:

auth_param basic program /usr/local/squid/libexec/pam_auth

auth_param basic children 10

auth_param basic realm My Awesome Squid Cache

auth_param basic credentialsttl 1 hour

acl KnownUsers proxy_auth REQUIRED

http_access allow KnownUsers

下面我将讨论squid自带的基本验证辅助器程序。

12.2.1 NCSA
./configure —enable-basic-auth-helpers=NCSA

NCSA验证辅助器相对流行,这归咎于它的简单性和历史原因。它将用户名和密码存储在一个单独的文本文件里,类似于Unix的/etc/passwd文件。这个密码文件格式最初是作为NCSA HTTP服务器项目的一部分发展而来的。在squid.conf里,只须指定密码文件的路径作为程序的单一命令行参数。

auth_param basic program /usr/local/squid/libexec/ncsa_auth

/usr/local/squid/etc/passwd

可以使用Apache自带的htpasswd程序来创建和更新密码文件。也可以在 http://www.squid-cache.org/htpasswd/ 这里下载。在该页面里,你也可以下载chpasswd CGI脚本,它允许用户改变自己的密码(假如必要)。

12.2.2 LDAP
./configure —enable-basic-auth-helpers=LDAP

LDAP辅助器是到轻量级目录访问协议(LDAP)服务器的接口。在编译squid_ldap_auth辅助器之前,OpenLDAP库和头文件必须安装到系统中。可以在这里找到OpenLDAP:http://www.openldap.org/.

squid_ldap_auth程序至少需要2个参数:基本开放名(DN)和LDAP服务器主机名。例如:

auth_param basic program /usr/local/squid/libexec/squid_ldap_auth

-b "ou=people,dc=example,dc=com"  ldap.example.com

LDAP辅助器有Unix的man页,描述了其所有选项和参数。然而,在运行make install时,通常并未安装squid的这个man页。进入源代码树,手工运行nroff,你可以读到这个man页。例如:

% cd helpers/basic_auth/LDAP

% nroff -man squid_ldap_auth.8 | less
12.2.3 MSNT
./configure —enable-basic-auth-helpers=MSNT

MSNT验证器是通过服务消息块(SMB)协议到Microsoft NT域数据库的接口。它使用一个小配置文件,叫做msntauth.conf,它必须放在$prefix/etc或–sysconfidr目录。在该配置文件里,最多可以指定5个NT域控制器。例如:

server pdc1_host bdc1_host my_nt_domain

server pdc2_host bdc2_host another_nt_domain

默认情况下,MSNT验证器允许服务器验证任何用户。然而,它也能允许或拒绝指定用户名。假如创建一个allowusers文件,仅仅在该文件里列出的用户可允许访问squid。假如你的NT服务器上有很多用户,但仅仅允许少数用户使用cache,那就可以用到这个功能。另外,可以创建一个denyusers文件。任何列举在该文件里的用户会被拒绝访问,这点甚至发生在检查allowusers文件之前。

可选择的,还可以把用户名放在proxy_auth ACL里,从而允许或拒绝指定用户名,请见6.1.2.12章的描述。

附加的文档,请见helpers/basic_auth/MSNT目录下的README.html文件。

12.2.4 Multi-domain-NTLM
./configure —enable-basic-auth-helpers=multi-domain-NTLM

multi-domain-NTLM验证器类似于MSNT,两者都会查询Windows NT域数据库。不同于MSNT最多查询5个域控制器,multi-domain-NTLM验证器要求用户在其用户名前插入NT域名,象这样:

ntdomain\username

multi-domain-NTLM辅助器程序是个相对较短的perl脚本。它依赖于CPAN的Authen::SMB包。假如你没有在perl脚本里硬编码域控制器的主机名,它会利用Samba包里的nmblookup程序来自动查找它们。perl脚本命名为smb_auth.pl,它在squid.conf里看起来如下:

auth_param basic program /usr/local/squid/libexec/smb_auth.pl

multi-domain-NTLM的文档很少,但假如你熟悉perl,通过阅读源代码就可以了解更多。

12.2.5 PAM
./configure —enable-basic-auth-helpers=PAM

感觉上,插件式验证模块(PAM)是在验证方式(例如一次性密码, kerberos, smart cards)和要求验证服务的应用(例如ssh,ftp,imap)之间的胶合剂。系统的/etc/pam.conf文件描述了对每种应用使用何种验证方式。

为了使用squid的PAM验证辅助器,你必须将”squid”作为一个服务增加到/etc/pam.conf文件,并且指定使用哪个PAM模块。例如,为了使用FreeBSD的Unix密码文件,必须将这个放在pam.conf里:

squid  auth  required  pam_unix.so  try_first_pass

为了检查Unix密码数据库,pam_auth进程必须以root运行。这是个安全风险,你必须手工设置可执行setuid root。假如pam_auth不以root运行,并且它被配置为检查Unix密码数据库,那么每个验证请求都会失败。

PAM验证器的文档以man页形式提供,可在helpers/basic_auth/PAM目录下找到。

12.2.6 SASL
./configure —enable-basic-auth-helpers=SASL

简单验证和安全层(SASL)是个IETF提议标准,文档在RFC 2222里。它是个为面向连接的协议(例如FTP,SMTP,HTTP)提供安全参数协商的协议。然而,SASL验证器类似于PAM验证器。它使用第三方库接口,查询许多不同的验证数据库。

特别的,squid的SASL验证器要求Cyrus SASL库,它由Carnegie Mellon大学开发。可在这里找到: http://asg.web.cmu.edu/sasl/.

可以配置SASL验证器来检查传统密码文件,PAM系统,或任何其他被CMU的库支持的数据库。更多信息,请见helpers/basic_auth/SASL目录的README文件。

12.2.7 SMB
./configure —enable-basic-auth-helpers=SMB

SMB是另一个对Microsoft Windows数据库的验证器。该验证器自身是一个C程序。该程序在每次与Windows域控制器会话时,执行一个shell脚本。这个shell脚本包含来自Samba包的命令。这样,在使用SMB验证器之前,你必须安装Samba。

SMB验证器程序,smb_auth取Windows域名作为参数。例如:

auth_param basic program /usr/local/squid/libexec/smb_auth -W MYNTDOMAIN

通过重复-W选项,可以列举多个域。完全的文档,请见:

http://www.hacom.nl/~richard/software/smb_auth.html

12.2.8 YP
./configure —enable-basic-auth-helpers=YP

YP验证器检查系统的”Yellow Pages”(例如NIS)目录。为了在squid里使用它,必须在验证器命令行里提供NIS域名和密码数据库的名字,通常是passwd.byname:

auth_param basic program /usr/local/squid/libexec/yp_auth my.nis.domain passwd.byname

yp_auth程序相对简单,但没有任何文档。

12.2.9 getpwnam
./configure —enable-basic-auth-helpers=getpwnam

该验证器是个简单的到getpwnam()函数的接口,该函数在Unix系统的C库里。对给定的用户名,getpwnam()函数查询系统的密码文件。假如使用YP/NIS,getpwnam()也检查那些数据库。在某些操作系统上,它也利用PAM系统。假如你的cache用户在squid运行的系统上也有登陆帐号,就可使用该验证器。另外,可在密码文件里对cache用户建立nologin帐号。

12.2.10 winbind
./configure —enable-basic-auth-helpers=winbind

Winbind是Samba套件的功能之一。它允许Unix系统利用Windows NT的用户帐号信息。winbind验证器是Samba winbindd服务进程的客户端。在使用该验证器之前,必须安装Samba和运行winbindd服务。

winbind基本验证器的名字是wb_basic_auth。它在squid.conf里看起来如下:

auth_param basic program /usr/local/squid/libexec/wb_basic_auth
12.2.11 基本验证API

在squid和基本验证器之间的接口非常简单。squid发送用户名和密码到验证器进程,它们以空格分开并以新行结束。验证器在其stdin里读取用户名和密码。在检查信用项后,验证器将OK或ERR写入stdout。

任何“不安全的URL”字符会参照RFC 1738规则进行编码。这样,名字 “jack+jill”变成了”jack%2bjill”。squid接受包含空格的用户名和密码。例如”a password”变成了”a%20password”。在解码用户名和密码后,验证器程序能处理空格和其他的特殊字符。

可在命令行上轻易测试基本验证器。简单的在终端窗口里运行验证器程序,并输入用户名和密码。或者,可以这样做:

% echo "bueller pencil" | ./ncsa_auth /tmp/passwd

OK

如下是个用perl写成的简单的验证器模板:

#!/usr/bin/perl -wl

    use URI::Escape;

    $|=1;                   # don't buffer stdout

    while (<>) {

        ($u,$p) = split;

        $u = uri_unescape($u);

        $p = uri_unescape($p);

        if (&valid($u,$p)) {

            print "OK";

        } else {

            print "ERR";

        }

    }

    sub valid {
        my $user = shift;
        my $pass = shift;
        ...
}
12.3 HTTP摘要验证

摘要验证被设计为比基本验证更安全。它广泛利用了加密hash函数和其他技巧。本质上,与发送明文密码不同,用户代理发送密码、用户名和其他信息的”消息摘要”(见RFC 2617和O’Reilly’s HTTP: The Definitive Guide的更多信息)。

HTTP摘要验证支持下列auth_param参数:

auth_param digest program command

auth_param digest children number

auth_param digest realm string

auth_param digest nonce_garbage_interval time-specification

auth_param digest nonce_max_duration time-specification

auth_param digest nonce_max_count number

auth_param digest nonce_strictness on|off

program, children, 和realm参数与基本验证的一样。与摘要验证相关的唯一不同参数是nonce。

nonce是个特殊的数据串,它偶尔改变。在验证过程中,服务器(这里就是squid)提供一个nonce值到客户端。客户端在产生摘要时要用到这个nonce值。没有nonce数据,攻击者能简单的拦截和重现(replay)摘要值来获取对squid的访问。

nonce_garbage_interval参数告诉squid每隔多久清空nonce缓存。默认值是每5分钟。对于有许多摘要验证客户端的非常忙的cache,更频繁的回收nonce碎片可能有益。

nonce_max_duration参数指定每个nonce值保持多长时间的有效期。当客户端试图使用某个已过期的nonce值时,squid产生401(未验证)响应,并随之发送一个新的nonce值,以便客户端能重新认证。默认值是30分钟。注意任何被截获的Authorization头部能被用于replay攻击,直到nonce值过期。然而将nonce_max_duration值设得过低,导致squid过频繁的产生401响应。对每个401响应,客户端和服务器要重新协商它们的验证信用项,这本质上浪费了用户的时间。

nonce_max_count参数对nonce值可使用多少次设置一个上限。在指定数量的请求后,squid返回401(未验证)响应和一个新的nonce值。默认是50个请求。

nonce计数是另一个设计成阻止replay攻击的功能。squid在401响应里发送qop=auth。这导致用户代理在它们的响应里包含nonce计数,并在产生摘要自身时使用这个nonce计数。nonce计数值必须逐次增加。下降的nonce计数意味着replay攻击。然而,计数可能跳跃的增加,跨过某些数值,例如:5,6,8,9。nonce_strictness 参数决定在这种情形下squid如何做。若设置为on,假如某个nonce计数不等于前次nonce计数加1,squid会返回401响应。若设置为off,squid允许不连续的nonce计数值。

如下是个完整示例:

auth_param digest program /usr/local/squid/libexec/digest_pw

auth_param digest children 8

auth_param digest realm Access to Squid

auth_param digest nonce_garbage_interval 10 minutes

auth_param digest nonce_max_duration 45 minutes

auth_param digest nonce_max_count 100

auth_param digest nonce_strictness on

acl KnownUsers proxy_auth REQUIRED

http_access allow KnownUsers

下面我将讨论squid自带的摘要验证辅助器程序。

12.3.1 password
./configure —enable-auth=digest —enable-digest-auth-helpers=password

这是squid摘要验证的简单可参考执行的方法。它展示了如何编写基于摘要验证的辅助器。这个代码简单的从明文文件里读取用户名和密码。该文件的格式类似如下:

username:password

密码文件的路径是digest_pw_auth程序的单一参数,例如:

auth_param digest program /usr/local/squid/libexec/digest_pw_auth

        /usr/local/squid/etc/digest_passwd

auth_param digest realm Some Nifty Realm

squid不提供任何工具来维护这种格式的密码文件。假如你选择使用摘要验证,就必须管理自己的密码文件,可使用文本编辑器或perl脚本来做到。

12.3.2 摘要验证API

假如要编写自己的摘要验证辅助器,你必须理解在squid和辅助器进程间的通信。数据交换类似于基本验证,但稍微复杂点。

第一个不同是squid将用户名和域值,而不是用户名和密码,写往辅助器进程。这些串被引用起来,并以冒号分隔。例如:

"bobby":"Tom Landry Middle School"

第二个不同是假如用户名有效,辅助器进程返回一个MD5摘要串,而不是返回OK。与基本验证一样,假如用户不存在,或者来自squid的输入不可解析,辅助器进程返回ERR。

辅助器随着用户名,域值和密码返回一个MD5摘要。这3个串连在一起,并以冒号分割:

username:realm:password

记住密码不会在HTTP请求里发送。辅助器从数据库里(类似于password辅助器使用的明文文件)获取用户的密码。例如,假设Bobby的密码是CapeRs。辅助器从squid接受用户名和域值,从它的数据库里获取密码,并计算这个串的MD5校验和:

bobby:Tom Landry Middle School:CapeRs

Squid的源代码包含一个库函数叫做DigestCalcHA1( ), 它用来执行这个计算。我们可以在终端窗口里来测试这些,并观察辅助器返回什么:

% echo 'bobby:CapeRs' > /tmp/pw

% echo bogus_input | digest_pw_auth /tmp/pw

ERR

% echo "nouser":"some realm" | digest_pw_auth /tmp/pw

ERR

% echo '"bobby":"Tom Landry Middle School"' | digest_pw_auth /tmp/pw

c7ca3efda238c65b2d48684a51baa90e

Squid存储这个MD5校验和,并将其用于摘要验证算法的其他部分。注意这个校验和仅在用户改变其密码时才会改变。在squid的当前摘要执行里,只要用户保持活跃状态,这些校验和就保存在内存里。假如用户不活跃的时间达到authenticate_ttl秒,MD5校验和可能从squid的内存里移除。若该用户下次请求,squid会要求外部辅助器进程重新计算校验和。

12.4 Microsoft NTLM验证

NTLM是Microsoft的私有连接验证协议。许多组织,包括squid的开发者,通过少许可用信息以及检查网络传输,已经反向工程了该协议。可以在这里找到一些技术细节: http://www.innovation.ch/java/ntlm.html.

NTLM使用三次握手来验证一个连接。首先,客户端发送请求,它带一对标识符。接着,服务器发送回一个挑战消息(译者注:即用于加密的随机种子)。第三步,客户端再次发送其请求,包含了对这个种子的响应。这时,连接验证成功,在同一连接里的任何进一步的请求,不再需要挑战/响应信息。假如关闭了连接,客户端和服务器必须重复整个三次握手过程。持续连接有助于减少NTLM验证的负载。

NTLM使用加密的hash函数和nonce值,类似于摘要验证,尽管专家认为NTLM要脆弱一些。

NTLM验证支持下列auth_param参数:

auth_param ntlm program command

auth_param ntlm children number

auth_param ntlm max_challenge_reuses number

auth_param ntlm max_challenge_lifetime time-specification

program和children参数与基本验证和摘要验证的相同。剩余的参数决定squid每隔多久重用某个挑战令牌。

max_challenge_reuses参数指定某个挑战令牌可被重用多少次。默认值是0,所以这个令牌永不会被重用。增加该值可以减少squid和NTLM辅助器进程的计算负载,但带来了弱化协议安全的风险。

类似的,max_challenge_lifetime参数对令牌重用设置了一个时间限制,即使max_challenge_reuses次数还没有用完。默认值是60秒。

如下是个完整示例:

auth_param ntlm program /usr/local/squid/libexec/ntlm_auth foo\bar

auth_param ntlm children 12

auth_param ntlm max_challenge_reuses 5

auth_param ntlm max_challenge_lifetime 2 minutes

acl KnownUsers proxy_auth REQUIRED

http_access allow KnownUsers

Squid自带了下列NTLM验证辅助器程序:

12.4.1 SMB
./configure —enable-auth=ntlm —enable-ntlm-auth-helpers=SMB

NTLM的服务消息块(SMB)验证器与基本验证的相似。用户简单的提供他们的windows NT域名,用户名和密码即可。该验证器能在多个域控制器间负载均衡。域和控制器名字出现在命令行中:

auth_param ntlm program /usr/local/squid/libexec/ntlm_auth

                domain\controller [domain\controller ...]
12.4.2 winbind
./configure —enable-auth=ntlm —enable-ntlm-auth-helpers=winbind

该验证器类似于基本验证的winbind。两者都要求安装和运行了Samba winbindd服务。NTLM的winbind验证器名字是wb_nltm_auth。它在squid.conf里的配置看起来如下:

auth_param basic program /usr/local/squid/libexec/wb_ntlm_auth
12.4.3 NTLM验证API

在squid和NTLM验证器之间的通信相对于基本和摘要验证而言,要复杂得多。理由之一是每个辅助器进程实际创建了它自己的挑战令牌。这样,辅助器变得与状态相关,squid必须记住哪个连接属于哪个辅助器。

Squid和辅助器进程使用一些2字符的代码来指示它们正在发送什么。这些代码如下:

YR

在squid需要新的挑战令牌时,它发送这个给辅助器。这总是在两个进程间的第一个通信。它也可能在squid需要新挑战令牌的任何时候发生,归咎于auth_param max_challenge_lifetime和max_challenge_uses参数的设置。辅助器响应一个TT消息。

TT 挑战令牌

辅助器发回这个消息给squid,包含了一个挑战令牌。它是对YR请求的响应。挑战令牌用base64编码,在RFC 2045里有定义。

KK 信用项

当squid想要验证某个用户的信用项时,它发送这个到辅助器。辅助器响应如下几个代码:AF, NA, BH, 或LD。

AF 用户名

当用户的验证信用项有效时,辅助器发回这个消息给squid。辅助器在本消息里发送用户名,是因为squid不去尝试解码NTLM验证头部。

NA 理由

当用户的信用项无效时,辅助器发回这个消息给squid。它也包含了一个“理由”字符串,squid能将其显示到错误页面。

BH 理由

当验证过程失败时,辅助器发回这个消息给squid。这点可能发生在,例如,辅助器进程无法与windows NT域控制器通信的时候。squid拒绝用户请求。

LD 用户名

这个辅助器到squid的响应类似于BH,除了squid允许用户请求之外。类似于AF,它返回用户名。为了使用该功能,必须在编译squid时使用 —enable-ntlm-fail-open选项。

既然该协议相对复杂,你最好从包含在squid源代码发布里的2个基本验证器起步。no_check辅助器用perl写的,fakeauth用C写的。可以在helpers/ntlm_auth目录找到它们。

12.5 外部ACL

在版本2.5,Squid包含了一个新功能,叫做外部ACL。这些ACL元素在外部辅助器进程里被执行。你指示squid将某些信息写往辅助器,然后辅助器以OK或ERR来响应squid。请参考6.1.3章关于external_acl_type语法的描述。这里,我仅仅讨论一些特殊的外部ACL辅助器程序,它们随着squid的源代码发布。

12.5.1 ip_user
./configure —enable-external-acl-helpers=ip_user

该辅助器读取用户名和客户端IP地址作为输入。它根据配置文件来检查这2个值,以决定其是否有效。为了使用这个ACL辅助器,要在squid.conf里增加如下行:

external_acl_type ip_user_helper %SRC %LOGIN

    /usr/local/squid/libexec/ip_user -f /usr/local/squid/etc/ip_user.conf

acl AclName external ip_user_helper

对每个请求,%SRC替换成客户端的IP地址,%LOGIN替换成用户名。ip_user.conf配置文件有如下格式:

ip_addr[/mask]          user|@group|ALL|NONE

例如:

127.0.0.1               ALL

192.168.1.0/24          bob

10.8.1.0/24             @lusers

172.16.0.0/16           NONE

该配置文件导致ip_user对任何来自127.0.0.1的请求返回OK,对来自192.168.1.0/24网络的Bob的请求返回OK,对来自10.8.1.0/24网络的,位于luser组里的任何用户名返回OK。对来自172.16.0.0/16网络的任何请求返回ERR。它也对任何不在这个列表里出现的地址和用户名对返回ERR。

12.5.2 ldap_group
./configure —enable-external-acl-helpers=ldap_group

该辅助器决定是否某个用户属于一个特殊的LDAP组。在acl行里指定LDAP组名。它可能在你的配置文件里看起来如下:

external_acl_type ldap_group_helper %LOGIN /usr/local/squid/libexec/squid_ldap_group

    -b "ou=people,dc=example,dc=com"  ldap.example.com

acl AclName external ldap_group_helper GroupRDN ...

注意为了编译squid_ldap_group辅助器程序,你必须在系统中安装OpenLDAP库(http://www.openldap.org )。

12.5.3 unix_group
./configure —enable-external-acl-helpers=unix_group

该辅助器在Unix组数据库(例如/etc/group文件)里查找用户名。在辅助器的命令行指定要检查的组:

external_acl_type unix_group_helper %LOGIN

    /usr/local/squid/libexec/check_group -g group1 -g group2 ...

acl AclName external unix_group_helper

另外,可在acl行指定组。这样就允许对不同的组使用相同的辅助器:

external_acl_type unix_group_helper %LOGIN /usr/local/squid/libexec/check_group

acl AclName1 external unix_group_helper group1 ...

acl AclName2 external unix_group_helper group2 ...
12.5.4 wbinfo_group
./configure —enable-external-acl-helpers=wbinfo_group

该辅助器是个简短的perl脚本,它利用了Samba包的wbinfo程序。wbinfo是winbindd服务的客户端。对每个请求,该脚本期待一个单一的Unix组名跟随在用户名后。这样,必须在acl行里放置组名:

external_acl_type wbinfo_group_helper %LOGIN /usr/local/squid/libexec/wbinfo_group.pl

acl AclName external wbinfo_group_helper group
12.5.5 winbind_group
./configure —enable-external-acl-helpers=winbind_group

该辅助器用C写成,也要求winbindd服务提供的windows NT用户名的组成员关系。它基于基本验证和NTLM验证的winbind辅助器。可在acl命令行指定多个组名:

external_acl_type winbind_group_helper %LOGIN /usr/local/squid/libexec/wb_check_group

acl AclName external winbind_group_helper group1 group2 ...
12.5.6 编写自己的外部ACL辅助器

外部ACL接口提供了许多兼容性,可以用它来执行几乎任何不被squid内在支持的访问控制检测。编写外部ACL分2步走。首先,你必须决定辅助器程序需要对什么样的请求信息来作出决定。在external_acl_type行上放置相应的关键字,紧跟着辅助器程序的路径。例如,假如你想编写一个外部ACL辅助器,它使用了客户端IP地址,用户名,和Host头部的值,那就可这样写:

external_acl_type MyAclHelper %SRC %LOGIN %{Host} 

/usr/local/squid/libexec/myaclhelper

第2步是编写myaclhelper程序。它必须在stdin里读取请求元素,作出它自己的决定,然后将OK或ERR写往stdout。继续以前的示例,该perl脚本描述了如何去做:

#!/usr/bin/perl -wl

    require 'shellwords.pl';

    $|=1;

    while (<>) {

        ($ip,$name,$host) = &shellwords;

        if (&valid($ip,$name,$host)) {

            print "OK";

        } else {

            print "ERR";

        }

    }

    sub valid {
        my $ip = shift;
        my $name = shift;
        my $host = shift;
        ...
    }

参考6.1.3章关于从squid传递到辅助器的元素列表(%SRC, %LOGIN等)。注意当某个元素包含空格时,squid会在双引号里封装它。如同示例显示的那样,可以使用perl的shellwords库来解析被双引号封装的元素。

当然,为了利用外部ACL,你必须在某个acl行里引入它。无论何时,若外部辅助器返回OK,则ACL元素匹配成功。

外部ACL辅助器接口允许从辅助器提交附加的信息到squid(在OK/ERR行)。这些以keyword=value对的形式出现。例如:

OK user=hank

当前squid了解的唯一关键字是error和user。假如设置了user值,squid将它用于access.log。squid当前没有用到error值。

第13章 日志文件

13.1 cache.log

cache.log包含多种消息,例如Squid的配置信息、性能警告、以及严重错误。如下是cache.log的输出样本:

2003/09/29 12:09:45| Starting Squid Cache version 2.5.STABLE4 for i386-

unknown-freebsd4.8...

2003/09/29 12:09:45| Process ID 18990

2003/09/29 12:09:45| With 1064 file descriptors available

2003/09/29 12:09:45| Performing DNS Tests...

2003/09/29 12:09:45| Successful DNS name lookup tests...

2003/09/29 12:09:45| DNS Socket created at 0.0.0.0, port 1154, FD 5

2003/09/29 12:09:45| Adding nameserver 24.221.192.5 from /etc/resolv.conf

2003/09/29 12:09:45| Adding nameserver 24.221.208.5 from /etc/resolv.conf

2003/09/29 12:09:45| helperOpenServers: Starting 5 'redirector.pl' processes

2003/09/29 12:09:45| Unlinkd pipe opened on FD 15

2003/09/29 12:09:45| Swap maxSize 10240 KB, estimated 787 objects

2003/09/29 12:09:45| Target number of buckets: 39

2003/09/29 12:09:45| Using 8192 Store buckets

2003/09/29 12:09:45| Max Mem  size: 8192 KB

2003/09/29 12:09:45| Max Swap size: 10240 KB

2003/09/29 12:09:45| Rebuilding storage in /usr/local/squid/var/cache (CLEAN)

2003/09/29 12:09:45| Using Least Load store dir selection

2003/09/29 12:09:45| Set Current Directory to /usr/local/squid/var/cache

2003/09/29 12:09:45| Loaded Icons.

2003/09/29 12:09:45| Accepting HTTP connections at 0.0.0.0, port 3128, FD 16.

2003/09/29 12:09:45| Accepting ICP messages at 0.0.0.0, port 3130, FD 17.

2003/09/29 12:09:45| WCCP Disabled.

2003/09/29 12:09:45| Ready to serve requests.

每个cache.log条目以时间戳开始,指示消息何时产生。本示例里的日志报告了squid的版本(2.5.STABLE4),以及squid所运行的操作系统标识符(i386-unknown-freebsd4.8)。接下来是进程ID(18990)。许多cache.log条目看起来含义不明(例如Target number of buckets: 39)。大多数正常情形下,可以忽略这些不易理解的条目。另一方面,你也许该仔细看一下本质的配置细节,例如名字服务器的地址,或HTTP服务器地址。本示例日志最后陈述了Squid准备接受请求。此时Squid可以接受来自客户端的HTTP连接。

通常,cache.log增长缓慢。然而,不正常的HTTP事务或类似的事件可以导致squid发布一个debug消息。假如这样的事件经常发生(例如DOS攻击、新的病毒、磁盘意外等),日志文件会增长很快。定期轮转日志减少了用光磁盘的风险。

主要的错误和异常条件最可能报告在cache.log里。我推荐存档这些日志,以便以后回查事件的源头。当在Squid的邮件列表或类似论坛描述这些故障时,相应的cache.log非常有用。某些情形下,你也许应该调大日志的debug级别,以便其他人能更好的理解和修正你的问题。

13.1.1 debug级别

debug_options指令控制cache.log的日志级别。默认值(ALL,1)通常是最佳选择。在更高级别上,不重要的消息会混淆视线。请参考16.2节关于debug_options指令的完整描述。

请注意最高级别的debug(9或10)会对每个请求产生数千行日志,快速消耗磁盘空间和显著影响squid的性能。

可以使用squid的-X命令行选项来对所有情形激活完整的debug。假如squid拒绝启动,并且squid.conf里的debug级别不足以诊断问题时,该模式特别有用。这也是在配置文件解析器解析到debug_options指令之前,激活它的完整debug的好方法。在squid运行正常时,请勿使用-X。

对运行的squid进程,可使用squid的-k debug命令行选项来立刻激活完整debug。这个命令是循环使用的:第一次调用打开完整debug,第二次调用则关闭它。请见第5章关于-k选项的通用讨论。

如前所述,完整debug会产生难以控制的日志增长。这会使squid和操作系统运行缓慢。在极端情形下,你会发现终端session在运行第一个squid -k debug命令后,变得没有响应。在squid狂写日志的同时让操作无法进行,这情形并不好。如下技巧也许有用,它获取5秒钟的debug数据快照:

% squid -k debug; sleep 5; squid -k debug
13.1.2 转发cache.log消息到系统日志

为了让squid发送cache.log消息的拷贝到系统日志,请使用-s命令行选项。仅仅在debug级别0和1的消息会被转发。级别0的消息以syslog级别LOG_WARNING记录,级别1的消息以syslog级别LOG_NOTICE记录。所有消息使用LOCAL4的syslog设备。如下是配置syslogd的一个方法,以便这些消息能保存下来:

local4.warning                           /var/log/squid.log

在维护多个squid主机时,使用syslog来记录cache.log特别方便。可以配置每个本地syslog进程,转发这些消息到中央日志主机,这样就可在一个地方统一浏览所有cache日志。例如,可在/etc/syslogd.conf里使用如下接口:

local4.notice                            @192.168.45.1
13.1.3 dump cache.log消息到终端

-d level命令行选项指示squid去dump cache.log消息到终端(例如stderr)。level参数指明dump出的消息的最大级别。注意你只会见到出现在cache.log里的消息,它遵循于debug_options设置。例如,假如设置了debug_options ALL,1,然后运行squid -d2,你不会见到级别2的debug消息。

-d level和-N选项在debug squid问题或快速测试配置文件的改变时,特别有用。它们允许你容易启动squid和观察cache.log消息。在squid从crontab或类似的设备启动时,该选项也有用,crontab会捕获squid的标准错误并将其报告回用户。例如,可能有如下crontab,它自动重配运行中的squid进程:

15 */4 * * * /usr/local/squid/sbin/squid -d1 -k reconfigure
13.2 access.log

Squid把关于HTTP事务的关键信息存放在access.log里。该文件是基于行的,也就是说每行对应一个客户端请求。squid记录客户端IP(或主机名)、请求URL、响应size、和其他信息。

Squid在access.log里记录所有HTTP访问,除了那些在还没有发送数据前就断开的连接。Squid也记录所有的ICP(非HTCP)事务,除非你使用log_icp_queries指令关闭了这个功能。第13.2.4节描述了其他影响access日志的squid.conf指令。

默认的access.log格式包含了10个域。如下是日志样本,长行分割并且缩进排版:

1066037222.011  126389 9.121.105.207 TCP_MISS/503 1055

        GET http://home.gigigaga.com/n8342133/Miho.DAT.019 -

        DIRECT/203.187.1.180 -

1066037222.011   19120 12.83.179.11 TCP_MISS/200 359

        GET http://ads.x10.com/720x300/Z2FtZ3JlZXRpbmcxLmRhd/7/AMG -

        DIRECT/63.211.210.20 text/html

1066037222.011   34173 166.181.33.71 TCP_MISS/200 559

        GET http://coursesites.blackboard.com:8081/service/collab/./1010706448190/ -

        DIRECT/216.200.107.101 application/octet-stream

1066037222.011   19287 41.51.105.27 TCP_REFRESH_MISS/200 500

        GET http://fn.yam.com/include/tsemark/show.js -

        DIRECT/210.59.224.59 application/x-javascript

1066037222.011   19395 41.51.105.27 TCP_MISS/304 274

        GET http://fnasp.yam.com/image/coin3.gif -

        DIRECT/211.72.254.133 -

1066037222.011   19074 30.208.85.76 TCP_CLIENT_REFRESH_MISS/304 197

        GET http://ads.icq.com/content/B0/0/..bC6GygEYNeHGjBUin5Azfe68m5hD1jLk$/aol -

        DIRECT/64.12.184.121 -

1066037222.011   19048 12.83.179.11 TCP_MISS/200 261

        GET http://ads.adsag.com/js.ng/...ne&cat=friendship&subcat=girltalk -

        DIRECT/209.225.54.119 application/x-javascript

1066037222.118     106 41.51.105.27 TCP_HIT/200 536

        GET http://rcm-images.amazon.com/./images/G/01/rcm/privacy.gif -

        NONE/- image/gif

1066037222.352   19475 27.34.49.248 TCP_MISS/200 12387

        GET http://espanol.geocities.com/lebastias/divulgacion/budismo-tarot.html -

        DIRECT/209.1.225.139 text/html

1066037222.352     132 144.157.100.17 TCP_MISS/504 1293

        GET http://ar.atwola.com/image/93101912/aol -

        NONE/- -

如下是对每个域的详细解释:

1.时间戳

请求完成时间,以Unix纪元(UTC 1970-01-01 00:00:00)以来的秒数表示,它是毫秒级的。squid使用这种格式而不是人工可读的时间格式,是为了简化某些日志处理程序的工作。

可以使用一个简单的perl命令来转化Unix时间戳到本地时间,例如:

perl -pe 's/^\d+\.\d+/localtime($&)/e;' access.log

2.响应时间

对HTTP事务来说,该域表明squid花了多少时间来处理请求。在squid接受到HTTP请求时开始计时,在响应完全送出后计时终止。响应时间是毫秒级的。

对ICP查询来说,响应时间通常是0。这是因为squid回答ICP查询非常迅速。甚至,squid在接受到ICP查询和发送完响应之间,不会更新进程时钟。

尽管时间值是毫秒级的,但是精度可能是10毫秒。在squid负载繁重时,计时变得没那么精确。

3.客户端地址

该域包含客户端的IP地址,或者是主机名–假如激活了log_fqdn。出于安全或隐私的理由,你可能需要使用client_netmask指令来掩盖客户端地址的一部分。然而,这样让来自同一客户端的组请求变得不可能。

4.结果/状态码

该域包含2个token,以斜杠分隔。第一个token叫结果码,它把协议和事务结果(例如TCP_HIT或UDP_DENIED)进行归类。这些是squid专有的编码,在13.2.1节里有定义。以TCP_开头的编码指HTTP请求,以UDP_开头的编码指ICP查询。

第2个token是HTTP响应状态码(例如200,304,404等)。状态码通常来自原始服务器。在某些情形下,squid可能有义务自己选择状态码。这些编码在HTTP的RFC里定义,在随后的Table 13-1里有概述。

5.传输size

该域指明传给客户端的字节数。严格的讲,它是squid告诉TCP/IP协议栈去发送给客户端的字节数。这就是说,它不包括TCP/IP头部的overhead。也请注意,传输size正常来说大于响应的Content-Length。传输size包括了HTTP响应头部,然而Content-Length不包括。

传输size可用于近似的带宽使用分析,但并非精确的HTTP实体size计算。假如需要了解响应的Content-Length,可在store.log里找到它。

6.请求方式

该域包含请求方式。因为squid客户端可能使用ICP或HTTP,请求方式就可能是HTTP-或ICP-这2种。最普通的HTTP请求方式是GET。ICP查询总以ICP_QUERY的形式被记载。请见6.1.2.8节关于squid了解的HTTP方式列表。

7.URI

该域包含来自客户端请求的URI。大多数记录下来的URI实际是URL(例如,它们有主机名)。

Squid对某些失败使用特殊的记录格式。例如Squid不能解析HTTP请求,或者不能决定URI,这时你可能见到类似于”error:invalid-request.” 的字串出现在URI的位置。例如:

1066036250.603 310 192.0.34.70 NONE/400 1203 GET error:invalid-request - NONE/- -

另外在该域里,也请留心URI里的空格字符。取决于uri_whitespace设置,squid可能在日志文件里打印URI时带空格字符。若发生这种情况,则阅读access.log文件的日志分析工具可能会遇到麻烦。

在记日志时,squid删掉了在第一个问号(?)之后的所有URI字符,除非禁用了strip_query_terms指令。

8.客户端身份

Squid有2种不同的办法来决定用户的身份。一种是RFC 1413身份协议,另一种来自HTTP验证头部。

Squid试图基于ident_lookup_access规则进行身份查询,假如有的话。另外,假如使用代理验证(或在代理人模式下的规范服务验证),squid会在该域放置给定的用户名。假如2者都提供给squid一个用户名,并且你使用了原始access.log格式,那么HTTP验证名字会记录下来,RFC 1413名字会忽略掉。普通日志文件格式会把两者都独立的记录。

9.对端编码/对端主机

对端信息包含了2个token,以斜杠分隔。它仅仅与cache丢失的请求有关。第一个token指示如何选择下一跳,第二个token是下一跳的地址。对端编码列在13.2.3节里。

当squid发送一个请求到邻居cache时,对端主机地址是邻居的主机名。假如请求是直接送到原始服务器的,则squid会写成原始服务器的IP地址或主机名–假如禁用了log_ip_on_direct。NONE/-这个值指明squid不转发该请求到任何其他服务器。

10.内容类型

原始access.log的默认的最后一个域,是HTTP响应的内容类型。squid从响应的Content-Type头部获取内容类型值。假如该头部丢失了,squid使用一个横杠(-)代替。

假如激活了log_mime_headers指令,squid在每行追加2个附加的域:

11.HTTP请求头部

Squid编码HTTP请求头部,并且在一对方括号之间打印它们。方括号是必须的,因为squid不编码空格字符。编码方案稍许奇怪。回车(ASCII 13)和换行(ASCII 10)分别打印成\r和\n。其他不可打印的字符以RFC 1738风格来编码,例如Tab(ASCII 9)变成了%09。

12.HTTP响应头部

Squid编码HTTP响应头部,并且在一对方括号之间打印它们。注意这些是发往客户端的头部,可能不同于从原始服务器接受到的头部。

Squid只有在整个响应发送到客户端完成以后,才写access.log日志。这点允许squid在日志文件里包含请求和响应两者信息。然而,需要花费数分钟甚至数小时才能完成的事务,请求期间的日志在access.log里不可见。当这类型的事务呈现出性能或策略问题时,access.log可能对你没有帮助。代替的,可使用cache管理器来浏览挂起事务的列表(见14章)。

13.2.1 access.log结果编码

相应于HTTP请求,下列标签可能出现在access.log文件的第四个域。

TCP_HIT

Squid发现请求资源的貌似新鲜的拷贝,并将其立即发送到客户端。

TCP_MISS

Squid没有请求资源的cache拷贝。

TCP_REFERSH_HIT

Squid发现请求资源的貌似陈旧的拷贝,并发送确认请求到原始服务器。原始服务器返回304(未修改)响应,指示squid的拷贝仍旧是新鲜的。

TCP_REF_FAIL_HIT

Squid发现请求资源的貌似陈旧的拷贝,并发送确认请求到原始服务器。然而,原始服务器响应失败,或者返回的响应Squid不能理解。在此情形下,squid发送现有cache拷贝(很可能是陈旧的)到客户端。

TCP_REFRESH_MISS

Squid发现请求资源的貌似陈旧的拷贝,并发送确认请求到原始服务器。原始服务器响应新的内容,指示这个cache拷贝确实是陈旧的。

TCP_CLIENT_REFRESH_MISS

Squid发现了请求资源的拷贝,但客户端的请求包含了Cache-Control: no-cache指令。Squid转发客户端的请求到原始服务器,强迫cache确认。

TCP_IMS_HIT

客户端发送确认请求,Squid发现更近来的、貌似新鲜的请求资源的拷贝。Squid发送更新的内容到客户端,而不联系原始服务器。

TCP_SWAPFAIL_MISS

Squid发现请求资源的有效拷贝,但从磁盘装载它失败。这时squid发送请求到原始服务器,就如同这是个cache丢失一样。

TCP_NEGATIVE_HIT

在对原始服务器的请求导致HTTP错误时,Squid也会cache这个响应。在短时间内对这些资源的重复请求,导致了否命中。negative_ttl指令控制这些错误被cache的时间数量。请注意这些错误只在内存cache,不会写往磁盘。下列HTTP状态码可能导致否定cache(也遵循于其他约束): 204, 305, 400, 403, 404, 405, 414, 500, 501, 502, 503, 504。

TCP_MEM_HIT

Squid在内存cache里发现请求资源的有效拷贝,并将其立即发送到客户端。注意这点并非精确的呈现了所有从内存服务的响应。例如,某些cache在内存里,但要求确认的响应,会以TCP_REFRESH_HIT, TCP_REFRESH_MISS等形式记录。

TCP_DENIED

因为http_access或http_reply_access规则,客户端的请求被拒绝了。注意被http_access拒绝的请求在第9域的值是NONE/-,然而被http_reply_access拒绝的请求,在相应地方有一个有效值。

TCP_OFFLINE_HIT

当offline_mode激活时,Squid对任何cache响应返回cache命中,而不用考虑它的新鲜程度。

TCP_REDIRECT

重定向程序告诉Squid产生一个HTTP重定向到新的URI(见11.1节)。正常的,Squid不会记录这些重定向。假如要这样做,必须在编译squid前,手工定义LOG_TCP_REDIRECTS预处理指令。

NONE

无分类的结果用于特定错误,例如无效主机名。

相应于ICP查询,下列标签可能出现在access.log文件的第四域。

UDP_HIT

Squid在cache里发现请求资源的貌似新鲜的拷贝。

UDP_MISS

Squid没有在cache里发现请求资源的貌似新鲜的拷贝。假如同一目标通过HTTP请求,就可能是个cache丢失。请对比UDP_MISS_NOFETCH。

UDP_MISS_NOFETCH

跟UDP_MISS类似,不同的是这里也指示了Squid不愿去处理相应的HTTP请求。假如使用了-Y命令行选项,Squid在启动并编译其内存索引时,会返回这个标签而不是UDP_MISS。

UDP_DENIED

因为icp_access规则,ICP查询被拒绝。假如超过95%的到某客户端的ICP响应是UDP_DENIED,并且客户端数据库激活了(见附录A),Squid在1小时内,停止发送任何ICP响应到该客户端。若这点发生,你也可在cache.log里见到一个警告。

UDP_INVALID

Squid接受到无效查询(例如截断的消息、无效协议版本、URI里的空格等)。Squid发送UDP_INVALID响应到客户端。

13.2.2 HTTP响应状态码

Table 13-1列出了数字HTTP响应CODE和理由短句。注意Squid和其他HTTP客户端仅仅关注这些数字值。理由短句是纯解释性的,不会影响响应的意义。对每个状态码,也提供了一个到RFC 2616的具体节的索引。注意状态码0和600是squid使用的非标准的值,不会在RFC里提到。

Table 13-1. HTTP response status codes

Code
Reason phrase
RFC 2616 section

0
No Response Received (Squid-specific)
N/A

1xx
Informational
10.1

100
Continue
10.1.1

101
Switching Protocols
10.1.2

2xx
Successful
10.2

200
OK
10.2.1

201
Created
10.2.2

202
Accepted
10.2.3

203
Non-Authoritative Information
10.2.4

204
No Content
10.2.5

205
Reset Content
10.2.6

206
Partial Content
10.2.7

3xx
Redirection
10.3

300
Multiple Choices
10.3.1

301
Moved Permanently
10.3.2

302
Found
10.3.3

303
See Other
10.3.4

304
Not Modified
10.3.5

305
Use Proxy
10.3.6

306
(Unused)
10.3.7

307
Temporary Redirect
10.3.8

4xx
Client Error
10.4

400
Bad Request
10.4.1

401
Unauthorized
10.4.2

402
Payment Required
10.4.3

403
Forbidden
10.4.4

404
Not Found
10.4.5

405
Method Not Allowed
10.4.6

406
Not Acceptable
10.4.7

407
Proxy Authentication Required
10.4.8

408
Request Timeout
10.4.9

409
Conflict
10.4.10

410
Gone
10.4.11

411
Length Required
10.4.12

412
Precondition Failed
10.4.13

413
Request Entity Too Large
10.4.14

414
Request-URI Too Long
10.4.15

415
Unsupported Media Type
10.4.16

416
Requested Range Not Satisfiable
10.4.17

417
Expectation Failed
10.4.18

5xx
Server Error
10.5

500
Internal Server Error
10.5.1

501
Not Implemented
10.5.2

502
Bad Gateway
10.5.3

503
Service Unavailable
10.5.4

504
Gateway Timeout
10.5.5

505
HTTP Version Not Supported
10.5.6

6xx
Proxy Error
N/A

600
Unparseable Response Headers (Squid-specific)
N/A

假如Squid从原始服务器没有接受到任何响应,你可在access.log里看到状态码0。假如Squid接受到的响应没有包含HTTP头部,就会出现状态码600。在少数情况下,某些原始服务器仅发送响应body,而忽略了任何头部。

13.2.3 access.log对端编码

下列编码可能出现在access.log的第9域。请参考10.10节关于Squid如何对cache丢失情况,选择有效的下一跳。

NONE

这指明Squid对本次请求,不会与任何其他服务器(邻居或原始服务器)通信。它通常与cache命中、拒绝请求、cache管理请求、错误、和所有的ICP查询这些类型联合出现。

DIRECT

Squid直接转发请求到原始服务器。该域的第2半部分显示原始服务器的IP地址,或主机名–假如禁止了log_ip_on_direct。

SIBLING_HIT

在姐妹cache返回ICP或HTCP命中后,Squid发送请求到姐妹cache。

PARENT_HIT

在父cache返回ICP或HTCP命中后,Squid发送请求到父cache。

DEFAULT_PARENT

Squid选择该父cache,因为其在squid.conf的cache_peer行里被标志为default。

FIRST_UP_PARENT

Squid转发请求到该父cache,因为它是位于已知活跃列表里的第一个父cache。

FIRST_PARENT_MISS

Squid转发请求到该父cache,它第一个响应ICP/HTCP丢失消息。换句话说,对这个特殊的ICP/HTCP查询,在这个特殊时刻,被选中的父cache有最佳的往返时间(RTT)。注意标准RTT可能被人工矫正过,取决于cache_peer指令的weight选项。

CLOSEST_PARENT_MISS

Squid选择该父cache,因为它报告到原始服务器的RTT最低。这点仅在2个cache都激活了netdb,并且原始服务器(或在同一子网内的其他server)返回ICMP ping消息。

CLOSEST_PARENT

这点类似CLOSEST_PARENT_MISS,除了RTT计算不是来自ICP/HTCP响应消息外。代替的,它们来自Squid保留的更老的计算方式,例如netdb交换功能。

CLOSEST_DIRECT

Squid基于netdb算法,转发请求到原始服务器。这点在满足下述任何条件时发生:

  • 1)在Squid和原始服务器之间的RTT小于配置的minimum_direct_rtt值。
  • 2)在Squid和原始服务器之间的标准路由跳数少于配置的minimum_direct_hops值。
  • 3)在ICP/HTCP响应里返回的RTT值,指示Squid离原始服务器近于任何其他邻居。
ROUNDROBIN_PARENT

Squid转发请求到该父cache,因为设置了round-robin选项,并且它有最低的使用计数器。

CD_PARENT_HIT

Squid基于cache摘要算法(见10.7节)转发请求到该父cache。

CD_SIBLING_HIT

Squid基于cache摘要算法转发请求到该姐妹cache。

CARP

Squid选择该父cache,基于cache数组路由协议算法(见10.9节)。

ANY_PARENT

作为最后的手段,Squid选择该父cache,因为没有其他方法能选择可行的下一跳。

注意大部分上述编码可能以TIMEOUT_开头,这表明在等待ICP/HTCP响应时发生超时。例如:

1066038165.382    345 193.233.46.21 TCP_MISS/200 2836

        GET http://www.caida.org/home/./images/home.jpg

        TIMEOUT_CLOSEST_DIRECT/213.219.122.19 image/jpeg

可使用icp_query_timeout指令来调整超时。

13.2.4 影响access.log的配置指令

下列配置文件指令会影响到access.log。

13.2.4.1 log_icp_queries

该指令默认激活,导致squid记录所有的ICP查询。假如运行了一个繁忙的父cache,这点可能让access.log文件变得巨大。为了节省磁盘空间,可禁止该指令:

log_icp_queries off

假如禁止了ICP查询的日志,我建议你监视查询数量–通过cache管理器或SNMP。

13.2.4.2 emulate_httpd_log

access.log文件有2种格式:普通格式和原始格式。普通格式就如同大部分HTTP服务器(如Apache)的日志格式一样。它包含的信息少于Squid的原始格式。然而,假如运行Squid在代理人模式下(见15章),你可能想要普通日志文件格式。普通格式或许也对你现有的日志文件分析工具有用。使用该指令来激活普通格式:

emulate_httpd_log on

请见http://www.w3.org/Daemon/User/Config/Logging.html#common-logfile-format 关于该格式的描述。

13.2.4.3 log_mime_hdrs

使用log_mime_hdrs让squid记录HTTP请求和响应的头部:

log_mime_headers on

在激活时,squid追加请求和响应头部到access.log。这会在每行增加2个域。每个域都以方括号引用起来,便于分析。某些字符会被编码来保证日志文件可读。Table 13-2显示了这些编码方案。

Table 13-2. Character encoding rules for HTTP headers in access.log

Character
Encoding

Newline
\n

Carriage return
\r

Backslash
\\

[
%5b

]
%5d

%
%25

ASCII 0-31
%xx (hexadecimal value)

ASCII 127-255
%xx (hexadecimal value)

13.2.4.4 log_fqdn

Squid默认把客户端IP地址放在access.log里。也可以记录可用的主机名,激活如下指令:

log_fqdn on

这点让Squid在接受到请求时,对客户端的地址发起反向DNS查询。假如在请求完成时查到了主机名,Squid就将它放在第3域。

13.2.4.5 ident_lookup_access

该访问规则列表决定Squid是否对客户端的TCP连接发起RFC 1413身份查询。默认情况下,Squid不会发布身份查询。为了激活这点,简单的增加一个或多个规则:

acl All src 0/0

ident_lookup_access allow All

假如在请求完成时查到了答案,Squid将其放在第8域。假如同时使用了HTTP验证,从验证得到的用户名会取代身份查询答案。

13.2.4.6 log_ip_on_direct

当Squid转发cache丢失到原始服务器时,它在第9域记录原始服务器的IP地址。可以禁止这个指令,以便squid记录主机名:

log_ip_on_direct off

在此情形下,主机名来自于URI。假如URI包含了IP地址,Squid不会将其转换为主机名。

13.2.4.7 client_netmask

该指令存在主要是为了保护用户的隐私。不同于记录完整的IP地址,你也可以掩盖一些位。例如:

client_netmask 255.255.255.0

在此设置下,access.log里的所有客户端IP地址的最后一个八位组是0:

1066036246.918     35 163.11.255.0 TCP_IMS_HIT/304 266 GET http://...

1066036246.932     16 163.11.255.0 TCP_IMS_HIT/304 266 GET http://...

1066036247.616    313 140.132.252.0 TCP_MISS/200 1079 GET http://...

1066036248.598  44459 140.132.252.0 TCP_MISS/500 1531 GET http://...

1066036249.230     17 170.210.173.0 TCP_IMS_HIT/304 265 GET http://...

1066036249.752   2135 140.132.252.0 TCP_MISS/200 50230 GET http://...

1066036250.467      4 170.210.173.0 TCP_IMS_HIT/304 265 GET http://...

1066036250.762    102 163.11.255.0 TCP_IMS_HIT/304 265 GET http://...

1066036250.832     20 163.11.255.0 TCP_IMS_HIT/304 266 GET http://...

1066036251.026     74 203.91.150.0 TCP_CLIENT_REFRESH_MISS/304 267 GET http://...
13.2.4.8 strip_query_terms

该指令是另一个隐私保护功能。在记录请求前,Squid删除了查询条件。假如日志文件不幸落入坏人之手,他们不会找到任何用户名和密码。当该指令激活时,在问号(?)之后的所有字节被删除。例如,某个URI如下:

http://auto.search.msn.com/response.asp?MT=www.kimo.com.yw&srch=3&prov=&utf8

会被记录为:

http://auto.search.msn.com/response.asp?
13.2.4.9 uri_whitespace

早前我提到过出现在某些URI里的空格字符的问题。RFC申明URI必须不包括空格字符,但在实际中情况并非如此。uri_whitespace指令指明Squid如何处理这种情况。允许的设置是:strip (default), deny, allow, encode, 和chop。在这些设置里,strip,encode和chop保证URI域不包含任何空格字符(空格字符会给access.log增加多余的域)。

allow设置允许请求不加修改的通过Squid。它很可能会给重定向器和日志文件解析器带来麻烦。与之相反的是deny设置,它导致Squid拒绝这种请求。用户会接受到错误消息,但请求仍带着空格字符被记录到access.log。

假如设置为encode,Squid将空格字符按RFC 1738规范来编码。这点其实用户代理应该先做到。chop设置导致Squid把第一个空格字符后的URI都截断。

默认设置是strip,它让Squid从URI里移除空格字符。这确保日志文件解析器和重定向器工作正常,但可能会破坏某些事情,例如不正确编码的搜索引擎查询。

13.2.4.10 buffered_logs

默认情况下,Squid禁止写cache.log文件的buffer,这允许你运行tail -f 命令实时的观察日志文件变化。假如你认为这点导致不必要的性能开销,就可以禁用buffer:

buffered_logs off

然而,除非以完整debug模式运行Squid,这点可能无关紧要。注意该选项仅仅影响cache.log。其他的日志文件总使用非缓冲的写方式。

13.2.5 access.log分析工具

access.log包含很多信息,远不止你简单的浏览该文件所见。为了完整的浏览,必须使用第三方的日志文件分析包。你可在Squid的web页面的链接里,找到它们的列表。或者直接访问:http://www.squid-cache.org/Scripts/.

最流行的工具之一是Calamaris — 一个Perl脚本,解析日志文件并产生基于文本的或HTML的报告。它提供关于会话的详细分类包括请求方式、客户端IP地址、原始服务器域名、内容类型、文件名扩展、响应size、以及更多。Calamaris也报告ICP查询会话,甚至其他cache产品的日志分析。其站点是:http://calamaris.cord.de.

Squeezer以及它的派生Squeezer2,是Squid专有的分析工具。它们提供许多统计,能帮助你了解Squid的性能,特别是在有邻居cache时。两者都产生HTML文件作为输出。squid-cache.org站点的Logfile Analysis页有这些程序的链接。

Webalyzer是另一个有用工具。它运行快速,并且产生带表格和柱形统计表的HTML页面。它原始是设计成分析原始服务器的访问日志的。尽管它能解析Squid的日志,但不会报告诸如命中率和响应时间的事件。它使用的某些条款不同于我的做法。例如,Webalyzer把任何请求叫做一个”命中”,这不同于cache命中。它也把”页面”和”文件”加以区别。更多信息请访问Webalyzer的主页:http://www.mrunix.net/webalyzer/.

13.3 store.log

store.log记录Squid关于存储或删除cache目标的决定。对每个存在cache里的目标、每个不可cache的目标、以及每个被轮换策略删除的目标,Squid都会创建相应的日志条目。该日志文件内容既包含了内存cache又包含了磁盘cache。

store.log提供了下述不能从access.log获取的内容:

  • 1)某个特定的响应是否被cache。
  • 2)cache目标的文件号。对UFS基础的存储机制,你可转换该文件号到路径名,并且检查cache文件的内容。
  • 3)响应的内容长度:包括Content-Length值和实际的body大小。
  • 4)Date, Last-Modified, 和Expires头部的值。
  • 5)响应的cache关键字(例如MD5哈希值)。

如你所见,这些都是相对低级的信息,在日常管理中可能用不上。除非你要做专业的分析,或打算debug某程序,否则store.log可有可无。可以如下来禁止它:

cache_store_log none

跟其他日志文件一样,Squid将最新的日志条目写到该文件的末尾。某个给定的URI可能出现在日志文件里多次。例如,它先被cache,然后删除,接着又cache住。仅仅最近来的日志条目才反映目标的当前值。

store.log是文本基础的,看起来如下:

1067299212.411 RELEASE -1 FFFFFFFF A5964B32245AC98592D83F9B6EA10B8D 206

    1067299212 1064287906 -1 application/octet-stream 6840/6840

    GET http://download.windowsupdate.com/msdownload/update/v3-19990518/cab...

1067299212.422 SWAPOUT 02 0005FD5F 6F34570785CACABC8DD01ABA5D73B392 200

    1067299210 1057899600 -1 image/gif 1125/1125

    GET http://forum.topsportsnet.com/shf./images/nav_members1.gif

1067299212.641 RELEASE -1 FFFFFFFF B0616CB4B7280F67672A40647DD08474 200

    1067299212 -1 -1 text/html -1/67191

    GET http://www.tlava.com/

1067299212.671 RELEASE -1 FFFFFFFF 5ECD93934257594825659B596D9444BC 200

    1067299023 1034873897 1067299023 image/jpeg 3386/3386

    GET http://ebiz0.ipixmedia.com/abc/ebiz/_EBIZ_3922eabf57d44e2a4c3e7cd234a...

1067299212.786 RELEASE -1 FFFFFFFF B388F7B766B307ADEC044A4099946A21 200

    1067297755 -1 -1 text/html -1/566

    GET http://www.evenflowrocks.com/pages/100303pic15.cfm

1067299212.837 RELEASE -1 FFFFFFFF ABC862C7107F3B7E9FC2D7CA01C8E6A1 304

    1067299212 -1 1067299212 unknown -1/0

    GET http://ebiz0.ipixmedia.com/abc/ebiz/_EBIZ_3922eabf57d44e2a4c3e7cd234a...

1067299212.859 RELEASE -1 FFFFFFFF 5ED2726D4A3AD83CACC8A01CFDD6082B 304

    1066940882 1065063803 -1 application/x-javascript -1/0

    GET http://www.bellsouth.com/scripts/header_footer.js

每个日志条目包含如下13个域:

  • 1. 时间戳
    事件何时发生,表现为Unix纪元以来的秒数,它是毫秒级的。
  • 2. 动作
  • cache目标发生的动作。该域有3个可能值:SWAPOUT,RELEASE,和SO_FAIL。
    • 1)SWAPOUT在Squid成功的存储目标到磁盘时发生。某些目标例如那些消极cache的,仅保存在内存而不是磁盘,Squid不会在store.log里记录它们。
    • 2)SO_FAIL表明Squid不能完整的存储目标到磁盘。多半意味着存储机制拒绝以写方式打开新的磁盘文件。
    • 3)RELEASE在Squid从cache里删除目标,或首先就决定响应不可存储时发生。
  • 3. 目录号
  • 目录号是十进制小数形式,它是个到cache目录的7位索引。对没有存储到磁盘的目标,该域包含-1值。
  • 4. 文件号
  • 文件号是25位的标识符,内在的被squid使用。它被写成8字符的十六进制号。对UFS基础的存储机制,有算法可以转换文件号到路径名(见13.3.1节)。没有存储到磁盘的目标,没有有效的文件号。对这些目标,该域的值是FFFFFFFF。仅仅在RELEASE和SO_FAIL情况下才会出现这个值。
  • 5. cache关键字
    Squid使用MD5哈希值作为主要的索引来定位目标。该关键字基于请求方式、URI、和其他可能的信息计算得来。可以从cache关键字来查找store.log条目。然而请注意,目标的cache关键字可能改变。当Squid在access.log里记录TCP_REFRESH_MISS请求时,这点会发生。情况类似如下:

    1065837334.045 SWAPOUT ... 554BACBD2CB2A0C38FF9BF4B2239A9E5 ... http://blah
    
    1066031047.925 RELEASE ... 92AE17121926106EB12FA8054064CABA ... http://blah
    
    1066031048.074 SWAPOUT ... 554BACBD2CB2A0C38FF9BF4B2239A9E5 ... http://blah

    发生了什么呢?该目标原本cache在某个关键字下(554B…)。一段时间后,Squid接受到对该目标的另一请求,并转发确认请求到原始服务器。当响应以新内容返回时,Squid改变旧目标的cache关键字(92AE…),以便它能授予新目标正确的关键字(554B…)。然后旧目标删除,新目标存储到磁盘。

  • 6. 状态码
  • 该域显示响应的HTTP状态码,跟access.log一样。表13.1是状态码列表。
  • 7. 日期
  • HTTP响应的Date头部值,表现为Unix纪元以来的秒数。值-1表示Date头部不可解析,-2意味着头部完缺。
  • 8. 最后修改时间
  • HTTP响应的Last-Modified头部值,表现为Unix纪元以来的秒数。值-1表示Last-Modified头部不可解析,-2意味着头部完缺。
  • 9. 过期时间
  • HTTP响应的Expires头部值,表现为Unix纪元以来的秒数。值-1表示Expires头部不可解析,-2意味着头部完缺。
  • 10. 内容类型
  • HTTP响应的Content-Type头部值,排除了任何media-type参数。假如Content-Type丢失了,Squid插入值unknown。
  • 11. 内容长度/大小
  • 该域包含2个数字,以斜杠分开。第一个是Content-Length头部值。-1表明Content-Length头部不存在。第二个是HTTP消息body的实际大小。你可使用这2个数字来部分的验证接受到的响应,并验证原始服务器是否不正确的计算了内容长度。大多数情形下,这2个数字相等。
  • 12. 方式
  • 请求目标的HTTP方式,跟access.log里的一样。
  • 13. URI
  • 最后一个域是请求URI,跟access.log里的一样。该域也有前述章节提到的空格问题。然而,这里不必为此担忧,因为你可安全的忽略任何多余的域。
  • 对许多RELEASE的条目,在最后8个域出现的是疑问号(?)。这是因为这些域的大部分值来自squid称为MemObject的结构。该结构仅在目标已被接受时,或目标被完整存储在内存时,才会出现。Squid cache里的大部分目标没有MemObject结构,因为它们仅存在于磁盘。对这些情况,Squid在相应域放置一个疑问号。
13.3.1 转换文件号到路径名

假如想要检查某个特定的cache文件,你可稍费工夫将文件号转换到路径名。另外目录号和L1和L2值也是必需的。在squid的源代码里,storeUfsDirFullPath( )函数做这个事情。可在src/fs/ufs/store_dir_ufs.c文件里找到它。如下短小的perl脚本模拟了当前算法:

#!/usr/bin/perl

$L1 = 16;

$L2 = 256;

while (<>) {

    $filn = hex($_);

    printf("%02X/%02X/%08X\n",

        (($filn / $L2) / $L2) % $L1,

        ($filn / $L2) % $L2,

        $filn);

}

这样使用它:

% echo 000DCD06 | ./fileno-to-pathname.pl

0D/CD/000DCD06

要在第N个cache_dir里找到该文件,简单的进入到相应的目录,并列出或查看该文件:

% cd /cache2

% ls -l 0D/CD/000DCD06

-rw-------  1 squid  squid  391 Jun  3 12:40 0D/CD/000DCD06

    % less 0D/CD/000DCD06
13.4 referer.log

可选的referer.log包含了来自客户端请求的Referer头部。为了使用该功能,必须在./configure时打开–enable-referer-log选项。还必须用referer_log指令来指定一个路径。例如:

referer_log /usr/local/squid/var/logs/referer.log

假如想禁止referer.log,则可设置文件名为none。

Referer头部正常情况下包含一个URI,从这个URI获取到了请求(见RFC2616的14.36节)。例如,当web浏览器发布请求到某个内嵌图片时,Referer头部被设置成包含该图片的HTML网页的URI。当你点击HTML超链接时,它也被设置。某些web站点管理员使用Referer值来查找死链接。在使用Squid作为代理人模式时,你也许发现referer.log特别有用。

referer.log格式简单,仅有4个域。如下是一些示例:

1068047502.377 3.0.168.206

    http://www.amazon.com/exec/obidos/search-handle-form/002-7230223-8205634

    http://www.amazon.com/exec/obidos/ASIN/0596001622/qid=1068047396/sr=2-1/...

1068047503.109 3.0.168.206

    http://www.amazon.com/exec/obidos/ASIN/0596001622/qid=1068047396/sr=2-1/...

    http://g-images.amazon.com/./images/G/01/gourmet/gourmet-segway.gif

1068047503.196 3.0.168.206

    http://www.amazon.com/exec/obidos/ASIN/0596001622/qid=1068047396/sr=2-1/...

    http://g-images.amazon.com/./images/G/01/marketing/cross-shop/arnold/appar...

1068047503.198 3.0.168.206

    http://www.amazon.com/exec/obidos/ASIN/0596001622/qid=1068047396/sr=2-1/...

    http://g-images.amazon.com/./images/G/01/marketing/cross-shop/arnold/appar...

1068047503.825 3.0.168.206

    http://www.amazon.com/exec/obidos/ASIN/0596001622/qid=1068047396/sr=2-1/...

    http://images.amazon.com/./images/P/B00005R8BC.01.TZZZZZZZ.jpg

1068047503.842 3.0.168.206

    http://www.amazon.com/exec/obidos/ASIN/0596001622/qid=1068047396/sr=2-1/...

    http://images.amazon.com/./images/P/0596001622.01._PE_PI_SCMZZZZZZZ_.jpg

注意缺少Referer头部的请求不会被记录。这4个域描述如下:

  • 1. 时间戳
  • 请求时间,表现为Unix纪元以来的秒数,是毫秒级的。

注意的是,不像access.log,referer.log在Squid接受到完整请求时,会立刻记录。这样,referer.log条目在access.log之前发生,后者等待响应完成才记录。

  • 2. 客户端地址
  • 客户端地址跟access.log里的一样。log_fqdn和client_netmask指令也影响该日志文件。
  • 3. referer
  • 来自客户端请求的Referer头部值。注意referer值可能有空格字符或其他字符,在写referer.log前Squid不会对其进行编码。
  • 4. URI
  • 客户端正请求的URI。它匹配access.log里的URI。
13.5 useragent.log

可选的useragent.log包含来自客户端请求的User-Agent头部值。为了使用该功能,必须在运行./configure时打开–enable-useragent-log选项。还必须使用useragent_log指令来提供一个路径名。例如:

useragent_log /usr/local/squid/var/logs/useragent.log

User-Agent头部正常情况下包含了发起请求的user-agent的描述。大多数情形下,该描述只是简单的产品名列表和版本信息。你应该清楚应用程序可以轻易的提供伪造的user-agent信息。现代user-agent提供途径可定制该描述。甚至Squid在转发请求里能改变这个User-Agent头部。

useragent.log格式相对简单,看起来如下:

3.0.168.206 [05/Nov/2003:08:51:43 -0700]

    "Mozilla/5.0 (compatible; Konqueror/3; FreeBSD)"

3.0.168.207 [05/Nov/2003:08:52:18 -0700]

    "Opera/7.21 (X11; FreeBSD i386; U)  [en]"

4.241.144.204 [05/Nov/2003:08:55:11 -0700]

    "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/103u (KHTM..."

3.0.168.206 [05/Nov/2003:08:51:43 -0700]

    "Java1.3.1_01"

64.68.82.28 [05/Nov/2003:08:52:50 -0700]

    "Googlebot/2.1 (http://www.googlebot.com/bot.html)"

3.0.168.205 [05/Nov/2003:08:52:50 -0700]

    "WebZIP/4.1 (http://www.spidersoft.com)"

4.241.144.201 [05/Nov/2003:08:52:50 -0700]

    "Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt; Hotbar 3.0)"

3.0.168.206 [05/Nov/2003:08:54:40 -0700]

    "Bookmark Renewal Check Agent [http://www.bookmark.ne.jp/] (Version 2.0..."

不像其他日志文件,它仅有3个域:

  • 1. 客户端地址
  • 跟access.log里的一样。log_fqdn和client_netmask指令也影响该日志文件。
  • 2. 时间戳
  • 不像其他日志文件那样,用Unix纪元以来的秒数来描述时间,这里使用人工可读的格式。它是HTTP通用日志文件格式的时间戳,看起来如下:
    [10/Jun/2003:22:38:36 -0600]

    注意方括号界定时间戳,它包含一个空格。也请注意,跟referer.log一样,这些条目在Squid接受到完整请求时,立刻被记录。

  • 3. user-agent
  • User-Agent头部的值。这些字串几乎总包含空格。在将其写入日志文件时,Squid不会编码User-Agent值。
13.6 swap.state

swap.state文件是目标写入cache目录、或从cache目录删除的日志写照。每个cache_dir有它自己的swap.state文件。当Squid启动时,它读取swap.state文件来重建cache目标的内存索引。这些文件对Squid管理来说,至关重要。

默认情况下,每个cache.state文件位于它相应的cache目录。这样,每个state文件自动驻留在每个cache_dir下。这点很有用--假如你想重新排序cache_dir行,或想从cache_dir列表里删除条目的话。

如果想将它们放在其他位置,可使用cache_swap_log指令来做:

cache_swap_log /usr/local/squid/var/logs/swap.state

在此情况下,Squid对每个cache目录创建一个swap.state文件,并增加数字后缀。例如,假如有4个cache目录,Squid创建如下日志:

/usr/local/squid/var/logs/swap.state.00

/usr/local/squid/var/logs/swap.state.01

/usr/local/squid/var/logs/swap.state.02

/usr/local/squid/var/logs/swap.state.03

在这个情形下,如果你要增加、删除、或重排序cache_dir行,就必须手工重命名swap.state文件,以保持事情一致。

技术上,swap.state格式是独立于存储机制的。然而,在当前版本的Squid里,所有的存储机制使用同一种格式。swap.state文件使用修正大小(48位)的二进制格式。各个域值以主机字节顺序记录,这样在不同的操作系统之间不便迁移。表13-3描述了swap.state日志条目的各个域的说明。

Table 13-3. swap.state entry fields

Name
Size, in bytes
Description

op
1
Operation on the entry: added (1) or deleted (2).

file number
4
Same as the fourth field of store.log, except it is stored in binary.

timestamp
4
A timestamp corresponding to the time when the response was generated or last validated. Taken from the Date header for responses that have one. Stored as the number of seconds since the Unix epoch.

lastref
4
A timestamp corresponding to the most recent access to the object.

expires
4
The object’s expiration time, taken from an Expires header or Cache-Control max-age directive.

last-modified
4
The object’s Last-Modified value.

swap file size
4
The amount of space the object occupies on disk. This includes HTTP headers and other Squid-specific meta-information.

refcount
2
The number of times this object has been requested.

flags
2
Various internal flags used by Squid.

key
16
The MD5 hash of the corresponding URI. Same as the key in store.log, except this one is stored in binary.

13.7 轮转日志

Squid不断的写日志,假如cache非常忙,那么在一段时间后,这些日志文件可能变得很大。某些操作系统甚至限制了文件的最大size(例如2G),假如写文件超过了这个size就会报错。为了保持日志文件容易管理,以及让Squid正常工作,必须定期轮转日志。

Squid有内建的功能用于轮转日志。可通过squid -k rotate命令来调用它,然后告诉Squid对每个日志文件保持多少份旧拷贝。例如,假如设置它为7,对每个日志文件会有8个版本:1个当前的,和7个旧的。

旧日志文件以数字扩展来重命名。例如,当执行一次轮转时,Squid重命名log.6到log.7,然后是log.5到log.6,依此类推。当前log变成log.0,并且Squid创建一个新的空文件,命名为log。

每次执行squid -k rotate时,Squid轮转下述文件:cache.log, access.log, store.log, useragent.log (假如激活), 以及referer.log (假如激活)。Squid也会创建最新版本的swap.state文件。然而请注意,swap.state不会以数字扩展形式来轮转。

Squid不会自己轮转日志,最好的办法是在crontab里自动执行。例如:

0 0 * * * /usr/local/squid/sbin/squid -k rotate

假如你想编写自己的脚本来管理日志文件,Squid提供了一个有用的模式,简单的设置logfile_rotate指令为0。这样,当你运行squid -k rotate命令时,Squid简单的关闭当前日志文件,并且打开新的。如果操作系统允许重命名被其他进程打开的文件,则这点非常有用。下述shell脚本描述了一个思路:

#!/bin/sh

set -e

yesterday_secs=`perl -e 'print time -43200'`

yesterday_date=`date -r $yesterday_secs +%Y%m%d`

cd /usr/local/squid/var/logs

# rename the current log file without interrupting the logging process

mv access.log access.log.$yesterday_date

# tell Squid to close the current logs and open new ones

/usr/local/squid/sbin/squid -k rotate

# give Squid some time to finish writing swap.state files

sleep 60

mv access.log.$yesterday_date /archive/location/

    gzip -9 /archive/location/access.log.$yesterday_date
13.8 隐私和安全

Squid的日志文件特别是access.log,包含了用户的活跃记录,因此它受隐私问题支配。作为Squid管理员,你必须采取额外的小心来保证日志文件安全。最好的办法是限制访问Squid主机的人员的数量。假如这点行不通,那么就要谨慎的检查文件和目录许可,确保日志文件不会被非信任的、或未授权的用户访问。

也可利用client_netmask和strip_query_terms指令来保护用户隐私。前者让识别access.log里的用户困难;后者移除了URI查询条件以避免泄露用户私人信息。更多信息见13.2.4节。

如果想要保持历史数据相当长的时间,你也许可裁减日志来保证日志文件匿名。假如你仅对哪个URI被访问感兴趣,而不是谁访问了它们,就可从access.log里抽取出该域。这样也让文件更小,并且减少了隐私违背的风险。另一个技术是随机处理客户端IP地址。换句话说,就是创建一个过滤器,把真正的IP地址映射到假的地址,前提是同一个真IP地址总是映射到同一个假IP。假如你在使用RFC 1413身份验证协议或HTTP认证,也可考虑保持这些域匿名。

第14章 监视Squid

14.1 cache.log告警

在碰到Squid有问题时,应该首先查看cache.log里的警告信息。在正常运行时,你可发现不同的警告或信息,它们会或不会表明问题存在。我在13.1节里讲到了cache.log的结构。这里我重提一些可能在日志文件里见到的警告信息。

在中值响应时间超过限制时,high_response_time_warning指令让Squid打印一条警告。该值是毫秒级的,默认禁止。假如增加如下行到squid.conf:

high_response_time_warning 1500

如果大于1分钟的时间范围内的中值响应时间超过1.5秒,Squid会发布如下警告:

2003/09/29 03:17:31| WARNING: Median response time is 2309 milliseconds

在设置该指令前,你应该对Squid的正常响应时间级别有较好理解。假如设置过低,会导致很多假报警。在上述示例里,意味着一半用户的请求需要花费2.3秒去完成。高响应时间可能由本地程序产生,例如运行超出文件描述符;也可能是远程问题,例如拥挤的Internet连接。

high_page_fault_warning作用类似。假如每分钟的页面错误次数超过给定限制,它会导致Squid发布一条警告。高页面错误率通常意味着Squid进程不能完全放在内存,必须被交换到磁盘。这种交换严重影响了Squid的性能,所以你必须尽快解决问题,见16.1.8节的描述。

Squid使用Unix的getrusage()函数来获取页面错误计数。在某些操作系统上(例如Solaris),页面错误计数器表现异常。这样,high_page_fault_warning在这些系统上会导致假报警。

high_memory_warning指令也类似于前面提到的报警。在此情况下,它检查Squid进程的size,假如size超过了限制,就会在cache.log里告警。在某些操作系统上,进程size只增不降。这样,除非Squid关闭,你会经常得到这个警告。

进程size来自于如下3个函数之一:mallinfo( ), mstats( ), 或sbrk( ) 。假如这些函数在你的操作系统上不可用,则high_memory_warning不能工作。

Squid有其他一些硬编码的告警,可在cache.log里见到:

DNS lookup for 'neighbor.host.name' failed!

在Squid查询邻居cache的IP地址失败时,这点会发生。Squid大约每小时刷新一次邻居的地址。只要邻居的地址不可知,Squid不会发送会话到那边。

Detected DEAD Sibling: neighbor.host.name/3128/3130

在Squid不能与某个邻居cache通信时,它记录这个消息。例如,太多连续的ICP查询没有得到响应,这点就会发生。见10.3.2节的更多信息。

95% of replies from 'neighbor.host.name' are UDP_DENIED

该消息表明邻居cache拒绝回答Squid的查询。可能意味着Squid发送未经许可的查询到邻居cache。假如邻居cache使用地址基础的访问控制,并且你近来更改了地址,那它们就不会知道这个更改。在检测到该条件后,Squid拒绝发送更多查询到邻居cache。

Probable misconfigured neighbor at 192.168.121.5

若有未经授权的cache客户端向你发送ICP或HTCP查询,这点就会发生。最好的处理方法就是找到负责这个cache的组织或个人,询问他们为什么要查询你的cache。

Forwarding loop detected for:

回想一下,当单个请求遍历Squid 2次时,就发生了转发循环。请求的Via头部包含了遍历过的所有代理的列表。假如Squid在Via列表里检测到了自己的名字,它发布转发循环警告,并将请求直接发送到原始服务器。见10.2节关于转发循环的解释。

Closing client 192.168.121.5 connection due to lifetime timeout

client_lifetime指令对单个HTTP请求的存活期设置一个上限。当这样的请求终止时,Squid发布警告,它可能意味着某人正发起长时间连接来滥用cache,例如,无穷的download目标。

如你所见,caceh.log仅提供了异常事件的通知。对周期性的监控,还需要其他工具。cache管理器可能是最好的选择,尽管它的接口还不完美。

14.2 Cache管理器

译者注:由于本节的内容本人从未涉及,为避免误导,请读者自行阅读原书的该章节。 也有可能以后会更新本节内容,请关注本书中文版release的web目录:

http://home.arcor.de/jeffpang/squid

14.3 使用SNMP

译者注:由于本节的内容本人从未涉及,为避免误导,请读者自行阅读原书的该章节。 也有可能以后会更新本节内容,请关注本书中文版release的web目录:

http://home.arcor.de/jeffpang/squid

第15章 服务加速模式

15.1 概述

假如你已在某台机器上运行了原始服务器,就必须将它移到不同的IP地址或TCP端口。例如,可以这样做:(1)在独立的主机上安装squid;(2)给原始服务器分配一个新的IP地址;(3)将旧的IP地址分配给squid。为了安全起见,在squid和后台服务器通信的链路上,可使用私网地址。见图15-1。

Figure 15-1. How to replace your origin server with Squid

::__IHACKLOG_REMOTE_IMAGE_AUTODOWN_BLOCK__::9

另一个方法是配置squid成HTTP拦截,见第9章的描述。例如,可以配置离原始服务器最近的路由器或交换机,拦截HTTP请求,将其驱向squid。

假如你资源有限,不能将squid运行在独立的系统上,就可以让它随着HTTP服务一起运行。然而,这2个应用不能共享相同的IP地址和端口号。必须将后台服务绑定在不同的地址(例如127.0.0.1)或将它移到另一个端口。看起来改变端口最容易,但我推荐改变IP地址。

改变端口可能会带来问题。例如,当后台服务产生错误消息时,它可能会泄露错误的端口。更糟的是,假如服务产生一个HTTP重定向,它典型的将非标准端口号追加到Location URI后面。

HTTP/1.1 301 Moved Permanently

Date: Mon, 29 Sep 2003 03:36:13 GMT

Server: Apache/1.3.26 (Unix)

Location: http://www.squid-cache.org:81/Doc/

假如客户端接受到这样的响应,它会发起连接到非标准断口(81),这样就绕过了服务加速器。假如你必须让squid和后台服务运行在同一主机上,那最好让后台服务侦听在本地回路地址上(127.0.0.1)。在apache上,可以这样做:

BindAddress 127.0.0.1

ServerName www.squid-cache.org

一旦你决定如何重新部署原始服务器,下一步就是配置squid。

15.2 配置Squid

技术上,一个单一的配置文件指令,足以让squid从cache代理状态转换到加速状态。不幸的是,生活总非如此简单。因为许多组织的web服务器以不同方法实现,所以squid也有很多指令要考虑。

15.2.1 http_port

一般squid仅对80端口的HTTP服务加速。使用http_port指令让squid侦听在该端口:

http_port 80

假如想让squid既作cache代理,又作加速器,那么列出这2个端口:

http_port 80

http_port 3128

你也可以配置客户端发送代理请求到80端口,但我强烈不鼓励那样做。使用独立的端口,假如以后必要,你可以更容易将2个服务迁移到不同的主机上。

15.2.2 https_port

可以配置squid来处理加密的HTTP(SSL和TLS)连接。该功能要求在运行./configure时,激活–enable-ssl选项。在该模式下,squid解密来自客户端的SSL/TLS连接,并将未加密的请求转发到后台服务器。https_port指令有如下格式:

https_port [host:]port cert=certificate.pem [key=key.pem] [version=1-4]

           [cipher=list] [options=list]

cert和key参数是OpenSSL兼容的证书和私钥文件的路径。假如忽略了key参数,OpenSSL库会在证书文件里寻找私钥。

可选的version参数指定支持何种SSL/TLS协议:1为自动选择,2为支持SSLv2,3为支持SSLv3,4为支持TLSv1。

可选的cipher参数是冒号分隔的密码列表。squid简单的将它传递给SSL_CTX_set_cipher_list( )函数。更多信息,请阅读系统中的ciphers(1) manpage,或试着运行:openssl ciphers.

可选的options参数是冒号分隔的OpenSSL选项列表。squid简单的将它传递给SSL_CTX_set_options( )函数。更多信息,请阅读系统中的SSL_CTX_set_options(3) manpage。

如下是一些https_port行的示例:

https_port 443 cert=/usr/local/etc/certs/squid.cert

https_port 443 cert=/usr/local/etc/certs/squid.cert version=2

https_port 443 cert=/usr/local/etc/certs/squid.cert cipher=SHA1

https_port 443 cert=/usr/local/etc/certs/squid.cert options=MICROSOFT_SESS_ID_BUG
15.2.3 httpd_accel_host

在这里告诉squid后台服务器的IP地址或主机名。假如后台服务器使用前面描述的本地环路地址,那么这样写:

httpd_accel_host 127.0.0.1

Squid会预先将这个值作为要加速的URI。它也会改变Host头部的值。例如,假如客户端发起这样的请求:

GET /index.html HTTP/1.1

Host: squidbook.org

Squid会将它改变成这个形式:

GET http://127.0.0.1/index.html HTTP/1.1

Host: 127.0.0.1

你可以看到,请求不再包含对squidbook.org的任何信息。只要后台服务没有配置成多域虚拟主机,这样做就不会有问题。

假如想让squid使用原始服务器的主机名,那么可将它放在httpd_accel_host指令里:

httpd_accel_host squidbook.org

这样请求如下:

GET http://squidbook.org/index.html HTTP/1.1

Host: squidbook.org

另一个选项是激活httpd_accel_uses_host_header指令。这时squid对大部分请求,会将Host头部插入URI里;仅对那些缺少Host头部的请求,squid才使用httpd_accel_host值。

当使用主机名时,squid通过正常途径来查询其IP地址。因为期望该主机名被解析到2个不同的地址(一个是客户端连接到squid的地址,另一个是squid连接到后台服务器的地址),你应该增加静态的DNS接口到系统的/etc/hosts文件里。例如:

127.0.0.1       squidbook.org

也可以使用重定向器代替。例如,可以编写一个简单的perl程序,将http://squidbook.org/…改变为http://127.0.0.1/… 见11章关于重定向客户请求的具体细节。

httpd_accel_host指令有1个特殊值。假如将它设为virtual,在Host头部丢失时,squid将原始服务器的IP地址插入URI里。然而,该功能仅在使用HTTP拦截时有用。

15.2.4 httpd_accel_port

该指令告诉squid后台服务器的端口号。默认是80。不必改变该值,除非后台服务器运行在不同端口。如下是示例:

httpd_accel_port 8080

假如在多个端口上加速原始服务器,可以将该值设为0。在该情形下,squid从Host头部里获取端口号。

15.2.5 httpd_accel_uses_host_header

该指令控制squid如何决定它插入加速URI里的主机名。假如激活了,请求里的Host头部值,会优先于httpd_accel_host值。

httpd_accel_uses_host_header指令与后台服务器上运行的虚拟域配合工作。假如后台服务器仅处理1个域,那么可禁用它。然而,假如你在加速多个域名,就请打开它:

httpd_accel_uses_host_header on

假如激活了httpd_accel_uses_host_header指令,记得要配置一些本章后面描述的访问控制。为什么要这样做?请考虑如下配置:

httpd_accel_host does.not.exist

httpd_accel_uses_host_header on

因为大多数请求有Host头部,squid会忽略掉httpd_accel_host设置,很少将does.not.exist名字插入到URI里。那些非常聪明的假冒HTTP请求的人,会让squid从加速模式进入cache代理模式。假如我知道你正在使用squid作为加速器,并且没有正确的访问控制,那我能发送这样的请求:

GET /index.html HTTP/1.1

Host: www.mrcranky.com

假如你已经激活了httpd_accel_uses_host_header,并且没有任何基于目的地址的访问控制,squid会转发这个请求到www.mrcranky.com 。阅读15.4章,配置访问控制,以确保squid不会与外部原始服务器会话。

15.2.6 httpd_accel_single_host

前面的httpd_accel_uses_host_header指令决定squid插入URI里的主机名,这里的指令决定squid转发它的cache丢失到哪里。默认的(httpd_accel_single_host 禁止),squid转发cache丢失到URI里的主机。假如URI里包含主机名,squid执行DNS查询来获取后台服务器的IP地址。

当激活了httpd_accel_single_host时,squid总是转发cache丢失到httpd_accel_host里定义的主机。换句话说,URI里的内容和Host头部不会影响到转发决定。也许激活该指令的最好的理由是避免DNS查询。简单的将httpd_accel_host设置为后台服务器的IP地址。激活它的另一个理由是假如你有其他设备(负载均衡器,病毒扫描器等)在squid和后台服务器之间。你可以让squid转发请求到这样的设备,不用对HTTP请求作任何改变。

注意同时激活httpd_accel_single_host和httpd_accel_uses_host_header是危险的,可能让攻击者破坏cache。考虑如下配置:

httpd_accel_single_host on

httpd_accel_host 172.16.1.1

httpd_accel_uses_host_header on

和这样的HTTP请求:

GET /index.html HTTP/1.0

Host: www.othersite.com

Squid将请求转发到172.16.1.1的后台服务器,但存储响应在URI http://www.othersite.com/index.html下。既然172.16.1.1实际上并非www.othersite.com,squid现在包含了对该URI的伪响应。假如激活了httpd_accel_with_proxy(下一节的),或者cache参与了某个层叠,它可能对信任用户发布坏的响应。为了阻止这样的滥用,记得阅读15.4章。

假如使用了httpd_accel_single_host指令,服务端持久连接可能不能工作。这是因为squid存储空闲连接在原始服务器主机名下面,但连接建立代码会查找由httpd_accel_host值命名的空闲连接。假如2个值不同,squid查找相应的空闲连接会失败。在超时后,空闲连接关闭,不会被重用。可以用server_persistent_connections指令(见附录A)来禁止服务端持久连接,就避免了这个小问题。

15.2.7 httpd_accel_with_proxy

默认的,无论何时你激活了httpd_accel_host指令,squid进入加速模式。那就是说,它拒绝代理http请求,仅仅接受加速请求,就好像它真正是原始服务器一样。squid也禁用了ICP端口(但不是HTCP,如果你激活它的话)。假如想让squid既接受加速请求又接受代理请求,请激活这个指令:

httpd_accel_with_proxy on
15.3 令人疑惑之处

诚然,杂乱的配置让我也疑惑。让我们换种方法来看它。实际使用的配置,依赖于有多少后台服务器,以及需要加速多少原始服务器主机名。让我们考虑如下4个独立的案例。

15.3.1 一个主机,一个主机名

这是最简单的配置。因为你只有一个主机和一个主机名,Host头部就无关紧要。可以这样做:

httpd_accel_host www.example.com

httpd_accel_single_host on

httpd_accel_uses_host_header off

假如愿意,可以在httpd_accel_host后使用IP地址,尽管它将出现在access.log的URI里。

15.3.2 一个主机,多个主机名

因为有多个虚拟主机的主机名,Host头部就变得重要。我们想让squid将它插入转发URI里。这样配置:

httpd_accel_host www.example.com

httpd_accel_single_host on

httpd_accel_uses_host_header on

在该情形下,squid基于Host头部产生URI。假如缺少Host头部,squid会插入www.example.com。假如愿意,你可以禁止httpd_accel_single_host。跟前面一样,可在httpd_accel_host后使用IP地址,以避免DNS查询。

15.3.3 多个主机,一个主机名

这点听起来象负载均衡配置。实现它的方法之一是,对多个IP地址的后台服务器创建一个DNS名字。对每个cache丢失,squid会轮循请求所有后台地址。在该情形下,squid配置与单主机/单主机名的情况一样:

httpd_accel_host roundrobin.example.com

httpd_accel_single_host on

httpd_accel_uses_host_header off

唯一不同之处是httpd_accel_host名被解析到多个IP地址。在BIND zone文件里,DNS配置可能看起来如下:

$ORIGIN example.com.

roundrobin      IN      A      192.168.1.2

                IN      A      192.168.1.3

                IN      A      192.168.1.4

在这样的DNS配置里,squid每次打开到roundrobin.example.com的新连接时,都使用列表里的下一个地址。当抵达列表末尾时,又会从头开始。注意squid内在的根据TTLs来缓存DNS响应。对每次DNS查询,不能依赖于名字服务器以不同顺序返回地址列表。

另一个选择是使用重定向器(见11章)来选择后台服务器。可以编写一个简单的脚本,将URI主机名(例如roundrobin.example.com)替换成不同的主机名或IP地址。如果重定向器足够智能,它甚至可以基于后台服务器的当前状态来进行选择。使用下列配置:

httpd_accel_host roundrobin.example.com

httpd_accel_single_host off

httpd_accel_uses_host_header off
15.3.4 多个主机,多个主机名

在该情形下,要使用Host头部。也要让squid基于原始服务器名字(例如,一个DNS查询)来选择后台服务器。配置如下:

httpd_accel_host www.example.com

httpd_accel_single_host off

httpd_accel_uses_host_header on

也许你凭错觉将httpd_accel_host设为virtual。然而,除非使用HTTP拦截,否则那将是一个错误。

15.4 访问控制

典型配置的加速器接受来自整个Internet的HTTP请求。然而这不意味着,可以忘记访问控制。你不应该让squid接受指向外部原始服务器的请求。唯一的例外是当你激活了httpd_accel_with_proxy时。

对仅作为加速器的配置,请使用目的基础的访问控制。例如,dst类型可执行这个任务:

acl All src 0/0

acl TheOriginServer dst 192.168.3.2

http_access allow TheOriginServer

http_access deny All

另外,假如愿意,可使用dstdomain ACL:

acl All src 0/0

acl TheOriginServer dstdomain www.squidbook.org

http_access allow TheOriginServer

http_access deny All

注意激活了httpd_accel_single_host某种程度上绕过了访问控制规则。这是因为在squid执行了访问控制检测后,原始服务器域(例如httpd_accel_host值)才被设置。

若在单个squid实例里,既使用加速模式又使用代理模式,访问控制会变得很麻烦。你不能简单的拒绝到外部原始服务器的所有请求。然而你能确保外部用户不允许对任意原始主机发起代理请求。例如:

acl All src 0/0

acl ProxyUsers src 10.2.0.0/16

acl TheOriginServer dst 192.168.3.2

http_access allow ProxyUsers

http_access allow TheOriginServer

http_access deny All

可以在访问控制规则里使用本地端口号。它实际上不会真正保护squid不被滥用,但确实能保证,例如,用户代理发送其代理请求到正确的端口。这点也让你以后可以方便的将加速服务和代理服务分拆到独立的主机上。假设配置squid侦听在80和3128端口,可以使用:

acl All src 0/0

acl ProxyPort myport 3128

acl ProxyUsers src 10.2.0.0/16

acl SurrogatePort myport 80

acl TheOriginServer dst 192.168.3.2

http_access allow ProxyUsers ProxyPort

http_access allow TheOriginServer SurrogatePort

http_access deny All

不幸的是,若同时激活了httpd_accel_single_host, httpd_accel_uses_host_header, 和httpd_accel_with_proxy,则这些访问控制规则不能阻止用户破坏cache的企图。这是因为有效的代理请求:

GET http://www.bad.site/ HTTP/1.1

Host: www.bad.site

和假的加速请求:

GET / HTTP/1.1

Host: www.bad.site

有同样的访问控制结果,但被转发到不同的服务器。它们有相同的访问控制结果是因为,在squid重写了加速请求后,它有和代理请求相同的URI。然而,它们不会被送到同一地方。加速请求到达由httpd_accel_host定义的服务器,因为激活了httpd_accel_single_host。

可以采取步骤来解决这个问题。确保后台服务器对未知的服务名(例如,Host头部指向非本地服务)产生一个错误。然而,最好不要同时运行squid作为加速和代理服务。

15.5 内容协商

近来的squid版本支持HTTP/1.1 Vary头部。假如后台服务器使用内容协商,这就是个好消息。例如,它能依赖于哪种web浏览器发起请求(User-Agent头部),或基于用户语言参数(Accept-Language头部),来发送不同的响应。

当对某URI的响应基于请求行为而变化时,原始(后台)服务器包含了一个Vary头部。该头部包含用于选择变量的请求头部列表。这些是selecting头部。当squid接受到带有Vary头部的响应时,在它产生内部cache key过程中,会包含这个selecting头部值。这样,随后而来的带有同样selecting头部值的请求会产生cache命中。

假如在运行./configure时,使用了 —enable-x-accelerator-vary选项,squid会查找一个名为X-Accelerator-Vary的响应头部。squid严格的把这个头部和Vary头部一样对待。然而,因为这是个扩展头部,它被下游用户代理忽略。它本质上提供一个方法,用于squid和后台服务器之间的私有内容协商。为了使用它,你也必须修改服务应用,以使其在响应里发送这个头部。我不知道该头部在何种情形下有用。假如你服务于协商响应,你也许该使用标准的Vary头部,以便所有的用户代理知道什么在进行。

15.6 补充

使用squid作为加速器可能改进原始服务器的安全和性能。然而,也有一些潜在的消极影响。请记住如下事情。

15.6.1 日志

当使用加速器时,原始服务器的访问日志仅包含了来自squid的cache丢失。并且,这些日志文件只记录了squid的IP地址,而不是客户端的。换句话说,squid的access.log才是所有有用信息的存放之处。

回想一下,squid默认不使用通用日志文件格式。可以使用emulate_httpd_log指令,来让squid的access.log看起来象Apache的默认日志文件格式。

15.6.2 忽略Reload

大部分浏览器的reload按钮产生带Cache-Control: no-cache指令的HTTP请求。虽然这点对客户端缓存代理来说很有用,但它可能破坏squid加速器的性能。假如后台服务器负载很高,这点尤其明显。reload请求强迫squid刷新当前cache响应,并从原始服务器重新获取新的响应。假如原始服务器的响应抵达很慢,squid会耗费超出正常数量的文件描述符和网络资源。

为了解决这个问题,可使用refresh_pattern的一个选项。当设置了ignore-reload选项时,squid假装请求没有包含no-cache指令。ignore-reload选项对加速器来说通常是安全的,尽管它在技术上确实违背了HTTP协议。

为了让squid忽略所有请求里的reload,在squid.conf里这样写:

refresh_pattern . 0 20% 4320 ignore-reload

其他的一种更安全的选择,你可以使用reload-into-ims选项。当请求包含no-cache时,它导致squid去验证其cache响应。然而请注意,这点仅在响应有cache验证选项(例如Last-Modified时间戳)时才能工作。

15.6.3 不可cache的内容

作为加速器,squid对来自后台服务器的cache响应,遵从标准HTTP头部规范。这意味着,例如,某些动态响应可能不被cache。你也许想使用refresh_pattern指令来强迫cache这些目标。例如:

refresh_pattern \.dhtml$ 60 80% 180

这样的欺骗仅对某些类型的响应有效,也就是说,那些没有Last-Modified或Expires头部的响应。squid默认不cache这样的响应。然而,在refresh_pattern规则里使用非零的最小时间,会让squid去cache这些响应,并在该数量的时间范围内,将响应作为cache命中处理。请见7.7章的细节。

假如后台服务器产生其他类型的不可cache响应,你也许不能欺骗squid来缓存它们。

15.6.4 错误

把squid作为原始服务器前面的加速器,你应该知道站点访问者有可能见到squid产生的错误消息,而不是原始服务器产生的。换句话说,你的squid的用法通过某些错误消息可能会暴露出来。例如,当squid解析客户HTTP请求失败时,它会返回自己的错误消息,假如请求不完整或畸形构造,就可能发生这种情况。若squid因为某些理由不能连接到后台服务器,它也会返回错误消息。

如果站点调整得好,可能不必担心squid的错误消息。虽然如此,你也应该经常仔细观察access.log,看看有何错误;任何错误,都有可能被你的用户看到。

15.6.5 刷新目标

在操作加速器时,你也许发现PURGE方式特别有用。如果你对服务的内容有良好理解,就该知道何时cache目标必须被刷新。刷新目标的技术跟我以前提过的一样。请见7.6章的细节。

15.6.6 邻居

尽管我不推荐,你仍可以配置squid作为加速器,并且作为cache层叠的一部分。假如选择了如此部署,请注意,squid默认转发cache丢失到父cache(而不是后台服务器)。假如这不是你想要的,请确保使用cache_peer_access指令,以便对后台服务器的请求不会抵达邻居cache。

第16章 调试和故障处理

16.1 一些通用问题

在讨论通用debug前,我先提起一些经常发生的问题。

16.1.1 “Failed to make swap directory”
Failed to make swap directory /var/spool/cache: (13) Permission denied

这点发生在你运行squid -z,并且squid的用户ID没有对/var/spool目录的写权限的时候。记住假如以root来启动squid,并且没有增加cache_effective_user行,那么squid默认以nobody用户运行。解决方法很简单:

# chown nobody:nobody /var/spool
16.1.2 “Address already in use”
commBind: Cannot bind socket FD 10 to *:3128: Address already in use

这个消息出现在bind()系统调用失败时,因为请求端口已经被其他应用程序所打开。通常,若已有一个squid在运行,而又试图启动第2个squid实例,就会发生这种情况。假如你见到这个错误消息,请使用ps来观察是否squid已经在运行。

Squid使用SO_REUSEADDR socket选项,以便bind()调用总能成功,即使仍有一些残余的socket位于TIME_WAIT状态。若该消息出现,尽管squid没有在运行,但你的操作系统可能在处理这个问题上有bug。重启操作系统是解决问题的一个方法。

另一个可能性是端口(例如3128)当前已被其他应用程序使用。假如你怀疑这点,就可使用lsof程序来发现哪个应用正在该端口上侦听。FreeBSD用户能使用sockstat代替。

16.1.3 “Could not determine fully qualified hostname”
FATAL: Could not determine fully qualified hostname.  Please set 'visible_hostname'

假如squid不能识别它自己的完整可验证域名,就会报这个错。如下是squid使用的算法:

  • 1) 假如你将squid的HTTP端口绑定在指定的接口地址上,squid试图对该地址执行反向DNS查询。假如成功,查询答案就被用上。
  • 2) Squid调用gethostname( )函数,然后使用 gethostbyname( )函数,试着解析其IP地址。假如成功,squid使用后者函数返回的官方主机名串。

假如以上2项技术都不能工作,squid以前面提到的致命错误消息退出。在该情形下,必须使用visible_hostname指令来告诉squid它的主机名。例如:

visible_hostname my.host.name
16.1.4 “DNS name lookup tests failed”

默认情况下,squid在启动前执行一些DNS查询。这点确保你的DNS服务器可到达,并且运行正确。假如测试失败,可在cache.log或syslog里见到如下消息:

FATAL: ipcache_init: DNS name lookup tests failed

假如你在内网里使用squid,squid可能不能查询到它的标准主机名列表。可使用dns_testnames指令来指定你自己的主机名。只要接受到响应,squid就会认为DNS测试成功。

假如你想完全跳过DNS测试,简单的在启动squid时,使用-D命令行选项:

% squid -D ...
16.1.5 “Illegal character in hostname”
urlParse: Illegal character in hostname 'super_bikes.tripod.com'

默认情况下,squid检查URL的主机名部分的字符,假如它发现了非标准的字符,squid会抱怨。参考RFC 1034和1035,名字必须由字母A-Z,数字0-9,以及短横线(-)组成。下划线(_)是最有问题的字符之一。

Squid验证主机名是因为,在某些情形下,DNS对畸形字符的解析会很困难。例如:

% host super_bikes.tripod.com

super_bikes.tripod.com has address 209.202.196.70

% ping super_bikes.tripod.com

ping: cannot resolve super_bikes.tripod.com: Unknown server error

Squid事先检查主机名,这好过于以后返回Unknown server error消息。然后它会告诉用户主机名包含畸形字符。

某些DNS解析器确实能处理下划线和其他非标准字符。假如你想让squid不检查主机名,请在运行./configure时,使用—disable-hostname-checks选项。假如你允许下划线作为唯一的例外,那么使用—enable-underscores选项。

16.1.6 “Running out of filedescriptors”
WARNING! Your cache is running out of filedescriptors

上述消息出现在squid用完了所有可用文件描述符时。假如这点发生在正常条件下,就有必要增加内核的文件描述符限制,并且重新编译squid。请见3.3.1章。

假如squid成为了拒绝服务攻击的目标,那也会见到这条消息。某些人可能有意或无意的,同时对squid发送成百上千条请求。在这种情形下,可以增加一条包过滤规则,阻止来自恶意地址的TCP进入连接。假如攻击是分布式的,或使用假冒源地址,就很难阻止它们。

转发循环(见10.2章)也可能耗尽squid的所有文件描述符,但仅仅发生在squid不能检测到死循环时。Via头部包含了某个请求遍历过的所有代理的主机名。squid在头部里查找它自己的主机名,假如发现了,就报告这个循环。假如因为某些理由,Via头部从外出或进入HTTP请求里过滤掉了,squid就不能检测到循环。在该情形下,所有文件描述符被循环遍历squid的同一请求迅速耗完。

16.1.7 “icmpRecv: Connection refused”

假如pinger程序没有正确的安装,可见到下列消息:

icmpRecv: recv: (61) Connection refused

不过看起来更象是因为没有打开ICMP socket的权限,pinger立刻退出了。因为该进程未在运行,当squid试图与它会话时,会接受到I/O错误。为了解决该问题,请到源代码目录以root运行:

# make install-pinger

假如成功,你可见到pinger程序有下列文件属主和许可设置:

# ls -l /usr/local/squid/libexec/pinger

-rws--x--x  1 root  squid  140728 Sep 16 19:58 /usr/local/squid/libexec/pinger
16.1.8 在运行一段时间后,Squid变慢了

看起来更象squid与其他进程,或与它自己,在竞争系统中的内存。当squid进程的内存不再充足时,操作系统被迫从交换空间进行内存读写。这对squid的性能有强烈影响。

为了证实这个想法,请使用top和ps等工具检查squid的进程大小。也检查squid自己的页面错误计数器,见14.2.1.24章的描述。一旦你已确认内存耗费是问题所在,请执行下列步骤来减少squid的内存使用:

  • 1. 减少cache_mem值,见附录B。
  • 2. 关掉内存池,用该选项:
    memory_pools off
  • 3. 通过降低一个或多个cache目录的size,减少磁盘cache大小。
16.1.9 调试访问控制

假如访问控制不能正确工作,如下是一些有用帮助。编辑squid.conf文件,设置debug_options行如下:

debug_options ALL,1 33,2

然后,重配置squid:

% squid -k reconfigure

现在,对每个客户端请求以及每个响应,squid都写一条消息到cache.log。该消息包含了请求方式,URI,是否请求/响应被允许或拒绝,以及与之匹配的最后ACL的名字。例如:

2003/09/29 20:22:05| The request

    GET http://images.slashdot.org:80/topics/topicprivacy.gif is ALLOWED,

    because it matched 'localhost'

2003/09/29 20:22:05| The reply for

    GET http://images.slashdot.org/topics/topicprivacy.gif is ALLOWED,

    because it matched 'all'

知道ACL的名字,并非总能知道相应的http_access行,但也相当接近了。假如必要,可以复制acl行,并给予它们唯一的名字,以便给定的ACL名字仅仅出现在一个http_access规则里。

16.2 通过cache.log进行调试

从13.1章已了解到,cache.log包含了不同的操作消息,squid认为这些消息足够重要,从而告诉了你。我们也将这些作为debug消息考虑。可以使用debug_options指令来控制出现在cache.log里的消息的冗长度。通过增加debug等级,可以见到更详细的消息,有助于理解squid正在做什么。例如:

debug_options ALL,1 11,3 20,3

在squid源代码里的每个debug消息有2个数字特征:1个节和1个等级。节范围从0到100,等级范围从0到10。通常来说,节号对应着源代码的组成成分。换句话说,在单一源文件里的所有消息,有相同的节号。在某些情形下,多个文件使用同一debug节,这意味着某个源文件变得太大,从而被拆分成多个小块。

每个源文件的顶部有一行,用于指示debug节。它看起来如此:

* DEBUG: section 9     File Transfer Protocol (FTP)

我不指望你通过查看源文件来查找节号,所有相关信息定义在表16-1里。

Table 16-1. Debugging section numbers for the debug_options directive

Number
Description
Source file(s)

0
Client Database
client_db.c

1
Startup and Main Loop
main.c

2
Unlink Daemon
unlinkd.c

3
Configuration File Parsing
cache_cf.c

4
Error Generation
errorpage.c

5
Socket Functions
comm.c

5
Socket Functions
comm_select.c

6
Disk I/O Routines
disk.c

7
Multicast
multicast.c

8
Swap File Bitmap
filemap.c

9
File Transfer Protocol (FTP)
ftp.c

10
Gopher
gopher.c

11
Hypertext Transfer Protocol (HTTP)
http.c

12
Internet Cache Protocol
icp_v2.c

12
Internet Cache Protocol
icp_v3.c

13
High Level Memory Pool Management
mem.c

14
IP Cache
ipcache.c

15
Neighbor Routines
neighbors.c

16
Cache Manager Objects
cache_manager.c

17
Request Forwarding
forward.c

18
Cache Manager Statistics
stat.c

19
Store Memory Primitives
stmem.c

20
Storage Manager
store.c

20
Storage Manager Client-Side Interface
store_client.c

20
Storage Manager Heap-Based Replacement
repl/heap/store_heap_replacement.c

20
Storage Manager Logging Functions
store_log.c

20
Storage Manager MD5 Cache Keys
store_key_md5.c

20
Storage Manager Swapfile Metadata
store_swapmeta.c

20
Storage Manager Swapin Functions
store_swapin.c

20
Storage Manager Swapout Functions
store_swapout.c

20
Store Rebuild Routines
store_rebuild.c

21
Misc Functions
tools.c

22
Refresh Calculation
refresh.c

23
URL Parsing
url.c

24
WAIS Relay
wais.c

25
MIME Parsing
mime.c

26
Secure Sockets Layer Proxy
ssl.c

27
Cache Announcer
send-announce.c

28
Access Control
acl.c

29
Authenticator
auth/basic/auth_basic.c

29
Authenticator
auth/digest/auth_digest.c

29
Authenticator
authenticate.c

29
NTLM Authenticator
auth/ntlm/auth_ntlm.c

30
Ident (RFC 1413)
ident.c

31
Hypertext Caching Protocol
htcp.c

32
Asynchronous Disk I/O
fs/aufs/async_io.c

33
Client-Side Routines
client_side.c

34
Dnsserver Interface
dns.c

35
FQDN Cache
fqdncache.c

37
ICMP Routines
icmp.c

38
Network Measurement Database
net_db.c

39
Cache Array Routing Protocol
carp.c

40
Referer Logging
referer.c

40
User-Agent Logging
useragent.c

41
Event Processing
event.c

42
ICMP Pinger Program
pinger.c

43
AIOPS
fs/aufs/aiops.c

44
Peer Selection Algorithm
peer_select.c

45
Callback Data Registry
cbdata.c

45
Callback Data Registry
leakfinder.c

46
Access Log
access_log.c

47
Store COSS Directory Routines
fs/coss/store_dir_coss.c

47
Store Directory Routines
fs/aufs/store_dir_aufs.c

47
Store Directory Routines
fs/diskd/store_dir_diskd.c

47
Store Directory Routines
fs/null/store_null.c

47
Store Directory Routines
fs/ufs/store_dir_ufs.c

47
Store Directory Routines
store_dir.c

48
Persistent Connections
pconn.c

49
SNMP Interface
snmp_agent.c

49
SNMP Support
snmp_core.c

50
Log File Handling
logfile.c

51
File Descriptor Functions
fd.c

52
URN Parsing
urn.c

53
AS Number Handling
asn.c

54
Interprocess Communication
ipc.c

55
HTTP Header
HttpHeader.c

56
HTTP Message Body
HttpBody.c

57
HTTP Status-Line
HttpStatusLine.c

58
HTTP Reply (Response)
HttpReply.c

59
Auto-Growing Memory Buffer with printf
MemBuf.c

60
Packer: A Uniform Interface to Store Like Modules
Packer.c

61
Redirector
redirect.c

62
Generic Histogram
StatHist.c

63
Low Level Memory Pool Management
MemPool.c

64
HTTP Range Header
HttpHdrRange.c

65
HTTP Cache Control Header
HttpHdrCc.c

66
HTTP Header Tools
HttpHeaderTools.c

67
String
String.c

68
HTTP Content-Range Header
HttpHdrContRange.c

69
HTTP Header: Extension Field
HttpHdrExtField.c

70
Cache Digest
CacheDigest.c

71
Store Digest Manager
store_digest.c

72
Peer Digest Routines
peer_digest.c

73
HTTP Request
HttpRequest.c

74
HTTP Message
HttpMsg.c

75
WHOIS Protocol
whois.c

76
Internal Squid Object handling
internal.c

77
Delay Pools
delay_pools.c

78
DNS Lookups; interacts with lib/rfc1035.c
dns_internal.c

79
Squid-Side DISKD I/O Functions
fs/diskd/store_io_diskd.c

79
Storage Manager COSS Interface
fs/coss/store_io_coss.c

79
Storage Manager UFS Interface
fs/ufs/store_io_ufs.c

80
WCCP Support
wccp.c

82
External ACL
external_acl.c

83
SSL Accelerator Support
ssl_support.c

84
Helper Process Maintenance
helper.c

debug等级这样分配:重要消息有较低值,非重要消息有较高值。0等级是非常重要的消息,10等级是相对不紧要的消息。另外,关于等级其实并没有严格的向导或要求。开发者通常自由选择适应的debug等级。

debug_options指令决定哪个消息出现在cache.log,它的语法是:

debug_options section,level section,level ...

默认设置是ALL,1,这意味着squid会将所有等级是0或1的debug消息打印出来。假如希望cache.log里出现更少的debug消息,可设置debug_options为ALL,0。

假如想观察某个组件的其他debug信息,简单的将相应的节号和等级增加到debug_options列表的末端。例如,如下行对FTP服务端代码增加了等级5的debug:

debug_options ALL,1 9,5

如同其他配置指令一样,可以改变debug_options,然后给squid发送重置信号:

% squid -k reconfigure

注意debug_options参数是按顺序处理的,后来的值会覆盖先前的值。假如使用ALL关键字,这点尤其要注意。考虑如下示例:

debug_options 9,5 20,9 4,2 ALL,1

在该情形下,最后的值覆盖了所有先前的设置,因为ALL,1对所有节设置了debug等级为1。

选择合适的debug节号和等级有时非常困难,尤其是对squid新手而言。许多更详细的debug消息仅对squid开发者和熟悉源代码的用户有意义。无经验的squid用户会发现许多debug消息无意义和不可理解。进一步的说,假如squid相对忙的话,你可能对某个特殊请求或事件进行独立debug有困难。假如你能一次用一个请求来测试squid,那么高的debug等级通常更有用。

若以高debug等级来运行squid较长时间,需要特别谨慎。假如squid繁忙,cache.log增长非常快,并可能最终耗尽它的分区的剩余空间。假如这点发生,squid以致命消息退出。另一个关注点是性能可能下降明显。因为有大量的debug消息,squid要耗费许多CPU资源来格式化和打印字符串。将所有debug消息写往cache.log,也浪费了大量的磁盘带宽。

16.3 Coredump,断点,和堆栈跟踪

假如不幸,squid可能在运行时遭遇致命错误。这类型的错误来自3个风格:断点,总线错误,和异常分片。

断点是源代码里的正常检测。它是一个工具,被开发者用来确认在处理某事情前,相应的条件总为真。假如条件为假,程序退出并创建一个core文件,以便开发者能分析形势。如下是个典型的示例:

int some_array[100];

void

some_func(int idx)

{
        ...

        assert(idx < 100);

        some_array[idx]++;

        ...
}

这里,断点确保数组索引的值位于数组范围内。假如去访问大于或等于100的数组元素,就会遇到错误。假如不知何故,idx的值不小于100,程序运行时会打印如下消息:

assertion failed: filename.c:123: "idx < 100"

假如这点发生在squid上,就可在cache.log里见到”assertion failed”消息。另外,操作系统会创建一个core文件,这对事后分析有用。在本节结尾,我会解释如何去处理core文件。

总线错误是:由于处理器检测到其总线上的异常条件,会引发机器语言指令执行时致命失败。当处理器试图操作非连续的内存地址时,通常会发生这种错误。在64位处理器系统上可能更容易见到总线错误,例如Alpha和某些SPARC CPU。幸运的是,它们容易修复。

异常分片错误不幸的更常见,且有时难以修复。SEGV通常发生在进程试图访问无效内存区域时(可能是个NULL指针,或超出进程空间之外的内存地址)。当bug原因和SEGV影响在不同时间呈现时,它们特别难于捕获到。

Squid默认捕获总线错误和异常分片,当它们发生时,squid试图执行一个clean shutdown(清理关闭)。可在cache.log里见到类似的语句:

FATAL: Received Bus Error...dying.

2003/09/29 23:18:01| storeDirWriteCleanLogs: Starting...

大多数情形下,squid能够写swap.state文件的clean版本。在退出前,squid调用abort()函数来创建core文件。core文件可以帮助你或其他开发者来捕获和修复bug。

在错误发生时马上创建core文件,而不是先调用clean shutdown过程,这样更利于调试。使用-C命令行选项,可以告诉squid不去捕获总线错误和异常分片:

% squid -C ...

注意某些操作系统使用文件名core,而另外一些优先考虑进程名(例如squid.core)。一旦找到core文件,请使用调试器来进行堆栈跟踪。gdb是GNU调试器–GNU C编译器的配套工具。假如没有gdb,可试着运行dbx或adb代替。如下显示如何使用gdb来进行堆栈跟踪:

% gdb /usr/local/squid/sbin/squid /path/to/squid.core

...

Core was generated by 'squid'.

Program terminated with signal 6, Abort trap.
...

然后,敲入where来打印堆栈轨迹:

(gdb) where

#0  0x28168b54 in kill ( ) from /usr/lib/libc.so.4

#1  0x281aa0ce in abort ( ) from /usr/lib/libc.so.4

#2  0x80a2316 in death (sig=10) at tools.c:301

#3  0xbfbfffac in ?? ( )

#4  0x80abe0a in storeDiskdSend (mtype=4, sd=0x82101e0, id=1214000,

    sio=0x9e90a10, size=4096, offset=-1, shm_offset=0)

    at diskd/store_io_diskd.c:485

#5  0x80ab726 in storeDiskdWrite (SD=0x82101e0, sio=0x9e90a10,

    buf=0x13e94000 "...", size=4096, offset=-1, free_func=0)

    at diskd/store_io_diskd.c:251

#6  0x809d2fb in storeWrite (sio=0x9e90a10, buf=0x13e94000 "...",

    size=4096, offset=-1, free_func=0) at store_io.c:89

#7  0x80a1c2d in storeSwapOut (e=0xc5a7800) at store_swapout.c:259

#8  0x809b667 in storeAppend (e=0xc5a7800, buf=0x810f9a0 "...", len=57344)

    at store.c:533

#9  0x807873b in httpReadReply (fd=134, data=0xc343590) at http.c:642

#10 0x806492f in comm_poll (msec=10) at comm_select.c:445

#11 0x8084404 in main (argc=2, argv=0xbfbffa8c) at main.c:742

#12 0x804a465 in _start ( )

你可见到,堆栈轨迹打印了每个函数的名字,它的参数,以及源代码文件名和行数。当捕获bug时,这些信息特别有用。然而在某些情形下,这些还不够。可能要求你在调试器里执行其他命令,例如打印来自某个函数的变量的值:

(gdb) frame 4

#4  0x80abe0a in storeDiskdSend (mtype=4, sd=0x82101e0, id=1214000,

    sio=0x9e90a10, size=4096, offset=-1, shm_offset=0)

    at diskd/store_io_diskd.c:485

485         x = msgsnd(diskdinfo->smsgid, &M,

 msg_snd_rcv_sz, IPC_NOWAIT);

(gdb) set print pretty

(gdb) print M

$2 = {

  mtype = 4,

  id = 1214000,

  seq_no = 7203103,

  callback_data = 0x9e90a10,

  size = 4096,

  offset = -1,

  status = -1,

  shm_offset = 0

}

在报告了某个bug后,请保留core文件一些天,可能还需要从它获取其他信息。

16.3.1 不能找到core文件?

core文件写在进程的当前目录。squid在启动时默认不改变其当前目录。这样你的core文件(如果有的话),会写在启动squid的目录。假如文件系统没有足够的自由空间,或进程属主没有对该目录的写权限,就无法产生core文件。可以使用coredump_dir指令来让squid使用指定的coredump目录–位于其他地方的有充足自由空间和完全权限的目录。

进程资源限制也会阻止产生core文件。进程限制参数之一是coredump文件的大小。大部分操作系统默认设置这个值为”无限”。在当前shell里使用limits或ulimit命令,可以检查当前限制。然而请注意,你的shell的限制可能不同于squid的进程限制,特别是当squid随系统启动而自动启动时。假如怀疑进程限制阻止了core文件的产生,试试这样:

csh% limit coredumpsize unlimited

csh% squid -NCd1

在FreeBSD上,某个sysctl参数控制了操作系统对调用了setuid()或setgid()函数的进程,是否产生core文件。假如以root启动,squid会用到这些函数。这样为了得到coredump,必须告诉内核创建core文件,用这个命令:

# sysctl kern.sugid_coredump=1

请见sysctl.conf的manpage,关于在系统启动时如何自动设置变量的信息。

16.4 重现问题

有时候可能遇到这样的问题:某个请求,或原始服务器看起来不能与squid协调工作。可以使用下面的技术来确定问题在于squid,客户端,或原始服务器。技巧就是捕获HTTP请求,然后用不同的方法响应它,直到你验证了问题。

捕获HTTP请求意味着获取除了URL外的更多信息,包括请求方式,HTTP版本号,和所有请求头部。捕获请求的一个方法是,短期激活squid的完整debug模式。在squid主机上,敲入:

% squid -kdebug

然后,到web浏览器上发布请求。squid几乎会立刻接受到请求。在若干秒后,回到squid主机,并发布同样的命令:

% squid -kdebug

现在cache.log文件包含了上述客户端的请求。假如squid繁忙,cache.log会包含大量的请求,所以你必须查找它。它看起来如下:

2003/09/29 10:37:40| parseHttpRequest: Method is 'GET'

2003/09/29 10:37:40| parseHttpRequest: URI is 'http://squidbook.org/'

2003/09/29 10:37:40| parseHttpRequest: Client HTTP version 1.1.

2003/09/29 10:37:40| parseHttpRequest: req_hdr = {

User-Agent: Mozilla/5.0 (compatible; Konqueror/3)

Pragma: no-cache

Cache-control: no-cache

Accept: text/*, image/jpeg, image/png, image/*, */*

Accept-Encoding: x-gzip, gzip, identity

Accept-Charset: iso-8859-1, utf-8;q=0.5, *;q=0.5

Accept-Language: en

Host: squidbook.org

注意squid把首行元素分开打印,必须手工组合它们如下:

GET http://squidbook.org/ HTTP/1.1

捕获完整请求的另一个方法是使用工具例如netcat或socket (http://www.jnickelsen.de/socket/ )。启动socket程序侦听在某个端口,然后配置浏览器使用该端口作为代理地址。当再次发起请求时,socket打印出HTTP请求:

% socket -s 8080

GET http://squidbook.org/ HTTP/1.1

User-Agent: Mozilla/5.0 (compatible; Konqueror/3)

Pragma: no-cache

Cache-control: no-cache

Accept: text/*, image/jpeg, image/png, image/*, */*

Accept-Encoding: x-gzip, gzip, identity

Accept-Charset: iso-8859-1, utf-8;q=0.5, *;q=0.5

Accept-Language: en

Host: squidbook.org

最后,还可以使用网络包分析工具例如tcpdump或ethereal。使用tcpdump捕获到一些包后,可以使用tcpshow来查看它们:

# tcpdump -w tcpdump.log -c 10 -s 1500 port 80

# tcpshow -noHostNames -noPortNames < tcpdump.log | less

...
Packet 4

TIME:   08:39:29.593051 (0.000627)

LINK:   00:90:27:16:AA:75 -> 00:00:24:C0:0D:25 type=IP

  IP:   10.0.0.21 -> 206.168.0.6 hlen=20 TOS=00 dgramlen=304 id=4B29

        MF/DF=0/1 frag=0 TTL=64 proto=TCP cksum=15DC

 TCP:   port 2074 -> 80 seq=0481728885 ack=4107144217

        hlen=32 (data=252) UAPRSF=011000 wnd=57920 cksum=EB38 urg=0

 DATA:  GET / HTTP/1.0.

        Host: www.ircache.net.

        Accept: text/html, text/plain, application/pdf, application/

        postscript, text/sgml, */*;q=0.01.

        Accept-Encoding: gzip, compress.

        Accept-Language: en.

        Negotiate: trans.

        User-Agent: Lynx/2.8.1rel.2 libwww-FM/2.14.

.

注意tcpshow按数据里的新行字符为周期来进行打印。

一旦捕获到了某个请求,就将它存到文件。然后可以使用netcat或socket来让它重新通过squid:

% socket squidhost 3128 < request | less

假如响应看起来正常,问题可能在于用户代理。否则,可以改变不同事情来孤立问题。例如,假如你看到一些古怪的HTTP头部,那就从请求里删除它们,然后再试一次。让请求直接到达原始服务器,而不是经过squid,这样做也可调试。方法就是从请求里删除http://host.name/,并将请求发送到原始服务器:

% cat request

GET / HTTP/1.1

User-Agent: Mozilla/5.0 (compatible; Konqueror/3)

Pragma: no-cache

Cache-control: no-cache

Accept: text/*, image/jpeg, image/png, image/*, */*

Accept-Encoding: x-gzip, gzip, identity

Accept-Charset: iso-8859-1, utf-8;q=0.5, *;q=0.5

Accept-Language: en

Host: squidbook.org

% socket squidbook.org 80 < request | less

以这种方式使用HTTP时,请参考RFC 2616和Oreilly的HTTP:The Definitive Guide这本书。

16.5 报告Bug

假如你的squid版本已经有几个月未更新了,在报告bug前你应该更新它。因为其他人可能也注意了同样的bug,并且它已被修复。

假如你发现了squid的合理bug,请将它填入到squid的bug跟踪数据库:http://www.squid-cache.org/bugs/ 。 它当前是个”bugzilla”数据库,需要你创建一个帐号。当bug被squid开发者处理了时,你会接到更新通知。

假如你对报告bug很陌生,请先花时间阅读Simon Tatham写的”How to Report Bugs Effectively” (http://www.chiark.greenend.org.uk/~sgtatham/bugs.html )。

当报告bug时,确认包含下列信息:

  • 1) Squid版本号。假如bug发生在不止一个版本上,就也要写上其他版本号。
  • 2) 操作系统名字和版本。
  • 3) bug每次都发生,还是偶尔发生。
  • 4) 所发生事情的精确描述。类似于”它不能工作”,”请求失败”之类的语句,本质上对bug修复者无用。记得要非常详细。
  • 5) 对断点,总线错误,或异常分片的堆栈跟踪。

记住squid开发者通常是无报酬的义务劳动,所以要有耐心。严重bug比小问题享有更高的解决优先级。

译后序

当译完本书最后一章时,心头袭来深深的寂寞。

在计算机领域,国内外技术水平差之甚远,部分原因归咎于语言的差异。

某种技术在国外流行若干年后,才有相应的中文文档出现。

没有文档,技术人员无法起步;而不规范的发行文档,更是误导了一批又一批的初学者。

本书的作者Duane Wessels是位大师级的人物,除了精湛的技术外,他写的本书文笔通畅,脉络清晰,丝毫不晦涩。

若对研究Squid抱着严肃的态度,那么请认真的拜读原著。

而我所能做的,只是按照我自己的理解,把原著译成中文。

好的软件只是解决了某一方面的问题,而真正可贵的,是开源的精神。

在开源的世界里,可以体会到现实中所没有的无私与奉献。

想起陈玉莲演的小龙女,和金轮法王有过这么一段对话:

金轮法王:你能接住十招,我就放过你们。

小龙女:试试看咯。

金轮法王:若接不住呢?

小龙女:接不住就接不住咯。

多年来让我记住的,是陈玉莲这种不食人间烟火,与世无争的神韵。

彭勇华

yonghua_peng@yahoo.com.cn

2005年10月于广州

最后编辑:
作者:wy182000
这个作者貌似有点懒,什么都没有留下。

留下一个回复