FreeBSD 内核网络处理流程分析, 新闻中心 - 上海晶石道 www.kingstor...

来源:百度文库 编辑:神马文学网 时间:2024/04/30 02:27:14

FreeBSD 内核网络处理流程分析

    对于不了解内核的,特别是内核网络的人来说,内核的网路处理就像一个巧克力盒子。
不打开就不会知道里面是什么,打开了就会觉得里面是丰富多彩的。

    本文试图从一个原始数据包处理流程的角度,结合源代码(相应的函数)简单扼要地
分析FreeBSD的内核网络处理。

    主机对主机的方式是比较简单的,数据包从链路层上来,一路上行,达到用户空间的
应用程序,一个数据包的生命期就结束了。对于像网关或防火墙之类包转发的方式,处理起
来就相对复杂了一些,这也是许多人迷惑不解之处。

    对于不了解内核的,特别是内核网络的人来说,内核的网路处理就像一个巧克力盒子。
不打开就不会知道里面是什么,打开了就会觉得里面是丰富多彩的。

    本文试图从一个原始数据包处理流程的角度,结合源代码(相应的函数)简单扼要地
分析FreeBSD的内核网络处理。

    主机对主机的方式是比较简单的,数据包从链路层上来,一路上行,达到用户空间的
应用程序,一个数据包的生命期就结束了。对于像网关或防火墙之类包转发的方式,处理起
来就相对复杂了一些,这也是许多人迷惑不解之处。

    上面是开场白,接下来就转入正题。
    老规矩,先建立场景,场景总是要假设并建立起来的。设:
    
    hostA  --  GW  --  hostB
    
    主机A通过GW互访hostB
    
    谈到数据的通讯,总是双向的,如同2人谈话,如果仅仅是一个人说,那就成了演讲--
广播。GW就是扮演了一个传递员的角色,将2人的话传来传去,粗俗的话,优化的GW或防火
墙十有八九是不传的,免得制造矛盾。

    对于主机如何产生包,本文不作详细讨论。关心此项内容的,可以参见tcp/udp处理以
及内核中的socket等系统调用。本文的重点放在GW上,分析GW是如何处理转发数据包的。

    hostA 想要访问hostB的ftp(21端口):
    0. 先广播询问并获得网关的MAC地址。谁是网兀偎俦ɡ?!!
    1. 连接hostB的ftp端口
    2. 成功后,发送数据包
    ....
    hostA找到网关的MAC地址后,发往非本网段的数据包的目标 MAC地址都是网关的 MAC地址
但目标 IP地址不是网关的。    
        
    下面就看看GW都作了哪些工作
    
    1. GW听到一个包
    
         NIC               <-- 硬中断发生了,
          |                    调用驱动的rxeof函数。包处理开始。对于polling
          |                    方式,是cpu主动去网卡读包,这样硬中断数会少,
          |                    但是如果处理不及时,数据包就丢了。对于小包,而
          |                    且网卡芯片上的buf很大时,polling方式的好处就很
          |                    大了。反过来,在遭受小包攻击时,系统的中断数就
          |                    会异常高,这是因为需要不停地响应处理。
          |
       if_xxx.c            <-- rxeof
          |                    m_devget 申请mbuf,从网卡的buf拷贝数据到mbuf,
          |                    一个数据包出现。剥离 ether_header 后,调用
          |                    ether_input(ifp, eh, m)
          |                     
       if_ethersubr.c      <-- ether_input:
                                 a. 一定要获取 ether_header,拿不到就释放 mbuf
                                    丢掉这个包。
                                    后续的处理中,该数据包随时面临着被丢弃的危险
                                 b. bpf想要看看这个包,那就给他看看,反正他不会
                                    更改这个包,tcpdump可以通过bpf看到这个包
                                 c. netgraph也要处理吗,呜,处理就处理,不怕。
                                    netgraph是FreeBSD独特的网络处理?椋笠?
                                    植到了其他BSD,这里是一个钩子,挂接在驱动
                                    层可以处理最原始的数据包。
                                    正常的钩子入口在 ng_ether中。
                                 d. 是网桥模式吗?如果是的话,数据包就从这里转
                                    到另一网卡的发送队列中了。参见bridge.c
                                    
                                 预处理作完了,该 ether_demux(ifp, eh, m) 出场了
               
                           <-- ether_demux:
                                 开始为 ip 预处理
                                 a. 这个包需要流量控制吗? 先转到ipfw再处理它
                                 b. 这个包是我的吗?上层准备接收了吗,否那就丢
                                    弃这个包
                                 c. 如果是多播,就置位多播,告诉上层是多播
                                 
                                 预处理就要结束了,根据包类型,分拣到不同的上
                                 层队列中
      ----------------------------------------------------------------------
       上面就是在驱动一层的包处理过程,在这个过程中,插接了 bpf, netgraph, ipfw,
       ipfilter,vlan 等处理。bpf 是只读的,其他都可以更改原始包(包括包头,包内
       容)。FreeBSD 之所以可以在桥模式过滤 IP包,是因为在 bridge.c 中有 ipfilter
       等 filter的钩子,通过抽取包内的 IP等信息就可以完成各种规则作用。对于软 vlan,
       ether_demux 通过调用相应的钩子,剥离标签后,重新调用 ether_input,相对
       netgraph 中的 vlan, 个人觉得效率低,虽然实现起来相对简单。
       netgraph 处理完的包后,不再预处理了,直接调用 ether_demux 继续 ip 的处理
       或 ether_output_frame 将包发出网关。
       
       在这一层上,包处理的效率是非常高的,而且也要求必须高效率。
     
       说完了2层的处理,下面就是3层的了。文件的目录也就从dev(pci)、net 转到 netinet
       
    2. 三层--arp处理
       if_ether.c          <-- arp的处理
                               首先出场的是 arpintr,看名字知道是处理中断的。
                               从队列中取出一个包,不管三七二十一,看看包头,
                               注意这时的包已经没有 ether_header了。如果是 arp
                               类型的包,并符合处理要求,转到 in_arpinput(m)。
                               当然如果不合规矩照丢不误。
               
                           <-- in_arpinput(m)
                               针对各种情况判断处理,其中会调用 arplookup
                               判断处理后,发送 reply. 将路由指针 rt 置NULL,
                               调用 ether_output, 虽然调用的是 if_output,但大
                               多数网卡驱动都将此函数指针设为 ether_output。
                               这时,数据包就回到了2层,发送回去了。之所以,
                               用“回到”,因为表面上看来是这样的,还是相同的
                               mbuf,只是内容不同了。arp 的请求应答包是对称的
               
                           <-- arplookup(addr, create, proxy)    
                               完成arp的缓冲,将此 MAC地址放到 rt路由表中,以备
                               将来发送包时查询使用。
                               
        这个文件中还有一个重要的函数,arpresolve,用于通过 IP 地址获取 MAC地址,
        如果在 rt树中没有找到(或超时了),就调用 arprequest,广播获取与此 IP对
        应的 MAC地址
        
        系统命令 arp,就是通过 ioctl和这个文件打交道。
        
    3. 三层--ip处理        
       ip_input.c          <-- 流入网关的ip处理
                               ipintr,自然就是IP队列的中断处理了,它的任务很
                               简单,从队列中取出一个 mbuf,也就是一个数据包。
                               将其交给 ip_input 处理。
                           
                           <-- ip_input
                                 a. 先判断要不要进行 ipfw等的处理,是的话,跳转c
                                    处理
                                 b. 接下来,拿到 IP头,针对IP头判断处理
                                 c. ipfw 和 ipfilter开始处理
                                    在ipfw 和 ipfilter中,这个包可能会被丢弃、
                                    转发,这时流入包的处理就会到此结束
                                 d. 经过了包过滤的开包流检,开始处理 IP 选项,
                                    当然了多播也不要忘了处理一下
                                 5. 判断一下,是送给自己IP的吗? 如果不是,要不
                                    要调用 ip_forward,传出网关?
                                 6. 看来需要传递给上层处理了,根据不同的协议
                                    tcp/udp,调用位于4层的协议处理函数,该他们
                                    干活了
                                    
                           <-- ip_forward
                                 这是该文件中另一个重要的函数
                                 该函数,会根据目标地址,查找路由,如果找到路由了,
                                 就调用 ip_output,将数据包转发走,否则回应一个
                                 icmp, 告诉发送方出错了。
                                 
       真不容易,这个数据包经过了重重关卡,终于要继续前进,准备出城了。且慢,出城
       也不是那么容易了,这比乘火车坐飞机的安检严多了。真是宁可错杀一千不漏一个。
       
       ip_output.c          <-- 流出网关的ip处理
                                   ip_output, ip流出的处理主体函数,处理的方式类似
                                   包流入的处理,先是,
                                     a. 先判断要不要进行 ipfw的处理,是的话,跳转 d
                                     处理
                                  b. 嗯,要判断是不是来自 4层,看看是否要处理一下
                                     IP 头
                                  c. 看看路由表,这个包该何去何从? 不要忘了多播哟!
                                     当然了,如果是 IP的广播包,也要处理的。
                                     例如 PPPOE 会发送 IP的广播包
                                  d. 又开始 ipfw 和 ipfilter的处理了
                                  e. 对于 loopback的包,怎么能放出去呢,丢掉它
                                  f. ip包 DF了吗,包太大又不让分拆的话,只好对不
                                     起了,丢弃它。否则拆分它,形成成mbuf簇,每个
                                     簇由多个链构成。ip_fragment做的就是这件事
                                     包转发几乎涉及不到包重组。
                                  g. 到此,终于可以通过 if_ouput-- ether_output
                                     将包传送到了二层
      ----------------------------------------------------------------------
      在三层上,是各种安全处理的最佳地点,这时候,原始的包该处理的都处理,剩下的
      就是怎么根据 IP完成各种各样的规则处理了。在这一层,数据包可以被还原为一个
      发送方的 IP包,并能够进一步解包成tcp/udp,形成会话甚至应用。
      由于分层的结构,采用 SMP对包作进一步处理时,并不会对下层造成很大的影响(mbuf
      处理不及时,造成mbuf耗尽等等)

    4. 二层--ether_output
         if_ethersubr.c    <-- ether_output:
                                  a. 需要判断路由? 那就看看,不合适的话就丢弃这个包
                                  b. 看看 arp表,有目的地址的MAC?  没有就去要一个回
                                     来,没要来? 那就返回吧,出不去了。
                                  c. 添加 ether_header
                                  d. 什么,目标地址是自己,if_simloop 这个包
                                  e. 看看 netgraph要处理吗?
                                  f. 将包转给 ether_output_frame继续处理
                                  
                           <-- ether_output_frame
                                  a. 网桥要处理吗?
                                  b. ipfw 还要处理一下?
                                  c. 都处理完了吧,那就把包送到网卡的输出队列中吧,
                                     等候网卡驱动处理了
        
        if_xxx.c           <-- xxx_intr
                                网卡设备的中断处理,负责发送接受等工作
                           
                           <-- if_start
                                从队列中取出包,调用xxx_encap,将包转换为 frame
                                最后再看一眼 bpf。

      ----------------------------------------------------------------------
      if_simloop在 if_loop.c 文件中。
                                      
      千辛万苦,数据包终于走出了网关。
      
    网络处理程序的分支非常多,但是只要抓住主线,就会非常清晰其处理流程。其中涉及
到的处理函数也就那么几个。
    其中涉及到的数据结构也非常得多,队列、mbuf链(簇),ifp,rt等是非常重要的数
据体,很多时候如果不清楚这些结构,读懂这些程序是非常困难的。同时针对某协议的封装
格式也要了解清楚,tcp/udp->ip->mbuf,层层封装的,不要仅仅是停留在书本上。