分页: 1 / 1

《The BOOK Of PF》中文版 —— 23. 队列、整形和冗余

发表于 : 2010-07-20 5:50
whoami
OpenBSD的防火墙 —— 23. 队列、整形和冗余
本章涉及的两个主题可以潜移默化地提高你的网络经验。中心思想是管理可用资源。 本章的第一部分是讲如何使用ALTQ
通讯整形(traffic-shaping)子系统有效地和按照策略分配带宽资源。本章的第二部分探讨怎样通过使用CARP和pfsync协议提供的冗余特性确保资源可用。

用ALTQ分流通讯
ALTQ, 是ALTernate Queuing(交替排队)的简称, 是实现网络通讯整形的一种非常灵活的机制, 网络通讯整形在被整合进OpenBSD的PF前就已经存在了。详说明1 在OpenBSD3.3的发行版里ALTQ被整合进PF的代码并且已经在pf.conf里进行了配置,这主要是因为这样做对将通讯整形和过滤组合在一起是很有意义的。port进其它BSD的PF也尝试这样做, 至少带有一些ALTQ整合选项。 并非所有的BSD系统都完全完成了整合过程, 我们将描述一下相关系统的差异。
说明1
最早关于ALTQ的研究在USENIX 1999 conference。
你可以阅读Kenjiro Cho的文章 “Managing Traffic with ALTQ” 在线地址为 http://www.usenix.org/publications/libr ... 9/cho.html

基本的ALTQ概念
管理带宽资源很大程度上和平衡家庭的收入支出或处理匮乏或有限的资源类似。资源是十分有限的,你需要根据优先权或特定的目的最有效地分配使用这些资源。
ALTQ合理分配带宽的实质核心就是通过队列。 队列是一种网络数据包缓存的形式。 数据包被保存在队列里,然后根据应用在这个队列上的标准,要么丢弃这个数据包,要么将其发送到目的地; 数据包的发送受限于队列的有效带宽。队列附着在特定的网络接口上, 而带宽也是基于每个接口进行管理的, 根据你的定义将一个给定接口上的有效带宽分成队列。
队列即可以用指定的网络带宽来定义、也可以用可用带宽的比例来定义, 而有时队列有等级优先权。 在这里优先权是指一个运行指标, 或者换句话说也就是为哪个队列最先提供服务。我们后面会看到, 甚至可以用带宽分配加上优先权来配置有些队列类型。为了更精确的配置, 在某些队列类型上,你可以将每个队列的带宽共享给子队列, 这里的子队列换句话说就是队列中的队列, 它们共享父队列的资源。一旦定义了队列, 你可以通过重新编写pass规则分配通讯到一个指定的队列来将通讯整形整合到你的规则集里。我们将在后面详述怎样实现这个目的。
说明
在ALTQ设置里, 任何没有指派给特定队列的通讯将划归默认队列。

队列调度程序, 也称为队列规则
在默认的、不带ALTQ队列的网络设置里, TCP/IP堆栈和其过滤子系统处理数据包的顺序是按照这些数据包到达接口的先后顺序进行的。这就是我们通常所说的先进先出规则(First In, First Out), 或者简称为FIFO规则。

不同的ALTQ队列设置可能产生很不同的结果, 有时候效果令人惊讶。 下面有三种队列调度算法、或称作规则。其中每一种都具有自己独特的选项:
priq
[INDENT]priq又称为优先队列,是完全依据总体带宽里的优先权来处理的。priq队列允许的优先权范围从0到15, 数值越高就拥有更大的优先权。在此模式下会先处理具有更高优先权的队列。
[/INDENT]cbq
[INDENT]类队列法(Class-based queues)是基于固定的带宽分配的方法 , 以总可用带宽百分比的形式或者以Kb/s、Mb/s、Gb/s为单位。一个cbq队列还可以被分成多个队列, 你可以为这些队列分配优先权(范围是0 到7), 同样, 系统会先处理优先权值最高的队列。 在系统将带宽分配给这个队列前数据包会一直保存在队列中。 对于那些系统分配了固定带宽队列来说,如果这个队列又被分成几个不同优先权的子队列, 那些匹配匹配高优先权标准的队列会被优先处理。
[/INDENT]hfsc
[INDENT]这个规则采用等级公平服务曲线算法(Hierarchical Fair Service Curve——简称HFSC) 来确保 “公平”地按等级分配带宽。无论是其算法还是相应的设置都很复杂, 有一系列可调参数。 因为这个原因, 多数的ALTQ用户仅喜欢使用相对简单的队列类型, 但是那些精通HFSC的用户却非常喜欢使用这个算法规则。
[/INDENT]在PF里,通常的ALTQ语法像这样:

代码: 全选

altq on interface type [options ... ] main_queue { sub_q1, sub_q2 ..}
queue sub_q1 [ options ... ]
queue sub_q2 [ options ... ] { subA, subB, ... }
[...]
pass [ ... ] queue sub_q1
pass [ ... ] queue sub_q2
说明
这里的cbq和hfsc队列可以用几层子队列, 而priq队列很简单,仅有一层队列。在本章的下几个小节中,我们将介绍每种类型的不同语法。

设置ALTQ
要在启用ALTQ以便使用排队逻辑,你需要一些额外的步骤 , 这要根据你的平台上使用的是哪种操作系统。

在OpenBSD上ALTQ
在OpenBSD里面, 所有的队列支持规则都被编译进了GENERIC和GENERIC.MP内核, 所以你只要在pf.conf上进行配置就可以了。

在FreeBSD上ALTQ
在FreeBSD里, 你要先确认内核编译时包含了ALTQ以及ALTQ队列规则选项。默认情况下FreeBSD的GENERIC内核并未启用ALTQ选项。我们在此列出相关选项:

代码: 全选

options ALTQ
options ALTQ_CBQ # Class-Based Queuing (CBQ)
options ALTQ_RED # Random Early Detection (RED)
options ALTQ_RIO # RED In/Out
options ALTQ_HFSC # Hierarchical Packet Scheduler (HFSC)
options ALTQ_PRIQ # Priority Queuing (PRIQ)
options ALTQ_NOPCC # Required for SMP build
这里的ALTQ选项是在内核中启用ALTQ必须的, 你还需要ALTQ_NOPCC选项。根据你使用哪种类型的队列方式, ALTQ_CBQ, ALTQ_PRIQ, 或者ALTQ_HFSC,你至少需要启用其中之一。最后, 你可以分别启用具有避免拥塞技术的随即早期检测(Random Early Detection ——简称为RED) 以及RED In/Out, ALTQ_RED和ALTQ_RIO 选项。请参阅FreeBSD的Handbook了解怎样编译一个定制内核。

在NetBSD上ALTQ
在我写本章时, ALTQ正在被整合进NetBSD 4.0。和FreeBSD很类似, NetBSD的默认内核配置里虽然已经包含了ALTQ相关选项,但是并未启用, 要使用只需在将这些选项的注释去掉就可以了。主要的内核选项如下:

代码: 全选

options ALTQ # Manipulate network interfaces' output queues
options ALTQ_CBQ # Class-Based Queuing
options ALTQ_HFSC # Hierarchical Fair Service Curve
options ALTQ_PRIQ # Priority Queuing
options ALTQ_RED # Random Early Detection
这里的ALTQ选项是在内核中启用ALTQ必须的, 你还需要ALTQ_NOPCC选项。根据你使用哪种类型的队列方式, ALTQ_CBQ, ALTQ_PRIQ, 或者ALTQ_HFSC,你至少需要启用其中之一。
在NetBSD 4.0以前的版本里, Peter Postma维护着一个系统补丁来,用它来启用 PF或者ALTQ功能。最新的信息, 包括如何通过pkgsrc获取ALTQ补丁可以在NetBSD的PF相关网页 (http://nedbsd.nl/~ppostma/pf ) 和NetBSD的 PF 文档(http://www.netbsd.org/Documentation/network/pf.html) 里找到。

不管怎样, 现在你应该知道如何在你的系统上启用并运行ALTQ了。
什么是总可用带宽?
可能很难测定出某个用于队列的接口的实际带宽。如果你没有指定一个总带宽, 这个有效的总带宽将被用来计算分配。(<-----whoami:啥意思这里?是否为笔误?) 不管怎样, 某些类型的接口不能提供可靠的实际带宽。这个差异最常见的例子是,如果你网关的外部接口是一个100Mb的以太接口, 连接到一根实际只提供了8Mb下载和1Mb上传的网线上。* 这个以太网接口会信誓旦旦地告诉你这是100Mb的带宽, 而非时间的DSL带宽值。

基于这个原因,通常将总带宽设置为一个固定值是很有意义的。具体到如何设置这个固定值,很遗憾,这个固定值和你的网络供应商告诉你的带宽值没有固定的联系。因为各种技术和实施方案不同,带宽损失也会有一些差别。常规通过以太网线的TCP/IP通讯的带宽损失最高是9%, 而通过ATM或者ADSL的TCP/IP通讯的带宽损失在20-25%的范围内。 如果你的带宽提供商不合作, 你就需要根据自己的经验猜测了。 在任何情况下, 你都应该清楚地知道总有效带宽永远不会高于通讯路径中最慢的那一环。

还值得一提的是你只需要对出战通讯进行队列支持。 当你进行带宽管理时, 你应该考虑实际的有效带宽,它等于你的连接路径里最慢的那一环的实际带宽, 即便及更换了不同的接口也一样。
-----------------------------------
* 我知道,这些仅是在写这本书时的数据。也许再过几年, 这些数值会让人觉得不可思议。

有了前面这些铺垫性质的描述, 我们下面看一些ALTQ的配置实例。

理解优先权队列 (priq)
优先权队列 (priq) 很简单,也可能是读者最容易理解的一个规则。 将所有的带宽分配给最重要的队列, 这个方法仅根据通讯的优先权。你可以为队列分配不同的优先权值(0到15), 系统将按队列优先权值的高低依次处理(whomai:高先低后)。

这里我们举一个真实的例子, 看一下Daniel Hartmeier的网络。它发现了一个简单有效的办法——通过ALTQ来提高他的家庭的网络吞吐量。想许多人一样, Daniel的家庭网络也是使用不对称连接, 因为带宽有限,他十分希望可以更有效地利用带宽。

此外, 当速度达到或接近极限值时, 会发生一些奇怪的现象。 一个特殊的现象说明还有提升的空间: 无论何时,只要出战通讯开始进站通讯(下载, 进站的email, 或诸如此类的通讯)就会不可思议得变慢, 难以用原始的数据传输量解释。要明白这一切,我们要回溯到基本的TCP特性。

发出一个TCP数据包后发送者希望收到来自接收端的一个回应 (以一个ACK数据包的形式),并且在一个指定的时间内会一直等待着这个回应。如果这个ACK没有在指定的时间内抵达, 发送者将假设这个数据包并未被接收,并且会重新发送同样的数据包。
问题在于, 在默认设置里, 系统会根据数据包抵达接口的先后顺序依次处理它们。 这就不可避免地出现一种情况——这个基本上没有任何数据负载的ACK数据包要排队等候先期抵达的那些大数据量的数据包传送完毕。
一个可验证的理论就形成了: 如果能将小的、实际上没有什么数据的ACK数据包插入到大型的数据包前面, 会提升网络带宽的使用效率。这个理论最简单的实际验证或测试的方法就是设置两个不同优先权的队列,并且将它们整合进规则集。下面仅显示了相关部分的PF规则:

代码: 全选

ext_if="kue0"
altq on $ext_if priq bandwidth 100Kb queue { q_pri, q_def }
queue q_pri priority 7
queue q_def priority 1 priq(default)
pass out on $ext_if proto tcp from $ext_if to any flags S/SA \
keep state queue (q_def, q_pri)
pass in on $ext_if proto tcp from any to $ext_if flags S/SA \
keep state queue (q_def, q_pri)
我们可以看到,在外部接口上设置了优先权队列, 还有两个附属队列。第一个子队列q_pri的优先权值为7, 而第二个子队列的优先权值为,要低很多。
这个似乎简单的规则很有效,它正是利用了ALTQ对不同优先权的队列进行前后处理的特性。一旦一个连接被分配到主队列上, ALTQ会检查每个数据包的服务类新(type of service——简称ToS)区域。 ACK数据包的Tos延迟位设置为低, 着就说明发送者想要用尽可能最快速度送达。当ALTQ看到一个低延迟的数据包而且有不同权限的队列可用, 它会将这个数据包分配到高优先权队列。
这意味着所有的ACK数据包将跳过前面低优先权的队列并被更快速地传送 , 反过来也就是这些数据包将被更快的处理。从网络方面看,和纯FIFO配置相比,这样的配置在同样的硬件条件和有效的带宽下提供了更好的网络性能。详说明2
说明2
Daniel的文章里有关此版本的设置在 http://www.benzedrine.cx/ackpri.html ,它包含了一个更为细致的分析。

对小型网络的基于类的带宽分配方案 (cbq)
网络性能最大化通常会令人感觉很好,不过, 你可能会发现你的网络还有一些其它的需要。例如, 可能对你来说有些通讯例如emial和其它的重要服务需要在任何时候有一个基本的带宽保障, 而此时其它服务 (比如点到点文件共享) 不应该允许其超过一个额度。为了解决此类需求或担忧, 就需要使用基于类的队列(cbq) 规则,它提供了更多的选项。
为了解释如何使用cbq, 我们将为您举另外一个例子, 这个例子以前面几章的例子为基础。我们想让一个小型本地网络的用户群连接到外部预定义的一组服务,而且也允许从外部访问一台本地的web服务器。
这里所有的队列都设置在外部、也就是面向Ineternet的接口上。这样的做法是有道理的,因为带宽的限制更多是因为外部连接而非由于本地网络的速度限制。原则上, 不管怎样, 分配队列和运行通信整形可以在任何一个网络接口上完成。本例的设置包含了一个总带宽为2M的cbq队列及六个子队列。

代码: 全选

altq on $ext_if cbq bandwidth 2Mb queue { main, ftp, udp, web, ssh, icmp }
queue main bandwidth 18% cbq(default borrow red)
queue ftp bandwidth 10% cbq(borrow red)
queue udp bandwidth 30% cbq(borrow red)
queue web bandwidth 20% cbq(borrow red)
queue ssh bandwidth 20% cbq(borrow red) { ssh_interactive, ssh_bulk }
queue ssh_interactive priority 7 bandwidth 20%
queue ssh_bulk priority 0 bandwidth 80%
queue icmp bandwidth 2% cbq

我们看到队列main的默认分配带宽是18%,这里的意思是任何匹配pass规则但没有精确地分配给其它队列的通讯将属于这个队列。这里的borrow和red关键字表示当系统企图避免拥塞使用RED算法时该队列可以从父父队列中“借用”带宽。
其它的队列也或多或少地依据这个特征, 一直到子队列ssh, 它有两个具有不同优先权的子队列。在这里我们看到一个ACK优先权变量的例子: Bulk SSH 传输, 做为代表的就是SCP文件的传输, 它用一个Tos指定吞吐量, 而interactive SSH通讯的Tos标志位设置为低延迟并会跳过前面的bulk传输(译者:这里的跳过指插队到bulk的前面) 。这里的interactive通讯看似占用较小比例的总带宽以及共享带宽, 但是它具有高的优先权值,所以会被优先处理。
这个方案也有助于提高SCP文件的传输速度, 因为SCP文件的ACK数据包将被分配到高优先权的子队列。

最后, 我们还有一个icmp队列, 系统给它预留了2%的总带宽。 这给ICMP通讯保留了一个最小的使用带宽,我们要放行这些不匹配其它队列标准的CMP通讯。

要让流量分配像我们希望的那样,我们用一些pass规则来指派通讯到相应的队列和其标准:

代码: 全选

set skip on { lo0, $int_if }
pass log quick on $ext_if proto tcp from any to any port ssh flags S/SA \
keep state queue (ssh_bulk, ssh_interactive)
pass in quick on $ext_if proto tcp from any to any port ftp flags S/SA \
keep state queue ftp
pass in quick on $ext_if proto tcp from any to any port www flags S/SA \
keep state queue http
pass out on $ext_if proto udp all keep state queue udp
pass out on $ext_if proto icmp all keep state queue icmp
pass out on $ext_if proto tcp from $localnet to any port $client_out
这些对应于ssh, ftp, www, udp和icmp的规则将通讯指派到各自的队列中, 而最后一条pass规则涵盖了所有来自本地网络的其它通讯, 将它们全部置入默认的main队列中。

DMZ服务器的队列
回到49页的 “一种物理级别上的分割: 介绍DMZ”, 我们设置了只有一个网关的网络,但是位于DMZ区的所有服务全可以从外部访问。那种情形下, 无论是来自Internet或者是内部网络的所有对服务器的请求通讯都必须通过这个网关。

网络结构就像图f7-1, 它与图F5-2一样。

我们用第五章的过滤规则作为基础, 在加上一些队列设置以便优化网络资源。在物理和逻辑上这个网络并未放生变化。
总的来说,这个系统的最大的瓶颈是在网关的外部接口和Ineternet之间的连接带宽。当然,在我们设置中其它地方的带宽也并非是没有限制的, 但是在我们内部的任何接口上的带宽远远超过连接到internet的带宽。 为了让提供最好的服务性能, 我们需要设置队列,这样才能让站点的有效带宽用于我们希望的通讯。
就此而论,理解接口带宽和到用户家里的实际连接带宽之间的区别是很重要的。 在本例中, 可能DMZ的接口带宽为100Mb或者1Gb, 而连接到internet的实际带宽则要小得多。 这方面的考虑可以从我们的队列定义中看出来, 从定义我们可以看出对外通讯的有效实际带宽是队列设置的主要限制。

代码: 全选

total_ext = 2Mb
total_dmz = 100Mb
altq on $ext_if cbq bandwidth $total_ext queue { ext_main, ext_web, ext_udp, ext_mail, ext_ssh }
queue ext_main bandwidth 25% cbq(default borrow red) { ext_hi, ext_lo }
queue ext_hi priority 7 bandwidth 20%
queue ext_lo priority 0 bandwidth 80%
queue ext_web bandwidth 25% cbq(borrow red)
queue ext_udp bandwidth 20% cbq(borrow red)
queue ext_mail bandwidth 30% cbq(borrow red)
altq on $dmz_if cbq bandwidth $total_dmz queue { ext_dmz, dmz_main, dmz_web, dmz_udp, dmz_mail }
queue ext_dmz bandwidth $total_ext cbq(borrow red) queue { ext_dmz_web, ext_dmz_udp, ext_dmz_mail }
queue ext_dmz_web bandwidth 40% priority 5
queue ext_dmz_udp bandwidth 10% priority 7
queue ext_dmz_mail bandwidth 50% priority 3
queue dmz_main bandwidth 25Mb cbq(default borrow red) queue { dmz_main_hi, dmz_main_lo }
queue dmz_main_hi priority 7 bandwidth 20%
queue dmz_main_lo priority 0 bandwidth 80%
queue dmz_web bandwidth 25Mb cbq(borrow red)
queue dmz_udp bandwidth 20Mb cbq(borrow red)
queue dmz_mail bandwidth 20Mb cbq(borrow red)

注意:
这里的 total_ext 定义了所有队列可使用的外部带宽的限制。要应用新的队列结构, 我们还需要修改一些过滤规则。一定要记住的是任何没有明确指派到某个队列的通讯将会被分配到这个接口的默认队列上。因此, 你需要根据自己的实际情况调整过滤规则和队列定义。


加上队列部分后,主要的规则内容大致如下:

代码: 全选

pass in on $ext_if proto { tcp, udp } from any to $nameservers port domain queue ext_udp
pass in on $int_if proto { tcp, udp } from $localnet to $nameservers port domain
pass out on $dmz_if proto { tcp, udp } from any to $nameservers port domain queue ext_dmz_udp
pass out on $dmz_if proto { tcp, udp } from $localnet to $nameservers port domain queue dmz_udp
pass in on $ext_if proto tcp from any to $webserver port $webports queue ext_web
pass in on $int_if proto tcp from $localnet to $webserver port $webports
pass out on $dmz_if proto tcp from any to $webserver port $webports queue ext_dmz_web
pass out on $dmz_if proto tcp from $localnet to $webserver port $webports queue dmz_web
pass in log on $ext_if proto tcp from any to $mailserver port smtp
pass in log on $ext_if proto tcp from $localnet to $mailserver port smtp
pass in log on $int_if proto tcp from $localnet to $mailserver port $email
pass out log on $dmz_if proto tcp from any to $mailserver port smtp queue ext_mail
pass in on $dmz_if from $mailserver to any port smtp queue dmz_mail
pass out log on $ext_if proto tcp from $mailserver to any port smtp queue ext_dmz_mail

你会发现只有那些放行到DMZ接口或者外部接口的通讯才会被指派到队列中。在这个配置里并没有位于本地网络的外部可访问服务(whoami: 因为服务在DMZ不在本地网络。), 在内部接口上进行队列意义不大, 因为它是我们网络里可用带宽限制最少的部分。(whoami:限制主要是在外接口上。)

使用ALTQ处理那些不想要的通讯
到目前为止,我们已经讨论了如何根据你网络的特定情况尽可能和更有效地保证特定类型的通讯通过。我们先将队列的介绍放一放, 介绍一下用不同的方法甄别和处理那些不想要的通讯的例子。 这些例子告诉你一些队列相关的技巧,这些技巧告诉你如何让控制和减少恶意通讯。

将过载的通讯放入一个很慢的队列
我们回忆一下68页的“打法不速之客”, 在那一小节,我们综合了状态跟踪(state-tracking)选项和过载(overload)规则,将那些要特殊处理的IP地址放在一个表格内。 这里所谓的特殊处理在前面章节中我们已经示范过了,也就是切断所有的连接, 但这实际上可以用将过载通讯放到一个指定的队列中来替代,效果是一样的。
参考93页的“对小型网络的基于类的带宽分配方案 (cbq)”:

代码: 全选

pass log quick on $ext_if proto tcp from any to any port ssh flags S/SA \
keep state queue (ssh_bulk, ssh_interactive)

如果我们加上状态跟踪选项, 像这样

代码: 全选

pass log quick on $ext_if proto tcp from any to any port ssh flags S/SA \
keep state (max-src-conn 15, max-src-conn-rate 5/3, \
overload <bruteforce> flush global) queue (ssh_bulk, ssh_interactive)
而且为过载通讯设置一个带宽的更慢一些的队列, 例如,将web队列占用的带宽少一些,然后加上

代码: 全选

queue smallpipe bandwidth 1% cbq

我们用如下规则将恶意通讯指派到一个很慢的队列中:

代码: 全选

pass inet proto tcp from <bruteforce> to any port $tcp_services queue smallpipe

也可以用这些规则加上表格失效选项, 在第70页的 “用pfctl整理Table” 里有相关描述。

基于操作系统指纹的队列分配
PF有一个相当可靠的操作系统指纹(鉴别)机制, 它通过对方设置连接时的初始化SYN数据包的特性检测操作系统类型。
我们最后一个 ALTQ 的例子扩展前面简单的规则集,这个规则集基于一个常识——发送垃圾信息的机器往往使用一个特定的操作系统。例如, 假设在你不想在你的网络里运行spamd, 如果你确定使用某个特定的操作系统给你发送的邮件全是非法邮件,例如这个OS是"windows", 用一个像 pass quick proto tcp from any os "Windows" to $ext_if port smtp queue smallpipe 的规则可能是一个较好的替代。
这样, 如果不借用其它队列的带宽,来自运行特定操作系统(windows)主机的email通讯将只能占用你1%的总带宽。

冗余和故障转移: CARP和pfsync
我们耳熟能详的“高可用性和不间断服务”不但是IT专家和网络管理员的时髦商业术语,而且是他们不断追求的目标。 为了满足这类需要和解决一些相关问题, 在OpenBSD 3.5中添加了两个人们非常期待的新特性CARP和pfsync。

公共地址冗余协议(Common Address Redundancy Protocol —— 简称CARP) 被开发成为一个没有专利问题的替代方案VRRP (虚拟路由器冗余协议——Virtual Router Redundancy Protocol, RFC 2281, RFC 3768), 即便抛开可能存在的专利问题,它要成为IETF认可的标准还有一段很长的路要走。 详说明3
CARP的一个主要目的是即便因为错误或一些计划内的维护工作(例如升级)使一台防火墙或其它的服务终止了,也要保证网络功能(译者:仍可访问)。与CARP互补, pfsync协议被设计为在冗余的包过滤节点间或网关之间处理PF状态同步。两个协议全是为了确保基本网络的冗余和故障转移功能。

CARP是基于设置一组机器,将其中的一台作为主力机而其余的一台或多台作为热备份, 所有这些机器都使用一个公用的IP地址。如果主力机宕机, 备份中的一台将继承这个IP地址。从一台CARP主机到另一台的交换可以被认证, 本质上说是设置一个共享的密语, 实际上很像一个密码。
在PF防火墙方面, pfsync可被设置为处理同步, 如果配置的正确, 实际连接将在不易察觉的情况下实现交接(切换)。本质上, 开发者将pfsync设计为一种在PF防火墙之间实现状态同步的虚拟的网络接口。 可以通过ifocnfig命令将它的那些接口被指派到一个物理接口上。在那些对连续运行时间有严格要求的网络要求自动故障转移,同时的网络连接数和伴随着很大的状态表格变化,这些使为pfsync网络指定一个自己的物理网络更有意义。 此外, 因为pfsync不对其同步伙伴执行任何认证, 你只有在为pfsync通讯使用专用接口时才能保证正确的同步。

项目说明: 一对冗余的网关
在我们完全迷失在一大堆选项以前, 现在正是说明这个任务的好时机。为了说明如何用CARP和pfsync实现故障转移 , 我们想象一下,一个网络经一个网关连接到Internet的情形。
精确的PF规则细节现在并不重要; 多重要的则是这次练习的目标。






我们希望重新配置后, 这个网络可以做以下事情:
  • 保持以前的网络功能
  • 在不易让用户察觉系统宕机
  • 不中断当前的活动连接实现平滑的故障转移
我们将以第三章中相对简单的网络为基础, 网络情况如如图F7-2。

我们用两台冗余网关代替原来的单个网关,这两台网关共享一个私有网络的状态信息,这些信息由pfsync进行更新,如图F7-3。

接下来, 我们将讲述设置的细节。就像前几章一样, 前提是你已经完成了了OpenBSD的设置, 但是我们会在后面说明在其它相关的BSD系统上如何设置。
理解CARP的地址是虚拟地址是很重要的。除非你可以通过控制台访问CARP组内的所有机器, 通常情况下,可能你总想为物理接口设置分配一个IP地址以便自己可以和这台主机通讯以及操控这台机器。

根据惯例, 分配给这个物理接口的IP地址将和虚拟、共享IP地址处于同一子网。事实上, 内核默认情况下也会尝试把CARP地址分配给一个物理接口,这个物理接口已经被分配了一个与CARP地址处于同一个子网的IP地址。你可以不选择这个接口,只需要在设置CARP接口时用ifconfgi命令行加上carpdev选项就可以指定一个不同的接口。
说明
当你重新配置你的网络以及默认网关的地址变成了一个虚拟地址,而不是原来的固定在某台主机和接口上的真实地址时, 很难避免临时性的网络连接中断。(译者:这个读者就不要强求了,只要是配置好了不中断就行。)

建立CARP系统: 内核选项, sysctl, 和ifconfig命令
要获得一个冗余设置主要的任务就是布线、设置sysctl值和运行ifocnfig命令。我们这里所说的布线大多是指常规的概念 (主要请参考图示说明或根据您自己的实际情况设置)。 不过, 在有些操作系统内你需要检查一下编译的内核是否已经开启了所需的设备支持。有关操作系统的说明在这里。

OpenBSD用户
[INDENT]carp和pfsync设备两者都已包含在默认内核 GENERIC 和 GENERIC.mp 的配置里,除非你运行了一个定制内核并删除了这些选项, 否则你无需重新配置内核。

[/INDENT]FreeBSD用户
[INDENT]检查已经编译的内核是否包含了carp和pfsync设备。 GENERIC 内核默认情况下不包含这些选项。 请参阅FreeBSD的Handbook以了解怎样编译和安装一个包含这些选项的定制内核。

[/INDENT]NetBSD用户
[INDENT]检查你使用的内核里是否包含了伪设备carp。NetBSD的默认 GENERIC 内核配置并未将carp编译进去。不过, 你会在内核配置文件GENERIC中发现带注释符的相关选项。NetBSD还不支持 pfsync, 因为在我们些这章的时候存在未解决的协议编号问题。
在所有有CARP功能的系统上, 基本的功能取决于少数几个的sysctl变量。一个主要的就是 net.inet.carp.allow, 它在默认情况下是开启的。在一个典型的OpenBSD系统上你会看到
[/INDENT]

代码: 全选

$ sysctl net.inet.carp.allow
net.inet.carp.allow=1
这说明系统已经配备了CARP。

警告:
如果你的内核不带CARP设备配置, 运行这条命令时可以产生类似这样的显示信息——在FreeBSD上 sysctl: unknown oid 'net.inet.carp.allow' ,而在NetBSD上 sysctl: third level name 'carp' in 'net.inet.carp.allow' is invalid 。

请检查你的系统是否设置正确, 你可以使用sysctl命令查看所有CARP相关变量:详说明4

代码: 全选

$ sysctl net.inet.carp
net.inet.carp.allow=1
net.inet.carp.preempt=0
net.inet.carp.log=0
net.inet.carp.arpbalance=0
说明4
在FreeBSD里, 你会遇到变量 net.inet.carp.suppress_preempt, 它是一个只读状态变量,它表示是否禁止抢占(译者注: 要详细了解,请读者自行上FreeBSD的用户手册上查阅http://www.freebsd.org/doc/zh_CN/books/ ... /carp.html。)
重要的变量是前两个, 而一般情况下你不需要修改后两项。为了便于参考, 将net.inet.carp.log 设置为 1 可以记录有关CARP通讯的调试信息, 但是这项默认是关闭的。同样还有 net.inet.carp.arpbalance 变量可以被用来启用CARP的arp平衡, 它为本地网络的主机间提供了一些有限的负载均衡。 不过,我们的计划是在网关之间设置平稳的进行故障转移, 我们需要设置 net.inet.carp.preempt 变量:

代码: 全选

$ sudo sysctl net.inet.carp.preempt=1
设置 net.inet.carp.preempt 变量的意思是在那些具有一个以上的网络接口的计算机中, 例如我们的网关, 在其所有的接口将advskew (the meaning of which we will explain more thoroughly in a moment) 设置为240的极高值,这样做的目的是为了当一个接口失效时督促CARP组内的其它主机t开始故障转移。 在CARP组内的所有主机的这个设定要相同,当你设置你的系统时, 你需要在所有的主机上重复同样的设定。
接下来, 你需要设置网络接口。 根据网络结构示意图, 我们看到本地网络是地址为92.168.12.0 , 而外部、面向Inetrnet的接口是192.0.2.0网络。
请记住这些地址和CARP的行为特性, 用于设置虚拟网络的命令也就不难理解了。
在组内的主力机(master)上运行如下的命令:

代码: 全选

$ sudo ifconfig carp0 192.0.2.19 vhid 1
$ sudo ifconfig carp1 192.168.1.1 vhid 2
说明
我们不需要明确地设定物理接口。这里的carp0和carp1虚拟接口会自己绑定到已经配置了同一子网地址的那些物理接口上。(译者:这些物理接口被分配了CARP地址。)使用ifconfig你可以检查每个CARP接口是否配置正确:

代码: 全选

$ ifconfig carp0
carp0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
lladdr 00:00:5e:00:01:01
carp: MASTER carpdev ep0 vhid 1 advbase 1 advskew 0
groups: carp
inet 192.0.2.19 netmask 0xffffff00 broadcast 192.0.2.255
inet6 fe80::200:5eff:fe00:101%carp0 prefixlen 64 scopeid 0x5
其它CARP接口的输出也基本类似。 注意carp: 这行说明了主力机(MASTER)的状态。 在备份机方面, 设置几乎是一样的, 除了你要添加advskew参数:

代码: 全选

$ sudo ifconfig carp0 192.0.2.19 vhid 1 advskew 100
$ sudo ifconfig carp1 192.168.1.1 vhid 2 advskew 100
这里的advskew参数我们要解释一下。简而言之, 它指明了这台主机有多低的可能性取代主力机。长一些的解释是当主力机失效后,使用advskew和advbase来计算当前主机之间的公告间隔。默认的advbase值为1; 默认的advskew值为0。在我们的例子里, 这台主力机是每秒钟公告一次 (1 + 0/256), 而备份将将等待 1 + 100/256 秒。 net.inet.carp.preempt=1, 当主力机停止公告时或无法公告时, 备份将接管其工作, 而新的主力机则会按照配置的速率进行公告。advskew值越小表示公告间隔越短从而月可能成为新主力机(master)。如果多台主机的具有相同的advskew值, 则已经成为主力机的那台将维持其主力机状态。
从OpenBSD 4.1开始还有一个因素决定哪台主机将接管CARP主力机的工作。每台主机为它的carp接口组宣布的demotion counter值是用来衡量其CARP接口的准备程度的。 当 demotion counter 值为0时, 则表示该主机完全准备就绪, 而大于0则表示改主机在退化。你可以在命令行用ifconfig -g设置demotion counter, 但是这个数值一般由系统自己设定, 通常在启动工程中这个值设置的比较高。所有其它值相同的情况下, 那台demotion counter值最低的主机将成为CARP主力机。

在备份机方面, 你还需要使用ifconfig来检查每个CARP接口是否配置正确:

代码: 全选

$ ifconfig carp0
carp0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
lladdr 00:00:5e:00:01:01
carp: BACKUP carpdev ep0 vhid 1 advbase 1 advskew 100
groups: carp
inet 192.0.2.19 netmask 0xffffff00 broadcast 192.0.2.255
inet6 fe80::200:5eff:fe00:101%carp0 prefixlen 64 scopeid 0x5
这里的输出仅有很小的差异; 注意 carp: 这行用advbase和advskew参数说明了备份状态。
在实际生产环境中, 你可能想添加一个加密方法——配置一个CARP组员间共享的加密短语以阻止未经认证的CARP活动, 例如

代码: 全选

$ sudo ifconfig carp0 pass mekmitasdigoat 192.0.2.19 vhid 1
$ sudo ifconfig carp1 pass mekmitasdigoat 192.168.1.1 vhid 2
请使用自己的加密短语。详说明5 就像所有其它的密码一样, 这个加密短语将成为所有你配置的CARP通讯的一部分, 所以请小心为同一个故障转移组内所有的CARP接口配置同样的加密短语、或者都不设置。一旦你完成了你的设定, 如果你想让下次启动后配置仍有效,你需要将这个配置放在 /etc目录下的适当的文件里: 在OpenBSD里, 你将适当的ifconfig参数放在hostname.carp0 和hostname.carp1文件内; 在FreeBSD和NetBSD, 你需要将相关的内容放进 rc.conf 文件,文件的内容是 ifconfig_carp0= 和 ifconfig_carp1= 变量。

保持状态同步: 添加pfsync
在研究规则集本身前需要完成的最后最后一项工作是配置冗余组的主机间的状态表同步。在冗余的防火墙上拥有了同步的状态表(state tables) 时, 几乎在所有情况下的故障转移都不会被察觉。 就像我们在前面暗示的那样, 你所需的工具是一组正确配置的pfsync接口。
说明
因为在写本章时protocol-numbering问题仍未解决, 很遗憾,目前NetBSD不支持pfsync。
说明5
不过,这个特殊的密码短语有一个很特殊的含义。通过web搜索你可以知道其重要性以及为何它是现代网络文档所需要的。 如果你太懒, 直接看这里好了 http://marc.info/?l=openbsd-misc&m=98027812528843&w=2.

配置pfsync接口仅需实现进行规划和一些相当简单的ifconfig命令。在任何配置好的物理接口上都可以设置pfsync, 但是为同步建立一个单独的网络是一个好主意。
在的示例配置中 (参图F7-3), 我们另外设置了一个小型网络以实现这个目的。在这里, 一根交叉电缆连接着两个以太接口, 但是配置中故障转移组有两台以上的主机, 你可能需要设置一个单独的switch, hub, 或者vlan。
在这个示例配置中, 我们计划用于同步的接口已经被分别指派了IP地址, 10.0.12.16 和 10.0.12.17。当基本的 TCP/IP 配置结束后, 这两个同步伙伴接口的pfsync的完整设置是:

代码: 全选

$ sudo ifconfig pfsync0 syncdev ep2
它说明了具有相同的硬件配置、同时将pfsync设置在一个单独的网络里的优点。
pfsync协议本身提供了很微弱的加密功能, 它没有认证机制,而且默认的通信方式是通过IP组播通讯。 不管怎样, 在单独物理网络不可行时, 你可以通过两种方式增强你的pfsync安全: 一种是指定pfsync仅与指定的对象进行同步

代码: 全选

$ sudo ifconfig pfsync0 syncpeer 10.0.12.16 syncdev ep2
还有一种是通过IPsec来保护通讯

代码: 全选

$ sudo ifconfig pfsync0 syncpeer 10.0.12.16 syncdev enc0
这个命令的意思是用enc0封装接口取代原来的物理接口作为同步设备。
说明
如果可能, 让你的同步在一个单独、专用的物理网络上进行。
基于CARP故障转移的基本网络配置就到此为止了。下一小节, 我们将考虑如何为冗余的故障转移编写规则。

合成一个规则集
前面我们在基本网络配置里兜了很多圈子, 你可能想知道要花费多大的工作量才能将新设置的这些规则加入到你现有规则集pf.conf 里。

答案是, 不太多。外部用户不会察觉系统的主要变化, 而且精心设计的单台网关的配置同样适用于冗余的环境。不过, 因为引入了两种额外的协议, CARP和pfsync, 而且很可能, 你需要稍微修改一下你的规则集以便故障转移工作正常。

在适当的接口上放行CARP通讯
[INDENT]最易读的方式是将carpdevs设置为宏和一个相应的放行规则, 例如

代码: 全选

pass on $carpdevs proto carp keep state
[/INDENT]在适当的接口上放行pfsync通讯
[INDENT]最易读的方式是将syncdev设置为宏和一个相应的放行规则, 例如

代码: 全选

pass on $syncdev proto pfsync
或者, 如果你想不对pfsync设备进行过滤

代码: 全选

set skip on $syncdev
[/INDENT]同样, 你应该考虑虚拟的CARP接口的作用和其相对于物理接口的地址。因为PF在起作用, 所有的进站通讯将通过这个物理接口, 但是这些通讯可能使用CARP接口的IP地址作为源或者目标的地址。
在故障转移时可能不需要同步你配置文件中的每一项过滤规则(也就是说对连接的服务在网关本身上运行)。 一条重要的基本规则是允许管理员ssh:

代码: 全选

pass in on $int_if from $ssh_allowed to self
对那些规则, 你可以使用状态选项 no-sync 来防止在故障转移发生后因为那些实际上并无关联的连接造成同步状态发生变化:

代码: 全选

pass in on $int_if from $ssh_allowed to self keep state (no-sync)
用我们目前的配置, 系统管理员方便时就能在一个CARP组的成员主机上有计划地进行操作系统升级以及类似前面提到过的停机生产活动, 而你服务的用户完全无法判断或察觉到你的停机活动。