Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2473906
  • 博文数量: 867
  • 博客积分: 10010
  • 博客等级: 上将
  • 技术积分: 9800
  • 用 户 组: 普通用户
  • 注册时间: 2006-07-27 14:44
文章分类

全部博文(867)

文章存档

2007年(6)

2006年(861)

我的朋友

分类: BSD

2006-08-04 13:55:28

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

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

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

        上面是开场白,接下来就转入正题.

        老规矩,先建立场景,场景总是要假设并建立起来的.:

        hostA  --  GW  --  hostB

        主机A通过GW互访hostB

        谈到数据的通讯,总是双向的,如同2人谈话,如果仅仅是一个人说,那就成了演讲--广播.GW就是扮演了一个传递员的角色,2人的话传来传去,粗俗的话,优化的GW或防火墙十有八九是不传的,免得制造矛盾.

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

        hostA想要访问hostBFTP(21端口):

        0. 先广播询问并获得网关的MAC地址.谁是网关,速速报来!!!
        1.
连接hostBFTP端口
        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中有ipfilterfilter的钩子,通过抽取包内的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.将路由指针rtNULL, 
                              
调用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开始处理
                                   
ipfwipfilter,这个包可能会被丢弃,
                                   
转发,这时流入包的处理就会到此结束
                                 d.
经过了包过滤的开包流检,开始处理IP选项
                                   
当然了多播也不要忘了处理一下
                                 e.
判断一下,是送给自己IP的吗?如果不是,要不
                                   
要调用ip_forward,传出网关?
                                 f.
看来需要传递给上层处理了,根据不同的协议
                                    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.
又开始ipfwipfilter的处理了
                               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,层层封装的,不要仅仅是停留在书本上.

 

阅读(2086) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~