精华内容
下载资源
问答
  • linux使用 netlink 添加路由简单代码

    千次阅读 2011-12-19 15:07:22
    1,在网上查了关于netlink的实现路由查询和添加路由的文章,资料很少,提供的代码都没有运行结果或者编译不了。所以从网上拷贝的代码仔细研究,一步一步的调试,添加很多printf语句查看各结构的值。 2,我主要参考...

    1,在网上查了关于netlink的实现路由查询和添加路由的文章,资料很少,提供的代码都没有运行结果或者编译不了。所以从网上拷贝的代码仔细研究,一步一步的调试,添加很多printf语句查看各结构的值。

    2,我主要参考的代码网页链接有:

    http://downloads.open-mesh.org/svn/batman/tags/batman-0.3/linux/route.c   

    (上面这个链接主要是用来实现添加/删除路由的程序的模块,没有这个源程序我也无从下手,大部分代码是从这个文件上面拷贝下来的)

    3,需要注意的地方

          rtmsg结构体中的rtm_dst_len代表的是目的网络或目的主机掩码的位数,我以前总是以为是ip地址长度所以就填4,这是不对的,同理rtm_src_len也是一样,不过本程序填0就可以了,添加路由不需要源地址。

          nlmsghdr结构体中的nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;如果单填 NLM_F_CREATE好像是不行的添加不了路由。

         我的linux是ubutun9.10,内核是2.6.31,其他linux系统编译是否有问题不清楚了。

    4,长话不多说了,就看代码吧:

    #include <stdio.h>
    #include <sys/errno.h>
    #include <sys/time.h>
    #include <sys/socket.h>
    #include <asm/types.h>
    #include <sys/types.h>
    #include <linux/rtnetlink.h>
    #include <linux/netlink.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/uio.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <stdlib.h>


    typedef __u32 u32;


    int addattr_l(struct nlmsghdr *n, int maxlen, int type, void *data, int alen)
    {
        int len = RTA_LENGTH(alen);
        struct rtattr *rta;
        if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen)
            return -1;
        rta = (struct rtattr*)(((char*)n) + NLMSG_ALIGN(n->nlmsg_len));
        rta->rta_type = type;
        rta->rta_len = len;
        memcpy(RTA_DATA(rta), data, alen);
        n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len;
        fprintf(stderr,"\nattr len=%d\n",n->nlmsg_len);
        return 0;
    }


    void route_add(int fd, __u32* destination,__u32* gateway,unsigned int netmask)
    {
        struct sockaddr_nl nladdr;
        int status;

        __u32 index=2; /* Output Interface ::: eth1,因为我的虚拟机没有eth0,是eth1接口所以是2,lo接口1,不知道其他机器是否有出入*/
        //__u32 source=254;

        // structure of the netlink packet.
        struct
        {
            struct nlmsghdr n;
            struct rtmsg r;
            char buf[1024];
        } req;
        nladdr.nl_family=AF_NETLINK;
        nladdr.nl_pad=0;
        nladdr.nl_pid=0;
        nladdr.nl_groups=0;
        status=bind(fd,(struct sockaddr *)&nladdr,sizeof(nladdr));
        if(status<0){
            perror("bind");
            exit(1);
        }
    // Forming the iovector with the netlink packet.
    //    struct iovec iov = { (void*)&req.n, req.n.nlmsg_len };

    // Forming the message to be sent.
    //    struct msghdr msg = { (void*)&nladdr, sizeof(nladdr), &iov, 1, NULL, 0, 0 };


        memset(&req, 0, sizeof(req));

    // Initialisation of a few parameters
        memset(&nladdr,0,sizeof(nladdr));
        nladdr.nl_family= AF_NETLINK;
        nladdr.nl_pid=0;
        nladdr.nl_groups=0;

        req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
        req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
        req.n.nlmsg_type = RTM_NEWROUTE;

        req.r.rtm_family = AF_INET;
        req.r.rtm_table = RT_TABLE_MAIN;
        req.r.rtm_protocol = RTPROT_STATIC;
        req.r.rtm_scope = RT_SCOPE_UNIVERSE;
        req.r.rtm_type = RTN_UNICAST;
        req.r.rtm_dst_len=netmask;   //目的网络子网掩码位数
        req.r.rtm_src_len=0;
        req.r.rtm_tos=0;
        req.r.rtm_flags=RT_TABLE_MAIN;


        
        req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)) ;

    // RTA_DST and RTA_GW are the two esential parameters for adding a route,
    // there are other parameters too which are not discussed here. For ipv4,
    // the length of the address is 4 bytes.
        addattr_l(&req.n, sizeof(req), RTA_DST, destination, 4);
        addattr_l(&req.n, sizeof(req), RTA_GATEWAY, gateway, 4);
        addattr_l(&req.n, sizeof(req), RTA_OIF,&index, 4);


    // sending the packet to the kernel.
        status=send(fd,&req,req.n.nlmsg_len,0);
        fprintf(stderr,"\nstatus=%d,添加路由成功\n",status);

    }

    int main(int argc ,char ** argv)
    {
        int fd;
        __u32 dest,gate;  //dest 代表目的网络或主机,gate代表网关;
        if(argc!=4){
            printf("usage:%s 10.168.0.0 24 10.168.3.1\n",argv[0]);
            exit(1);
        }
        if(atoi(argv[2])<0||atoi(argv[2])>32){
            printf("unvalible netmask bits!!must be 0-32\n");
            exit(1);
        }

        if((fd=socket(AF_NETLINK,SOCK_RAW,NETLINK_ROUTE))<0){
            printf("error creating a socket\n");
            exit(1);
        }

        dest=inet_addr(argv[1]); /* unsigned int value for dest Ip address  ,这里的目的网络或主机地址都要转换成网络字节顺序*/
        gate=inet_addr(argv[3]); /* unsigned int value for gateway address ,也要转换成网络字节顺序 */
        route_add(fd,&dest,&gate,(unsigned int)atoi(argv[2]));
        return 0;
    }


    编译后要用root权限运行。


    展开全文
  • linux路由的实现

    万次阅读 2015-06-30 16:39:18
    好比最普通的RS232协议,如果你的硬件系统集成了相关模块,CPU直接从缓冲区读写数据即可,否则就要自己模拟时序,深入到协议细节里去。TCP/IP协议也一样,一般的通信芯片都会集成两层和三层的转发控制,MAC表和...

    一、前言

    一套通信协议的实现除了硬件编/解码与信号传输之外,其余的部分均可以有所选择的由软件或者硬件实现。好比最普通的RS232协议,如果你的硬件系统集成了相关模块,CPU直接从缓冲区读写数据即可,否则就要自己模拟时序,深入到协议细节里去。TCP/IP协议也一样,一般的通信芯片都会集成两层和三层的转发控制,MAC表和路由表都存在通信芯片的寄存器里(片内ram区),用户只要将关键信息写入寄存器即可实现转发。

    至于如何平衡软件控制与硬件控制,由项目的实际需求与工程师的具体经验决定。在通信行业,一个庞大的项目会面临巨大的均衡负载的问题。此时,一个好的架构/解决方案也许卖得比一套产品还贵。而对于一个相对较小的项目,比如SOHO级别的家用路由器,压缩单个产品的成本就显得至关重要。动辄几十美刀的通信芯片显然太贵,于是主芯片只集成wifi模块,其余大部分协议用软交换实现的路由器芯片就有了用武之地。

    接下来我想分析一下linux软路由的实现方式,顺便回忆一些过去的工作。

    二、主要架构分析

    路由器可以简单得抽象成一个图灵机,即在输入给定封包的情况下,输出处理过后的封包。但同时要实现一定的用户接口,一旦用户通过用户接口输入命令后,图灵机的内部算法就应该有所改变。大致的模型如下图:

    这里写图片描述
    用户接口可以由网页和命令行来提供,也可以由远程的TR069封包来实现。之所以用linux,是因为linux内核里实现了tcp/ip协议,并有相当丰富的文档和开源代码,大部分代码都无需重构,只需要稍作修改即可。

    这里写图片描述

    三、具体工作

    一般来说,芯片供应商会提供该芯片的linux版SDK,不同的厂商会对SDK进行不同层次的移植。

    用户接口层

    有些公司将SDK上的网页改上自己的logo就立马上市,这样产品的研发周期的确很快。缺点是不能对路由器的功能进行定制,而且很难让不同的产品形成统一的界面风格,产品也更加难以迭代。假如使用风格统一的界面,再重写web接口,实际上这样做会使每个产品都需要再验证每个功能的稳定性,工作量非常大。

    APP层

    这些公司有一套已验证的APP,在这些APP之上是风格统一的web界面。他们可以对相应的产品定制不同的功能,主要的工作量集中在硬件相关的代码移植上,特别是wifi模块的驱动。

    驱动层

    厂商提供的SDK驱动一般不会修改,除非板卡使用了非标准配件而进行少数修改。如果需要对SDK的驱动去做优化,一般在国内只有华为会做这样的事,因为只有华为用的芯片是他们自己的。

    内核层

    很遗憾,linux内核是有崩溃的可能的,而且内核级别的BUG极难重现和解决。凡是在这一层进行了大量工作的公司,路由器的稳定性一定会提升很多,但相应的边际成本也会很高。

    编译器级别

    有些极变态的公司,为了优化部分核心算法,会直接查看编译后的汇编代码,然后一条一条得优化。当然会这样的做的只是一些经常被调用的模块,或者开机时加载的模块。

    四、openwrt

    openwrt无疑是现有开源路由器固件里的佼佼者,据说03年的时候思科的一款路由器被发现使用了linux系统,出于压力,思科公开了该路由器的源码。openwrt就是基于这套代码扩展的,要复习路由器的相关知识,在一个路由器上DIY一个开源的openwrt是一个不错的选择。就连小米路由器,也是用openwrt来改的,并且可以刷上原生openwrt固件。我想选一款tp-link的路由器,刷一个openwrt。在github上专门有人为用openwrt在路由器实现自动翻墙写了应用,不仅可以玩而且很实用。

    展开全文
  • 一般而言,访问控制并不是路由模块完成的,而是防火墙的职责,如果你使用Linux的,这是iptables的职责。然而有时候,特别是在策略很多的情况下,使用iptables会极大降低网络性能,这是Netfilter的filter表的本质决定...

    引:

    一般而言,访问控制并不是路由模块完成的,而是防火墙的职责,如果你使用Linux的,这是iptables的职责。然而有时候,特别是在策略很多的情况下,使用iptables会极大降低网络性能,这是Netfilter的filter表的本质决定的,具体的优化参见《 Linux的Netfilter框架深度思考-对比Cisco的ACL》。
         Linux有一个很实用的特性可以在某些情形下代替iptables,从而实现访问控制。本文给出一个方法,说明如何使用策略路由来控制数据访问的入口网卡,具体来讲就是:只有通过特定的网卡才能访问机器上的某一个地址。具体来讲,Linux服务器有如下配置:
    eth0:192.168.1.1/24
    eth1:192.168.2.1/24
    eth2:172.16.1.1/24
    lo:127.0.0.1
    只能通过eth0访问192.168.1.1这个地址,而不能通过eth1或者eth2访问,甚至本机都不能访问192.168.1.1。
         但是在探讨如何做之前,首先要明白一些理论知识,这样才能知其然且知其所以然。

    1.完成这个需求必须要明白的背景知识

    1.1.Linux路由查找流程

    所有的路由器设计都要遵循以下规则:
    IF 目的地址配置在本机
        THEN 本机接收
    ELSE
        查找路由表并在找到路由的情况下转发
    END

    当然Linux也不能例外,但是Linux并没有将这这两种情况进行区分,而是使用“多张路由表”将二者统一了起来。在Linux中,内置了三张路由表:
    local,main,default,其中local路由表的优先级最高,并且不能被替换,在有数据包进来的时候,首先无条件的查找local路由表,如果找到了路由,则数据包就是发往本机的,如果找不到,则接着在其它的路由表中进行查找。使用ip route ls table local命令可以看到local表的内容,比如机器的eth0网卡上配有192.168.0.7,则在local表中会有如下的路由:
    local 192.168.0.7 dev eth0  proto kernel  scope host  src 192.168.0.7
    值得注意的是,local表中的路由是可以删除的。路由的src项指的是当数据包从本机发出时,在local表中找到了源地址的路由,首选的源ip地址
         在local表和main表之间,可以插入251张策略路由表,因此如果有策略路由表的话,如果local表中没有找到路由,则会查找策略路由表。
         总结一下本节的内容,Linux内置了三张路由表,其中local路由表优先级最高且不可替换,它完成“IF 目的地址配置在本机 THEN 本机接收”这个逻辑,在local表之后,可以配置多张策略路由表,策略路由的知识本文不谈,但是基本就是根据源地址,目的地址,出接口,入接口等元素来决定数据包在路由前是否进入该张策略路由表,本质上是一种过滤行为(然则结果是可以缓存的,其要点就在于此!)。

    1.2.bind地址/no-bind地址

    有一个问题,那就是如果一个数据包从本机发出,如何确定其源地址,这个问题如果搞不明白,就可能面临很多奇怪的现象而无法解释,在这个问题上,TCP和UDP的行为是不同的,TCP比较简单,因为一个TCP连接是四元素决定的(源IP地址,目的IP地址,源端口,目的端口),因此在建立连接后,源/目的IP地址是确定的。对于UDP而言,情况就复杂了,下节详述。但是不管什么协议,在API接口层次上,一个socket分为bind地址的和不bind地址的。
         如果是bind地址,那么源地址就是bind的那个地址,如果没有bind,那么源地址在路由之后根据路由的结果确定。这就意味着,策略路由的from关键字将无法匹配到所有没有bind地址的应用程序从本地发出的包-原因是策略路由匹配是在路由前做的,而此时还没有源IP地址。
         想明白协议栈如何这么设计,还是要从IP路由的本质以及传输层语义来分析。IP路由的职责就是能将IP数据报送到目的地,在路由之后选择源IP地址可以使返回的IP数据报在完全逆向路径上返回。考虑传输层的语义,对于TCP而言,其源地址的确定性是TCP做的,而不是IP层做的,这一点一定要清楚。对于不bind地址的情况,应用程序在数据包到达网络层之前不需要考虑网络层协议头的内容,这个工作完全有网络层的IP路由模块来完成,应用程序只需要指出目的IP即可,完全由协议栈负责网络层协议头的添加。
         想明白协议栈如何实现这个逻辑,最好的办法是看Linux的源代码,方法是跟踪一个数据包发送的全过程源码,具体看ip_route_output_slow。

    1.3.UDP踢皮球

    讨论TCP的文章很多,TCP也有很多复杂的特性值得去深究,然而UDP也不是吃素的,有一种现象就是UDP连接会踢皮球,最终用TCPDUMP抓取的数据包结果会让人焦头烂额。其实只要明白1.2节的内容,本节的内容就很简单了。
         UDP无连接,不可靠,只负责将数据尽力而为传到目的主机,它对源和目的IP地址的管理很松散,UDP数据流(更确切的并不能称为数据流)是单包的。在两端都没有显式bind到具体的IP地址的情况下,最终的数据包可以使用任意的本机地址,关键看路由的结果。数据到达对端之后,如果对端也没有显示bind到具体的IP地址,那么回复包的源地址也可能不再是初始包的目的地址。我们还是用实例来说明吧,先看网络拓扑:


    路由配置如下:
    host1:default 192.168.0.2
    host2:default 172.16.0.2

    host1上运行一个UDP服务器,绑到0.0.0.0这个地址,也就是不绑定地址,host2向host1 192.168.1.1的UDP端口8888发送数据,抓包发现其源地址是172.16.0.1,目的地址是192.168.1.1,而返回包却抓不到了,意外抓取到一个源地址是192.168.0.1,目的地址是172.16.0.1的数据包。这是正常的,因为数据包到达host1时,查找返回路由时,会查到下一跳192.168.0.2,进而根据这个下一跳选择同一网段的192.168.0.1这个地址,此时如果添加一条路由:172.16.0.1 gw 192.168.1.2,那么就会看到返回数据包源地址为192.168.1.1了。
         还有更奇怪的现象,那就是,初始时从host2向host1发送数据,源和目的分别是172.16.0.1和192.168.1.1,可是后来,在没有断开UDP客户端和服务器的情况下(host2更改了路由),源和目的分别成了172.16.1.1和192.168.0.1,这也是正常的,因为UDP本来就是无连接的,在不bind地址的情况下,关键是根据路由来选择源地址,选择源地址原则是:优先选择路由结果接口上和下一跳地址为同一网段的第一个primary地址,否则选择其它网卡上的primary地址,在选择过程中,有三个scope会影响选择结果,一个是下一跳地址scope,它表示该地址到达本地的“距离”,另一个是路由scope,它表示到达该路由的“距离”,还有一个是本地地址的scope,它限制了该地址的使用范围,路由模块保证下一跳的scope要小于路由的scope-背后的思想就是下一跳一定距离目的地比本机更近,而选择的本地地址的scope必须小于等于给定的scope(作为一个参数存在)。
         UDP踢皮球有一个后果就是会影响Netfilter的ip_conntrack,我们知道,ip_conntrack是基于五元素来跟踪连接的,UDP的混乱情况可能使一个UDP数据流被跟踪好几次,从而使得后续的NAT规则(如果有的话)很复杂,NAT配置必须考虑到皮球的每一个方向,否则就会漏掉本来应该被NAT的数据包。
         总之,网络是很复杂的,千万不要觉得就是配置几个IP地址以及几条路由那么简单的事。

    1.4.路由前对本机出发数据包的源地址的检查

    如果是本机发出数据包,最终要进入路由模块的ip_route_output_slow函数来查找路由,该函数对bind地址的源地址进行了检查,它保证到该本地地址的路由必须在local路由表中被找到。
    static int ip_route_output_slow(...)
    {
        if (oldflp->fl4_src) {
            ...
            if (!(oldflp->flags & FLOWI_FLAG_ANYSRC)) {
                /* It is equivalent to inet_addr_type(saddr) == RTN_LOCAL */
                dev_out = ip_dev_find(net, oldflp->fl4_src);
                if (dev_out == NULL)
                    goto out;
                ...
            }
        }
    }


    ip_dev_find的实现中,有以下逻辑:
    local_table = fib_get_table(net, RT_TABLE_LOCAL);
        if (!local_table || local_table->tb_lookup(local_table, &fl, &res))
            return NULL;


    这意味着本机出发的数据包的源地址如果有的话,必须要在local表中找到一条local路由,否则则返回EINVAL错误。然而可以取消这一限制,具体见1.6节。总而言之,FLOWI_FLAG_ANYSRC这个标志是基于socket,通过setsockopt可以设置socket,使与之相关的oldflp的flags中有FLOWI_FLAG_ANYSRC标志。

    1.5.Linux的IP地址属于主机而不属于网卡

    在Linux中,不要以为IP地址是属于网卡的,它是属于主机的,实际上就算是UNIX或者其它的OS,IP地址都不应该是网卡的,IP地址是三层概念,网卡是二层设备。很多人都认为IP地址是属于网卡的是因为在Linux中配置IP地址时都要给定一个网卡参数,比如ip address add dev ethX XXX/YY。
         IP地址是属于主机的,这就意味着,只要IP数据报到达本机,没有常规的方式使用路由限制该IP数据报不到达本地应用程序(local表是无条件最先被查询的,除非在local表中将该地址对应的local路由删除)。在procfs中使用sysctl能通过配置网卡参数达到限制数据包的目的吗?比如什么“关闭eth0的forwarding,这样数据包就不能从eth0 forward到eth2了”,根本不是那回事。

    1.6.取消本地地址必须存在于local路由表的限制

    2.6.27以上内核的socket选项IP_TRANSPARENT可以影响本机出发数据包路由查找时的源地址检测,具体做法是在应用程序中使用下列代码段:
    int value = 1;
    setsockopt(fd, SOL_IP, IP_TRANSPARENT, &value, sizeof(value));
    设置后,服务器回包的源地址不再限制在local表内,而是可以使用任何地址,包括非本机地址,这个技术一般用于透明代理。因此可以用这一特性来利用策略路由表完成本应该由防火墙完成的功能,不损耗性能。这样可以做到在local表中将eth0上的local路由删除,将该local路由加到策略路由中,本地应用程序将不能访问配置在eth0上地址。
         在2.6.27之前,协议栈在添加源地址(或者用户指定了侦听地址)时,要确保local路由表中拥有该地址,否则会报错,而我们就是想把该地址的本机local路由移出local表,因此此功能不可实现。在2.6.27之后,增加了FLOWI_FLAG_ANYSRC标志,可以通过设置该标志做到限制的取消,具体做法有两种,一种是全局的,那就是将ip_route_output_slow中的if (!(oldflp->flags & FLOWI_FLAG_ANYSRC))判断取消掉,改为if (0);第二种改法是基于socket的,实际上FLOWI_FLAG_ANYSRC是基于单个socket设置的。完成此功能的前提:
    1).内核编译了CONFIG_IP_MULTIPLE_TABLES
    2).修改管理服务程序,为其socket增加IP_TRANSPARENT选项

    2.具体操作

    为了很简单的几步操作,前面啰里啰嗦说了那么多,实际上做技术本来就应该这样,必须挖掘深层次的原理,否则就只能算个IT工人。
    配置:
    eth0:192.168.0.1/24
    eth1:172.16.0.1/24
    eth2:10.0.0.1/24
    lo:127.0.0.1

    2.1.限制从其它网卡接口访问特定网卡接口上配置的IP地址(这个说法不准确,具体见1.5节)对应的服务

    2.1.1.添加一个策略路由表

    echo "100 my" >> /etc/iproute2/rt_tables
    这样可以在local和main表之间增加一个路由表my,内核路由模块的查找顺序是:local->my->main...

    2.1.2.增加一条策略

    ip rule add from 192.168.0.1 table my

    2.1.3.在策略路由表中增加所有从eth0出去的路由

    ip route add 12.34.0.0/16 via 192.168.0.2 dev eth0 table my
    ip route add ... table my
    ...
    ip route add default dev eth0 table my

    所有匹配到my这个策略路由表的数据包,将从上述的路由项中查询结果。注意,最后一条默认路由是必须要的,且一定要从eth0出去,否则根据Linux策略路由查找原则,如果在my表中没有找到路由的话,还是会继续往下进行的,所谓的策略路由表只是一个优先级问题,而不是强制的查找约束。

    2.1.4.结论和问题

    通过以上的配置,所有从eth0进来的数据包才能安全返回,否则,比如从eth1进来一个访问192.168.0.1的数据包,由于它只有从eth1返回才可以(不考虑多径路由),然而返回包的源地址却是192.168.0.1(对应的服务不管是TCP的还是UDP必须显式bind到192.168.0.1这个地址,否则对于UDP就会有踢皮球的现象),这样路由查找就会进入my表(对于踢皮球的情况,就可能不进入my表),然则my表中没有一个从eth1出去的路由,且该包起码会匹配到my表的默认路由,数据包因此无法返回。
         现在考虑一下一个问题,如果是bind到192.168.0.1这个地址的服务主动访问外部,是不是也一定要从eth0出去呢?答案是肯定的,因为它bind了一个明确的地址,而源地址是该地址的数据包一定会匹配到my,最终起码会匹配到my的默认路由...

    2.1.5.进一步的问题

    现在已经实现了不能从除eth0之外的其它接口进入访问bind到eth0上地址的服务,然而如果希望做到连本地都不能访问该服务,那才是名副其实的“除eth0之外的...都不能...”,无疑本地出发的访问192.168.0.1的数据包肯定不是从eth0进入的。
         有一个办法可以解决这个问题,那就是禁用掉lo,因为在Linux中,所有从本地到本地的包都会被定向掉lo,禁掉lo后,所有本地到本地的包就都无法到达目的地。但是这种方法并不好,管不着人家就把人家关起来,非真的猛士!下一节我们就看看怎么做到本地不能访问本地eth0上的192.168.0.1这个地址,做到名副其实的“只有eth0进入的数据包才能访问”

    2.2.限制本地访问本地bind到eth0上192.168.0.1这个地址的服务

    想理解这个配置原理,还要回顾一下1.4节和1.6节,当理解了这两节之后,这里的配置就手到擒来了

    2.2.0.前提:两种方式

    之一:直接将内核中的检查取消掉,见1.6节
    之二:改写bind到192.168.0.1地址服务程序,为其socket增加IP_TRANSPARENT选项,

    2.2.1.添加一个新路由表

    echo “100  my_rule” >> /etc/iproute2/rt_tables

    2.2.2.增加一条策略:从eth0到来的数据包,开放my_rule路由表

    ip rule add iif eth0 table my_rule
    所有从eth0进来的数据包,查找my_rule表中的路由

    2.2.3.为新增策略增加190.7 local路由

    ip route add local 192.168.0.1/32 dev lo table my_rule
    由于到达本地的数据包要想成功到达,必须要找到一条local路由(类型对即可,无需非要在local表),因此在my_rule中增加一条到达192.168.0.1的local路由

    2.2.4.删除原有的local表中的192.168.0.1路由

    ip route del local 192.168.0.1/32 table local
    由于2.2.0中的两种前提,对源地址的检测已经取消了,到达源地址的路由现在没有必要非要在local表中了,因此即使删除了local中到达192.168.0.1的路由,也无所谓,返回包会直接使用源地址192.168.0.1而不被检查。

    2.2.5.结论和问题

    2.2.1-2.2.4的结果就是:访问192.168.0.1的数据包从eth0而来,查找my_rule表,找到,对于返回包,由于IP_TRANSPARENT取消了限制,可以正常返回;对于从非eth0到来的访问192.168.0.1的包,由于192.168.0.1的local路由已然被删,my_rule由于只匹配入口为eth0的数据包因而不对其开放,将无法访问。
         这个配置在2.1节的基础上做了增强,然而由于要修改bind到192.168.0.1地址的服务程序,对于既有的闭源程序的类似的访问控制将没有用武之地(这些程序没有源码,不能修改)。

    3.总的结论

    本文给出了一种使用路由进行访问控制的方式,对于规则比较简单,且访问控制都在第三层的场景中,路由的方式要比用防火墙更好,不会影响性能。然而本文的讨论完全是基于Linux的,对于非Linux的系统,比如Cisco,可能人家的ACL防火墙实现得比较高效,比iptables配置的更好,也就不需要用路由的方式进行访问控制了,就算对于Linux本身,nf-HiPAC对filter表做了优化之后,路由的方式进行访问控制的优势也减少了。

         总之,Linux网络或者说网络本身是一个超级复杂的系统,不同的实现对于配置方式的选型影响巨大,然而有一个问题,比如像Windows这样的系统,你还不知道它的网络协议栈实现的内幕,那么它的配置肯定也就比较固定,那就是Microsoft的建议配置。
         实际上,由于增加了策略路由表,查表的过程也是遍历,并且根据策略路由表的match一个一个比较,这个过程和filter表的查询过程几乎是一样的,则策略路由的优势体现在何方?实际上,路由和filter有一个区别,那就是路由是可以缓存的,而filter则是每包匹配的,有一种基于状态的防火墙可以“缓存”过滤结果,但是由于需要维护连接状态,这笔开销也是不可小觑的。路由缓存是完全独立的,路由完全缓存在一个hash表当中。
         但是,如果路由缓存hash表的冲突链表过长(缓存太大了),或者hash算法太菜,在配置大量策略路由和配置iptables之间权衡的话,后者也不是总是处于劣势,孰是孰非,只有具体情况具体分析,只有分析了具体的性能数据才能有结论,否则只是纸上谈兵一纸空文。

    4.两篇文档

    linux-source/Documentation/networking/tproxy.txt
    linux-source/Documentation/networking/policy-routing.txt
    展开全文
  • linux 路由

    千次阅读 2007-04-16 13:44:00
    LINUX能为你做什么 3 2.4. 内务声明 3 2.5. 访问,CVS和提交更新 4 2.6. 邮件列表 4 2.7. 本文档的布局 4 第3章 介绍 IPROUTE2 6 3.1 为什么使用 IPROUTE2 6 3.2 IPROUTE2 概览 6 3.3 先决条件 6 3.4 浏
      
    
    目录
    第1章 贡献 1
    第2章 简介 2
    2.1. 除外责任与许可 2
    2.2. 预备知识 2
    2.3. LINUX能为你做什么 3
    2.4. 内务声明 3
    2.5. 访问,CVS和提交更新 4
    2.6. 邮件列表 4
    2.7. 本文档的布局 4
    第3章 介绍 IPROUTE2 6
    3.1 为什么使用 IPROUTE2 6
    3.2 IPROUTE2 概览 6
    3.3 先决条件 6
    3.4 浏览你的当前配置 7
    3.4.1. 让ip显示我们的链路 7
    3.4.2. 让ip显示我们的 IP 地址 7
    3.4.3. 让ip显示路由 8
    3.5. ARP 9
    第4章 规则——路由策略数据库 11
    4.1. 简单的源策略路由 11
    4.2. 多重上连ISP的路由 12
    4.2.1. 流量分割 13
    4.2.2. 负载均衡 14
    第5章 GRE 和其他隧道 15
    5.1. 关于隧道的几点注释 15
    5.2. IP-IN-IP 隧道 15
    5.3. GRE 隧道 16
    4
    5.3.1. IPv4隧道 16
    5.3.2. IPv6隧道 18
    5.4. 用户级隧道 18
    第6章 用CISCO和6BONE实现IPV6隧道 19
    6.1. IPV6隧道 19
    第7章 IPSEC:INTERNET上安全的IP 22
    7.1. 从手动密钥管理开始 22
    7.2. 自动密钥管理 25
    7.2.1. 理论 26
    7.2.2. 举例 26
    7.2.3. 使用X.509证书进行自动密钥管理 29
    7.3. IPSEC隧道 32
    7.4. 其它IPSEC软件 33
    7.5. IPSEC与其它系统的互操作 33
    7.5.1. Windows 33
    第8章 多播路由 34
    第9章 带宽管理的队列规定 36
    9.1. 解释队列和队列规定 36
    9.2. 简单的无类队列规定 37
    9.2.1. pfifo_fast 37
    9.2.2. 令牌桶过滤器(TBF) 39
    9.2.3. 随机公平队列(SFQ) 41
    9.3. 关于什么时候用哪种队列的建议 42
    9.4. 术语 43
    9.5. 分类的队列规定 45
    9.5.1. 分类的队列规定及其类中的数据流向 45
    9.5.2. 队列规定家族:根,句柄,兄弟和父辈 45
    9.5.3. PRIO队列规定 46
    9.5.4. 著名的CBQ队列规定 48
    9.5.5. HTB(Hierarchical Token Bucket, 分层的令牌桶) 54
    5
    9.6. 使用过滤器对数据包进行分类 55
    9.6.1. 过滤器的一些简单范例 56
    9.6.2. 常用到的过滤命令一览 57
    9.7. IMQ(INTERMEDIATE QUEUEING DEVICE,中介队列设备) 58
    9.7.1. 配置范例 58
    第10章 多网卡的负载均衡 60
    10.1. 告诫 61
    10.2. 其它可能性 61
    第11章 NETFILTER和IPROUTE——给数据包作标记 62
    第12章 对包进行分类的高级过滤器 64
    12.1. U32分类器 65
    12.1.1. U32选择器 65
    12.1.2. 普通选择器 66
    12.1.3. 特殊选择器 67
    12.2. 路由分类器 67
    12.3. 管制分类器 68
    12.3.1. 管制的方式 68
    12.3.2. 越限动作 69
    12.3.3. 范例 70
    12.4. 当过滤器很多时如何使用散列表 70
    第13章 内核网络参数 72
    13.1. 反向路径过滤 72
    13.2. 深层设置 73
    13.2.1. ipv4一般设置 73
    13.2.2. 网卡的分别设置 78
    13.2.3. 邻居策略 79
    13.2.4. 路由设置 80
    第14章 不经常使用的高级队列规定 82
    14.1. BFIFO/PFIFO 82
    14.1.1. 参数与使用 82
    6
    14.2. CLARK-SHENKER-ZHANG算法 (CSZ) 82
    14.3. DSMARK 83
    14.3.1. 介绍 83
    14.3.2. Dsmark与什么相关? 83
    14.3.3. Differentiated Services指导 84
    14.3.4. 使用Dsmark 84
    14.3.5. SCH_DSMARK如何工作 84
    14.3.6. TC_INDEX过滤器 85
    14.4. 入口队列规定 87
    14.4.1. 参数与使用 87
    14.5. RED(RANDOM EARLY DETECTION,随机提前检测) 87
    14.6. GRED(GENERIC RANDOM EARLY DETECTION,一般的随机提前检测) 88
    14.7. VC/ATM模拟 89
    14.8. WRR(WEIGHTED ROUND ROBIN,加权轮转) 89
    第15章 方便菜谱 90
    15.1. 用不同的SLA运行多个网站. 90
    15.2. 防护SYN洪水攻击 90
    15.3. 为防止DDOS而对ICMP限速 91
    15.4. 为交互流量设置优先权 92
    15.5. 使用NETFILTER,IPROUTE2和SQUID实现WEB透明代理 93
    15.5.1. 实现之后的数据流图 96
    15.6. 与PMTU发现有关的"基于路由的MTU设置" 96
    15.6.1. 解决方案 97
    15.7. 与PMTU发现有关的MSS箝位(给ADSL,CABLE,PPPOE和PPTP用户) 98
    15.8. 终极的流量控制:低延迟,高速上/下载 98
    15.8.1. 为什么缺省设置不让人满意 99
    15.8.2. 实际的脚本(CBQ) 100
    15.8.3. 实际的脚本(HTB) 102
    15.9. 为单个主机或子网限速 103
    15.10. 一个完全NAT和QOS的范例 104
    7
    15.10.1. 开始优化那不多的带宽 104
    15.10.2. 对数据包分类 106
    15.10.3. 改进设置 107
    15.10.4. 让上面的设置开机时自动执行 108
    第16章 构建网桥以及用ARP代理构建伪网桥 109
    16.1. 桥接与IPTABLES的关系 109
    16.2. 桥接与流量整形 109
    16.3. 用ARP代理实现伪网桥 109
    16.3.1. ARP和ARP代理 110
    16.3.2. 实现 110
    第17章 动态路由——OSPF和BGP 112
    17.1. 用ZEBRA设置OSPF 112
    17.1.1. 必要条件 113
    17.1.2. 配置Zebra 113
    17.1.3. 运行Zebra 115
    第18章 其它可能性 117
    第19章 进一步学习 119
    第20章 鸣谢 120
    8
    第1章 贡献
    本文档的成形得益于很多人的贡献,我希望能够回报他们.列出其中几个:
    Rusty Russell
    Alexey N. Kuznetsov
    来自Google的一些好心人
    Casema Internet的工作人员
    1
    第2章 简介
    欢迎,亲爱的读者.
    希望这篇文档能对你更好地理解Linxs2.2/2.4的路由有所帮助和启发.不被大
    多数使用者所知道的是,你所使用工具,其实能够完成相当规模工作.比如route
    和ifconfig,实际上暗中调用了非常强大的iproute 2的底层基本功能.
    我希望这个HOWTO能够象Rusty Russell的作品那样通俗易懂.
    你可以随时给HOWTO工作组发电子邮件来找到我们.但是如果您的问题并不
    直接与这个HOWTO文档相关,请首先考虑发给邮件列表(参考相关章节).我们
    可不是免费的帮助平台,但我们经常会在邮件列表上回答问题.
    在钻研这个HOWTO之前,如果您想做的只是一点简单的流量整形,不妨直接
    去看看其它可能性这一章里面的CBQ.init.
    2.1. 除外责任与许可
    这个文档依着对公众有利用价值的目的而发布,但不提供任何担保,即使是在经
    销或者使用在特定场合时的潜在担保.
    简单地说,如果您的STM-64骨干网瘫痪,并向您尊敬的客户们散布黄色图片,
    对不起,那绝对不关我的事.
    Copyright (c) 2002 所有:bert hubert,Gregory Maxwell,Martijn van Oosterhout,
    Remco van Mook,Paul B. Schroeder等等.这份材料可以在遵从Open Publication
    License, v1.0(或更新版)各项条款的前提下发布.Open Publication License的最新
    版可以在http://www.opencontent.org/openpub/ 得到.
    请随意复制并发布(出售或者赠送)本文档,格式不限.只是请求将纠正和/或注解
    转发给文档的维护者.
    还希望如果你出版本HOWTO的硬拷贝,请给作者们发一份以备复习之用.
    2.2. 预备知识
    就像标题所暗示的,这是一个"高级"HOWTO.虽然它不是终极的航天科技,
    但还是要求一定的基础知识.
    这里是一些可能对你有帮助的参考文献:
    2
    Rusty Russell的networking-concepts-HOWTO
    非常精彩的介绍,解释了什么是网络以及一个网络如何与其它网络互联.
    Linux Networking-HOWTO (以前叫做Net-3 HOWTO)
    好东西,虽然非常冗长.它讲授的内容就是你连接到Internet所需的的配置内
    容.应该在/usr/doc/HOWTO/NET3-4-HOWTO.txt中,也可以在线阅读.
    2.3. Linux能为你做什么
    一个小列表:
    管制某台计算机的带宽
    管制通向某台计算机的带宽
    帮助你公平地共享带宽
    保护你的网络不受DoS攻击
    保护Internet不受到你的客户的攻击
    把多台服务器虚拟成一台,进行负载均衡或者提高可用性
    限制对你的计算机的访问
    限制你的用户访问某些主机
    基于用户账号(没错!),MAC地址,源IP地址,端口,服务类型,时间
    或者内容等条件进行路由.
    现在,很多人都没有用到这些高级功能.这有很多原因.比如提供的文档过于冗
    长而且不容易上手,而且流量控制甚至根本就没有归档.
    2.4. 内务声明
    关于这个文档有些事情要指出.当我写完这个文档的绝大部分的时候,我真的不
    希望它永远就是那个样子.我是一个坚信开放源代码的人,所以我希望你能够给
    我发回反馈,更新,补丁等等.所以你应该尽可以告知我你的手稿或者指出一些
    哪怕是无关紧要的错误,不必犹豫.如果我的英语有些晦涩,请原谅那不是我的
    母语,尽可以给我建议.
    如果你认为自己更有资格维护某个章节,或者认为自己可以写作并维护一个新的
    章节,请您一定不要客气.这个HOWTO的SGML可以通过CVS得到,我估计
    肯定有很多人还在为它出力.
    作为请求援助,你会在文档中发现很多"求助"的字样.我们永远欢迎您的补丁!
    无论您在哪里发现"求助",都应该明白您正在踏入一个未知的领域.这并不是
    说在别的地方就没有错误,但您应该倍加小心.如果您确认了某些事情,请您一
    3
    定通知我们,以便我们能够把"求助"的标记去掉.
    关于这个HOWTO,I will take some liberties along the road. For example, I postulate
    a 10Mbit Internet connection, while I know full well that those are not very common.
    2.5. 访问,CVS和提交更新
    本HOWTO的规范位置在这里.
    我们现在向全球开放了匿名CVS访问.从各个角度来说这都是一件好事.你可
    以轻松地升级到本HOWTO的最新版本,而且提交补丁也不再成为问题.
    另外的好处是,这可以让作者在源码上独立地继续工作.
    $ export CVSROOT=:pserver:anon@outpost.ds9a.nl:/var/cvsroot
    $ cvs login
    CVS password: [enter 'cvs' (without 's)]
    $ cvs co 2.4routing
    cvs server: Updating 2.4routing
    U 2.4routing/lartc.db
    如果您做了修改并希望投稿,运行:
    cvs -z3 diff -uBb
    然后把输出用电子邮件发给,我们就可以很轻松地把它集成进去
    了.谢谢!请确认你修改的是.db文件,其它文件都是通过它生成的.
    提供了一个Makefile帮助您生成postscript,dvi,pdf,html和纯文本格式的文件.
    你可能需要安装docbook,docboot-utils,ghostscript和tetex等等支持软件才能生
    成各种格式的文本.
    注意,不要更改2.4routing.sgml!那里面有旧版本的HOWTO.正确的文件是
    lartc.db.
    2.6. 邮件列表
    作者已经开始收到关于这个HOWTO越来越多的邮件了.为了把大家的兴趣条
    理化,已经决定启动一个邮件列表,让大家在那里互相探讨有关高级路由和流量
    控制的话题.你可以在这里进行订阅.
    需要指出的是,作者们对于列表中没有问及的问题不可能及时回答.我们愿意让
    列表的归档成为一个知识库.如果你有问题,请搜索归档,然后在post到邮件
    列表里.
    2.7. 本文档的布局
    我们几乎马上就要做一些有趣的实验,也就意味着最开始部分的基本概念解释并
    不完整或者不完善,请您不必管它,后面会一点点说清楚.
    4
    路由和包过滤是完全不同的概念.关于过滤的问题,Rusty的文档说得很清楚,
    你可以在这里找到:
    Rusty出色的不可靠指南
    我们则将致力于netfilter与iproute2相结合后能做什么.
    5
    第3章 介绍 iproute2
    3.1 为什么使用 iproute2
    现在,绝大多数 Linux 发行版和绝大多数 UNIX都使用古老的arp, ifconfig和
    route命令.虽然这些工具能够工作,但它们在Linux2.2和更高版本的内核上显
    得有一些落伍.比如,现在GRE隧道已经成为了路由的一个主要概念,但却不
    能通过上述工具来配置.
    使用了iproute2,隧道的配置与其他部分完全集成了.
    2.2 和更高版本的Linux 内核包含了一个经过彻底重新设计的网络子系统.这些
    新的代码让Linux在操作系统的竞争中取得了功能和性能上的优势.实际上,
    Linux新的路由,过滤和分类代码,从功能和性能上都不弱于现有的那些专业的
    路由器,防火墙和流量整形产品.
    随着新的网络概念的提出,人们在现有操作系统的现有体系上修修补补来实现他
    们.这种固执的行为导致了网络代码中充斥着怪异的行为,这有点像人类的语言.
    过去,Linux模仿了SunOS的许多处理方式,并不理想.
    这个新的体系则有可能比以往任何一个版本的Linux都更善于清晰地进行功能
    表达.
    3.2 iproute2 概览
    Linux有一个成熟的带宽供给系统,称为Traffic Control(流量控制).这个系统
    支持各种方式进行分类,排序,共享和限制出入流量.
    我们将从 iproute2 各种可能性的一个简要概览开始.
    3.3 先决条件
    你应该确认已经安装了用户级配置工具.这个包的名字在RedHat和Debian中都
    叫作"iproute",也可以在这个地方找到:
    ftp://ftp.inr.ac.ru/ip-routing/iproute2-2.2.4-now-ss .tar.gz
    你也可以试试在这里找找最新版本.
    iproute 的某些部分需要你打开一些特定的内核选项.应该指出的是,RedHat6.2
    及其以前的所有发行版中所带的缺省内核都不带有流量控制所需要的绝大多数
    功能.
    6
    而RedHat 7.2在缺省情况下能满足所有要求.
    另外,确认一下你的内核支持netlink ,Iproute2需要它.
    3.4 浏览你的当前配置
    这听上去确实让人惊喜:iproute2已经配置好了!当前的ifconfig和route命令已
    经正在使用新的系统调用,但通常使用了缺省参数(真无聊).
    新的工具ip成为中心,我们会让它来显示我们的网卡配置.
    3.4.1. 让ip显示我们的链路
    [ahu@home ahu]$ ip link list
    1: lo: mtu 3924 qdisc noqueue
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    2: dummy: mtu 1500 qdisc noop
    link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
    3: eth0: mtu 1400 qdisc pfifo_fast qlen 100
    link/ether 48:54:e8:2a:47:16 brd ff:ff:ff:ff:ff:ff
    4: eth1: mtu 1500 qdisc pfifo_fast qlen 100
    link/ether 00:e0:4c:39:24:78 brd ff:ff:ff:ff:ff:ff
    3764: ppp0: mtu 1492 qdisc pfifo_fast qlen 10
    link/ppp
    你的结果可能有所区别,但上述显示了我家里NAT路由器的情况.我将只解释
    输出中并非全部直接相关的部分.因为并不是所有部分都与我们的话题有关,所
    以我只会解释输出的一部分.
    我们首先看到了 loopback 接口. While your computer may function somewhat
    without one, I'd advise against it. MTU (最大传输单元)尺寸为 3924 字节,并且不
    应该参与队列.这是因为 loopback 接口完全是内核想象出来的,并不存在的接
    口.
    现在我们跳过这个无关的接口,它应该并不实际存在于你的机器上.然后就是两
    个物理网络接口,一个接在我的 cable modem 上,另一个接到我家里的以太网
    端上.再下面,我们看见了一个 ppp0 接口.
    应该指出,我们没有看到 IP 地址.iproute 切断了"链路"和"IP 地址"两个
    概念的直接联系.当使用 IP 别名的时候,IP地址的概念显得更加不相关了.
    尽管如此,还是显示出了标识以太网卡硬件的 MAC 地址.
    3.4.2. 让ip显示我们的 IP 地址
    [ahu@home ahu]$ ip address show
    1: lo: 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: dummy: mtu 1500 qdisc noop
    7
    link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
    3: eth0: mtu 1400 qdisc pfifo_fast qlen 100
    link/ether 48:54:e8:2a:47:16 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.1/8 brd 10.255.255.255 scope global eth0
    4: eth1: mtu 1500 qdisc pfifo_fast qlen 100
    link/ether 00:e0:4c:39:24:78 brd ff:ff:ff:ff:ff:ff
    3764: ppp0: mtu 1492 qdisc pfifo_fast qlen 10
    link/ppp
    inet 212.64.94.251 peer 212.64.94.1/32 scope global ppp0
    这里包含了更多信息.显示了我们所有的地址,以及这些地址属于哪些网卡.
    "inet"表示Internet (IPv4).还有很多其它的地址类型,但现在还没有涉及到.
    让我们先就近看看eth0.上面说它与IP地址10.0.0.1/8相关联.这是什么意思呢?
    "/8"表示IP地址表示网络地址的位数.因为一共是32个bit,所以我们的这个
    网络有了24 bit的主机空间. 10.0.0.1 的开始8bit是10.0.0.0,也就是我们的网络
    地址,我们的子网掩码是255.0.0.0.
    其它的bit直接连接在这个网卡上,所以10.250.3.13可以直接通过eth0联络到,
    就象10.0.0.1一样.
    对于ppp0,仍是相同的概念,虽然数字看上去有所不同.它的地址是
    212.64.94.251,不带子网掩码.这意味着这是一个点到点的连接,而且除了
    212.64.94.251之外的地址是对端的.当然,还有很多信息.它还告诉我们这个链
    路的另一端只有一个地址:212.64.94.1./32意思是说没有表示网络的bit.
    掌握这些概念是绝对重要的.如果有问题,不妨先参考以下这个HOWTO文件
    开头曾经提到的那些文档.
    你应该注意到了"qdisc",它是基于对列规范的一个概念.它在后面会变得很重
    要.
    3.4.3. 让ip显示路由
    好的,现在我们已经知道如何找到10.x.y.z了,然后我们就可以到达212.64.94.1.
    但这还不够,我们还得说明如何找到全世界.可以通过我们的ppp连接找到
    Internet,212.64.94.1愿意把我们的数据包发给全世界,并把回应的数据包传回给
    我们.
    [ahu@home ahu]$ ip route show
    212.64.94.1 dev ppp0 proto kernel scope link src 212.64.94.251
    10.0.0.0/8 dev eth0 proto kernel scope link src 10.0.0.1
    127.0.0.0/8 dev lo scope link
    default via 212.64.94.1 dev ppp0
    字面的意思相当清楚.前4行的输出明确地说明了ip address show的意思,最
    后一行说明了世界的其它部分可以通过我们的缺省网关212.64.94.1找到.我们
    通过"via"这个词断定这是一个网关,我们要把数据包交给它.这就是我们要
    留心的问题
    下面列出以前route 命令的输出作为参考:
    [ahu@home ahu]$ route -n
    8
    Kernel IP routing table
    Destination Gateway Genmask Flags Metric Ref Use
    Iface
    212.64.94.1 0.0.0.0 255.255.255.255 UH 0 0 0 ppp0
    10.0.0.0 0.0.0.0 255.0.0.0 U 0 0 0 eth0
    127.0.0.0 0.0.0.0 255.0.0.0 U 0 0 0 lo
    0.0.0.0 212.64.94.1 0.0.0.0 UG 0 0 0 ppp0
    3.5. ARP
    ARP 是由 RFC 826 所描述的"地址解析协议".ARP是网络上的计算机在居域
    网中用来解析另一台机器的硬件地址/位置的时候使用的.互联网上的机器一般
    都是通过机器名解析成IP地址来互相找到的.这就能够解决foo.com网络能够
    与bar.net网络通讯.但是,仅仅依靠IP地址,却无法得到一台计算机在一个网
    络中的物理位置.这时候就需要ARP.
    让我们举一个非常简单的例子.假定我有一个网络,里面有几台机器.其中的两
    台在我的子网上,一台叫foo,IP地址是10.0.0.1,另一台叫bar,IP地址是10.0.0.2.
    现在,foo想ping一下bar看看是不是正常,但是呢,foo只知道bar的IP地址,
    却并不知道bar的硬件(MAC)地址.所以foo在ping bar之前就会先发出ARP询
    问.这个ARP询问就像在喊:"Bar(10.0.0.2)!你在哪里(你的MAC地址是多少)?!"
    结果这个广播域中的每台机器都能听到foo的喊话,但是只有bar(10.0.0.2)会回
    应.Bar会直接给foo发送一个ARP回应,告诉它"Foo (10.0.0.1),我的Mac地
    址是00:60:94:E9:08:12".经过这种简单的交谈,机器就能够在局域网中定位它
    要通话的对象.Foo会一直使用这个结果,直到它的ARP缓冲忘掉这个结果(在
    Unix系统上通常是15分钟之后).
    现在我们来看一看具体的工作过程.你可以这样察看你的ARP表(缓冲):
    [root@espa041 /home/src/iputils]# ip neigh show
    9.3.76.42 dev eth0 lladdr 00:60:08:3f:e9:f9 nud reachable
    9.3.76.1 dev eth0 lladdr 00:06:29:21:73:c8 nud reachable
    你可以看到,我的机器 espa041 (9.3.76.41) 知道如何找到 espa042 (9.3.76.42) 和
    espagate (9.3.76.1).现在让我们往缓冲中添加另一台机器.
    [root@espa041 /home/paulsch/.gnome-desktop]# ping -c 1 espa043
    PING espa043.austin.ibm.com (9.3.76.43) from 9.3.76.41 : 56(84) bytes of data.
    64 bytes from 9.3.76.43: icmp_seq=0 ttl=255 time=0.9 ms
    --- espa043.austin.ibm.com ping statistics ---
    1 packets transmitted, 1 packets received, 0% packet loss
    round-trip min/avg/max = 0.9/0.9/0.9 ms
    [root@espa041 /home/src/iputils]# ip neigh show
    9.3.76.43 dev eth0 lladdr 00:06:29:21:80:20 nud reachable
    9.3.76.42 dev eth0 lladdr 00:60:08:3f:e9:f9 nud reachable
    9.3.76.1 dev eth0 lladdr 00:06:29:21:73:c8 nud reachable
    由于espa041试图联络espa043,espa043的硬件地址已经添加到ARP缓冲里了.
    所以直到espa043的记录失效以前(也就是两个机器间长时间没有通讯),espa041
    知道如何找到espa043,也就不必频繁地进行ARP询问了.
    9
    现在让我们来删除 espa043 的ARP缓冲:
    [root@espa041 /home/src/iputils]# ip neigh delete 9.3.76.43 dev eth0
    [root@espa041 /home/src/iputils]# ip neigh show
    9.3.76.43 dev eth0 nud failed
    9.3.76.42 dev eth0 lladdr 00:60:08:3f:e9:f9 nud reachable
    9.3.76.1 dev eth0 lladdr 00:06:29:21:73:c8 nud stale
    现在espa041 已经忘记了espa043 的MAC地址,如果下次它要与espa043 通讯,
    需要再次发送 ARP询问.你在espagate (9.3.76.1) 上也会发现以上输出已经变成
    了"stale"状态.这意味着MAC地址仍然是在册,但是接下来第一次通讯的时候
    需要确认一下.
    10
    第4章 规则——路由策略数据库
    如果你有一个大规模的路由器,你可能不得不同时满足不同用户对于路由的不同
    需求.路由策略数据库可以帮助你通过多路由表技术来实现.
    如果你想使用这个特性,请确认你的内核配置中带有 "IP: advanced router" 和
    "IP: policy routing" 两项.
    当内核需要做出路由选择时,它会找出应该参考哪一张路由表.除了 "ip" 命令
    之外,以前的 "route" 命令也能修改 main 和 local 表.
    缺省规则:
    [ahu@home ahu]$ ip rule list
    0:
    f
    rom all lookup local
    32766:
    f
    rom all lookup main
    32767:
    f
    rom all lookup default
    上面列出了规则的优先顺序.我们看到,所有的规则都应用到了所有的包上
    ("from all").我们前面已经看到了 "main" 表,就是"ip route ls"命令的输出,
    但是"local"和"default"是初次见到.
    如果我们想做点有趣的事情,就可以生成一些指向不同路由表的规则,取代系统
    中的路由规则.
    对于内核如何处理一个IP包匹配多个规则的精确意义,请参见Alexey关于
    ip-cref文档.
    4.1. 简单的源策略路由
    让我们再来一个真实的例子.我有两个Cable Modem,连接到了一个 Linux的
    NAT ("伪装") 路由器上.这里的室友们向我付费使用 Internet.假如我其中的
    一个室友因为只想访问 hotmail 而希望少付一些钱.对我来说这没有问题, 他们
    肯定只能使用那个比较次的 Cable Modem.
    那个比较快的cable modem 的IP地址是 212.64.94.251, PPP 链路,对端IP是
    212.64.94.1.而那个比较慢的cable modem 的IP 地址是212.64.78.148,对端是
    195.96.98.253.
    local 表:
    [ahu@home ahu]$ ip route list table local
    11
    /etc/iproute2/rt_tables
    # ip rule add from 10.0.0.10 table John
    # ip rule ls
    0:
    f
    rom all lookup local
    32765:
    f
    rom 10.0.0.10 lookup John
    32766:
    f
    rom all lookup main
    32767:
    f
    rom all lookup default
    现在,剩下的事情就是为 John 的路由表创建路由项了.别忘了刷新路由缓存:
    # ip route add default via 195.96.98.253 dev ppp2 table John
    # ip route flush cache
    这样就做好了.至于如何在 ip-up 阶段实现就留给读者自己去研究吧.
    4.2. 多重上连ISP的路由
    下图是很常见的配置,同一个局域网(甚至是同一台计算机)通过两个ISP连接
    到互联网上.
    ________
    +------------+ /
    | | |
    +-------------+ ISP 1 +-------
    __ | | | /
    12
    ___/ /_ +------+-------+ +------------+ |
    _/ /__ | if1 | /
    / / | | |
    | 局域网 -----+ Linux 路由器 | | 国际互联网
    /_ __/ | | |
    /__ __/ | if2 | /
    /___/ +------+-------+ +------------+ |
    | | | /
    +-------------+ ISP 2 +-------
    | | |
    +------------+ /________
    这种情况下通常会出现两个问题.
    4.2.1. 流量分割
    首先是如何保证:回应来自某一个ISP的数据包时,仍然使用相同的ISP.
    让我们先定义一些符号. 令第一块网卡(上图的if1)的名字叫 $IF1,而第二块网
    卡叫做 $IF2 .然后设置 $IF1 的IP地址为 $IP1,$IF2 的IP地址为 $IP2.
    并且,令ISP1 的网关地址为 $P1,ISP2 的网关地址为 $P2.最后,令$P1的
    网络地址为 $P1_NET ,令$P2的网络地址为 $P2_NET.
    额外创建两个路由表, T1 和 T2. 加入到 /etc/iproute2/rt_tables 中.然后如下
    设置两个路由表中的路由:
    ip route add $P1_NET dev $IF1 src $IP1 table T1
    ip route add default via $P1 table T1
    ip route add $P2_NET dev $IF2 src $IP2 table T2
    ip route add default via $P2 table T2
    没什么大不了的,不过是建立了通向该网关的一条路由,并使之成为默认网关,
    分别负责一个单独的上行流,并且为这两个ISP都作这样的配置.要指出的是,
    那条网络路由是必要条件,因为它能够让我们找到那个子网内的主机,也包括上
    述那台网关.
    下一步,我们设置"main"路由表.把包通过网卡直接路由到与网卡相连的局域
    网上不失为一个好办法.要注意"src" 参数,他们能够保证选择正确的出口IP
    地址.
    ip route add $P1_NET dev $IF1 src $IP1
    ip route add $P2_NET dev $IF2 src $IP2
    然后,设置你的缺省路由:
    ip route add default via $P1
    接着,设置路由规则.这实际上在选择用什么路由表进行路由.你需要确认当你
    从一个给定接口路由出数据包时,是否已经有了相应的源地址:你需要保证的就
    是如果你已经有了相应的源地址,就应该把数据包从相应的网卡路由出去:
    ip rule add from $IP1 table T1
    ip rule add from $IP2 table T2
    13
    以上命令保证了所有的回应数据都会从他们来的那块网卡原路返回.
    现在,完成了非常基本的配置.这将对于所有运行在路由器上所有的进程起作用,
    实现IP伪装以后,对本地局域网也将起作用.如果不进行伪装,那么你要么拥
    有两个ISP的地址空间,要么你想对两个ISP中的一个进行伪装.无论哪种情况,
    你都要添加规则,基于发包的主机在局域网内的IP地址,选择从哪个ISP路由
    出去.
    4.2.2. 负载均衡
    第二个问题是如何对于通过两个ISP流出的数据进行负载均衡.如果你已经成功
    地实现了流量分割,这件事并不难.
    与选择两个ISP中的一个作为缺省路由不同,这次是设置缺省路由为多路路由.
    在缺省内核中,这会均衡两个ISP的路由.象下面这样做(基于前面的流量分割
    实验):
    ip route add default scope global nexthop via $P1 dev $IF1 weight 1 /
    nexthop via $P2 dev $IF2 weight 1
    这样就可以均衡两个ISP的路由.通过调整"weight"参数我们可以指定其中一
    个ISP的优先权高于另一个.
    应该指出,由于均衡是基于路由进行的,而路由是经过缓冲的,所以这样的均衡
    并不是100%精确.也就是说,对于一个经常访问的站点,总是会使用同一个ISP.
    进而,如果你对此不满意,你可能需要参考以下Julian Anastasov的内核补丁:
    http://www.linuxvirtualserver.org/~julian/#routes
    Julian的路由补丁会弥补上述缺陷.
    14
    第5章 GRE 和其他隧道
    Linux有3种隧道.它们是: IP-in-IP 隧道, GRE 隧道和非内核隧道(如PPTP).
    5.1. 关于隧道的几点注释
    隧道可以用于实现很多非常不一般而有趣的功能.但如果你的配置有问题,却也
    会发生可怕的错误.除非你确切地知道你在做什么,否则不要把缺省路由指向一
    个隧道设备.而且,隧道会增加协议开销,因为它需要一个额外的IP包头.一
    般应该是每个包增加20个字节,所以如果一个网络的MTU是1500字节的话,
    使用隧道技术后,实际的IP包长度最长只能有1480字节了.这倒不是什么原则
    性的问题,但如果你想使用隧道技术构建一个比较大规模的网络的话,最好仔细
    研究一下关于IP包的分片和汇聚的知识.哦,还有,挖一个隧道最好的方法当
    然是同时从两头挖.
    5.2. IP-in-IP 隧道
    这种隧道在Linux上已经实现很长一段时间了.需要两个内核模块:ipip.o 和
    new_tunnel.o.
    比如说你有3个网络:内部网A和B,中间网C(比如说:Internet).A网络的情
    况:
    网络地址
    1
    0.0.1.0
    子网掩码
    2
    55.255.255.0
    路由器
    1
    0.0.1.1
    路由器在C网络上的地址是172.16.17.18.
    B网络的情况:
    网络地址
    1
    0.0.2.0
    子网掩码
    2
    55.255.255.0
    路由器
    1
    0.0.2.1
    15
    路由器在C网络上的IP地址是 172.19.20.21.
    已知C网络已经连通,我们假定它会将所有的数据包从A传到B,反之亦然.
    而且你可以随便使用Internet.
    这就是你要做的:
    首先,确认模块是否加载:
    insmod ipip.o
    insmod new_tunnel.o
    然后,在A网络的路由器上输入:
    ifconfig tunl0 10.0.1.1 pointopoint 172.19.20.21
    route add -net 10.0.2.0 netmask 255.255.255.0 dev tunl0
    并且在B网络的路由器上输入:
    ifconfig tunl0 10.0.2.1 pointopoint 172.16.17.18
    route add -net 10.0.1.0 netmask 255.255.255.0 dev tunl0
    如果你想中止隧道,输入:
    ifconfig tunl0 down
    简单之极!但是你不能通过IP-in-IP隧道转发广播或者IPv6数据包.你只是连接
    了两个一般情况下无法直接通讯的IPv4网络而已.至于兼容性,这部分代码已
    经有很长一段历史了,它的兼容性可以上溯到1.3版的内核.据我所知,Linux
    的IP-in-IP 隧道不能与其他操作系统或路由器互相通讯.它很简单,也很有效.
    需要它的时候尽管使用,否则就使用GRE.
    5.3. GRE 隧道
    GRE是最初由CISCO开发出来的隧道协议,能够做一些IP-in-IP隧道做不到的
    事情.比如,你可以使用GRE隧道传输多播数据包和IPv6数据包.在Linux下,
    你需要ip_gre.o模块.
    5.3.1. IPv4隧道
    让我们先来做一做IPv4隧道:
    比如说你有3个网络:内部网A和B,中间网C(比如说:Internet).A网络的情
    况:
    网络地址
    1
    0.0.1.0
    子网掩码
    2
    55.255.255.0
    路由器
    1
    0.0.1.1
    16
    路由器在C网络上的地址是172.16.17.18.我们称之为neta.
    B网络的情况:
    网络地址
    1
    0.0.2.0
    子网掩码
    2
    55.255.255.0
    路由器
    1
    0.0.2.1
    路由器在C网络上的IP地址是 172.19.20.21.我们称之为netb.
    已知C网络已经连通,我们假定它会将所有的数据包从A传到B,反之亦然.
    至于原因,我们不考虑.
    在A网络的路由器上,输入:
    ip tunnel add netb mode gre remote 172.19.20.21 local 172.16.17.18 ttl 255
    ip link set netb up
    ip addr add 10.0.1.1 dev netb
    ip route add 10.0.2.0/24 dev netb
    让我们稍微讨论一下.第1行,我们添加了一个隧道设备,并且称之为netb(为
    了能够表示出这个隧道通向哪里).并且表示要使用GRE协议 (mode gre),对端地
    址是172.19.20.21(另一端的路由器),我们的隧道数据包发源于172.16.17.18(以便
    当你的路由器在C网络中拥有多个地址的时候,你可以指定哪一个应用于隧道)
    并且包的TTL字段应设置为255(ttl 255).
    第2行,启用该隧道.
    第3行,我们给这个新生的网卡配置了一个IP:10.0.1.1.对于小网络来说足够
    了,但如果你网络中的隧道多得象无证运营的小煤窑一样,你可能就要考虑给你
    的隧道规划一个单独的IP地址范围(在本例中,你可以使用10.0.3.0).
    第4行,我们为B网络设置了一条路由.注意子网掩码的另一种表示方法.如
    果你不熟悉这种表示,我就来解释一下:你把你的子网掩码写成二进制形式,数
    数里面由多少个1.如果你连这个也不会做,不妨就简单地记住:255.0.0.0 就是
    /8,255.255.0.0 就是 /16, 255.255.255.0 就是 /24.
    让我们再看看B网络的路由器.
    ip tunnel add neta mode gre remote 172.16.17.18 local 172.19.20.21 ttl 255
    ip link set neta up
    ip addr add 10.0.2.1 dev neta
    ip route add 10.0.1.0/24 dev neta
    如果你想从A路由器中停止隧道,输入:
    ip link set netb down
    ip tunnel del netb
    当然,你可以把netb换成neta,在B路由器上操作.
    17
    5.3.2. IPv6隧道
    关于IPv6地址,请参看第6章第1节.
    这就开始吧.
    我们假设你有如下的IPv6网络,你想把它连接到6bone或者一个朋友那里.
    Network 3ffe:406:5:1:5:a:2:1/96
    你的IPv4地址是172.16.17.18,6bone 路由器的IPv4地址是172.22.23.24.
    ip tunnel add sixbone mode sit remote 172.22.23.24 local 172.16.17.18 ttl 255
    ip link set sixbone up
    ip addr add 3ffe:406:5:1:5:a:2:1/96 dev sixbone
    ip route add 3ffe::/15 dev sixbone
    让我们来讨论一下.我们创建了一个叫做sixbone的隧道设备.我们设置它的模
    式是sit(也就是在IPv4隧道中使用IPv6)并且告诉它对端(remote)和本端 (local)
    在哪里.TTL设置为最大,255.接着,我们激活了这个设备(up).然后,我们
    添加了我们自己的网络地址,并添加了一条通过隧道去往3ffe::/15 (现在全部属
    于6bone)的路由.
    GRE隧道是现在最受欢迎的隧道技术.它也广泛地应用于Linux世界之外并成
    为一个标准,是个好东西.
    5.4. 用户级隧道
    在内核之外,还有很多实现隧道的方法,最闻名的当然要数PPP和PPTP,但实
    际上还有很多(有些是专有的,有些是安全的,有些甚至根本不用IP),但那远远
    超出了本HOWTO所涉及的范围.
    18
    第6章 用Cisco和6bone实现IPv6
    隧道
    Marco Davids marco@sara.nl 著
    NOTE to maintainer:
    As far as I am concerned, this IPv6-IPv4 tunneling is not per definition GRE
    tunneling. You could tunnel IPv6 over IPv4 by means of GRE tunnel devices (GRE
    tunnels ANY to IPv4), but the device used here ("sit") only tunnels IPv6 over IPv4
    and is therefore something different.
    6.1. IPv6隧道
    这是Linux隧道能力的另一个应用.这在IPv6的早期实现中非常流行.下面动
    手试验的例子当然不是实现IPv6隧道的唯一方法.然而,它却是在Linux与支
    持IPv6的CISCO路由器之间搭建隧道的常用方法,经验证明多数人都是照这样
    做的.八成也适合于你 .
    简单谈谈IPv6地址:
    相对于IPv4地址而言, IPv6地址非常大,有128bit而不是32bit.这让我们得到
    了我们需要的东西——非常非常多的IP地址.确切地说,有
    340,282,266,920,938,463,463,374,607,431,768,211,465个.同时,IPv6(或者叫Ipng,
    下一代IP)还能让Internet上的骨干路由器的路由表变得更小,设备的配置更简
    单,IP层的安全性更好以及更好地支持QoS.
    例如: 2002:836b:9820:0000:0000:0000:836b:9886
    写下一个IPv6地址确实是件麻烦事.所以我们可以使用如下规则来进行简化 :
    数字打头的零不要写,就像IPv4一样.
    每16bit或者两个字节之间使用冒号分隔.
    当出现很多连续的零时可简写成"::".在一个地址中只能使用一次.
    例如:地址2002:836b:9820:0000:0000:0000:836b:9886可以写成:
    2002:836b:9820::836b:9886,看上去更简单些.
    另一个例子:地址3ffe:0000:0000:0000:0000:0020:34A1:F32C可以写成
    3ffe::20:34A1:F32C,要短得多.
    19
    IPv6将可能取代现有的IPv4.因为它采用了相对更新的技术,所以现在还没有
    全球范围的IPv6网络.为了能够平滑地过渡,引入了6bone计划.
    IPv6网络中的站点通过现有的IPv4体系互联,把IPv6数据包封装在IPv4数据
    包中进行传输.
    这就是为什么引入隧道机制的原因.
    为了能够使用IPv6,我们需要一个能够支持它的内核.现在有很多文档都很好
    地说明了这个问题.不外乎以下几步:
    找到一个新版的Linux发行版,要有合适的glibc库.
    找到一份最新的内核源代码.
    都准备好了以后,就可以继续编译一个带IPv6支持的内核了:
    cd /usr/src/linux
    make menuconfig
    选择"Networking Options"
    选择"The IPv6 protocol","IPv6: enable EUI-64 token format", "IPv6:
    disable provider based addresses"
    提示:不要编译成内核模块,那样经常会出问题.换句话说,就是把IPv6内置
    入内核.
    然后你就可以象往常一样保存配置并编译内核了.
    提示:在编译之前,可以修改一下Makefile,把EXTRAVERSION = -x变成
    EXTRAVERSION = -x-IPv6
    有很多文档都很好地说明了如何编译并安装一个内核,我们这篇文档不是讨论这
    个问题的.如果你在这个过程中出现了问题,请参阅合适的资料.你可以先看看
    /usr/src/linux/README.
    当你完成之后,用新的内核重启系统,你可以输入"/sbin/ifconfig -a"看看有没
    有新的"sit0-device"设备.SIT的意思是"简单Internet过渡"(Simple Internet
    Transition).如果到这里没有问题,你就可以奖励自己了,你已经向着下一代IP
    网络迈进了一大步.
    现在继续下一步.你需要把你的主机,或甚至整个局域网连接到另外一个IPv6
    网络上.这个网络很可能是"6bone",它就是为了这个特定的目的而专门设立的.
    让我们假定你有如下IPv6网络: 3ffe:604:6:8::/64,并且希望连接到6bone,或者
    其他地方.请注意,/64这个子网声明的意义与IPv4相同.
    你的IPv4地址是145.100.24.181,6bone的路由器的IPv4地址是145.100.1.5.
    # ip tunnel add sixbone mode sit remote 145.100.1.5 [local 145.100.24.181 ttl 255]
    # ip link set sixbone up
    # ip addr add 3FFE:604:6:7::2/126 dev sixbone
    # ip route add 3ffe::0/16 dev sixbone
    20
    /proc/sys/net/ipv6/conf/all/forwarding
    # /usr/local/sbin/radvd
    下面的一行,radvd是一个类似于zebra的路由公告守护程序,用来支持IPv6的
    自动配置特性.如果感兴趣的话就用你最喜欢的搜索引擎找一找.你可以检查一
    下:
    # /sbin/ip -f inet6 addr
    如果你的Linux网关支持IPv6且运行了radvd,在局域网上启动后,你就可以享
    受IPv6的自动配置特性了:
    # /sbin/ip -f inet6 addr
    1: lo: mtu 3924 qdisc noqueue inet6 ::1/128 scope host
    3: eth0: mtu 1500 qdisc pfifo_fast qlen 100
    inet6 3ffe:604:6:8:5054:4cff:fe01:e3d6/64 scope global dynamic
    valid_lft forever preferred_lft 604646sec inet6 fe80::5054:4cff:fe01:e3d6/10
    scope link
    你可以继续进行了,为IPv6配置你的bind.与A记录等价的,支持IPv6的记录
    类型是"AAAA".与in-addr.arpa等价的是"ip6.int".这方面可以找到很多信息.
    支持IPv6的应用系统曾在增加,包括ssh,telnet,inetd,Mozilla浏览器,Apache
    WEB浏览器…….但那些都不是这个路由文档所应该涉及的.
    作为Cisco系统,应该这样配置:
    !
    interface Tunnel1
    description IPv6 tunnel
    no ip address
    no ip directed-broadcast
    ipv6 address 3FFE:604:6:7::1/126
    tunnel source Serial0
    tunnel destination 145.100.24.181
    tunnel mode ipv6ip
    !
    ipv6 route 3FFE:604:6:8::/64 Tunnel1
    但如果你没有Cisco作为disposal,试试Internet上的众多IPv6隧道提供者之一.
    他们愿意在他们的Cisco设备上为你额外创建一个隧道.大部分是友好的WEB
    界面.用你常用的搜索引擎搜索一下"ipv6 tunnel broker".
    21
    第7章 IPSEC:Internet上安全的IP
    现在,在Linux上有两种IPSEC可用.对于2.2和2.4,第一个比较正规的实现
    有FreeS/WAN.他们有一个官方站点和一个经常维护的非官方站点.出于多种
    原因,FreeS/WAN历来没有被合并到内核的主线上.最常提到的原因就是政治
    问题——违反了美国的密码产品扩散条例.所以它不会被集成到Linux内核中.
    另外,很多合作伙伴表明了他们对代码质量的忧虑.关于如何设置FreeS/WAN,
    有很多文档可以参考.
    Linux 2.5.47版内核里有一个内置的IPSEC实现,是由Alexey Kuznetsov和Dave
    Miller在USAGI IPv6 小组的启发下写成的.经过这次合并,James Morris的
    CrypoAPI也成了内核的一部分——它能够真正地加密.
    本HOWTO文档仅收录2.5以上版本内核的IPSEC.Linux 2.4内核的用户推荐使
    用FreeS/WAN.但是要注意,它的配置方法与内置IPSEC不同.
    2.5.49版内核的IPSEC不再需要任何补丁.
    我在这里收集了Alexey或Dave Miller发布的一些补丁.对于2.5.48版的内
    核,在报告BUG之前请确认已经打上了那些补丁!(迄今还没有2.5.49这方
    面的补丁).一些简单的用户级工具可以在这里 (编译好的可执行文件和手
    册)找到.编译这些用户级工具需要修改Makefiles指向你的2.5.x内核.这种
    情况可望很快解决.
    编译你的内核的时候,要确认已经打开CryptoAPI中的"PF_KEY","AH",
    "ESP"以及其他所有选项!netfilter中的TCP_MSS方法现在不能用,请关
    掉.
    本章的作者对于IPSEC完全是外行(nitwit)!如果你发现了错误,请email
    通知bert hubert .
    首先,我们展示一下如何在两个主机之间手动设置安全通讯.其间的大部分工作
    都可以自动完成,但是为了了解细节我们仍然用手动完成.
    如果你仅仅对自动密钥管理感兴趣,请跳过下面一节.但是要知道,了解手动密
    要管理是很有用的.
    7.1. 从手动密钥管理开始
    22
    IPSEC是一个复杂的主题.有很多在线信息可以查阅,这个HOWTO将集中讲
    解如何设置,运行并解释一些基本原理.
    很多iptables配置会丢弃IPSEC数据包!要想让IPSEC通过,运行
    iptables -A xxx -p 50 -j ACCEPT
    iptables -A xxx -p 51 -j ACCEPT
    IPSEC提供了一个安全的IP协议版本.所谓的"安全"意味着两件事情:加密
    与验证.如果认为安全仅仅就是加密哪就太天真了,很容易看出来那是不够的
    ——你的通讯过程或许是加密的,但是你如何保证与你通讯的对端就是你期望中
    的人呢?
    IPSEC使用ESP('Encapsulated Security Payload',安全载荷封装) 来支持加密,使用
    AH(Authentication Header,头部验证)来支持对端验证.你可以同时使用二者,
    也可以使用二者之一.
    ESP和AH都依靠SA(security associations,安全联盟)来工作.一个SA由一个源,
    一个目的和一个说明组成.一个验证SA看上去应该是:
    add 10.0.0.11 10.0.0.216 ah 15700 -A hmac-md5 "1234567890123456";
    意思是:"从10.0.0.11到10.0.0.216的数据包需要AH,使用HMAC-MD5签名,
    密码是1234567890123456".这个说明的SPI(Security Parameter Index,安全参数
    索引)号码是'15700',细节后面再谈.有意思的是一个会话的两端都使用相同的
    SA——它们是全等的,而不是镜像对称的.要注意的是,SA并没有自动翻转规
    则——这个SA仅仅描述了从10.0.0.11到10.0.0.216的认证.如果需要双向安全,
    需要2条SA.
    一个简单地ESP SA:
    add 10.0.0.11 10.0.0.216 esp 15701 -E 3des-cbc "123456789012123456789012";
    意思是:"从10.0.0.11到10.0.0.216的数据包需要ESP,使用3des-cbc加密算法,
    密码是123456789012123456789012".SPI号码是'15701'.
    到此,我们看到SA描述了所有的说明,但它并没有描述应该什么时候用到这些
    策略.实际上,可以有很多完全相同的SA,除了它们之间的SPI不同.顺便提
    一句,SPI的意思是Security Parameter Index(安全参数索引).为了进行加密操作,
    我们需要声明一个策略.这个策略可以包含诸如"如果可能的话使用ipsec"或
    者"除非我们有ipsec否则丢弃数据包"这样的东西.
    最简单的典型SP(Security Policy,安全策略)应该是这样:
    spdadd 10.0.0.216 10.0.0.11 any -P out ipsec /
    esp/transport//require /
    ah/transport//require;
    如果在10.0.0.216上输入这个,就意味着凡是去往10.0.0.11的数据包必须经过加
    密并附带AH头验证.要注意,它并没有指出使用哪一个SA,那是留给内核来
    23
    10.0.0.216: icmp: echo reply
    注意,可看出从10.0.0.11返回的包的确是明码传输.Ping的进一步细节用tcpdump
    当然看不到,但是它还是显示了用来告诉10.0.0.11如何进行解密和验证的参数
    ——AH和ESP的SPI值.
    还有几件事情必须提及.上面给出的配置在很多IPSEC的配置范例中都有引用,
    但确实是很危险的.问题就在于上述配置包含了10.0.0.216应该如何处理发往
    10.0.0.11的包,和10.0.0.1如何解释那些包,但是却没有指出10.0.0.11应当丢弃
    来自10.0.0.216的未进行加密及验证的包!
    任何人都可以插入完全未加密的欺骗包,而10.0.0.11会不假思索地接受.为了
    弥补上述漏洞我们必须在10.0.0.11上增加一个针对进入数据包的SP,如下:
    #!/sbin/setkey -f
    spdadd 10.0.0.216 10.0.0.11 any -P IN ipsec
    esp/transport//require
    ah/transport//require;
    这就指明了10.0.0.11收到来自10.0.0.216的包的时候需要正确的ESP和AH处理.
    现在,我们完成这个配置,我们当然也希望回去的数据包也进行加密和头验证.
    10.0.0.216上完整的配置应该是:
    24
    #!/sbin/setkey -f
    flush;
    spdflush;
    # AH
    add 10.0.0.11 10.0.0.216 ah 15700 -A hmac-md5 "1234567890123456";
    add 10.0.0.216 10.0.0.11 ah 24500 -A hmac-md5 "1234567890123456";
    # ESP
    add 10.0.0.11 10.0.0.216 esp 15701 -E 3des-cbc "123456789012123456789012";
    add 10.0.0.216 10.0.0.11 esp 24501 -E 3des-cbc "123456789012123456789012";
    spdadd 10.0.0.216 10.0.0.11 any -P out ipsec
    esp/transport//require
    ah/transport//require;
    spdadd 10.0.0.11 10.0.0.216 any -P in ipsec
    esp/transport//require
    ah/transport//require;
    10.0.0.11上:
    #!/sbin/setkey -f
    flush;
    spdflush;
    # AH
    add 10.0.0.11 10.0.0.216 ah 15700 -A hmac-md5 "1234567890123456";
    add 10.0.0.216 10.0.0.11 ah 24500 -A hmac-md5 "1234567890123456";
    # ESP
    add 10.0.0.11 10.0.0.216 esp 15701 -E 3des-cbc "123456789012123456789012";
    add 10.0.0.216 10.0.0.11 esp 24501 -E 3des-cbc "123456789012123456789012";
    spdadd 10.0.0.11 10.0.0.216 any -P out ipsec
    esp/transport//require
    ah/transport//require;
    spdadd 10.0.0.216 10.0.0.11 any -P in ipsec
    esp/transport//require
    ah/transport//require;
    注意,本例中通信双方的加密密钥是一样的.这在实际中是不会出现的.
    为了检测一下我们刚才的配置,运行一下:
    setkey -D
    就会显示出SA,或者用
    setkey -DP
    显示出SP.
    7.2. 自动密钥管理
    25
    在上一节中,使用了简单共享的密码进行加密.换句话说,为了保密,我们必须
    通过一个安全的通道把加密配置传给对方.如果我们使用telnet配置对端的机器,
    任何一个第三方都可能知道我们的共享密码,那么设置就是不安全的.
    而且,因为密码是共享的,所以它就不成为真正意义的密码.就算对端不能用它
    做什么,但我们需要为每一个要进行IPSEC通讯的对方都设置互补相同的密码.
    这需要生成大量的密钥,如果有10个成员需要通讯,就至少需要50个不同的密
    码.
    除了对称密钥的问题之外,还有解决密钥轮转的问题.如果第三方搜集到足够的
    数据包,就有可能反向计算出密钥.这可以通过每隔一段时间换用一个新密钥来
    解决,但是这必须自动完成.
    另一个问题是,如上所述的手动密钥管理,我们必须精确地指定算法和密钥长度,
    与对端的协调也是个大问题.我们渴望能够用一种比较宽松的方式来描述密钥策
    略,比如说:"我们可以用3DES或者Blowfish算法,密钥长度至少是多少多少
    位".
    为了满足这些要求,IPSEC提供了IKE(Internet Key Exchange,Internet密钥交换)
    来自动随机生成密钥,并使用协商好的非对称加密算法进行密钥交换.
    Linux 2.5的IPSEC实现利用KAME的"racoon"IKE守护程序来进行.截止到
    11月9日,在Alexey的iptools发布包中的racoon是可以编译的,但是需要从两
    个文件中删除#include .你也可以下载我提供的编译好的版本.
    IKE需要使用UDP的500端口,确认你的iptables不会挡住数据包.
    7.2.1. 理论
    象前面所解释的自动密钥管理会为我们做很多事情.特别地,它会自动地动态生
    成SA.不象大家所以为的那样,它并不会为我们设置SP.
    所以,如果想使用IKE,需要设置好SP,但不要设置任何SA.内核如果发现有
    一个IPSEC的SP,但不存在任何相应的SA,就会通知IKE守护程序,让它去
    协商.
    重申,一个SP决定了我们需要什么;而SA决定了我们如何得到它.使用自动
    密钥管理就可以让我们只关心需要什么就够了.
    7.2.2. 举例
    Kame的racoon有非常多的选项,其中绝大部分都已经配置好了缺省值,所以我
    们不用修改它们.象上面描述的,管理员需要定义一个SP而不配置SA,留给
    IKE守护程序去协商.
    在这个例子中,仍然是10.0.0.11和10.0.0.216之间需要配置安全通讯,但这次要
    26
    借助于racoon.为了简单起见,这个配置使用预先共享的密钥(又是可怕的共享
    密钥).X.509证书的问题单独讨论,参见后面的7.2.3.
    我们尽量保持缺省配置,两台机器是一样的:
    path pre_shared_key "/usr/local/etc/racoon/psk.txt";
    remote anonymous
    {
    exchange_mode aggressive,main;
    doi ipsec_doi;
    situation identity_only;
    my_identifier address;
    lifetime time 2 min; # sec,min,hour
    initial_contact on;
    proposal_check obey; #
    obey, strict or claim
    proposal {
    encryption_algorithm 3des;
    hash_algorithm sha1;
    authentication_method pre_shared_key;
    dh_group 2 ;
    }
    }
    sainfo anonymous
    {
    pfs_group 1;
    lifetime time 2 min;
    encryption_algorithm 3des ;
    authentication_algorithm hmac_sha1;
    compression_algorithm deflate ;
    }
    有很多设置,我认为仍然有很多可以去掉而更接近缺省配置.很少有值得注意的
    事情.我们已经配置了两个匿名配置支持所有的对端机器,让将来的配置简单些.
    这里没有必要为每台机器各写一个段落,除非真的必要.
    此外,我们还设置了我们基于我们的IP地址来识别我们自己('my_identifier
    address'),并声明我们可以进行3DES,sha1,并且我们将使用预先共享的密钥,
    写在psk.txt中.
    在psk.txt中,我们设置两个条目,两台机器上都不一样.
    在10.0.0.11上:
    10.0.0.216
    password2
    在10.0.0.216上:
    10.0.0.11
    password2
    确认这些文件必须为root所有,属性是0600,否则racoon将不信任其内容.两
    个机器上的这个文件是镜像对称的.
    现在,我们就剩下设置SP了,这比较简单.
    在10.0.0.216上:
    27
    #!/sbin/setkey -f
    flush;
    spdflush;
    spdadd 10.0.0.216 10.0.0.11 any -P out ipsec
    esp/transport//require;
    spdadd 10.0.0.11 10.0.0.216 any -P in ipsec
    esp/transport//require;
    在10.0.0.11上:
    #!/sbin/setkey -f
    flush;
    spdflush;
    spdadd 10.0.0.11 10.0.0.216 any -P out ipsec
    esp/transport//require;
    spdadd 10.0.0.216 10.0.0.11 any -P in ipsec
    esp/transport//require;
    请注意这些SP的镜像对称规律.
    我们可以启动racoon了!一旦启动,当我们试图从10.0.0.11到10.0.0.216进行
    telnet的时候,racoon就会开始协商:
    12:18:44: INFO: isakmp.c:1689:isakmp_post_acquire(): IPsec-SA
    request for 10.0.0.11 queued due to no phase1 found.
    12:18:44: INFO: isakmp.c:794:isakmp_ph1begin_i(): initiate new
    phase 1 negotiation: 10.0.0.216[500]10.0.0.11[500]
    12:18:44: INFO: isakmp.c:799:isakmp_ph1begin_i(): begin Aggressive mode.
    12:18:44: INFO: vendorid.c:128:check_vendorid(): received Vendor ID:
    KAME/racoon
    12:18:44: NOTIFY: oakley.c:2037:oakley_skeyid(): couldn't find
    the proper pskey, try to get one by the peer's address.
    12:18:44: INFO: isakmp.c:2417:log_ph1established(): ISAKMP-SA
    established 10.0.0.216[500]-10.0.0.11[500] spi:044d25dede78a4d1:ff01e5b4804f0680
    12:18:45: INFO: isakmp.c:938:isakmp_ph2begin_i(): initiate new phase 2
    negotiation: 10.0.0.216[0]10.0.0.11 spi=15863890(0xf21052)
    如果我们现在运行setkey -D列出SA,就会发现已经存在了:
    10.0.0.216 10.0.0.11
    esp mode=transport spi=224162611(0x0d5c7333) reqid=0(0x00000000)
    E: 3des-cbc 5d421c1b d33b2a9f 4e9055e3 857db9fc 211d9c95 ebaead04
    A: hmac-sha1 c5537d66 f3c5d869 bd736ae2 08d22133 27f7aa99
    seq=0x00000000 replay=4 flags=0x00000000 state=mature
    created: Nov 11 12:28:45 2002
    current: Nov 11 12:29:16 2002
    diff: 31(s)
    hard: 600(s)
    soft: 480(s)
    last: Nov 11 12:29:12 2002
    hard: 0(s)
    soft: 0(s)
    current: 304(bytes)
    hard: 0(bytes)
    soft: 0(bytes)
    28
    allocated: 3
    hard: 0
    soft: 0
    sadb_seq=1 pid=17112 refcnt=0
    10.0.0.11 10.0.0.216
    esp mode=transport spi=165123736(0x09d79698) reqid=0(0x00000000)
    E: 3des-cbc d7af8466 acd4f14c 872c5443 ec45a719 d4b3fde1 8d239d6a
    A: hmac-sha1 41ccc388 4568ac49 19e4e024 628e240c 141ffe2f
    seq=0x00000000 replay=4 flags=0x00000000 state=mature
    created: Nov 11 12:28:45 2002
    current: Nov 11 12:29:16 2002
    diff: 31(s)
    hard: 600(s)
    soft: 480(s)
    last:
    hard: 0(s)
    soft: 0(s)
    current: 231(bytes)
    hard: 0(bytes)
    soft: 0(bytes)
    allocated: 2
    hard: 0
    soft: 0
    sadb_seq=0 pid=17112 refcnt=0
    我们的SP是如下配置的:
    10.0.0.11[any] 10.0.0.216[any] tcp
    in ipsec
    esp/transport//require
    created:Nov 11 12:28:28 2002 lastused:Nov 11 12:29:12 2002
    lifetime:0(s) validtime:0(s)
    spid=3616 seq=5 pid=17134
    refcnt=3
    10.0.0.216[any] 10.0.0.11[any] tcp
    out ipsec
    esp/transport//require
    created:Nov 11 12:28:28 2002 lastused:Nov 11 12:28:44 2002
    lifetime:0(s) validtime:0(s)
    spid=3609 seq=4 pid=17134
    refcnt=3
    7.2.2.1. 问题和常见的疏忽
    如果不工作,检查一下所有的配置文件是不是为root所有,而且只有root才能
    读取.如想前台启动racoon,就加上"-F"参数.如想强制它读取某一个配置文
    件来取代缺省配置文件,使用参数"-f".如想看到超级详细的细节,往racoon.conf
    中加入"log debug;"一行.
    7.2.3. 使用X.509证书进行自动密钥管理
    如前所述,之所以共享密码很困难,是因为它们一旦共享,就不再成为真正意义
    的密码.幸运的是,我们仍可以用非对称加密技术来解决这个问题.
    如果IPSEC的每个参与者都生成一对公钥和私钥,就可以让双方公开它们的公
    29
    钥并设置策略,从而建立安全连接.
    虽然需要一些计算,但生成密钥还是相对比较简单的.以下都是基于openssl工
    具实现的.
    7.2.3.1. 为你的主机生成一个X.509证书
    OpenSSL搭好了很多基础结构,以便我们能够使用经过或者没有经过CA签署的
    密钥.现在,我们就围绕这些基础结构,并练习一下使用著名的Snake Oil安全,
    而不是使用CA.
    首先,我们为主机laptop发起一个"证书请求":
    $ openssl req -new -nodes -newkey rsa:1024 -sha1 -keyform PEM -keyout /
    laptop.private -outform PEM -out request.pem
    这是可能问我们的问题:
    Country Name (2 letter code) [AU]:NL
    State or Province Name (full name) [Some-State]:.
    Locality Name (eg, city) []:Delft
    Organization Name (eg, company) [Internet Widgits Pty Ltd]:Linux Advanced
    Routing & Traffic Control
    Organizational Unit Name (eg, section) []:laptop
    Common Name (eg, YOUR name) []:bert hubert
    Email Address []:ahu@ds9a.nl
    Please enter the following 'extra' attributes
    to be sent with your certificate request
    A challenge password []:
    An optional company name []:
    请你根据自己的实际情况完整填写.你可以把你的主机名写进去,也可以不写,
    取决于你的安全需求.这个例子中,我们写了.
    我们现在自己签署这个请求:
    $ openssl x509 -req -in request.pem -signkey laptop.private -out /
    laptop.public
    Signature ok
    subject=/C=NL/L=Delft/O=Linux Advanced Routing & Traffic /
    Control/OU=laptop/CN=bert hubert/Email=ahu@ds9a.nl
    Getting Private key
    现在,"request.pem"这个文件已经没用了,可以删除.
    在你需要证书的每台机器上都重复上述过程.你现在就可以放心地发布你的
    "*.public"文件了,但是一定要保证"*.private"是保密的!
    7.2.3.2. 设置并启动
    我们一旦拥有了一对公钥和私钥,就可以告诉racoon去使用它们了.
    现在我们回到上面的配置中的两台机器,10.0.0.11 (upstairs)和10.0.0.216(laptop).
    在10.0.0.11上的racoon.conf中,我们添加:
    30
    path certificate "/usr/local/etc/racoon/certs";
    remote 10.0.0.216
    {
    exchange_mode aggressive,main;
    my_identifier asn1dn;
    peers_identifier asn1dn;
    certificate_type x509 "upstairs.public" "upstairs.private";
    peers_certfile "laptop.public";
    proposal {
    encryption_algorithm 3des;
    hash_algorithm sha1;
    authentication_method rsasig;
    dh_group 2 ;
    }
    }
    它们告诉racoon:证书可以在/usr/local/etc/racoon/certs/那里找到.而且还包含了
    专门为10.0.0.216而写的配置项.
    包含"asn1dn"的行告诉racoon,本端和对端的标识都从公钥中提取.也就是上
    面输出的"subject=/C=NL/L=Delft/O=Linux Advanced Routing & Traffic
    Control/OU=laptop/CN=bert hubert/Email=ahu@ds9a.nl".
    "certificate_type"那一行配置了本地的公钥和私钥."peers_certfile"这行告诉
    racoon读取名叫"laptop.public"的文件取得对端的公钥.
    "proposal"这一段与你以前看到的基本一致,除了"authentication_method"
    的值变成了"rsasig",意思是使用RSA 公钥/私钥对.
    在10.0.0.216上面的配置文件与上面的是完全镜像关系,没有其它改变:
    path certificate "/usr/local/etc/racoon/certs";
    remote 10.0.0.11
    {
    exchange_mode aggressive,main;
    my_identifier asn1dn;
    peers_identifier asn1dn;
    certificate_type x509 "laptop.public" "laptop.private";
    peers_certfile "upstairs.public";
    proposal {
    encryption_algorithm 3des;
    hash_algorithm sha1;
    authentication_method rsasig;
    dh_group 2 ;
    }
    }
    现在,我们已经把两台机器的配置文件改好了,然后就应该把证书文件拷贝到正
    确的位置."upstairs"这台机器需要往/usr/local/etc/racoon/certs中放入
    upstairs.private,upstairs.public和laptop.public.请确认这个目录属于root,且属
    性为0600,否则racoon会拒绝使用!
    "laptop"这台机器需要往/usr/local/etc/racoon/certs 中放入laptop.private,
    laptop.public和upstairs.public.也就是说,每台机器都需要本端的公钥和私钥,
    31
    以及对端的公钥.
    确认一下已经写好了SP(执行在7.2.2中提到的spdadd).然后启动racoon,就应
    该可以工作了.
    7.2.3.3. 如何安全地建立隧道
    为了与对端建立安全的通讯,我们必须交换公钥.公钥没必要保密,重要的是要
    保证它不被替换.换句话说,要确保没有"中间人".
    为了简化这个工作,OpenSSL提供了"digest"命令:
    $ openssl dgst upstairs.public
    MD5(upstairs.public)= 78a3bddafb4d681c1ca8ed4d23da4ff1
    现在我们要做的就是检验一下对方是否能够得到相同的MD5散列值.这可以通
    过真实地接触来完成,也可以通过电话,但是一定不要与公钥放在同一封电子邮
    件里发送!
    另一个办法是通过一个可信的第三方(CA)来实现.这个CA会为你的密钥进
    行签名,而不是象上面那样由我们自己签名.
    7.3. IPSEC隧道
    迄今为止,我们只是认识了IPSEC的"transport"(透明)模式,也就是通讯的两
    端都能够直接理解IPSEC.这不能代表所有的情况,有时候我们只需要路由器理
    解IPSEC,路由器后面的机器利用它们进行通讯.这就是所谓的"tunnel mode"
    (隧道模式).
    设置这个极其简单.如果想通过10.0.0.216与10.0.0.11建立的隧道来传输从
    10.0.0.0/24到130.161.0.0/16的数据包,按下面配置就可以:
    #!/sbin/setkey -f
    flush;
    spdflush;
    add 10.0.0.216 10.0.0.11 esp 34501
    -m tunnel
    -E 3des-cbc "123456789012123456789012";
    spdadd 10.0.0.0/24 130.161.0.0/16 any -P out ipsec
    esp/tunnel/10.0.0.216-10.0.0.11/require;
    注意."-m tunnel"是关键!这里首先配置了一个隧道两端(10.0.0.216与10.0.0.11)
    使用ESP的SA.
    然后配置了实际的隧道.它指示内核,对于从10.0.0.0/24到130.161.0.0/16的数
    据包需要加密.而且这些数据包被发往10.0.0.11.
    10.0.0.11也需要相同的配置:
    #!/sbin/setkey -f
    flush;
    32
    spdflush;
    add 10.0.0.216 10.0.0.11 esp 34501
    -m tunnel
    -E 3des-cbc "123456789012123456789012";
    spdadd 10.0.0.0/24 130.161.0.0/16 any -P in ipsec
    esp/tunnel/10.0.0.216-10.0.0.11/require;
    注意,与上面基本一样,除了把"-P out"换成了"-P in".就象先前的例子一样,
    我们只配置了单向的传输.完整地实现双向传输就留给读者自己研究实现吧.
    这种配置的另一个更直观的名字叫做"ESP代理(proxy ESP)".
    IPSEC隧道需要内核能够进行IP转发!
    7.4. 其它IPSEC软件
    Thomas Walpuski报告说它已经写了一个补丁,可以让OpenBSD的isakpmd与
    Linux 2.5的IPSEC协同工作.可以在这个网页找到.他指出isakpmd在Linux
    上仅仅需要libkeynote支持.根据他的说法,在Linux 2.5.59上面工作得很好.
    isakpmd与前面提到的racoon由很大的不同,但是很多人喜欢用它,可以在这里
    找到.这里有OpenBSD CVS.Thomas还为那些不习惯用CVS或者patch的人
    们制作了一个tarball.
    7.5. IPSEC与其它系统的互操作
    求助: Write this
    7.5.1. Windows
    33
    /proc/sys/net/ipv4/ip_forward
    在这里,你可能想知道是否起了作用.所以我们ping一下缺省组224.0.0.1,看
    看有没有人在.在你的LAN上所有配置并启用了多播的机器都应该予以回应,
    其他机器则不会.但你会注意到,没有任何一台机器回应的时候声明自己是
    224.0.0.1,多么令人惊奇 !因为这是一个组地址(对于接收者来说是"广播"),
    所以组中的所有成员都用它们的地址来回应,而不是用组地址来回应.
    ping -c 2 224.0.0.1
    34
    到此,你已经可以实现真正的多播路由了.好的,假定你需要在两个网络间进行
    路由.
    (To Be Continued!)
    35
    byte/s
    9.1. 解释队列和队列规定
    利用队列,我们决定了数据被发送的方式.必须认识到,我们只能对发送数据进
    行整形.
    根据Internet的工作方式,我们无法直接控制别人向我们发送什么数据.有点象
    我们家里的信报箱,你不可能控制全世界,联系每一个人,修改别人对你发送邮
    件的数量.
    然而,Internet主要依靠TCP/IP,它的一些特性很有用.因为TCP/IP没办法知道
    两个主机之间的网络容量,所以它会试图越来越快地发送数据(所谓的"慢起技
    术") ,当因为网络容量不够而开始丢失数据时,再放慢速度.实际情况要比这
    种方法更聪明,我们以后再讨论.
    这就象当你尚未读完一半邮件时,希望别人停止给你寄信.与现实世界不同,在
    Internet上可以做到这一点.(译注:这个例子并不恰当,TCP/IP的这种机制并不
    是在网络层实现的,而是要靠传输层的TCP协议)
    如果你有一个路由器,并且希望能够防止某些主机下载速度太快,你需要在你路
    由器的内网卡——也就是向你的网内主机发送数据包的网卡——上进行流量整
    形.
    你还要保证你正在控制的是瓶颈环节.如果你有一个100M以太网卡,而你的路
    由器的链路速度是256k,你必须保证你发送的数据量没有超过路由器的处理能
    力.否则,就是路由器在控制链路和对带宽进行整形,而不是你.可以说,我们
    需要拥有的队列必须是一系列链路中最慢的环节.幸运的是这很容易.
    36
    9.2. 简单的无类队列规定
    如前所述,利用队列,我们决定了数据被发送的方式.无类队列规定就是那样,
    能够接受数据和重新编排,延迟或丢弃数据包.
    这可以用作对于整个网卡的流量进行整形,而不细分各种情况.在我们进一步学
    习分类的队列规定之前,理解这部分是必不可少的!
    最广泛应用的规定是pfifo_fast队列规定,因为它是缺省配置.这也解释了为什
    么其它那些复杂的功能为何如此健壮,因为那些都与缺省配置相似,只不过是其
    他类型的队列而已.
    每种队列都有它们各自的优势和弱点.
    9.2.1. pfifo_fast
    这个队列的特点就象它的名字——先进先出(FIFO),也就是说没有任何数据包
    被特殊对待.至少不是非常特殊.这个队列有3个所谓的"频道".FIFO规则应
    用于每一个频道.并且:如果在0频道有数据包等待发送,1频道的包就不会被
    处理,1频道和2频道之间的关系也是如此.
    内核遵照数据包的TOS标记,把带有"最小延迟"标记的包放进0频道.
    不要把这个无类的简单队列规定与分类的PRIO相混淆!虽然它们的行为有些类
    似,但对于无类的pfifo_fast而言,你不能使用tc命令向其中添加其它的队列规
    定.
    9.2.1.1. 参数与使用
    pfifo_fast队列规定作为硬性的缺省设置,你不能对它进行配置.它缺省是这样
    配置的:
    priomap:
    内核规定,根据数据包的优先权情况,对应相应的频道.这个对应是根据
    数据包的TOS字节进行的.TOS看上去是这样的:
    0 1 2 3 4 5 6 7
    +-----+-----+-----+-----+-----+-----+-----+-----+
    | | | |
    | 优先权 | TOS | MBZ |
    | | | |
    +-----+-----+-----+-----+-----+-----+-----+-----+
    TOS字段的4个bit是如下定义的:
    二进制 十进制 意义
    -----------------------------------------
    1000 8 最小延迟 (md)
    0100 4 最大throughput (mt)
    0010 2 最大可靠性 (mr)
    37
    7),它们不对应TOS映射,但是有其它的意图.
    下表来自RFC 1349,告诉你应用程序可能如何设置它们的TOS:
    TELNET 1000 (minimize delay)
    控制 1000 (minimize delay) FTP
    数据 0100 (maximize throughput)
    TFTP 1000 (minimize delay)
    命令阶段 1000 (minimize delay) SMTP
    数据阶段 0100 (maximize throughput)
    UDP 查询 1000 (minimize delay)
    TCP 查询 0000
    Domain Name Service
    区域传输 0100 (maximize throughput)
    NNTP 0001 (minimize monetary cost)
    报错 0000
    请求 0000 (mostly)
    ICMP
    响应 (mostly)
    txqueuelen
    38
    队列的长度来自网卡的配置,你可以用ifconfig和ip命令修改.如设置队
    列长度为10,执行:ifconfig eth0 txqueuelen 10
    你不能用tc命令设置这个!
    9.2.2. 令牌桶过滤器(TBF)
    令牌桶过滤器(TBF)是一个简单的队列规定:只允许以不超过事先设定的速率到
    来的数据包通过,但可能允许短暂突发流量朝过设定值.
    TBF很精确,对于网络和处理器的影响都很小.所以如果您想对一个网卡限速,
    它应该成为您的第一选择!
    TBF的实现在于一个缓冲器(桶),不断地被一些叫做"令牌"的虚拟数据以特定
    速率填充着. (token rate).桶最重要的参数就是它的大小,也就是它能够存储
    令牌的数量.
    每个到来的令牌从数据队列中收集一个数据包,然后从桶中被删除.这个算法关
    联到两个流上——令牌流和数据流,于是我们得到3种情景:
    数据流以等于令牌流的速率到达TBF.这种情况下,每个到来的数据包都
    能对应一个令牌,然后无延迟地通过队列.
    数据流以小于令牌流的速度到达TBF.通过队列的数据包只消耗了一部分
    令牌,剩下的令牌会在桶里积累下来,直到桶被装满.剩下的令牌可以在
    需要以高于令牌流速率发送数据流的时候消耗掉,这种情况下会发生突发
    传输.
    数据流以大于令牌流的速率到达TBF.这意味着桶里的令牌很快就会被耗
    尽.导致TBF中断一段时间,称为"越限".如果数据包持续到来,将发
    生丢包.
    最后一种情景非常重要,因为它可以用来对数据通过过滤器的速率进行整形.
    令牌的积累可以导致越限的数据进行短时间的突发传输而不必丢包,但是持续越
    限的话会导致传输延迟直至丢包.
    请注意,实际的实现是针对数据的字节数进行的,而不是针对数据包进行的.
    9.2.2.1. 参数与使用
    即使如此,你还是可能需要进行修改,TBF提供了一些可调控的参数.第一个参
    数永远可用:
    limit/latency
    limit确定最多有多少数据(字节数)在队列中等待可用令牌.你也可以
    通过设置latency参数来指定这个参数,latency参数确定了一个包在TBF
    中等待传输的最长等待时间.后者计算决定桶的大小,速率和峰值速率.
    39
    burst/buffer/maxburst
    桶的大小,以字节计.这个参数指定了最多可以有多少个令牌能够即刻被
    使用.通常,管理的带宽越大,需要的缓冲器就越大.在Intel体系上,
    10兆bit/s的速率需要至少10k字节的缓冲区才能达到期望的速率.
    如果你的缓冲区太小,就会导致到达的令牌没有地方放(桶满了),这会
    导致潜在的丢包.
    mpu
    一个零长度的包并不是不耗费带宽.比如以太网,数据帧不会小于64字
    节.Mpu(Minimum Packet Unit,最小分组单位)决定了令牌的最低消耗.
    rate
    速度操纵杆.参见上面的limits!
    如果桶里存在令牌而且允许没有令牌,相当于不限制速率(缺省情况).If the
    bucket contains tokens and is allowed to empty, by default it does so at infinite speed.
    如果不希望这样,可以调整入下参数:
    peakrate
    如果有可用的令牌,数据包一旦到来就会立刻被发送出去,就象光速一样.
    那可能并不是你希望的,特别是你有一个比较大的桶的时候.
    峰值速率可以用来指定令牌以多块的速度被删除.用书面语言来说,就是:
    释放一个数据包,但后等待足够的时间后再释放下一个.我们通过计算等
    待时间来控制峰值速率
    然而,由于UNIX定时器的分辨率是10毫秒,如果平均包长10k bit,我
    们的峰值速率被限制在了1Mbps.
    mtu/minburst
    但是如果你的常规速率比较高,1Mbps的峰值速率对我们就没有什么价
    值.要实现更高的峰值速率,可以在一个时钟周期内发送多个数据包.最
    有效的办法就是:再创建一个令牌桶!
    这第二个令牌桶缺省情况下为一个单个的数据包,并非一个真正的桶.
    要计算峰值速率,用mtu乘以100就行了. (应该说是乘以HZ数,Intel
    体系上是100,Alpha体系上是1024)
    9.2.2.2. 配置范例
    这是一个非常简单而实用的例子:
    # tc qdisc add dev ppp0 root tbf rate 220kbit latency 50ms burst 1540
    为什么它很实用呢?如果你有一个队列较长的网络设备,比如DSL modem或者
    cable modem什么的,并通过一个快速设备(如以太网卡)与之相连,你会发现上
    40
    载数据绝对会破坏交互性.
    这是因为上载数据会充满modem的队列,而这个队列为了改善上载数据的吞吐
    量而设置的特别大.但这并不是你需要的,你可能为了提高交互性而需要一个不
    太大的队列.也就是说你希望在发送数据的时候干点别的事情.
    上面的一行命令并非直接影响了modem中的队列,而是通过控制Linux中的队
    列而放慢了发送数据的速度.
    把220kbit修改为你实际的上载速度再减去几个百分点.如果你的modem确实很
    快,就把"burst"值提高一点.
    9.2.3. 随机公平队列(SFQ)
    SFQ(Stochastic Fairness Queueing,随机公平队列)是公平队列算法家族中的一个
    简单实现.它的精确性不如其它的方法,但是它在实现高度公平的同时,需要的
    计算量却很少.
    SFQ的关键词是"会话"(或称作"流") ,主要针对一个TCP会话或者UDP
    流.流量被分成相当多数量的FIFO队列中,每个队列对应一个会话.数据按照
    简单轮转的方式发送, 每个会话都按顺序得到发送机会.
    这种方式非常公平,保证了每一个会话都不会没其它会话所淹没.SFQ之所以被
    称为"随机",是因为它并不是真的为每一个会话创建一个队列,而是使用一个
    散列算法,把所有的会话映射到有限的几个队列中去.
    因为使用了散列,所以可能多个会话分配在同一个队列里,从而需要共享发包的
    机会,也就是共享带宽.为了不让这种效应太明显,SFQ会频繁地改变散列算法,
    以便把这种效应控制在几秒钟之内.
    有很重要的一点需要声明:只有当你的出口网卡确实已经挤满了的时候,SFQ才
    会起作用!否则在你的Linux机器中根本就不会有队列,SFQ也就不会起作用.
    稍后我们会描述如何把SFQ与其它的队列规定结合在一起,以保证两种情况下
    都比较好的结果.
    特别地,在你使用DSL modem或者cable modem的以太网卡上设置SFQ而不进
    行任何进一步地流量整形是无谋的!
    9.2.3.1. 参数与使用
    SFQ基本上不需要手工调整:
    perturb
    多少秒后重新配置一次散列算法.如果取消设置,散列算法将永远不会重
    新配置(不建议这样做).10秒应该是一个合适的值.
    quantum
    41
    一个流至少要传输多少字节后才切换到下一个队列.却省设置为一个最大
    包的长度(MTU的大小).不要设置这个数值低于MTU!
    9.2.3.2. 配置范例
    如果你有一个网卡,它的链路速度与实际可用速率一致——比如一个电话
    MODEM——如下配置可以提高公平性:
    # tc qdisc add dev ppp0 root sfq perturb 10
    # tc -s -d qdisc ls
    qdisc sfq 800c: dev ppp0 quantum 1514b limit 128p flows 128/1024 perturb 10sec
    Sent 4812 bytes 62 pkts (dropped 0, overlimits 0)
    "800c:"这个号码是系统自动分配的一个句柄号,"limit"意思是这个队列中可
    以有128个数据包排队等待.一共可以有1024个散列目标可以用于速率审计,
    而其中128个可以同时激活.(no more packets fit in the queue!)每隔10秒种散列
    算法更换一次.
    9.3. 关于什么时候用哪种队列的建议
    总之,我们有几种简单的队列,分别使用排序,限速和丢包等手段来进行流量整
    形.
    下列提示可以帮你决定使用哪一种队列.涉及到了第14章 所描述的的一些队列
    规定:
    如果想单纯地降低出口速率,使用令牌桶过滤器.调整桶的配置后可用于
    控制很高的带宽.
    如果你的链路已经塞满了,而你想保证不会有某一个会话独占出口带宽,
    使用随机公平队列.
    如果你有一个很大的骨干带宽,并且了解了相关技术后,可以考虑前向随
    机丢包(参见"高级"那一章).
    如果希望对入口流量进行"整形"(不是转发流量),可使用入口流量策略,
    注意,这不是真正的"整形".
    如果你正在转发数据包,在数据流出的网卡上应用TBF.除非你希望让数
    据包从多个网卡流出,也就是说入口网卡起决定性作用的时候,还是使用
    入口策略.
    如果你并不希望进行流量整形,只是想看看你的网卡是否有比较高的负载
    而需要使用队列,使用pfifo队列(不是pfifo_fast).它缺乏内部频道但是
    可以统计backlog.
    最后,你可以进行所谓的"社交整形".你不能通过技术手段解决一切问
    题.用户的经验技巧永远是不友善的.正确而友好的措辞可能帮助你的正
    确地分配带宽!
    42
    9.4. 术语
    为了正确地理解更多的复杂配置,有必要先解释一些概念.由于这个主题的历史
    不长和其本身的复杂性,人们经常在说同一件事的时候使用各种词汇.
    以下来自draft-ietf-diffserv-model-06.txt,Diffserv路由器的建议管理模型. 可以
    在以下地址找到:
    http://www.ietf.org/internet-drafts/draft-ietf-diffserv-model-06.txt.
    关于这些词语的严格定义请参考这个文档.
    队列规定
    管理设备输入(ingress)或输出(egress)的一个算法.
    无类的队列规定
    一个内部不包含可配置子类的队列规定.
    分类的队列规定
    一个分类的队列规定内可一包含更多的类.其中每个类又进一步地包含一
    个队列规定,这个队列规定可以是分类的,也可以是无类的.根据这个定
    义,严格地说pfifo_fast算是分类的,因为它实际上包含3个频道(实际上
    可以认为是子类).然而从用户的角度来看它是无类的,因为其内部的子
    类无法用tc工具进行配置.

    一个分类的队列规定可以拥有很多类,类内包含队列规定.
    分类器
    每个分类的队列规定都需要决定什么样的包使用什么类进行发送.分类器
    就是做这个用的.
    过滤器
    分类是通过过滤器完成的.一个过滤器包含若干的匹配条件,如果符合匹
    配条件,就按此过滤器分类.
    调度
    在分类器的帮助下,一个队列规定可以裁定某些数据包可以排在其他数据
    包之前发送.这种处理叫做"调度",比如此前提到的pfifo_fast就是这样
    的.调度也可以叫做"重排序",但这样容易混乱.
    整形
    在一个数据包发送之前进行适当的延迟,以免超过事先规定好的最大速
    率,这种处理叫做"整形".整形在egress处进行.习惯上,通过丢包来
    降速也经常被称为整形.
    43

    | 队列规定 /__队列规定4__/ |
    | /-队列规定N_/ |
    | |
    +------------------------------------------------------------+
    感谢Jamal Hadi Salim制作的ASCII字符图像.
    整个大方框表示内核.最左面的箭头表示从网络上进入机器的数据包.它们进入
    Ingress队列规定,并有可能被某些过滤器丢弃.即所谓策略.
    这些是很早就发生的(在进入内核更深的部分之前).这样早地丢弃数据有利于
    节省CPU时间.
    数据包顺利通过的话,如果它是发往本地进程的,就会进入IP协议栈处理并提
    交给该进程.如果它需要转发而不是进入本地进程,就会发往egress.本地进程
    也可以发送数据,交给Egress分类器.
    然后经过审查,并放入若干队列规定中的一个进行排队.这个过程叫做"入队".
    在不进行任何配置的情况下,只有一个egress队列规定——pfifo_fast——总是接
    收数据包.
    数据包进入队列后,就等待内核处理并通过某网卡发送.这个过程叫做"出队".
    44
    这张图仅仅表示了机器上只有一块网卡的情况,图中的箭头不能代表所有情况.
    每块网卡都有它自己的ingress和egress.
    9.5. 分类的队列规定
    如果你有多种数据流需要进行区别对待,分类的队列规定就非常有用了.众多分
    类的队列规定中的一种——CBQ(Class Based Queueing,基于类的队列)——经常
    被提起,以至于造成大家认为CBQ就是鉴别队列是否分类的标准,这是不对的.
    CBQ不过是家族中最大的孩子而已,同时也是最复杂的.它并不能为你做所有
    的事情.对于某些人而言这有些不可思议,因为他们受"sendmail效应"影响较
    深,总是认为只要是复杂的并且没有文档的技术肯定是最好的.
    9.5.1. 分类的队列规定及其类中的数据流向
    一旦数据包进入一个分类的队列规定,它就得被送到某一个类中——也就是需要
    分类.对数据包进行分类的工具是过滤器.一定要记住:"分类器"是从队列规
    定内部调用的,而不是从别处.
    过滤器会返回一个决定,队列规定就根据这个决定把数据包送入相应的类进行排
    队.每个子类都可以再次使用它们的过滤器进行进一步的分类.直到不需要进一
    步分类时,数据包才进入该类包含的队列规定排队.
    除了能够包含其它队列规定之外,绝大多数分类的队列规定黑能够流量整形.这
    对于需要同时进行调度(如使用SFQ)和流量控制的场合非常有用.如果你用一个
    高速网卡(比如以太网卡)连接一个低速设备(比如cable modem或者ADSL modem)
    时,也可以应用.
    如果你仅仅使用SFQ,那什么用也没有.因为数据包进,出路由器时没有任何延
    迟.虽然你的输出网卡远远快于实际连接速率,但路由器中却没有队列可以调度.
    9.5.2. 队列规定家族:根,句柄,兄弟和父辈
    每块网卡都有一个出口"根队列规定",缺省情况下是前面提到的pfifo_fast队列
    规定.每个队列规定都指定一个句柄,以便以后的配置语句能够引用这个队列规
    定.除了出口队列规定之外,每块网卡还有一个入口,以便policies进入的数据流.
    队列规定的句柄有两个部分:一个主号码和一个次号码.习惯上把根队列规定称
    为"1:",等价于"1:0".队列规定的次号码永远是0.
    类的主号码必须与它们父辈的主号码一致.
    9.5.2.1. 如何用过滤器进行分类
    下图给出一个典型的分层关系:
    45
    12:2
    也就是说,根所附带的一个过滤器要求把数据包直接交给12:2.
    9.5.2.2. 数据包如何出队并交给硬件
    当内核决定把一个数据包发给网卡的时候,根队列规定1:会得到一个出队请求,
    然后把它传给1:1,然后依次传给10:,11:和12:,which each query their siblings,
    然后试图从它们中进行dequeue()操作.也就是说,内和需要遍历整颗树,因为
    只有12:2中才有这个数据包.
    换句话说,类及其兄弟仅仅与其"父队列规定"进行交谈,而不会与网卡进行交
    谈.只有根队列规定才能由内核进行出队操作!
    更进一步,任何类的出队操作都不会比它们的父类更快.这恰恰是你所需要的:
    我们可以把SFQ作为一个子类,放到一个可以进行流量整形的父类中,从而能
    够同时得到SFQ的调度功能和其父类的流量整形功能.
    9.5.3. PRIO队列规定
    PRIO队列规定并不进行整形,它仅仅根据你配置的过滤器把流量进一步细分.
    你可以认为PRIO队列规定是pfifo_fast的一种衍生物,区别在每个频道都是一
    个单独的类,而非简单的FIFO.
    当数据包进入PRIO队列规定后,将根据你给定的过滤器设置选择一个类.缺省
    情况下有三个类,这些类仅包含纯FIFO队列规定而没有更多的内部结构.你可
    以把它们替换成你需要的任何队列规定.
    每当有一个数据包需要出队时,首先处理:1类.只有当标号更小的类中没有需要
    处理的包时,才会标号大的类.
    46
    当你希望不仅仅依靠包的TOS,而是想使用tc所提供的更强大的功能来进行数
    据包的优先权划分时,可以使用这个队列规定.它也可以包含更多的队列规定,
    而pfifo_fast却只能包含简单的fifo队列规定.
    因为它不进行整形,所以使用时与SFQ有相同的考虑:要么确保这个网卡的带
    宽确实已经占满,要么把它包含在一个能够整形的分类的队列规定的内部.后者
    几乎涵盖了所有cable modems和DSL设备.
    严格地说,PRIO队列规定是一种Work-Conserving调度.
    9.5.3.1. PRIO的参数与使用
    tc识别下列参数:
    bands
    创建频道的数目.每个频道实际上就是一个类.如果你修改了这个数值,
    你必须同时修改:
    priomap
    如果你不给tc提供任何过滤器,PRIO队列规定将参考TC_PRIO的优先
    级来决定如何给数据包入队.
    它的行为就像前面提到过的pfifo_fast队列规定,关于细节参考前面章节.
    频道是类,缺省情况下命名为主标号:1到主标号:3.如果你的PRIO队列规定是
    12:,把数据包过滤到12:1将得到最高优先级.
    注意:0频道的次标号是1!1频道的次标号是2,以此类推.
    9.5.3.2. 配置范例
    我们想创建这个树:
    root 1: prio
    / | /
    1:1 1:2 1:3
    | | |
    10: 20: 30:
    sfq tbf sfq
    band 0 1 2
    大批量数据使用30:,交互数据使用20:或10:.
    命令如下:
    # tc qdisc add dev eth0 root handle 1: prio
    ## 这个命令立即创建了类: 1:1, 1:2, 1:3
    # tc qdisc add dev eth0 parent 1:1 handle 10: sfq
    # tc qdisc add dev eth0 parent 1:2 handle 20: tbf rate 20kbit buffer 1600 limit 3000
    # tc qdisc add dev eth0 parent 1:3 handle 30: sfq
    现在,我们看看结果如何:
    47
    # tc -s qdisc ls dev eth0
    qdisc sfq 30: quantum 1514b
    Sent 0 bytes 0 pkts (dropped 0, overlimits 0)
    qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms
    Sent 0 bytes 0 pkts (dropped 0, overlimits 0)
    qdisc sfq 10: quantum 1514b
    Sent 132 bytes 2 pkts (dropped 0, overlimits 0)
    qdisc prio 1: bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
    Sent 174 bytes 3 pkts (dropped 0, overlimits 0)
    如你所见,0频道已经有了一些流量,运行这个命令之后发送了一个包!
    现在我们来点大批量数据传输(使用能够正确设置TOS标记的工具):
    # scp tc ahu@10.0.0.11:./
    ahu@10.0.0.11's password:
    tc 100% |*****************************| 353 KB 00:00
    # tc -s qdisc ls dev eth0
    qdisc sfq 30: quantum 1514b
    Sent 384228 bytes 274 pkts (dropped 0, overlimits 0)
    qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms
    Sent 2640 bytes 20 pkts (dropped 0, overlimits 0)
    qdisc sfq 10: quantum 1514b
    Sent 2230 bytes 31 pkts (dropped 0, overlimits 0)
    qdisc prio 1: bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
    Sent 389140 bytes 326 pkts (dropped 0, overlimits 0)
    如你所见,所有的流量都是经过30:处理的,优先权最低.现在我们验证一下交
    互数据传输经过更高优先级的频道,我们生成一些交互数据传输:
    # tc -s qdisc ls dev eth0
    qdisc sfq 30: quantum 1514b
    Sent 384228 bytes 274 pkts (dropped 0, overlimits 0)
    qdisc tbf 20: rate 20Kbit burst 1599b lat 667.6ms
    Sent 2640 bytes 20 pkts (dropped 0, overlimits 0)
    qdisc sfq 10: quantum 1514b
    Sent 14926 bytes 193 pkts (dropped 0, overlimits 0)
    qdisc prio 1: bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
    Sent 401836 bytes 488 pkts (dropped 0, overlimits 0)
    正常——所有额外的流量都是经10:这个更高优先级的队列规定处理的.与先前
    的整个scp不同,没有数据经过最低优先级的队列规定.
    9.5.4. 著名的CBQ队列规定
    如前所述,CBQ是最复杂,最琐碎,最难以理解,最刁钻的队列规定.这并不
    是因为其作者的恶毒或者不称职,而是因为CBQ算法本身的不精确,而且与
    Linux的内在机制不协调造成的.
    48
    除了可以分类之外,CBQ也是一个整形器,但是从表面上看来工作得并不好.
    它应该是这样的:如果你试图把一个10Mbps的连接整形成1Mbps的速率,就应
    该让链路90%的时间处于闲置状态,必要的话我们就强制,以保证90%的闲置
    时间.
    但闲置时间的测量非常困难,所以CBQ就采用了它一个近似值——来自硬件层
    的两个传输请求之间的毫秒数——来代替它.这个参数可以近似地表征这个链路
    的繁忙程度.
    这样做相当慎重,而且不一定能够得到正确的结论.比如,由于驱动程序方面或
    者其它原因造成一块网卡的实际传输速率不能够达到它的标称速率,该怎么办?
    由于总线设计的原因,PCMCIA网卡永远也不会达到100Mbps.那么我们该怎么
    计算闲置时间呢?
    如果我们引入非物理网卡——像PPPoE,PPTP——情况会变得更糟糕.因为相
    当一部分有效带宽耗费在了链路维护上.
    那些实现了测量的人们都发现CBQ总不是非常精确甚至完全失去了其本来意
    义.
    但是,在很多场合下它还是能够很好地工作.根据下面的文档,你应该能够较好
    地配置CBQ来解决答多数问题.
    9.5.4.1. CBQ整形的细节
    如前所述,CBQ的工作机制是确认链路的闲置时间足够长,以达到降低链路实
    际带宽的目的.为此,它要计算两个数据包的平均发送间隔.
    操作期间,有效闲置时间的测量使用EWMA(exponential weighted moving average,
    指数加权移动均值)算法,也就是说最近处理的数据包的权值比以前的数据包按
    指数增加.UNIX的平均负载也是这样算出来的.
    计算出来的平均时间值减去EWMA测量值,得出的结果叫做"avgidle".最佳的
    链路负载情况下,这个值应当是0:数据包严格按照计算出来的时间间隔到来.
    在一个过载的链路上,avgidle值应当是负的.如果这个负值太严重,CBQ就会
    暂时禁止发包,称为"overlimit"(越限).
    相反地,一个闲置的链路应该有很大的avgidle值,这样闲置几个小时后,会造成
    链路允许非常大的带宽通过.为了避免这种局面,我们用maxidle来限制avgidle
    的值不能太大.
    理论上讲,如果发生越限,CBQ就会禁止发包一段时间(长度就是事先计算出来
    的传输数据包之间的时间间隔),然后通过一个数据包后再次禁止发包.但是最
    好参照一下下面的minburst参数.
    下面是配置整形时需要指定的一些参数:
    avpkt
    平均包大小,以字节计.计算maxidle时需要,maxidle从maxburst得出.
    49
    bandwidth
    网卡的物理带宽,用来计算闲置时间.
    cell
    一个数据包被发送出去的时间可以是基于包长度而阶梯增长的.一个800
    字节的包和一个806字节的包可以认为耗费相同的时间.也就是说它设置
    时间粒度.通常设置为8,必须是2的整数次幂.
    maxburst
    这个参数的值决定了计算maxidle所使用的数据包的个数.在avgidle跌
    落到0之前,这么多的数据包可以突发传输出去.这个值越高,越能够容
    纳突发传输.你无法直接设置maxidle的值,必须通过这个参数来控制.
    minburst
    如前所述,发生越限时CBQ会禁止发包.实现这个的理想方案是根据事
    先计算出的闲置时间进行延迟之后,发一个数据包.然而,UNIX的内核
    一般来说都有一个固定的调度周期(一般不大于10ms),所以最好是这样:
    禁止发包的时间稍长一些,然后突发性地传输minburst个数据包,而不是
    一个一个地传输.等待的时间叫做offtime.
    从大的时间尺度上说,minburst值越大,整形越精确.但是,从毫秒级的时
    间尺度上说,就会有越多的突发传输.
    minidle
    如果avgidle值降到0,也就是发生了越限,就需要等待,直到avgidle的
    值足够大才发送数据包.为避免因关闭链路太久而引起的以外突发传输,
    在avgidle的值太低的时候会被强制设置为minidle的值.
    参数minidle的值是以负微秒记的.所以10代表avgidle被限制在-10us
    上.
    mpu
    最小包尺寸——因为即使是0长度的数据包,在以太网上也要生成封装成
    64字节的帧,而需要一定时间去传输.为了精确计算闲置时间,CBQ需
    要知道这个值.
    rate
    期望中的传输速率.也就是"油门"!
    在CBQ的内部由很多的微调参数.比如,那些已知队列中没有数据的类就不参
    加计算,越限的类将被惩罚性地降低优先级等等.都非常巧妙和复杂.
    9.5.4.2. CBQ在分类方面的行为
    除了使用上述idletime近似值进行整形之外,CBQ还可以象PRIO队列那样,把
    50
    各种类赋予不同的优先级,优先权数值小的类会比优先权值大的类被优先处理.
    每当网卡请求把数据包发送到网络上时,都会开始一个WRR(weighted round
    robin,加权轮转)过程,从优先权值小的类开始.
    那些队列中有数据的类就会被分组并被请求出队.在一个类收到允许若干字节数
    据出队的请求之后,再尝试下一个相同优先权值的类.
    下面是控制WRR过程的一些参数:
    allot
    当从外部请求一个CBQ发包的时候,它就会按照"priority"参数指定的
    顺序轮流尝试其内部的每一个类的队列规定.当轮到一个类发数据时,它
    只能发送一定量的数据."allot"参数就是这个量的基值.更多细节请参
    照"weight"参数.
    prio
    CBQ可以象PRIO设备那样工作.其中"prio"值较低的类只要有数据就
    必须先服务,其他类要延后处理.
    weight
    "weight"参数控制WRR过程.每个类都轮流取得发包的机会.如果其
    中一个类要求的带宽显著地高于其他的类,就应该让它每次比其他的类发
    送更多的数据.
    CBQ会把一个类下面所有的weight值加起来后归一化,所以数值可以任
    意定,只要保持比例合适就可以.人们常把"速率/10"作为参数的值来
    使用,实际工作得很好.归一化值后的值乘以"allot"参数后,决定了每
    次传输多少数据.
    请注意,在一个CBQ内部所有的类都必须使用一致的主号码!
    9.5.4.3. 决定链路的共享和借用的CBQ参数
    除了纯粹地对某种数据流进行限速之外,CBQ还可以指定哪些类可以向其它哪
    些类借用或者出借一部分带宽.
    Isolated/sharing
    凡是使用"isolated"选项配置的类,就不会向其兄弟类出借带宽.如果你
    的链路上同时存在着竞争对手或者不友好的其它人,你就可以使用这个选
    项.
    选项"sharing"是"isolated"的反义选项.
    bounded/borrow
    一个类也可以用"bounded"选项配置,意味着它不会向其兄弟类借用带
    宽.选项"borrow"是"bounded"的反义选项.
    51
    一个典型的情况就是你的一个链路上有多个客户都设置成了"isolated"和
    "bounded",那就是说他们都被限制在其要求的速率之下,且互相之间不会借用
    带宽.
    在这样的一个类的内部的子类之间是可以互相借用带宽的.
    9.5.4.4. 配置范例
    这个配置把WEB服务器的流量控制为5Mbps,SMTP流量控制在3Mbps上.而
    且二者一共不得超过6Mbps,互相之间允许借用带宽.我们的网卡是100Mbps
    的.
    # tc qdisc add dev eth0 root handle 1:0 cbq bandwidth 100Mbit /
    avpkt 1000 cell 8
    # tc class add dev eth0 parent 1:0 classid 1:1 cbq bandwidth 100Mbit /
    rate 6Mbit weight 0.6Mbit prio 8 allot 1514 cell 8 maxburst 20 /
    avpkt 1000 bounded
    这部分按惯例设置了根为1:0,并且绑定了类1:1.也就是说整个带宽不能超过
    6Mbps.
    如前所述,CBQ需要调整很多的参数.其实所有的参数上面都解释过了.相应
    的HTB配置则要简明得多.
    # tc class add dev eth0 parent 1:1 classid 1:3 cbq bandwidth 100Mbit /
    rate 5Mbit weight 0.5Mbit prio 5 allot 1514 cell 8 maxburst 20 /
    avpkt 1000
    # tc class add dev eth0 parent 1:1 classid 1:4 cbq bandwidth 100Mbit /
    rate 3Mbit weight 0.3Mbit prio 5 allot 1514 cell 8 maxburst 20 /
    avpkt 1000
    我们建立了2个类.注意我们如何根据带宽来调整weight参数的.两个类都没
    有配置成"bounded",但它们都连接到了类1:1上,而1:1设置了"bounded".
    所以两个类的总带宽不会超过6Mbps.别忘了,同一个CBQ下面的子类的主号
    码都必须与CBQ自己的号码相一致!
    # tc qdisc add dev eth0 parent 1:3 handle 30: sfq
    # tc qdisc add dev eth0 parent 1:4 handle 40: sfq
    缺省情况下,两个类都有一个FIFO队列规定.但是我们把它换成SFQ队列,以
    保证每个数据流都公平对待.
    # tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip /
    sport 80 0xffff flowid 1:3
    # tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 match ip /
    sport 25 0xffff flowid 1:4
    这些命令规定了根上的过滤器,保证数据流被送到正确的队列规定中去.
    注意:我们先使用了"tc class add" 在一个队列规定中创建了类,然后使用"tc
    qdisc add"在类中创建队列规定.
    你可能想知道,那些没有被那两条规则分类的数据流怎样处理了呢?从这个例子
    来说,它们被1:0直接处理,没有限制.
    如果SMTP+web的总带宽需求大于6Mbps,那么这6M带宽将按照两个类的
    52
    weight参数的比例情况进行分割:WEB服务器得到5/8的带宽,SMTP得到3/8
    的带宽.
    从这个例子来说,你也可以这么认为:WEB数据流总是会得到
    5/8*6Mbps=3.75Mbps的带宽.
    9.5.4.5. 其它CBQ参数:split和defmap
    如前所述,一个分类的队列规定需要调用过滤器来决定一个数据包应该发往哪个
    类去排队.
    除了调用过滤器,CBQ还提供了其他方式,defmap和split.很难掌握,但好在
    无关大局.但是现在是解释defmap和split的最佳时机,我会尽力解释.
    因为你经常是仅仅需要根据TOS来进行分类,所以提供了一种特殊的语法.当
    CBQ需要决定了数据包要在哪里入队时,要检查这个节点是否为"split节点".
    如果是,子队列规定中的一个应该指出它接收所有带有某种优先权值的数据包,
    权值可以来自TOS字段或者应用程序设置的套接字选项.
    数据包的优先权位与defmap字段的值进行"或"运算来决定是否存在这样的匹
    配.换句话说,这是一个可以快捷创建仅仅匹配某种优先权值数据包的过滤器的
    方法.如果defmap等于0xff,就会匹配所有包,0则是不匹配.这个简单的配
    置可以帮助理解:
    # tc qdisc add dev eth1 root handle 1: cbq bandwidth 10Mbit allot 1514 /
    cell 8 avpkt 1000 mpu 64
    # tc class add dev eth1 parent 1:0 classid 1:1 cbq bandwidth 10Mbit /
    rate 10Mbit allot 1514 cell 8 weight 1Mbit prio 8 maxburst 20 /
    avpkt 1000
    一个标准的CBQ前导.
    Defmap参照TC_PRIO位(我从来不直接使用数字!):
    TC_PRIO.. Num 对应 TOS
    -------------------------------------------------
    BESTEFFORT 0 最高可靠性
    FILLER 1 最低成本
    BULK 2 最大吞吐量(0x8)
    INTERACTIVE_BULK 4
    INTERACTIVE 6 最小延迟(0x10)
    CONTROL 7
    TC_PRIO..的数值对应它右面的bit.关于TOS位如何换算成优先权值的细节可
    以参照pfifo_fast有关章节.
    然后是交互和大吞吐量的类:
    # tc class add dev eth1 parent 1:1 classid 1:2 cbq bandwidth 10Mbit /
    rate 1Mbit allot 1514 cell 8 weight 100Kbit prio 3 maxburst 20 /
    avpkt 1000 split 1:0 defmap c0
    # tc class add dev eth1 parent 1:1 classid 1:3 cbq bandwidth 10Mbit /
    rate 8Mbit allot 1514 cell 8 weight 800Kbit prio 7 maxburst 20 /
    avpkt 1000 split 1:0 defmap 3f
    53
    "split队列规定"是1:0,也就是做出选择的地方.c0是二进制的11000000,3F
    是00111111,所以它们共同匹配所有的数据包.第一个类匹配第7和第6位,也
    就是负责"交互"和"控制"的数据包.第二个类匹配其余的数据包.
    节点1:0现在应该有了这样一个表格:
    priority send to
    0 1:3
    1 1:3
    2 1:3
    3 1:3
    4 1:3
    5 1:3
    6 1:2
    7 1:2
    为了更有趣,你还可以传递一个"change掩码",确切地指出你想改变哪个优先
    权值.你只有在使用了"tc class change"的时候才需要.比如,往1:2中添加best
    effort数据流,应该执行:
    # tc class change dev eth1 classid 1:2 cbq defmap 01/01
    现在,1:0上的优先权分布应该是:
    priority send to
    0 1:2
    1 1:3
    2 1:3
    3 1:3
    4 1:3
    5 1:3
    6 1:2
    7 1:2
    求助: 尚未测试过"tc class change",资料上这么写的.
    9.5.5. HTB(Hierarchical Token Bucket, 分层的令牌桶)
    Martin Devera ()正确地意识到CBQ太复杂,而且并没有按照多数常见情
    况进行优化.他的Hierarchical能够很好地满足这样一种情况:你有一个固定速
    率的链路,希望分割给多种不同的用途使用.为每种用途做出带宽承诺并实现定
    量的带宽借用.
    HTB就象CBQ一样工作,但是并不靠计算闲置时间来整形.它是一个分类的令
    牌桶过滤器.它只有很少的参数,并且在它的网站能够找到很好的文档.
    随着你的HTB配置越来越复杂,你的配置工作也会变得复杂.但是使用CBQ的
    话,即使在很简单的情况下配置也会非常复杂!HTB3 (关于它的版本情况,请参
    阅它的网站)已经成了官方内核的一部分(2.4.20-pre1,2.5.31及其后).然而,你
    可能仍然要为你的tc命令打上HTB3支持补丁,否则你的tc命令不理解HTB3.
    如果你已经有了一个新版内核或者已经打了补丁,请尽量考虑使用HTB.
    54
    9.5.5.1. 配置范例
    环境与要求与上述CBQ的例子一样.
    # tc qdisc add dev eth0 root handle 1: htb default 30
    # tc class add dev eth0 parent 1: classid 1:1 htb rate 6mbit burst 15k
    # tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit burst 15k
    # tc class add dev eth0 parent 1:1 classid 1:20 htb rate 3mbit ceil 6mbit burst 15k
    # tc class add dev eth0 parent 1:1 classid 1:30 htb rate 1kbit ceil 6mbit burst 15k
    作者建议2在那些类的下方放置SFQ:
    # tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10
    # tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10
    # tc qdisc add dev eth0 parent 1:30 handle 30: sfq perturb 10
    添加过滤器,直接把流量导向相应的类:
    # U32="tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32"
    # $U32 match ip dport 80 0xffff flowid 1:10
    # $U32 match ip sport 25 0xffff flowid 1:20
    这就完了——没有没见过的或者没解释过的数字,没有不明意义的参数.
    HTB完成得相当不错——如果10:和20:都得到了保证的速率,剩下的就是分割
    了,它们借用的比率是5:3,正如你其网的那样.
    未被分类的流量被送到了30:,仅有一点点带宽,但是却可以任意借用剩下的带
    宽.因为我们内部使用了SFQ,而可以公平发包.
    9.6. 使用过滤器对数据包进行分类
    为了决定用哪个类处理数据包,必须调用所谓的"分类器链" 进行选择.这个
    链中包含了这个分类队列规定所需的所有过滤器.
    重复前面那棵树:
    根1:
    |
    _1:1_
    / | /
    / | /
    / | /
    10: 11: 12:
    / / / /
    10:1 10:2 12:1 12:2
    当一个数据包入队的时候,每一个分支处都会咨询过滤器链如何进行下一步.典
    型的配置是在1:1处有一个过滤器把数据包交给12:,然后12:处的过滤器在把包
    交给12:2.
    你可以把后一个过滤器同时放在1:1处,可因为…having more specific tests lower
    in the chain.…而得到效率的提高.
    55
    另外,你不能用过滤器把数据包向"上"送.而且,使用HTB的时候应该把所
    有的规则放到根上!
    再次强调:数据包只能向"下"进行入队操作!只有处队的时候才会上到网卡所
    在的位置来.他们不会落到树的最底层后送到网卡!
    9.6.1. 过滤器的一些简单范例
    就象在"分类器"那章所解释的,借助一些复杂的语法你可以详细地匹配任何事
    情.下面我们就开始,从简单地匹配一些比较明显的特征开始.
    比方说,我们有一个PRIO队列规定,叫做"10:",包含3个类,我们希望把去
    往22口的数据流发送到最优先的频道中去.应该这样设置过滤器:
    # tc filter add dev eth0 protocol ip parent 10: prio 1 u32 /
    match ip dport 22 0xffff flowid 10:1
    # tc filter add dev eth0 protocol ip parent 10: prio 1 u32 /
    match ip sport 80 0xffff flowid 10:1
    # tc filter add dev eth0 protocol ip parent 10: prio 2 flowid 10:2
    什么意思呢?是说:
    向eth0上的10:节点添加一个u32过滤规则,它的优先权是1:凡是去往22口(精
    确匹配)的IP数据包,发送到频道10:1.
    向eth0上的10:节点添加一个u32过滤规则,它的优先权是1:凡是来自80口(精
    确匹配)的IP数据包,发送到频道10:1.
    向eth0上的10:节点添加一个过滤规则,它的优先权是2:凡是上面未匹配的IP
    数据包,发送到频道10:2.
    别忘了添加"dev eth0"(你的网卡或许叫别的名字),因为每个网卡的句柄都有
    完全相同的命名空间.
    想通过IP地址进行筛选的话,这么敲:
    # tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 /
    match ip dst 4.3.2.1/32 flowid 10:1
    # tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 /
    match ip src 1.2.3.4/32 flowid 10:1
    # tc filter add dev eth0 protocol ip parent 10: prio 2 /
    flowid 10:2
    这个例子把去往4.3.2.1和来自1.2.3.4的数据包送到了最高优先的队列,其它的
    则送到次高权限的队列.
    你可以连续使用match,想匹配来自1.2.3.4的80口的数据包的话,就这么敲:
    56
    # tc filter add dev eth0 parent 10:0 protocol ip prio 1 u32 match ip src 4.3.2.1/32 match ip
    sport 80 0xffff flowid 10:1
    9.6.2. 常用到的过滤命令一览
    这里列出的绝大多数命令都根据这个命令改编而来:
    # tc filter add dev eth0 parent 1:0 protocol ip prio 1 u32 ……
    这些是所谓的"u32"匹配,可以匹配数据包的任意部分.
    根据源/目的地址
    源地址段 'match ip src 1.2.3.0/24'
    目的地址段 'match ip dst 4.3.2.0/24'
    单个IP地址使用"/32"作为掩码即可.
    根据源/目的端口,所有IP协议
    源 'match ip sport 80 0xffff'
    目的 'match ip dport 80 0xffff'
    根据IP协议 (tcp, udp, icmp, gre, ipsec)
    使用/etc/protocols所指定的数字.
    比如: icmp是1:'match ip protocol 1 0xff'.
    根据fwmark
    你可以使用ipchains/iptables给数据包做上标记,并且这个标记会在穿过
    网卡的路由过程中保留下来.如果你希望对来自eth0并从eth1发出的数
    据包做整形,这就很有用了.语法是这样的:
    t
    c filter add dev eth1 protocol ip parent 1:0 prio 1 handle 6 fw flowid 1:1
    注意,这不是一个u32匹配!
    你可以象这样给数据包打标记:
    #
    iptables -A PREROUTING -t mangle -i eth0 -j MARK --set-mark 6
    数字6是可以任意指定的.
    如果你不想去学习所有的tc语法,就可以与iptables结合,仅仅学习按
    fwmark匹配就行了.
    按TOS字段
    选择交互和最小延迟的数据流:
    #
    tc filter add dev ppp0 parent 1:0 protocol ip prio 10 u32 /
    57
    m
    atch ip tos 0x10 0xff flowid 1:4
    想匹配大量传输的话,使用"0x08 0xff".
    关于更多的过滤命令,请参照"高级过滤"那一章.
    9.7. IMQ(Intermediate queueing device,中介队列
    设备)
    中介队列设备不是一个队列规定,但它的使用与队列规定是紧密相连的.就Linux
    而言,队列规定是附带在网卡上的,所有在这个网卡上排队的数据都排进这个队
    列规定.根据这个概念,出现了两个局限:
    1. 只能进行出口整形(虽然也存在入口队列规定,但在上面实现分类的队列规定
    的可能性非常小).
    2. 一个队列规定只能处理一块网卡的流量,无法设置全局的限速.
    IMQ就是用来解决上述两个局限的.简单地说,你可以往一个队列规定中放任
    何东西.被打了特定标记的数据包在netfilter的NF_IP_PRE_ROUTING 和
    NF_IP_POST_ROUTING两个钩子函数处被拦截,并被送到一个队列规定中,该
    队列规定附加到一个IMQ设备上.对数据包打标记要用到iptables的一种处理方
    法.
    这样你就可以对刚刚进入网卡的数据包打上标记进行入口整形,或者把网卡们当
    成一个个的类来看待而进行全局整形设置.你还可以做很多事情,比如:把http
    流量放到一个队列规定中去,把新的连接请求放到一个队列规定中去,……
    9.7.1. 配置范例
    我们首先想到的是进行入口整形,以便让你自己得到高保证的带宽 .就象配置
    其它网卡一样:
    tc qdisc add dev imq0 root handle 1: htb default 20
    tc class add dev imq0 parent 1: classid 1:1 htb rate 2mbit burst 15k
    tc class add dev imq0 parent 1:1 classid 1:10 htb rate 1mbit
    tc class add dev imq0 parent 1:1 classid 1:20 htb rate 1mbit
    tc qdisc add dev imq0 parent 1:10 handle 10: pfifo
    tc qdisc add dev imq0 parent 1:20 handle 20: sfq
    tc filter add dev imq0 parent 10:0 protocol ip prio 1 u32 match /
    ip dst 10.0.0.230/32 flowid 1:10
    在这个例子中,使用了u32进行分类.其它的分类器应该也能实现.然后,被打
    上标记的包被送到imq0排队.
    iptables -t mangle -A PREROUTING -i eth0 -j IMQ --todev 0
    58
    ip link set imq0 up
    iptables的IMQ处理方法只能用在PREROUTING和POSTROUTING链的mangle
    表中.语法是:
    IMQ [ --todev n ]
    n: imq设备的编号
    注:ip6tables也提供了这种处理方法.
    请注意,如果数据流是事后才匹配到IMQ处理方法上的,数据就不会入队.数
    据流进入imq的确切位置取决于这个数据流究竟是流进的还是流出的.下面是
    netfilter(也就是iptables)在内核中预先定义优先级:
    enum nf_ip_hook_priorities {
    NF_IP_PRI_FIRST = INT_MIN,
    NF_IP_PRI_CONNTRACK = -200,
    NF_IP_PRI_MANGLE = -150,
    NF_IP_PRI_NAT_DST = -100,
    NF_IP_PRI_FILTER = 0,
    NF_IP_PRI_NAT_SRC = 100,
    NF_IP_PRI_LAST = INT_MAX,
    };
    对于流入的包,imq把自己注册为优先权等于NF_IP_PRI_MANGLE+1,也就是
    说数据包在经过了PREROUTING链的mangle表之后才进入imq设备.
    对于流出的包,imq使用优先权等于NF_IP_PRI_LAST,也就是说不会白白处理
    本应该被filter表丢弃的数据包.
    关于补丁和更多的文档请参阅imq网站.
    59
    第10章 多网卡的负载均衡
    有多种手段实现这个功能.最简单,最直接的方法之一就是"TEQL"——真(或
    "普通的")链路均衡.就象用队列实现的大多数事情一样,负载均衡也需要双
    向实现.链路的两端都要参与,才有完整的效果.
    想象下列情况:
    +-------+ eth1 +-------+
    | |==========| |
    "网络1" ------| A | | B |---- '网络 2'
    | |==========| |
    +-------+ eth2 +-------+
    A和B是路由器,我们当然假定它们全是Linux机器.如果从网络1发往网络2
    的流量需要A路由器同时使用两条链路发给B路由器.B路由器需要进行配置
    以便适应这种情况.反向传输时也一样,当数据包从网络2发往网络1时,B路
    由器同时使用eth1和eth2.
    分配的功能是用"TEQL"设备实现的,象这样(没有比这更简单的了):
    # tc qdisc add dev eth1 root teql0
    # tc qdisc add dev eth2 root teql0
    # ip link set dev teql0 up
    别忘了"ip link set up"命令!
    这在两台机器上都要做.teql0设备基本上是在eth1和eth2之间进行轮转发帧.
    用源也不会有数据从teql设备上进来,只是出现在原来的eth1和eth2上.
    我们现在有了网络设备,还需要有合适的路由.方法之一就是给两个链路分配一
    个/31的网络,teql0也一样:
    在A路由器上:
    # ip addr add dev eth1 10.0.0.0/31
    # ip addr add dev eth2 10.0.0.2/31
    # ip addr add dev teql0 10.0.0.4/31
    在B路由器上:
    # ip addr add dev eth1 10.0.0.1/31
    # ip addr add dev eth2 10.0.0.3/31
    # ip addr add dev teql0 10.0.0.5/31
    A路由器现在应该能够通过2个真实链路和一个均衡网卡ping通10.0.0.1,
    10.0.0.3和10.0.0.5.B路由器应该能够ping通10.0.0.0,10.0.0.2和10.0.0.4.
    如果成功的话,A路由器应该把10.0.0.5作为到达网络2的路由,B路由器应该
    把10.0.0.4作为去往网络1的路由.在网络1是你家里的网络,而网络2是Internet
    这种特定场合下,A路由器的缺省网关应该设为10.0.0.5.
    60
    /proc/sys/net/ipv4/conf/eth2/rp_filter
    包的乱序也是一个大问题.比如,有6个数据包需要从A发到B,eth1可能分到
    第1,3,5个包,而eth2分到第2,4,6个.在理想情况下,B路由器会按顺
    序收到第1,2,3,4,5,6号包.但实际上B路由器的内核很可能按照类似2,
    1,4,3,6,5这样的随机顺序收到包.这个问题会把TCP/IP搞糊涂.虽然在
    链路上承载不同的TCP/IP会话并没有问题,但你无法通过捆绑多个链路来增加
    一个ftp文件的下载速度,除非两端的操作系统都是Linux,因为Linux的TCP/IP
    协议栈不那么容易被这种简单的乱序问题所蒙蔽.
    当然,对于大多数应用系统来说,链路的负载均衡是一个好主意.
    10.2. 其它可能性
    William Stearns已经利用高级隧道来达到捆绑多重Internet连接的效果.可以在
    他的隧道网页找到.
    本HOWTO将来可能更多地描述这个问题.
    61
    /etc/iproute2/rt_tables
    # ip rule add fwmark 1 table mail.out
    # ip rule ls
    0:
    f
    rom all lookup local
    32764:
    f
    rom all fwmark 1 lookup mail.out
    32766:
    f
    rom all lookup main
    32767:
    f
    rom all lookup default
    现在我们建立一个通往那条便宜链路的路由,从而生成mail.out路由表:
    # /sbin/ip route add default via 195.96.98.253 dev ppp0 table mail.out
    这就做完了.我们可能需要一些例外,有很多方法都能达到目的.我们可以修改
    netfilter命令来排除一些主机,也可以插入一些优先权值更低的规则把需要排除
    的主机的数据包发往main路由表.
    我们还可以通过识别数据包的TOS位,来给不同服务类型的数据包打上不同的
    标记,再为它们分别建立规则.你甚至可以利用这种方法让诸如ISDN线路支持
    交互业务.
    62
    不用说,这当然也可以用于正在进行NAT("伪装")的机器上.
    重要提醒:我们收到报告说MASQ和SNAT功能与数据包标记有冲突.Rusty
    Russell在这个帖子中作了解释.关闭反方向的过滤就可以正常工作.
    注意:想给数据包打标记的话,你的内和需要一些配置:
    IP: advanced router (CONFIG_IP_ADVANCED_ROUTER) [Y/n/ ]
    IP: policy routing (CONFIG_IP_MULTIPLE_TABLES) [Y/n/ ]
    IP: use netfilter MARK value as routing key (CONFIG_IP_ROUTE_FWMARK) [Y/n/ ]
    参考方便菜谱一章中的15.5.
    63
    第12章 对包进行分类的高级过滤器
    就象在分类的队列规定一段中解释的,过滤器用与把数据包分类并放入相应的子
    队列.这些过滤器在分类的队列规定内部被调用.
    下面就是我们可用的分类器(部分):
    fw
    根据防火墙如何对这个数据包做标记进行判断.如果你不想学习tc的过
    滤器语法,这倒是一个捷径.细节请参见队列那一章.
    u32
    根据数据包中的各个字段进行判断,如源IP地址等等.
    route
    根据数据包将被哪条路由进行路由来判断.
    rsvp, rsvp6
    根据数据包的RSVP情况进行判断.只能用于你自己的网络,互联网并不
    遵守RSVP.
    tcindex
    用于DSMARK队列规定,参见相关章节.
    通常来说,总有很多途径可实现对数据包分类,最终取决于你喜欢使用哪种系统.
    分类器一般都能接受几个参数,为了方便我们列出来:
    protocol
    这个分类器所接受的协议.一般来说你只会接受IP数据.必要参数.
    parent
    这个分类器附带在哪个句柄上.句柄必须是一个已经存在的类.必要参数.
    prio
    这个分类器的优先权值.优先权值低的优先.
    handle
    对于不同过滤器,它的意义不同.
    后面所有的节都假定你试图对去往HostA的流量进行整形.并且假定根类配置为
    1:,并且你希望把选中的数据包送给1:1类.
    64
    12.1. u32分类器
    U32分类器是当前实现中最先进的过滤器.全部基于哈希表实现,所以当有很多
    过滤器的时候仍然能够保持健壮.
    U32过滤器最简单的形式就是一系列记录,每条记录包含两个部分:一个选择器
    和一个动作.下面要讲的选择器用来与IP包相匹配,一旦成功匹配就执行其指
    定的动作.最简单的动作就是把数据包发送到特定的类队列.
    用来配置过滤器的tc命令行由三部分组成:过滤器说明,选择器和动作.一个
    过滤器可以如下定义:
    tc filter add dev IF [ protocol PROTO ]
    [ (preference|priority) PRIO ]
    [ parent CBQ ]
    上面行中,protocol字段描述了过滤器要匹配的协议.我们将只讨论IP协议的情
    况.preference字段(也可以用priority代替)设置该过滤器的优先权.这非常重要,
    因为你可能有几条拥有不同优先权的过滤器.每个过滤器列表都按照输入的顺序
    被扫描一遍,然后优先权值低(更高的偏好值)的列表优先被处理."parent"字段
    定义了过滤器所属的CBQ的顶部(如1:0).
    上面描述的选项适用于所有过滤器,而不仅仅适用于U32.
    12.1.1. U32选择器
    u32选择器包含了能够对当前通过的数据包进行匹配的特征定义.它其实只是定
    义了IP包头中某些位的匹配而已,但这种看似简单的方法却非常有效.让我们
    看看这个从实际应用的系统中抄来的例子:
    # tc filter add dev eth0 protocol ip parent 1:0 pref 10 u32 /
    match u32 00100000 00ff0000 at 0 flowid 1:10
    现在,命令的第一行已经不用解释了,前面都说过了.我们把精力集中在用
    "match"选项描述选择器的第二行.这个选择器将匹配那些IP头部的第二个字
    节是0x10的数据包.你应该猜到了,00ff就是匹配掩码,确切地告诉过滤器应
    该匹配哪些位.在这个例子中是0xff,所以会精确地匹配这个字节是否等于0x10.
    "at"关键字的意思是指出从数据包的第几个字节开始匹配——本例中是从数据
    包的开头开始.完全地翻译成人类语言就是:"匹配那些TOS字段带有'最小延
    迟'属性的数据包".让我们看看另一个例子:
    # tc filter add dev eth0 protocol ip parent 1:0 pref 10 u32 /
    match u32 00000016 0000ffff at nexthdr+0 flowid 1:10
    "nexthdr"选项意味着封装在IP包中的下一个PDU的头部,也就是说它的上层
    协议的头.匹配操作就是从这个协议的头部开始的,应该发生在头部开始的第
    16位处.在TCP和UDP协议的头部,这个部分存放的是这个报文的目标端口.
    数字是按照先高厚低的格式存储的,所以0x0016就是十进制的22(如果是TCP
    的话就是ssh服务).其实,这个匹配在没有上下文的情况下含义很模糊,我们放
    65
    在后面讨论.
    理解了上面的例子之后,下面这条选择器就很好懂了:
    match c0a80100 ffffff00 at 16
    表示了:匹配从IP头开始数的第17个字节到第19个字节.这个选择器将匹配
    所有去往192.168.1.0/24的数据包.成功分析完上面这个例子后,我们就已经掌
    握u32选择器了.
    12.1.2. 普通选择器
    普通选择器定义了要对数据包进行匹配的特征,掩码和偏移量.使用普通选择器,
    你实际上可以匹配IP(或者上层协议)头部的任意一个bit,虽然这样的选择器比特
    殊选择器难读和难写.一般选择器的语法是:
    match [ u32 | u16 | u8 ] PATTERN MASK [ at OFFSET | nexthdr+OFFSET]
    利用u32,u16或u8三个关键字中的一个来指明特征的bit数.然后PATTERN
    和MASK应该按照它定义的长度紧挨着写.OFFSET参数是开始进行比较的偏
    移量(以字节计).如果给出了"nexthdr+"关键字,偏移量就移到上层协议头部
    开始的位置.
    一些例子:
    # tc filter add dev ppp14 parent 1:0 prio 10 u32 /
    match u8 64 0xff at 8 /
    flowid 1:4
    如果一个数据包的TTL值等于64,就将匹配这个选择器.TTL就位于IP包头的
    第9个字节.
    匹配带有ACK位的TCP数据包:
    # tc filter add dev ppp14 parent 1:0 prio 10 u32 /
    match ip protocol 6 0xff /
    match u8 0x10 0xff at nexthdr+13 /
    flowid 1:3
    用这个匹配小于64字节的ACK包:
    ## match acks the hard way,
    ## IP protocol 6,
    ## IP header length 0x5(32 bit words),
    ## IP Total length 0x34 (ACK + 12 bytes of TCP options)
    ## TCP ack set (bit 5, offset 33)
    # tc filter add dev ppp14 parent 1:0 protocol ip prio 10 u32 /
    match ip protocol 6 0xff /
    match u8 0x05 0x0f at 0 /
    match u16 0x0000 0xffc0 at 2 /
    match u8 0x10 0xff at 33 /
    flowid 1:3
    这个规则匹配了带有ACK位,且没有载荷的TCP数据包.这里我们看见了同时
    使用两个选择器的例子,这样用的结果是两个条件进行逻辑"与"运算.如果我
    们查查TCP头的结构,就会知道ACK标志位于第14个字节的第5个bit(0x10).
    66
    作为第二个选择器,如果我们采用更复杂的表达,可以写成"match u8 0x06 0xff
    at 9",而不是使用特殊选择器protocol,因为TCP的协议号是6(写在IP头的第
    十个字节).另一方面,在这个例子中我们不使用特殊选择器也是因为没有用来
    匹配TCP的ACK标志的特殊选择器.
    下面这个选择器是上面选择器的改进版,区别在于它不检查IP头部的长度.为
    什么呢?因为上面的过滤器只能在32位系统上工作.
    tc filter add dev ppp14 parent 1:0 protocol ip prio 10 u32 /
    match ip protocol 6 0xff /
    match u8 0x10 0xff at nexthdr+13 /
    match u16 0x0000 0xffc0 at 2 /
    flowid 1:3
    12.1.3. 特殊选择器
    下面的表收入了本节文档的作者从tc程序的源代码中找出的所有特殊选择器.
    它们能够让你更容易,更可靠地配置过滤器.
    求助: table placeholder - the table is in separate file ,,selector.html''
    求助: it's also still in Polish :-(
    求助: must be sgml'ized
    一些范例:
    # tc filter add dev ppp0 parent 1:0 prio 10 u32 /
    match ip tos 0x10 0xff /
    flowid 1:4
    求助: tcp dport match does not work as described below:
    上述规则匹配那些TOS字段等于0x10的数据包.TOS字段位于数据包的第二个
    字节,所以与值等价的普通选择器就是:"match u8 0x10 0xff at 1".这其实给了
    我们一个关于U32过滤器的启示:特殊选择器全都可以翻译成等价的普通选择
    器,而且在内核的内存中,恰恰就是按这种方式存储的.这也可以导出另一个结
    论:tcp和udp的选择器实际上是完全一样的,这也就是为什么不能仅用"match
    tcp dport 53 0xffff"一个选择器去匹配发到指定端口的TCP包,因为它也会匹配
    送往指定端口的UDP包.你一定不能忘了还得匹配协议类型,按下述方式来表
    示:
    # tc filter add dev ppp0 parent 1:0 prio 10 u32 /
    match tcp dport 53 0xffff /
    match ip protocol 0x6 0xff /
    flowid 1:2
    12.2. 路由分类器
    这个分类器过滤器基于路由表的路由结果.当一个数据包穿越一个类,并到达一
    个标有"route"的过滤器的时候,它就会按照路由表内的信息进行分裂.当一个
    67
    数据包遍历类,并到达一个标记"路由"过滤器的时候,就会按照路由表的相应
    信息分类.
    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 route
    我们向节点1:0里添加了一个优先级是100的路由分类器.当数据包到达这个节
    点时,就会查询路由表,如果匹配就会被发送到给定的类,并赋予优先级100.
    要最后完成,你还要添加一条适当的路由项:
    这里的窍门就是基于目的或者源地址来定义"realm".象这样做:
    # ip route add Host/Network via Gateway dev Device realm RealmNumber
    例如,我们可以把目标网络192.168.10.0定义为realm 10:
    # ip route add 192.168.10.0/24 via 192.168.10.1 dev eth1 realm 10
    我们再使用路由过滤器的时候,就可以用realm号码来表示网络或者主机了,并
    可以用来描述路由如何匹配过滤器:
    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 /
    route to 10 classid 1:10
    这个规则说:凡是去往192.168.10.0子网的数据包匹配到类1:10.
    路由过滤器也可以用来匹配源策略路由.比如,一个Linux路由器的eth2上连接
    了一个子网:
    # ip route add 192.168.2.0/24 dev eth2 realm 2
    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 /
    route from 2 classid 1:2
    这个规则说:凡是来自192.168.2.0子网(realm 2)的数据包,匹配到1:2.
    12.3. 管制分类器
    为了能够实现更复杂的配置,你可以通过一些过滤器来匹配那些达到特定带宽的
    数据包.你可以声明一个过滤器来来按一定比率抑制传输速率,或者仅仅不匹配
    那些超过特定速率的数据包.
    如果现在的流量是5M,而你想把它管制在4M,那么你要么可以停止匹配整个
    的5M带宽,要么停止匹配1M带宽,来给所配置的类进行4M速率的传输.
    如果带宽超过了配置的速率,你可以丢包,可以重新分类或者看看是否别的过滤
    器能匹配它.
    12.3.1. 管制的方式
    有两种方法进行管制.
    如果你编译内核的时候加上了"Estimators",内核就可以替你为每一个过滤器测
    量通过了多少数据,多了还是少了.这些评估对于CPU来讲非常轻松,它只不
    过是每秒钟累计25次通过了多少数据,计算出速率.
    68
    另一种方法是在你的过滤器内部,通过TBF(令牌桶过滤器)来实现.TBF只匹配
    到达到你配置带宽的数据流,超过的部分则按照事先指定的"越限动作"来处理.
    12.3.1.1. 靠内核评估
    这种方式非常简单,只有一个参数"avrate".所有低于avrate的数据包被保留,
    并被过滤器分到所指定的类中去,而那些超过了avrate的数据包则按照越限动作
    来处理,缺省的越限动作是"reclassify"(重分类).
    内核使用EWMA算法来核算带宽,以防止对瞬时突发过于敏感.
    12.3.1.2. 靠令牌桶过滤器
    使用下列参数:
    buffer/maxburst
    mtu/minburst
    mpu
    rate
    它们的意义与前面介绍TBF时所说的完全一样.但仍然要指出的是:如果把一
    个TBF管制器的mtu参数设置过小的话,将没有数据包通过,whereas the egress
    TBF qdisc will just pass them slower.
    另一个区别是,管制器只能够通过或者丢弃一个数据包,而不能因为为了延迟而
    暂停发送.
    12.3.2. 越限动作
    如果你的过滤器认定一个数据包越限,就会按照"越限动作"来处理它.当前,
    支持三种动作:
    continue
    让这个过滤器不要匹配这个包,但是其它的过滤器可能会匹配它.
    drop
    这是个非常强硬的选项——让越限的数据包消失.它的用途不多,经常被
    用于ingress管制.比如,你的DNS在请求流量大于5Mbps的时候就会失
    灵,你就可以利用这个来保证请求量不会超标.
    Pass/OK
    让数据包通过.可用于避免复杂的过滤器,但要放在合适的地方.
    reclassify
    69
    最经常用于对数据包进行重分类以达到最好效果.这是缺省动作.
    12.3.3. 范例
    现在最真实的范例就是下面第十五章提到的"防护SYN洪水攻击".
    求助: if you have used this, please share your experience with us
    12.4. 当过滤器很多时如何使用散列表
    如果你需要使用上千个规则——比如你有很多需要不同QoS的客户机——你可
    能会发现内核花了很多时间用于匹配那些规则.
    缺省情况下,所有的过滤器都是靠一个链表来组织的,链表按priority的降序排
    列.如果你有1000个规则,那么就有可能需要1000次匹配来决定一个包如何处
    理.
    而如果你有256个链表,每个链表4条规则的话,这个过程可以更快.也就是说
    如果你能把数据包放到合适的链表上,可能只需要匹配4次就可以了.
    利用散列表可以实现.比如说你有1024个用户使用一个Cable MODEM,IP地
    址范围是1.2.0.0到1.2.3.255,每个IP都需要不同容器来对待,比如"轻量级",
    "中量级"和"重量级".你可能要写1024个规则,象这样:
    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src /
    1.2.0.0 classid 1:1
    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src /
    1.2.0.1 classid 1:1
    ...
    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src /
    1.2.3.254 classid 1:3
    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src /
    1.2.3.255 classid 1:2
    为了提高效率,我们应该利用IP地址的后半部分作为散列因子,建立256个散
    列表项.第一个表项里的规则应该是这样:
    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src /
    1.2.0.0 classid 1:1
    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src /
    1.2.1.0 classid 1:1
    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src /
    1.2.2.0 classid 1:3
    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src /
    1.2.3.0 classid 1:2
    下一个表项应该这么开始:
    # tc filter add dev eth1 parent 1:0 protocol ip prio 100 match ip src /
    1.2.0.1 classid 1:1
    ...
    这样的话,最坏情况下也只需要4次匹配,平均2次.
    70
    具体配置有些复杂,但是如果你真有很多规则的话,还是值得的.
    我们首先生成root过滤器,然后创建一个256项的散列表:
    # tc filter add dev eth1 parent 1:0 prio 5 protocol ip u32
    # tc filter add dev eth1 parent 1:0 prio 5 handle 2: protocol ip u32 divisor 256
    然后我们向表项中添加一些规则:
    # tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 2:7b: /
    match ip src 1.2.0.123 flowid 1:1
    # tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 2:7b: /
    match ip src 1.2.1.123 flowid 1:2
    # tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 2:7b: /
    match ip src 1.2.3.123 flowid 1:3
    # tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 2:7b: /
    match ip src 1.2.4.123 flowid 1:2
    这是第123项,包含了为1.2.0.123,1.2.1.123,1.2.2.123和1.2.3.123准备的匹配
    规则,分别把它们发给1:1,1:2,1:3和1:2.注意,我们必须用16进制来表示
    散列表项,0x7b就是123.
    然后创建一个"散列过滤器",直接把数据包发给散列表中的合适表项:
    # tc filter add dev eth1 protocol ip parent 1:0 prio 5 u32 ht 800:: /
    match ip src 1.2.0.0/16 /
    hashkey mask 0x000000ff at 12 /
    link 2:
    好了,有些数字需要解释.我们定义散列表叫做"800:",所有的过滤都从这里
    开始.然后我们选择源地址(它们位于IP头的第12,13,14,15字节),并声明
    我们只对它的最后一部分感兴趣.这个例子中,我们发送到了前面创建的第2个
    散列表项.
    这比较复杂,然而实际上确实有效而且性能令人惊讶.注意,这个例子我们也可
    以处理成理想情况——每个表项中只有一个过滤器!
    71
    /proc/sys/net/ipv4/conf//log_martians
    求助: is setting the conf/[default,all]/* files enough - martijn
    72
    13.2. 深层设置
    有很多参数可以修改.我们希望能够全列出来.在Documentation/ip-sysctl.txt中
    也有部分记载.
    这些设置中的部分缺省值取决于你在内核配置时是否选择了"Configure as router
    and not host".
    Oskar Andreasson也有一个网页比我们讨论得更详细的网页:
    http://ipsysctl-tutorial.frozentux.net/
    13.2.1. ipv4一般设置
    作为一个一般性的提醒,多数速度限制功能都不对loopback起作用,所以不要
    进行本地测试.限制是由"jiffies" 来提供的,并强迫使用前面提到过的TBF.
    内核内部有一个时钟,每秒钟发生"HZ"个jiffies(滴嗒).在Intel平台上,HZ
    的值一般都是100.所以设置*_rate文件的时候,如果是50,就意味着每秒允许
    2个包.TBF配置成为如果有多余的令牌就允许6个包的突发.
    下面列表中的一些条目摘录自 Alexey Kuznetsov kuznet@ms2.inr.ac.ru和Andi
    Kleen ak@muc.de写的/usr/src/linux/Documentation/networking/ip-sysctl.txt.
    /proc/sys/net/ipv4/icmp_destunreach_rate
    一旦内核认为它无法发包,就会丢弃这个包,并向发包的主机发送ICMP
    通知.
    /proc/sys/net/ipv4/icmp_echo_ignore_all
    根本不要响应echo包.请不要设置为缺省,它可能在你正被利用成为DoS
    攻击的跳板时可能有用.
    /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts [Useful]
    如果你ping子网的子网地址,所有的机器都应该予以回应.这可能成为
    非常好用的拒绝服务攻击工具.设置为1来忽略这些子网广播消息.
    /proc/sys/net/ipv4/icmp_echoreply_rate
    设置了向任意主机回应echo请求的比率.
    /proc/sys/net/ipv4/icmp_ignore_bogus_error_responses
    设置它之后,可以忽略由网络中的那些声称回应地址是广播地址的主机生
    成的ICMP错误.
    /proc/sys/net/ipv4/icmp_paramprob_rate
    一个相对不很明确的ICMP消息,用来回应IP头或TCP头损坏的异常数
    据包.你可以通过这个文件控制消息的发送比率.
    73
    /proc/sys/net/ipv4/icmp_timeexceed_rate
    这个在traceroute时导致著名的"Solaris middle star".这个文件控制发送
    ICMP Time Exceeded消息的比率.
    /proc/sys/net/ipv4/igmp_max_memberships
    主机上最多有多少个igmp (多播)套接字进行监听.
    求助: Is this true
    /proc/sys/net/ipv4/inet_peer_gc_maxtime
    求助: Add a little explanation about the inet peer storage Minimum interval
    between garbage collection passes. This interval is in effect under low (or
    absent) memory pressure on the pool. Measured in jiffies.
    /proc/sys/net/ipv4/inet_peer_gc_mintime
    每一遍碎片收集之间的最小时间间隔.当内存压力比较大的时候,调整这
    个间隔很有效.以jiffies计.
    /proc/sys/net/ipv4/inet_peer_maxttl
    entries的最大生存期.在pool没有内存压力的情况下(比如,pool中entries
    的数量很少的时候),未使用的entries经过一段时间就会过期.以jiffies
    计.
    /proc/sys/net/ipv4/inet_peer_minttl
    entries的最小生存期.应该不小于汇聚端分片的生存期.当pool的大小
    不大于inet_peer_threshold时,这个最小生存期必须予以保证.以jiffies
    计.
    /proc/sys/net/ipv4/inet_peer_threshold
    The approximate size of the INET peer storage. Starting from this threshold
    entries will be thrown aggressively. This threshold also determines entries'
    time-to-live and time intervals between garbage collection passes. More
    entries, less time-to-live, less GC interval.
    /proc/sys/net/ipv4/ip_autoconfig
    这个文件里面写着一个数字,表示主机是否通过RARP,BOOTP,DHCP
    或者其它机制取得其IP配置.否则就是0.
    /proc/sys/net/ipv4/ip_default_ttl
    数据包的生存期.设置为64是安全的.如果你的网络规模巨大就提高这
    个值.不要因为好玩而这么做——那样会产生有害的路由环路.实际上,
    在很多情况下你要考虑能否减小这个值.
    /proc/sys/net/ipv4/ip_dynaddr
    74
    如果你有一个动态地址的自动拨号接口,就得设置它.当你的自动拨号接
    口激活的时候,本地所有没有收到答复的TCP套接字会重新绑定到正确
    的地址上.这可以解决引发拨号的套接字本身无法工作,重试一次却可以
    的问题.
    /proc/sys/net/ipv4/ip_forward
    内核是否转发数据包.缺省禁止.
    /proc/sys/net/ipv4/ip_local_port_range
    用于向外连接的端口范围.缺省情况下其实很小:1024到4999.
    /proc/sys/net/ipv4/ip_no_pmtu_disc
    如果你想禁止"沿途MTU发现"就设置它."沿途MTU发现"是一种技
    术,可以在传输路径上检测出最大可能的MTU值.参见Cookbook一章
    中关于"沿途MTU发现"的内容.
    /proc/sys/net/ipv4/ipfrag_high_thresh
    用于IP分片汇聚的最大内存用量.分配了这么多字节的内存后,一旦用
    尽,分片处理程序就会丢弃分片.When ipfrag_high_thresh bytes of memory
    is allocated for this purpose, the fragment handler will toss packets until
    ipfrag_low_thresh is reached.
    /proc/sys/net/ipv4/ip_nonlocal_bind
    如果你希望你的应用程序能够绑定到不属于本地网卡的地址上时,设置这
    个选项.如果你的机器没有专线连接(甚至是动态连接)时非常有用,即使
    你的连接断开,你的服务也可以启动并绑定在一个指定的地址上.
    /proc/sys/net/ipv4/ipfrag_low_thresh
    用于IP分片汇聚的最小内存用量.
    /proc/sys/net/ipv4/ipfrag_time
    IP分片在内存中的保留时间(秒数).
    /proc/sys/net/ipv4/tcp_abort_on_overflow
    一个布尔类型的标志,控制着当有很多的连接请求时内核的行为.启用的
    话,如果服务超载,内核将主动地发送RST包.
    /proc/sys/net/ipv4/tcp_fin_timeout
    如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态
    的时间.对端可以出错并永远不关闭连接,甚至意外当机.缺省值是60
    秒.2.2内核的通常值是180秒,你可以按这个设置,但要记住的是,即
    使你的机器是一个轻载的WEB服务器,也有因为大量的死套接字而内存
    溢出的风险,FIN-WAIT-2的危险性比FIN-WAIT-1要小,因为它最多只
    能吃掉1.5K内存,但是它们的生存期长些.参见tcp_max_orphans.
    75
    /proc/sys/net/ipv4/tcp_keepalive_time
    当keepalive起用的时候,TCP发送keepalive消息的频度.缺省是2小时.
    /proc/sys/net/ipv4/tcp_keepalive_intvl
    当探测没有确认时,重新发送探测的频度.缺省是75秒.
    /proc/sys/net/ipv4/tcp_keepalive_probes
    在认定连接失效之前,发送多少个TCP的keepalive探测包.缺省值是9.
    这个值乘以tcp_keepalive_intvl之后决定了,一个连接发送了keepalive之
    后可以有多少时间没有回应.
    /proc/sys/net/ipv4/tcp_max_orphans
    系统中最多有多少个TCP套接字不被关联到任何一个用户文件句柄上.
    如果超过这个数字,孤儿连接将即刻被复位并打印出警告信息.这个限制
    仅仅是为了防止简单的DoS攻击,你绝对不能过分依靠它或者人为地减
    小这个值,更应该增加这个值(如果增加了内存之后).This limit exists only
    to prevent simple DoS attacks, you _must_ not rely on this or lower the limit
    artificially, but rather increase it (probably, after increasing installed memory),
    if network conditions require more than default value, and tune network
    services to linger and kill such states more aggressively. 让我再次提醒你:每
    个孤儿套接字最多能够吃掉你64K不可交换的内存.
    /proc/sys/net/ipv4/tcp_orphan_retries
    本端试图关闭TCP连接之前重试多少次.缺省值是7,相当于50秒~16
    分钟(取决于RTO).如果你的机器是一个重载的WEB服务器,你应该考
    虑减低这个值,因为这样的套接字会消耗很多重要的资源.参见
    tcp_max_orphans.
    /proc/sys/net/ipv4/tcp_max_syn_backlog
    记录的那些尚未收到客户端确认信息的连接请求的最大值.对于有128M
    内存的系统而言,缺省值是1024,小内存的系统则是128.如果服务器不
    堪重负,试试提高这个值.注意!如果你设置这个值大于1024,最好同
    时调整include/net/tcp.h中的TCP_SYNQ_HSIZE,以保证
    TCP_SYNQ_HSIZE*16 ≤tcp_max_syn_backlo,然后重新编译内核.
    /proc/sys/net/ipv4/tcp_max_tw_buckets
    系统同时保持timewait套接字的最大数量.如果超过这个数字,time-wait
    套接字将立刻被清除并打印警告信息.这个限制仅仅是为了防止简单的
    DoS攻击,你绝对不能过分依靠它或者人为地减小这个值,如果网络实际
    需要大于缺省值,更应该增加这个值(如果增加了内存之后).
    /proc/sys/net/ipv4/tcp_retrans_collapse
    为兼容某些糟糕的打印机设置的"将错就错"选项.再次发送时,把数据
    包增大一些,来避免某些TCP协议栈的BUG.
    76
    /proc/sys/net/ipv4/tcp_retries1
    在认定出错并向网络层提交错误报告之前,重试多少次.缺省设置为RFC
    规定的最小值:3,相当于3秒~8分钟(取决于RIO).
    /proc/sys/net/ipv4/tcp_retries2
    在杀死一个活动的TCP连接之前重试多少次.RFC 1122规定这个限制应
    该长于100秒.这个值太小了.缺省值是15,相当于13~30分钟(取决
    于RIO).
    /proc/sys/net/ipv4/tcp_rfc1337
    这个开关可以启动对于在RFC1337中描述的"tcp的time-wait暗杀危机"
    问题的修复.启用后,内核将丢弃那些发往ti
     
    展开全文
  • 【原创】Linux实现路由转发功能开发总结关键词:linux防火墙,iptables Author: chad Mail: linczone@163.com 本文可以自由转载,但转载请务必注明出处以及本声明信息。一、准备知识需要用到的知识有: (1)路由...
  • Linux flow offload提高路由转发效率

    万次阅读 2019-12-07 08:38:52
    利用nf_conntrack机制存储路由,省去每包路由查找: https://blog.csdn.net/dog250/article/details/24101425 在Linux的连接跟踪(nf_conntrack)中缓存私有数据省去每次查找: https://blog.csdn.net/dog...
  • 路由fib表之初始化
  • 迈普学习总结 经过在公司里学习了几个月把大体的工作总结于下 在参与1800-20 3G 路由的开发中我参与了l2tp,gre静态路由,ipsec日志关键信息提取的编写并同时参与了ipsec-tools源码linuxkernel 网络协议栈源码l2tpd...
  • linux netlink操作路由小例子

    千次阅读 2014-05-14 12:42:49
    1,在网上查了关于netlink的实现路由查询和添加路由的文章,资料很少,提供的代码都没有运行结果或者编译不了。所以从网上拷贝的代码仔细研究,一步一步的调试,添加很多printf语句查看各结构的值。 2,我主要...
  • 基于Linux路由的访问控制

    千次阅读 2014-04-26 20:47:44
    RBAC根本不适宏内核协议栈的操作系统(UNIX,Linux,and so on...我可能在之前的文章把它们弄反了,...),但是这并不是每个人民中的一员都知道,包括我自己! 总之,数据包在内核协议栈的处理过程中一定要符合“快,...
  • 谈到什么是意义,话题总显得很大,近日每晚都和老城里的朋友聊老城的文化,老城的老房子,老城的...本文关键词:Linux策略路由,nf_conntrack,socket,路由缓存再谈“哪里来的回哪里”当人们部署双线服务器时,比如一
  • Linux中ip路由子系统剖析

    千次阅读 2018-05-30 11:26:04
    linux有很多子系统,例如IPv4子系统,进程管理子系统,pci子系统等,这次我们要面对的是路由选择子系统,久闻其大名却不曾亲近,让我们一起来看下。Linux网络栈最重要的目标之一就是转发流量,对骨干网中的核心...
  • 问题/发现:本人在为一款路由器写一个统计局域网互传流量的Linux内核模块的时候,发现如下问题: 局域网内的一台设备和该局域网内另一台设备进行通信时,我在路由器的netfilter链表处设下钩子,以捕获数据。后来...
  • 起因:  最近在完成网关的一个相关功能时,对于网关本身通过socket发送的数据包,没有从正确的wan... 也怪自己没有对路由代码有一个全面的分析,对于路由查找还不是很熟悉,后面会开始学习路由
  • linux内核模块获取设备IP地址

    千次阅读 2017-01-06 09:16:09
    linux内核模块获取设备IP地址
  • Linux从kernel 2.1.105开始支持QOS,不过,需要重新编译内核。运行 'make config'时将 EXPERIMENTAL_OPTIONS 设置成 'y',并且将 Class Based Queueing (CBQ), Token Bucket Flow, Traffic Shapers ...
  • 本节主要是分析NAT模块相关的hook函数与target函数,主要是理清NAT模块实现的原理等。   1.NAT相关的hook函数分析 NAT模块主要是在NF_IP_PREROUTING、NF_IP_POSTROUTING、NF_IP_LOCAL_OUT、NF_IP_LOCAL_IN四个...
  • 从现在开始学习路由相关的代码,在分析代码之前, 我们还是先分析数据结构,把数据结构之间的关系理解了以后,再理解代码就相对轻松多了。本小节先分析路由相关的数据结构。内核里面大多模块定义的数据结构之间一般...
  • 一、开发环境简介 开发系统: Ubuntu 14.04 ...需向linux内核中添加4G模块USB驱动和ppp网络协议的支持要向,让内核支持USB驱动和ppp拨号相关配置。 2.2.1. USB串口驱动编译配置项 Device Drivers —> [] US...
  • Linux内核模块划分 -- 摘自内核驱动

    千次阅读 2012-01-03 22:30:31
    在 Unix 系统中, 几个并发的进程专注于不同的任务. 每个进程请求系统资源, 象计算能力, 内存, 网络连接, 或者一些别的资源. 内核是个大块的可执行文件, 负责处理... 另外, 所有的路由和地址解析问题都在内核中实现.
  • 在内核中,连接跟踪表是一个二维数组结构的哈希表(hash table),哈希表的大小记作...需要明确的是,nf_conntrack 模块并不是所有 Linux 内核都会加载,最常见的导致加载该模块的原因是使用了 iptables、lvs 等内核态 NA
  • Linux从kernel 2.1.105开始支持QOS,不过,需要重新编译内核。运行 'make config'时将 EXPERIMENTAL _OPTIONS 设置成 'y',并且将 Class Based Queueing (CBQ), Token Bucket Flow, Traffic Shapers...
  •  linux 下 nf_conntrack_tuple 跟踪记录 其中可以根据内核提供的数据结构获取连接跟踪记录。  iptables 中的NAT使用总结 iptable的在防火墙上面的应用。 1:iptable中三个tables所挂接的HOOKs 其实这个问题很...
  • nat模块复制tso结构不完全导致SSL握手弹证书慢。 IP路由neighbour系统对pointopoint设备的处理不合理导致争锁。 IPv6路由缓存设计不合理导致争锁。 Overlayfs的mount设计不合理导致争锁。 凌晨将近两点半,本文再解...
  • 笔者在公司的项目中使用模块化的方式开发APP已经快一年的时间,其中经历过以模块化的方式来重构项目中一些相对来说业务比较独立的模块。遇到了一些问题,也积累了一些经验,所以想谈一谈我对Android模块化的理解,也...
  • linux gprs模块 sim900芯片 ppp拨号上网

    千次阅读 2016-08-14 11:00:10
    开发板:FL2440 内核版本:linux-3.0 芯片:sim900 一,GPRS介绍  GSM模块,是将GSM射频芯片、基带处理芯片、存储器、功放器件等集成在一块线路板上,具有独立的操作系统、GSM射频处理、基带处理并提供标准接口...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 33,537
精华内容 13,414
关键字:

linux代码路由模块

linux 订阅