Next Previous Contents

16. 食谱 (Cookbook)

本章所包含的 'cookbook' 单元﹐应可帮您解决一些问题。然而﹐离开了原理的理解﹐纵有 cookbook 也枉然﹐所以﹐请先温故而知新。

16.1 以不同的 SLA 来跑多个站点 (Running multiple sites with different SLAs)

您可以用好多方法来做啦。Apache 就可以透过模块来达到某些支持﹐不过我们这里要教您用 Linux 怎样做﹐并且举一反三﹐将其它服务也一并搞定。这些命令都偷师自下面提到的 Jamal Hadi 的演讲 。

假设我们有两个客户﹐要提供 http﹑ftp﹑还有 streaming audio 服务﹐我们只打算卖给他们一定数量的频宽而已。那我们可以在伺服器上面做手脚。

客户 A 最多只有 2 megabits﹐而客户 B 已经支付 5 megabits 的钱了。那我们在伺服器上建立虚拟 IP 位址﹐来将客户分开来。

# ip address add 188.177.166.1 dev eth0
# ip address add 188.177.166.2 dev eth0

以哪些适合的位址来分配给不同的伺服器﹐则悉从尊便了。所有常用 daemon 都支持这玩意。

接著﹐我们首先将 CBQ adisc 指派给 eth0﹕

# tc qdisc add dev eth0 root handle 1: cbq bandwidth 10Mbit cell 8 avpkt 1000 \
  mpu 64

然后﹐为我们的客户建立类别(classes)﹕

# tc class add dev eth0 parent 1:0 classid 1:1 cbq bandwidth 10Mbit rate \
  2MBit avpkt 1000 prio 5 bounded isolated allot 1514 weight 1 maxburst 21
# tc class add dev eth0 parent 1:0 classid 1:2 cbq bandwidth 10Mbit rate \
  5Mbit avpkt 1000 prio 5 bounded isolated allot 1514 weight 1 maxburst 21

再来﹐我们为这两个类别增加过滤器﹕

##FIXME: Why this line, what does it do?, what is a divisor?:
##FIXME: A divisor has something to do with a hash table, and the number of
##       buckets - ahu
# tc filter add dev eth0 parent 1:0 protocol ip prio 5 handle 1: u32 divisor 1
# tc filter add dev eth0 parent 1:0 prio 5 u32 match ip src 188.177.166.1
  flowid 1:1
# tc filter add dev eth0 parent 1:0 prio 5 u32 match ip src 188.177.166.2
  flowid 1:2

这样﹐就大功告成了啦。

FIXME: 为何不用 token bucket 过滤器﹖还是预设的 pfifo_fast 撤出了﹖

16.2 帮贵主机抵御 SYN floods (Protecting your host from SYN floods)

根据 Alexey 的 iproute 文件﹐已可以和 netfiler 搭配了﹐且有好些看来不错的途经。如果您要使用这个﹐请小心调整哪些数字﹐针对您的系统给予合理的数值。

假如您想要保护整个网路﹐可以跳过这里﹐这里是针对单一主机而已的。

#! /bin/sh -x
#
# sample script on using the ingress capabilities
# this script shows how one can rate limit incoming SYNs
# Useful for TCP-SYN attack protection. You can use
# IPchains to have more powerful additions to the SYN (eg 
# in addition the subnet)
#
#path to various utilities;
#change to reflect yours.
#
TC=/sbin/tc
IP=/sbin/ip
IPTABLES=/sbin/iptables
INDEV=eth2
#
# tag all incoming SYN packets through $INDEV as mark value 1
############################################################ 
$iptables -A PREROUTING -i $INDEV -t mangle -p tcp --syn \
  -j MARK --set-mark 1
############################################################ 
#
# install the ingress qdisc on the ingress interface
############################################################ 
$TC qdisc add dev $INDEV handle ffff: ingress
############################################################ 

#
# 
# SYN packets are 40 bytes (320 bits) so three SYNs equals
# 960 bits (approximately 1kbit); so we rate limit below
# the incoming SYNs to 3/sec (not very useful really; but
#serves to show the point - JHS
############################################################ 
$TC filter add dev $INDEV parent ffff: protocol ip prio 50 handle 1 fw \
police rate 1kbit burst 40 mtu 9k drop flowid :1
############################################################ 


#
echo "---- qdisc parameters Ingress  ----------"
$TC qdisc ls dev $INDEV
echo "---- Class parameters Ingress  ----------"
$TC class ls dev $INDEV
echo "---- filter parameters Ingress ----------"
$TC filter ls dev $INDEV parent ffff:

#deleting the ingress qdisc
#$TC qdisc del $INDEV ingress

16.3 以 ICMP 速率限制抵挡 dDoS (Ratelimit ICMP to prevent dDoS)

目前来说﹐分散式服务瘫痪(distributed denial of service)攻击已经成为 Internet 上头号骚扰行为。对贵网路使用适当的过滤和速率限制﹐可让您既避免成为炮灰﹐也避免成为炮手﹐一箭双雕。

如果您要对网路做过滤﹐那您或许会不让非本地 IP 来源位址的封包离开贵网路。这可以制止别人以匿名身份对 internet 发送垃圾。

如前面介绍的﹐速率限制考虑得更是周长。再看看下面的 ASCII 图例﹐帮您重温一下﹕

[The Internet] ---<E3, T3, whatever>--- [Linux router] --- [Office+ISP]
                                      eth1          eth0

让我们先将前提部份设定起来吧﹕

# tc qdisc add dev eth0 root handle 10: cbq bandwidth 10Mbit avpkt 1000
# tc class add dev eth0 parent 10:0 classid 10:1 cbq bandwidth 10Mbit rate \
  10Mbit allot 1514 prio 5 maxburst 20 avpkt 1000

假如您有 100Mbit﹐或是更快的界面﹐请调整这些数字。现在您需要判定要允许多大的 ICMP 流量。您可以用 tcpdump 进行测量﹐将结果写进一个文档﹐然后过一会看看有多少 ICMP 封包通过网路。请不要忘记将测量时间拉长一点。

如果测量结果看起来不切实际﹐那您可以以可用频宽的 5% 来计算。那就让我们把类别设好吧﹕

# tc class add dev eth0 parent 10:1 classid 10:100 cbq bandwidth 10Mbit rate \
  100Kbit allot 1514 weight 800Kbit prio 5 maxburst 20 avpkt 250 \
  bounded

目前的限制为 100Kbit。接下来我们需要一个过滤器﹐将 ICMP 流量拨给这个类别﹕

# tc filter add dev eth0 parent 10:0 protocol ip prio 100 u32 match ip
  protocol 1 0xFF flowid 10:100

16.4 为互动流量排优先次序 (Prioritizing interactive traffic)

如果有大量的数据传入您的线路﹐或是反向传出﹐而您需要用 telnet 或 ssh 进行某些维护工作﹐这或许不十分理想﹐因为其它封包或会打断您的键盘操作。假如有办法让这些互动封包从这些大块的流量底下暗渡陈仓﹐就最好不过了。Linux 可以帮您做到哦﹗

如前﹐我们需要操纵双向的流量。显然﹐如果线路两端都有 Linux 机器就最理想了﹐当然其它 UNIX's 也可以做得到啦。这点﹐就请教您身边的 Solaris/BSD 高手啰。

标准的 pfifo_fast 排程方法带有 3 个不同的 'bands'。当 band1 和 band2 都流量获得之后﹐在 band0 的流量会先传送。至为关键的是﹐我们的互动流量一定要在 band0 里面﹗

我们乾脆明目张胆的改编(即将淘汰的) ipchains HOWTO 好了﹕

在 IP 标头中﹐有 4 个位元并不常用的﹐称为 Type of Service (TOS) 位元。它们会影响封包被处理的程序﹔这 4 个位元分别是﹕"Minimum Delay"﹑"Maximum Throughput"﹑"Maximum Reliability"﹑和 "Minimum"。它们四者﹐只能设定其一。Ipchains 之 TOS-mangling 作者﹐Rob van Nieuwkerk﹐曾作如下述﹕

对我而言﹐"Minimum Delay" 犹为重要。我为了哪些“互动”封包 在我的上传(Linux) router 上将之打开。我只用一条 33k6 的 modem 线路而已。Linux 将封包的优先顺序排进 3 个伫列中。这样﹐ 在我进行大量下载的时候﹐还可以获得一个可接受的互动效率。

最常见的做法是将 telnet 和 ftp control 连线设为 "Minimum Delay" ﹐而 FTP data 设为 "Maximum Throughput"。在您的上传 router 上﹐可以如下那样动作﹕

# iptables -A PREROUTING -t mangle -p tcp --sport telnet \
  -j TOS --set-tos Minimize-Delay
# iptables -A PREROUTING -t mangle -p tcp --sport ftp \
  -j TOS --set-tos Minimize-Delay
# iptables -A PREROUTING -t mangle -p tcp --sport ftp-data \
  -j TOS --set-tos Maximize-Throughput

好了﹐这只对哪些 telnet 及从外面送来本地主机的数据有效而已。其它您也要一一设好﹐例如 telnet﹑ssh﹑朋友的﹐所有外送封包的 TOS 栏位全自动设好。

如果客户端并非设定如此﹐那您可以用 netfilter 来做。于您的本机上﹕

# iptables -A OUTPUT -t mangle -p tcp --dport telnet \
  -j TOS --set-tos Minimize-Delay
# iptables -A OUTPUT -t mangle -p tcp --dport ftp \
  -j TOS --set-tos Minimize-Delay
# iptables -A OUTPUT -t mangle -p tcp --dport ftp-data \
  -j TOS --set-tos Maximize-Throughput

16.5 用 netfilter﹑iproute2﹑ipchains﹑及 squid 做通透性 web-caching (Transparent web-caching using netfilter, iproute2, ipchains and squid)

本章节由 Internet for Education (泰国) 的读者 Ram Narula 提供。

以 Linux 来做的话﹐常规技巧上或会采用 ipchains﹐﹐将 "外送" 的 port 80(web) 流量送往伺服器所跑的 squid 服务程序。

有 3 种常见办法可确定 "外送" 的 port 80(web) 流量会送往伺服器所跑的 squid 服务程序﹐而第 4 种将是这里的重头戏。

用网关 router 来做

告诉网关 router 将外送目的端埠口为 80 的封包﹐送到 squid 伺服器的 IP 位址上。

但是

这会额外增加 router 的负载﹐而且有些商业 routers 未必支持这种做法。

用 Layer 4 switch 来做

Layer 4 switch 绝对能胜任此项工作。

但是

此类设备的成本非常高。一般而言﹐Layer 4 switch 的成本甚至比 router 加一台好的 Linux 伺服器还要高。

用 cache 伺服器来做网关

您可以强迫所有流量都经过 cache 伺服器。

但是

这有点冒险﹐因为 Squid 会耗掉相当的 cpu 资源﹐或会造成整体的网路效率降低﹐或是伺服器当掉而谁也连不上 internet。

Linux+NetFilter router

NetFilter 可以提供另一可行手段﹐就是用 NetFilter 来将哪些目的端埠口为 80 的封包 "标识" 起来﹐同时用 iproute2 将已 "标识" 的封包送到 Squid 伺服器那里。

|----------------|
| Implementation |
|----------------|

 Addresses used
 10.0.0.1 naret (NetFilter server)
 10.0.0.2 silom (Squid server)
 10.0.0.3 donmuang (Router connected to the internet)
 10.0.0.4 kaosarn (other server on network)
 10.0.0.5 RAS
 10.0.0.0/24 main network
 10.0.0.0/19 total network

|---------------|
|Network diagram|
|---------------|

Internet
|
donmuang
|
------------hub/switch----------
|        |             |       |
naret   silom        kaosarn  RAS etc.
首先﹐确定 naret 为预设网关(除 silom 外)﹐让所有流量均通过它。而 silom 的预设网关必须是 donmuang(10.0.0.3)﹐要不然会产生流量回圈(loop)。

(网路中的所有伺服器都以 10.0.0.1 为预设网关﹐也就是 donmuang router 的旧 IP 位址﹐所以我将 donmuang 的 IP 设为 10.0.0.3﹐而将 10.0.0.1 给 naret 用。)

Silom
-----
-setup squid and ipchains 

在 silom 上面设定 Squid 伺服器﹐要确定它能支持通透性 caching/proxying﹐其预设埠口通常为 3128﹐然后﹐所有给 port 80 的流量都会被转送到本机埠口 3128。用 ipchains 的话﹐可以这样做﹕

silom# ipchains -N allow1
silom# ipchains -A allow1 -p TCP -s 10.0.0.0/19 -d 0/0 80 -j REDIRECT 3128
silom# ipchains -I input -j allow1

或是﹐netfilter 来做﹕

silom# iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 3128

(注意﹕您或许还有其它项目的设定)

关于 Squid 伺服器的更多设定资料﹐请参考 Squid 的 faq ﹕ http://squid.nlanr.net)。

请确定在这台伺服器上面将 ip forwarding 功能打开﹐还有﹐这个伺服器的预设网关是 donmuang (而不是 naret)。

Naret
-----
-setup iptables and iproute2
-disable icmp REDIRECT messages (if needed)

  1. 将目的埠口为 80 的封包 "标识" 为数值 2
     
    naret# iptables -A PREROUTING -i eth0 -t mangle -p tcp --dport 80 \
     -j MARK --set-mark 2
    
  2. 设定好 iproute2﹐将 "标识" 为 2 的封包送到 silom 那边
    naret# echo 202 www.out >> /etc/iproute2/rt_tables
    naret# ip rule add fwmark 2 table www.out
    naret# ip route add default via 10.0.0.2 dev eth0 table www.out
    naret# ip route flush cache
    

    如果 donmuang 和 naret 都在同一 subnet 上的话﹐那 naret 就不要送出 REDIRECT 的 icmp 信息了。这时候﹐按如下方法将 icmp REDIRECT 关闭﹕

    naret# echo 0 > /proc/sys/net/ipv4/conf/all/send_redirects
    naret# echo 0 > /proc/sys/net/ipv4/conf/default/send_redirects
    naret# echo 0 > /proc/sys/net/ipv4/conf/eth0/send_redirects
    

如此﹐所有设定都完成了﹐请回去检查一下﹕

On naret:

naret# iptables -t mangle -L
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         
MARK       tcp  --  anywhere             anywhere           tcp dpt:www MARK set 0x2 

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

naret# ip rule ls
0:      from all lookup local 
32765:  from all fwmark        2 lookup www.out 
32766:  from all lookup main 
32767:  from all lookup default 

naret# ip route list table www.out
default via 203.114.224.8 dev eth0 

naret# ip route   
10.0.0.1 dev eth0  scope link 
10.0.0.0/24 dev eth0  proto kernel  scope link  src 10.0.0.1
127.0.0.0/8 dev lo  scope link 
default via 10.0.0.3 dev eth0 

(make sure silom belongs to one of the above lines, in this case
it's the line with 10.0.0.0/24)

|------|
|-DONE-|
|------|

实作之后的流量流程图 (Traffic flow diagram after implementation)


|-----------------------------------------|
|Traffic flow diagram after implementation|
|-----------------------------------------|

INTERNET
/\
||
\/
-----------------donmuang router---------------------
/\                                      /\         ||
||                                      ||         ||
||                                      \/         ||
naret                                  silom       ||
*destination port 80 traffic=========>(cache)      ||
/\                                      ||         ||
||                                      \/         \/
\\===================================kaosarn, RAS, etc.


注意﹕   因为在正常外送路径上﹐多了一个额外的跳站(hop)﹐
        所以此网路为非对称的。

这样﹐在 kaosarn 和 internet 之间的封包﹐到这里都会受到管制。

对于 web/http 流量﹕
kaosarn http request->naret->silom->donmuang->internet
http replies from internet->donmuang->silom->kaosarn

对于 非 web/http 请求(如﹐telnet)﹕
kaosarn outgoing data->naret->donmuang->internet
incoming data from internet->donmuang->kaosarn

16.6 以每个路由 MTU 设定来防范 Path MTU Discovery 问题 (Circumventing Path MTU Discovery issues with per route MTU settings)

为了传送 bulk 数据﹐internet 通常会使用大型封包以获得更好的效果。每一个封包都必须进行路由判断﹐假如传送一份 1 magabyte 的文件﹐如果仅可能的使用最大封包进行传送﹐那大概需要 700 个封包﹔如果预设使用最小封包﹐则近 4000 个。

然而﹐internet 上并非所有部份都支持完整的每个封包 1460 bytes 的过载。因此﹐必须找出 '合身' 的最大封包体积﹐以进行连线最佳化。

这个处理称为 '路径 MTU 发现(Path MTU Discovery)'﹐MTU 就是 'Maximum Transfer Unit' 的意思。

当路由器收到一个封包﹐因为太大而不能一次送出﹐但同时它的 "Don't Fragment" 位元又被设定起来﹐则会回送一个 ICMP 信息﹐说明它因此被迫丢弃这个封包。发送端主机收到这条提示之候﹐会改送较小的封包﹐并且﹐透过如此反复﹐就能于特定路径上找出最佳的封包体积。

这原本工作得非常顺利﹐不过﹐当 internet 上充斥众多无所不用其极进行连线破坏的不肖之徒之后﹐情况就一落千丈了。许多管理员透过封锁或引导错误导入的 ICMP 流量﹐来改善安全或增强他们的 internet 服务。

这样的结局是﹐Path MTU Discovery 在某些路由器上每下愈况﹐而导致奇怪的 TCP/IP 连线过一会就挂掉了。

尽管我还没有直接的证据来证明这点﹐我过去接触过两个站台﹐在受影响系统之前执行 Alteon Acedirectors 都有这样的问题 --- 或许对此更了解的朋友可以为我们画龙点睛。

解决方案 (Solution)

当您碰到站台受此问题困扰的时候﹐您可以手工的关闭 Path MTU discovery 功能。Koos van den Hout 曾如此写过(稍作修改)﹕

下列问题﹕我把 ppp 专线之 mtu/mrg 设定为 296﹐因为它只有 33k6 而已﹐而且我也不能影响另一端的伫列。在 296 的水平﹐键击回应尚能保持在合理的时间之内。

同时﹐在我这边﹐我用 Linux (当然啦)﹐进行伪装。

我目前将 '伺服器' 和 '路由器' 分开﹐所以大部份的应用程序都在另外的机器上面跑﹐而路由则依旧进行。

然后﹐当我要连上 irc 的时候却碰到问题。超麻烦的就是了﹗透过探查﹐发现我可以连接上 irc﹐而且在 irc 上也显示为 'connected' ﹐但就是收不到 irc 的信息。我开始检查是什么地方出槌了﹐而且也注意到在以前连接某些和 MTU 相关的网站也有问题﹐因为当 MTU 是 1500 的时候并没有问题﹐而 MTU 为 296 的时候就会出状况。因为 irc 伺服器会封锁所有即时作业不需要的流量﹐而且也挡掉 icmp。

我曾说服过某个有此问题的网站管理员﹐但 irc 伺服器的管理员却不大愿意修正它。

因此﹐我必须确定外送的伪装流量走外部线路要从低 mtu 开始。但是我要本地的 ethernet 流量可以获得正常的 mtu (诸如 nfs 流量)。

解决办法﹕

ip route add default via 10.0.0.1 mtu 296

(10.0.0.1 为预设网关﹐是伪装路由器的内部位址)

通常﹐对特定路由进行修改﹐可以来盖写 PMTU Discovery 的值。比方说﹐假如只有某一特定 subnet 会有问题﹐那下面的办法或许有助﹕

ip route add 195.96.96.0/24 via 10.0.0.1 mtu 1000

16.7 以 MMS 钳制来防范 Path MTU Discovery 问题 (Circumventing Path MTU Discovery issues with MSS Clamping) (for ADSL, cable, PPPoE & PPtP users)

如前面所述﹐Path MTU Discovery 已经不再随心所欲了。如果您知道事实上网路中某个跳站(hop)有限制 MTU(<1500)﹐您是不能依靠 PMTU Discovery 来发现它的。

在 MTU 之外﹐还有另外的方法设定封包的最大体积﹐这个就是传说中的 Maximum Segment Size。这是属于 SYN 封包的 TCP 选项中的栏位。

目前的 Linux 核心﹐以及少部份 pppoe 驱动程序﹐都提供 'MSS 钳制' 功能。

设定 MSS 数值所带来的好处是﹐您可以明确的告诉硬件﹕'送来的封包不要超过这个数值'﹐而无须倚重 ICMP 流量。

其坏处是﹐他是一个非常明显的破解(hack) --- 透过修改封包而打断 '端对端(end to end)' 状态。必须指出﹐我们在许多场合中使用这个技巧﹐而它就如孙悟空头上的紧箍咒。

为了让它工作﹐您起码需要 iptables-1.2.1a 和 Linux 2.4.3 或更新版本。基本命令如下﹕

# iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS  --clamp-mss-to-pmtu

这会为您的线路计算适合的 MSS。如果您有冒险精神﹐或自认艺高胆大﹐您也可以再做这样的动作﹕

# iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 128

这样会将所通过的 SYN 封包之 MMS 设为 128。如果您要用微型封包携载 VoIP﹐同时﹐庞大的 httpd 封包会削减您的语音通讯﹐那您就可以使用它了。


Next Previous Contents