原文作者:Matthew G. Marsh
原文出处:
编 译:
目前在计算机网络中使用的传统路由算法都是根据IP包目的地址进行路由选择.然而在现实应用中经常有这样的需求:进行路由选择时不仅仅根据数据报的目的地址,而且根据数据报的其他一些特性如:源地址、IP协议、传输层端口,甚至是数据包的负载部分内容,这种类型的路由选择被称作基于策略的路由。
在Linux中,从2.1版本的内核开始就实现了对基于策略的路由的支持,它是通过使用路由策略数据库(RPDB,routing policy database)替代传统的、基于目的地址的路由表来实现的。RPDB通过包含的一些规则来选定合适的IP路由。这些规则可能会包含很多各种不同类型的健值(key),因此这些规则没有默认的特定次序,规则查找次序或规则优先级都是由网络或系统管理员设定的。
Linux的RPDB是一个由数字优先级值进行排序的线性规则列表。RPDB能匹配数据报源地址、目的地址、TOS、进入接和fwmark值等。每个路由策略规则由一个选择器和一个动作指示组成。RPDB按照优先级递增的顺序被扫描,RPDB包含的每条规则的选择器被应用于数据报的源地址、目的地址、进入接口、TOS和fwmark值。若数据报匹配该规则对应于该规则的动作被执行。若动作成功返回,则规则输出将是一个有效的路由或是路由查找失败指示;否则查找RPDB的下一条规则。
当选择器和一个数据报匹配成功,会执行哪些动作呢?路由软件的标准动作一般是选择下一跳地址和输出接口,可以称这种动作为“匹配&设置”类型动作。然而Linux采取了更加灵活的方法,在Linux中有多种动作可供选择。默认的动作是查询特定的基于目的地址的路由表。因此“匹配&设置”动作就成为Linux路由选择的最简单情况。Linux支持多个路由表,每个路由表都包含多条路由信息。也就是Linux的每个路由表都相当于其他操作系统的系统路由表。Linux支持多达255个路由表。(Linux 2.2.12 支持255个路由表,255个汇聚域和232个策略规则优先级 (4294967296 decimal) 。
对于Linux2.1/2.2,启动时内核将包含一个由三条策略规则组成的默认的RPDB,察看这些默认规则的一个方法是使用命令来列出系统的所有规则:
root.netmonster ip rule list 0: from all lookup local 32766: from all lookup main 32767: from all lookup default下面的默认规则在对于理解启动复杂路由系统是非常重要的。
首先是最高级别的优先级规则,规则策略0:
规则0: 优先级 0 选择器 = 匹配任何数据报
动作=察看本地路由表(routing table local),ID为255。
local表是保留路由表,包含了到本地和广播地址的路由。规则0是特殊的规则,不可被删除或修改。
规则 32766: 优先级 32766 选择器 = 匹配所有数据报
动作 = 察看主路由表(routing table main), ID为254。
main路由表是默认的标准路由表,其包含所有非策略路由,main表是存放旧的路由命令(route命令)创建的路由。而且任何由ip route命令创建的没有明确指定路由表的路由都被加入到该路由表中。该规则不能被删除和被其他规则覆盖。
规则 32767: 优先级 32767 选择器 = 匹配所有数据报
动作 = 察看默认路由表(routing table default),ID为253。
default路由表是空的,为最后处理(post-processing)所预留,若前面的默认规则没有选择该数据报时保留用作最后的处理。该规则可以被删除。
不要将路由表和规则混淆,规则是指向路由表的。也许会出现多个规则指向同一个路由表,而有些路由表可能并不被任何规则指向。如果删除了指向某个路由表的所有规则,则该表将不发生作用,但是表将仍然存在。一个路由表只有在其中包含的所有路由信息被删除才会消失。
前面提到,Linux策略规对应的动作除了指向一个路由表以外还能是若干种不同的动作。当创建一个策略规则,有如下类型的动作可以选择:
unicast -- 在该规则指向的路由表中进行标准的路由查找。当一个路由表被指定,这是默认的动作。
blackhole -- 规则动作将仅仅直接丢弃该数据报。
unreachable -- 规则动作产生一条网络不可达错误信息,一个类型为3,代码为0的ICMP消息被返回给发送者。
prohibit -- 规则动作产生一个通信被禁止的错误消息,一个类型为3,代码为13的ICMP消息被返回给发送者。
其他类型的动作也可以被使用,但是都和策略路由没有关系。它们被用来在内核中实现其他高级流控制和数据报操作。因为只有一个工具命令:ip,所有的这些类型都是可运用于该命令,但我们仅仅使用和上面有关的部分,可以是返回一条路由或其他若干个动作。
在解释示例以前,首先看看ip工具命令的语法。ip命令可以用在很多地方,这里仅仅讨论和策略路由相关的部分。都是由root在命令行直接运行的。
首先,看ip addr命令语法:
root@netmonster# ip addr help Usage: ip addr {add|del} IFADDR dev STRING ip addr {show|flush} [ dev STRING ] [ scope SCOPE-ID ] [ to PREFIX ] [ FLAG-LIST ] [ label PATTERN ] IFADDR := PREFIX | ADDR peer PREFIX [ broadcast ADDR ] [ anycast ADDR ] [ label STRING ] [ scope SCOPE-ID ] SCOPE-ID := [ host | link | global | NUMBER ] FLAG-LIST := [ FLAG-LIST ] FLAG FLAG := [ permanent | dynamic | secondary | primary | tentative | deprecated ] Example - ip addr add 192.168.1.1/24 dev eth0该命令将添加IP地址192.168.2.2/24到eth0网卡上.
下面看看ip route命令:
root@netmonster# ip route help Usage: ip route { list | flush } SELECTOR ip route get ADDRESS [ from ADDRESS iif STRING ] [ oif STRING ] [ tos TOS ] ip route { add | del | replace | change | append | replace | monitor} ROUTE SELECTOR := [ root PREFIX ] [ match PREFIX ] [ exact PREFIX ] [ table TABLE_ID ] [ proto RTPROTO ] [ type TYPE ] [ scope SCOPE ] ROUTE := NODE_SPEC [ INFO_SPEC ] NODE_SPEC := [ TYPE ] PREFIX [ tos TOS ] [ table TABLE_ID ] [ proto RTPROTO ] [ scope SCOPE ] [ metric METRIC ] INFO_SPEC := NH OPTIONS FLAGS [ nexthop NH ]... NH := [ via ADDRESS ] [ dev STRING ] [ weight NUMBER ] NHFLAGS OPTIONS := FLAGS [ mtu NUMBER ] [ advmss NUMBER ] [ rtt NUMBER ] [ rttvar NUMBER ] [ window NUMBER] [ cwnd NUMBER ] [ ssthresh REALM ] [ realms REALM ] TYPE := [ unicast | local | broadcast | multicast | throw | unreachable | prohibit | blackhole | nat ] TABLE_ID := [ local | main | default | all | NUMBER ] SCOPE := [ host | link | global | NUMBER ] FLAGS := [ equalize ] NHFLAGS := [ onlink | pervasive ] RTPROTO := [ kernel | boot | static | NUMBER ] Example - ip route add 192.168.2.0/24 via 192.168.1.254该示例将添加一条通过192.168.1.254到网络192.168.2.0/24的路由。
最后,看看ip rule命令:
root@netmonster# ip rule help Usage: ip rule [ list | add | del ] SELECTOR ACTION SELECTOR := [ from PREFIX ] [ to PREFIX ] [ tos TOS ] [ fwmark FWMARK ] [ dev STRING ] [ pref NUMBER ] ACTION := [ table TABLE_ID ] [ nat ADDRESS ] [ prohibit | reject | unreachable ] [ realms [SRCREALM/]DSTREALM ] TABLE_ID := [ local | main | default | NUMBER ] Example - ip rule add from 192.168.2.0/24 prio 32777 reject该命令将丢弃源地址属于192.168.2.0/24网络的所有数据报。
在讨论了命令语法以后,下面是一些上面命令的示例。
例 1:拒绝访问Internet
假设有一个防火墙连接本地局域网和Internet,你希望禁止局域网的一个子网访问Internet。当然这可以通过Linux数据报过滤防火墙来实现。但是下面我们将介绍另外一种实现方法。首先我们来假设有如下的网络配置:
内部网络地址 192.168.0.0/16
被拒绝访问的子网 192.168.2.0/24
当前主路由表(Routing Table Main,表254):
root@netmonster# ip route list table 254 default via 192.168.254.254 dev eth0 proto static下面将针对该子网创建一条策略路由规则:
ip rule add from 192.168.2.0/24 priority 5000 prohibit现在任何从192.168.2.0/24子网发送来的数据报将得到一个类型为3,代码为13的ICMP消息,同时该数据报将被丢弃。
应该注意的是,在运行任何这些命令都需要发送“ip route flush cache"命令来刷新路由缓冲,否则命令在一段时间以后才会生效,这段时间的长短依赖于路由表结构的大小和负载。
将上面的例子需要的命令放在一起就如下所示:
ip rule del priority 5000 ip rule add from 192.168.2.0/24 priority 5000 prohibit ip route flush cache这个命令流通过首先删除5000号规则来确保当前系统没有该规则然后再添加新的5000号规则。如果系统本来不存在5000号的规则则会返回一个错误信息。然后添加5000号规则并通过清空运行时的路由缓存来重置RPDB,则新规则将马上产生作用。
多路由表和IP地址
为了完全理解基于策略路由的使用,就需要学会使用Linux多路由表和IP地址,这包括多个方面的知识,下面通过示例来加以说明。
当获得ip工具,你可能会注意到在发布中有一个名为etc的子目录,其中有一个名为iproute2的子目录。应该拷贝该子目录到/etc目录下或在/etc目录下创建iproute2子目录。该目录包含用来命名路由表及策略路由结构的其他方面的文件。在该目录中创建rt_tables文件,其中示例文件一般已经包含了某些内容,并为路由表1提供了示例名。
下面首先编辑该文件来创建若干在下面的示例中使用的表:
# reserved values # 255 local 254 main 253 default 0 unspec # # local # 1 goodnet1 2 goodnet2 3 badnet1 4 badnet2 5 internet可以看到通过为路由表命名,就可以使用表数字ID或表名来引用路由表。例如下面两个命令将对同一个路由表进行操作:
ip route list table 1 ip route list table goodnet1通过表名可以更好的理解在对哪个路由表进行操作。
例 2:创建多个路由表
如果已经创建了如上所示的rt_table信息,可以通过下面的命令来察看各个路由表的内容:
ip route list table <name>当然即使没有定义rt_table文件同样可以使用数字来引用所有的0-255个路由表,因为它们都是存在的,只是其中大部分没有路由信息数据罢了。在上面的例子中通过rt_table文件来将表1定义为goodnet1。
下面的例子是向定义的每个路由表都添加一条路由:
ip route add 10.10.10.0/24 via 192.168.1.2 table goodnet1 ip route add 10.10.11.0/24 via 192.168.1.2 table goodnet2 ip route add 10.10.12.0/24 via 192.168.1.2 table badnet1 ip route add 10.10.13.0/24 via 192.168.1.2 table badnet1 ip route add default via 192.168.1.254 table internet然后再通过ip rouite list talbe <name>命令就可以察看路由表中的各个路由。
例3:建立多个IP地址
这里的多个IP地址并不同于IP别名,在Linux2.1及更高版本中已经反对使用":#"的IP别名方式。而应该用新的方式来使用多IP地址。
假设eth0输出接口应该具有三个不同的IP地址,其中的两个应该属于同一个子网,但是应该被独立地设置,示例同样说明了在Linux2.2及以上版本的关闭自动路由添加的方法。在Linux2.2及以上版本内核的系统中,当为一个接口赋予一个IP地址时,内核将自动为该IP地址属于的网络在路由表中添加一条对应的路由。而由于这里将为同一个接口赋予属于同一个子网的两个不同的IP,所以在添加IP地址时不希望添加路由,否则会造成路由冲突,因此就需要添加该地址为一个主机地址。只需要设置该地址时指定完全主机地址掩码,然后手工添加必要的路由。
为接口设置如下地址:
192.168.1.1 192.168.1.128 192.168.3.1在添加192.168.1.0/24的两个地址时需要关闭自动路由添加,而允许对192.168.3.0/24时允许自动路由添加功能。
ip addr add 192.168.1.1/32 dev eth0 ip addr add 192.168.1.128/32 dev eth0 ip addr add 192.168.3.1/24 dev eth0这时候如果察看主路由表则会发现内核只为网络192.168.3.0/24添加了路由表,而没有为网络192.168.1.0/24添加路由。
通过ip addr命令可以察看系统的所有IP地址信息:
root@netmonster# ip addr 1: lo: <LOOPBACK,UP> mtu 3924 qdisc noqueue link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 brd 127.255.255.255 scope host lo 2: eth0: <BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast qlen 100 link/ether 00:00:49:61:32:bc brd ff:ff:ff:ff:ff:ff inet 192.168.1.1/32 scope global eth0 inet 192.168.1.128/32 scope global eth0 inet 192.168.3.1/24 scope global eth0下面我们将讨论几个更加复杂的例子。
例 4:多路由表和IP地址
Linux内核路由代码最强大的特色就是基于策略的路由和使用多地址、多路由表的结合使用。下面的示例讨论的是一个充当路由器的连接三个不同的网络的系统。
参考文章开头的图片,可以看到核心系统的外部接口连接了三个外部网络。每个网络都有自己的路由器和自己的IP地址空间。但是其中的两个地址空间是重叠的,因此增加了情况的复杂性。这里我们设置如下规则的路由表来实现互联:
首先,配置两个外部IP地址-在DMZ以太网接口eth0的两个地址:
ip addr add 10.254.254.2/30 dev eth0 ip addr add 172.17.1.128/24 dev eth0下一步将讨论创建哪些路由表,解决这个问题的最好办法是想到基于策略的路由能实现根据源地址来决定使用哪个路由表。策略规则具有划分内部网络的能力,因此首先可以在路由表中添加基于目的的路由,在示例中我们将使用前面创建的新的路由表。
当在路由表中添加路由时下面的方法有助于澄清应该采取的步骤。假设你配置的是只有两个接口的路由器,外接口直接连接Interet出口路由器,而内接口直接连接内部网络。配置这样的路由器是非常简单的,为了说明我们这里在表1-goodnet1这个路由表进行操作:
ip route add 10.10.0.0/16 via 10.254.254.2 table goodnet1 proto static ip route add default via 172.17.1.254 table goodnet1 proto static对于goodnet2进行配置则是:
ip route add 172.18.0.0/16 via 172.17.1.1 table goodnet2 proto static ip route add default via 172.17.1.254 table goodnet2 proto static可以看到对三个目标网络只需要两个路由表,这里对于连接Internet的默认路由在两个表中都进行了设置。为什么不将默认路由存放在第三个路由表中呢?首先应该考虑到规则和路由表之间的交互,而且规则是实现对基于策略路由的定义。多条规则可以指向同一个路由表。然而,一旦进入某个路由表以后,则只能是匹配一条路由或返回一条路由链。如果匹配到一条规则则就认为你已经拥有一条正确的策略匹配,则该规则指向的路由表包含了该数据报所有的路由可能。
如果有三个路由表,则需要添加一条规则来察看数据报目的。而检查数据报目的地址是标准路由的功能。为什么要对每种源、目的的组合都需要一个规则呢?通过使用路由表,可以实现定义尽量少的规则来达到目的。当然 系统的灵活性允许通过多条方法来实现这样的路由。可以根据在自己的喜好来决定那种方案最适合:
ip rule add from 192.168.1.32/27 to 172.18.0.0/16 pref 15000 table goodnet1 ip rule add from 192.168.2.64/28 to 10.10.0.0/16 pref 15001 table goodnet2 ip rule add from 192.168.1.0/24 pref 15002 table goodnet1 ip rule add from 192.168.2.0/24 pref 15003 table goodnet2上面的例子中使用了优先级参数设定来定义数据报匹配规则的顺序。现在来看看当一个数据报从内部网络经过路由系统时会发生什么情况。首先,它会通过优先级为0的规则检查;随后会遇到优先级为15000的规则,若匹配则会被goodnet1这个路由表进行操作,否则将会分别经过15001、15002、15003的规则。它肯定会被15000-15003几个规则中的一个所匹配。
下面为了说明定义路由结构的灵活性,我们将从另外一个角度来解决这个问题。Linux路由器的详细情况如下:
eth0 - DMZ ethernet - addresses: 10.254.254.2/30, 172.17.1.128/24 eth1 - Internal A - addresses: 192.168.1.254/24 eth2 - Internal B - addresses: 192.168.2.254/24首先假设重新开始,将重新定义路由和规则,首先编辑/etc/iproute2/rt_tables:
# reserved values # 255 local 254 main 253 default 0 unspec # # Local Tables # 1 int1 2 int2创建路由和规则:
ip route add 10.10.0.0/16 via 10.254.254.1 table int1 proto static ip route add throw 0/0 table int1 proto static ip route add 172.18.0.0/16 via 172.17.1.1 table int2 proto static ip route add throw 0/0 table int2 proto static ip route add 0/0 via 172.17.1.254 table main proto static ip rule add pref 15000 table int1 iif eth1 ip rule add pref 15001 table int2 iif eth2 ip rule add pref 15002 to 10.10.0.0/16 table int1 ip rule add pref 15003 to 172.18.0.0/16 table int2这些路由和规实施和前面示例一样的操作(仔细研究这些规则和路由并理解它们)
使用ipchains实现高级策略路由
在指定策略规则时可以使用的一个选项就是允许通过fwmark值来匹配某个规则。fwmark是一个数字标签,数据报过滤工具ipchains能将fwmark值附加给某个数据报。如果你对ipchains并不是很熟悉,你需要首先阅读ipchains-howto.
例 5:简单基于fwmard的策略路由
首先从一个简单的例子开始-利用上面示例中的多路由表,希望实现来自内部网B的、目的端口为80的数据发送到Internet,但是来自内部网A的、目的端口为80的数据则被禁止。首先清空这些路由表:
ip route flush table goodnet1 ip route flush table goodnet2 ip route flush table badnet1 ip route flush table badnet2 ip route flush table internet ip route flush cache而目前删除策略规则的方法就是将其一一列出来然后将其手工删除,也就是首先通过ip rule list命令将策略规则列出来然后使用ip rule del priority <#>将其删除。但这里假设当前没有任何规则并且路由表也是空的。
为了使用fwmark标记,首先应该指定希望使用ipchains标记的数据报,然后使用标记值来指定一条策略规则来处理该数据报。应该设定ipchians自动将十进制的标记转化为十六进制。ip rule希望输入为一个十六值。
首先配置ipchains规则使用合适的值标记输入数据报。假设当前没有其他防火墙规则:
ipchains -I input -p tcp -s 192.168.2.0/24 -d 0/0 80 -m 2 ipchains -I input -p tcp -s 192.168.1.0/24 -d 0/0 80 -m 16现在设立策略规则,在上面为内部网A的标记值为十机制的16,下面定义相关的策略(应该注意到策略定义中使用的是十六进制,因此为10):
ip rule add fwmark 2 table goodnet1 ip rule add fwmark 10 prohibit最后为路由表goodnet1定义如下的路由:
ip route add default via 172.17.1.254 table goodnet1关于策略路由的一个常见问题是策略路由和IP伪装之间如何交互,这里我们不对该问题进行深入研究但是通过一个快速的示例来加以说明。需要注意的是在转发链之前对路由表进行查询。这意味着如果使用IP伪装,则路由选择器返回的任何源地址都将被作为进行IP伪装的地址。
例6:朵IP地址的IP伪装
使用上面的网络配置,我这里将对到三个网络的连接进行伪装处理,希望从系统中得到如下的输出:
eth0配置有如下地址:
10.254.254.2/30 172.17.1.128/24因此为了满足条件2,给eth0添加地址:
ip addr add 172.17.1.2/32 dev eth0假设系统被设置为对所有的输出数据报都进行IP伪装。首先清空旧的策略规则,然后创建新的规则如下:
ip route add 10.10.0.0/16 via 10.254.254.2 src 10.254.254.2 table goodnet1 proto static ip route add default via 172.17.1.254 src 172.17.1.128 table goodnet1 proto static ip route add 172.18.0.0/16 via 172.17.1.1 src 172.17.1.2 table goodnet2 proto static ip route add default via 172.17.1.254 src 172.17.1.128 table goodnet2 proto static ip rule add from 192.168.1.0/24 pref 15000 table goodnet2 ip rule add from 192.168.2.0/24 pref 15001 table goodnet1例7:综合实例
假设例6中的路由、规则和地址仍然在起作用,我希望实现下面的需求:
应该记得我们仍然允许例6中的连接性,下面就是解决方案:
ip addr add 172.17.1.3/32 dev eth0 ip addr add 172.17.1.4/32 dev eth0 ip route del default table goodnet1 ip route del default table goodnet2 ip route add throw 0/0 table goodnet1 proto static ip route add throw 0/0 table goodnet2 proto static ip route add default via 172.17.1.254 src 172.17.1.128 table internet proto static ip route add 172.18.0.0/16 via 172.17.1.1 src 172.17.1.3 table badnet1 proto static ip route add 172.18.0.0/16 via 172.17.1.1 src 172.17.1.4 table badnet2 proto static ip rule add from 192.168.1.32/27 to 172.18.0.0/16 pref 14999 table badnet1 ip rule add fwmark 1 pref 14998 table badnet2 ip rule add fwmark 2 pref 14997 table goodnet1 ip rule add fwmark 3 pref 14996 blackhole ip rule add pref 15003 table internet ipchains -I input -p tcp -s 192.168.1.64/28 -d 172.18.0.0/16 80 -m 1 ipchains -I input -p tcp -s 192.168.2.64/28 -d 10.10.0.0/16 80 -m 2 ipchains -I input -p tcp -s 192.168.2.32/27 -d 172.18.0.0/16 80 -m 3其实有很多的有效方法,但是虽然这里使用名为badnet1和badnet2的表,但是名字是没有实际意义的,只是用来引用表3和表4的符号。
总结
希望你通过该文章能享受Linux2.2的强大路由功能,它提供的路由功能是很多路由器产品都是难以匹敌的, 如果考虑到它的免费性,它的性能价格比更是没有任何产品可以相比。例如例7中的例子可以很好的运行在只有16M内存的486/33的机器上。