FreeBSD内核网络处理流程分析

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

       本文试图从一个原始数据包处理流程的角度,结合源代码(相应的函数)简单扼要地分析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. ipfwipfilter开始处理
                                   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. ipDF了吗,包太大又不让分拆的话,只好对不
                                 起了,丢弃它.否则拆分它,形成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,层层封装的,不要仅仅是停留在书本上.

第一部分 综述 第1章 BSD系统的历史和目标 1.1 UNIX系统的历史 1.1.1 UNIX系统的起源 1.1.2 Research小组的UNIX系统 1.1.3 AT&T UNIX System III和System V 1.1.4 伯克利软件发布(BSD) 1.1.5 UNIX无处不在 1.2 BSD和其他系统 1.3 BSD向开放源代码的转变 1.3.1 Networking Release 2 1.3.2 法律诉讼 1.3.3 4.4BSD 1.3.4 4.4BSD-Lite Release 2 1.4 FreeBSD的开发模式 1.5 参考文献 第2章 FreeBSD设计概述 2.1 FreeBSD的功能和内核 2.2 内核结构 2.3 内核服务 2.4 进程管理 2.4.1 信号 2.4.2 进程组和会话 2.5 内存管理 2.5.1 BSD内存管理设计要点 2.5.2 内核中的内存管理 2.6 I/O系统 2.6.1 描述符与I/O 2.6.2 描述符管理 2.6.3 设备 2.6.4 套接口IPC 2.6.5 分散/聚集I/O 2.6.6 多文件系统支持 2.7 设备 2.8 文件系统 2.9 网络文件系统 2.10 终端 2.11 进程间通信 2.12 网络通信 2.13 网络实现 2.14 系统运行 2.15 复习题 2.16 参考文献 第3章 内核服务 3.1 内核结构 3.1.1 系统进程 3.1.2 系统入口 3.1.3 运行时刻的内核结构 3.1.4 内核的入口 3.1.5 从内核返回 3.2 系统调用 3.2.1 调用结果的处理 3.2.2 从系统调用返回 3.3 陷阱和中断 3.3.1 陷阱 3.3.2 I/O设备中断 3.3.3 软件中断 3.4 时钟中断 3.4.1 统计和进程调度 3.4.2 超时 3.5 内存管理服务 3.6 时间服务 3.6.1 真实时间 3.6.2 外部表示 3.6.3 调整时间 3.6.4 时间间隔 3.7 用户、用户组和其他身份标识 3.7.1 主机标识符 3.7.2 进程组和会话 3.8 资源服务 3.8.1 进程优先级 3.8.2 资源利用 3.8.3 资源限制 3.8.4 文件系统配额 3.9 系统运行服务 3.10 复习题 3.11 参考文献 第二部分 进程 第4章 进程管理 4.1 进程管理概述 4.1.1 多程序机制 4.1.2 调度 4.2 进程状态 4.2.1 进程结构 4.2.2 线程结构 4.3 上下文切换 4.3.1 线程状态 4.3.2 底层上下文切换 4.3.3 主动上下文切换 4.3.4 同步 4.3.5 互斥同步 4.3.6 锁管理器的锁 4.3.7 其他同步 4.4 线程调度 4.4.1 4.4BSD的调度程序 4.4.2 线程调度 4.4.3 线程优先级的计算 4.4.4 线程优先级例程 4.4.5 线程运行队列和上下文切换 4.4.6 ULE调度程序 4.5 创建进程 4.6 终止进程 4.7 信号 4.7.1 信号的历史 4.7.2 发送信号 4.7.3 接收信号 4.8 进程组和会话 4.8.1 会话 4.8.2 作业控制 4.9 监管环境 4.9.1 监管环境的语义 4.9.2 监管环境的实现 4.9.3 监管环境的限制 4.10 进程的调试 4.11 复习题 4.12 参考文献 第5章 存储管理 5.1 术语 5.1.1 进程与内存 5.1.2 调页机制 5.1.3 替换算法 5.1.4 工作集模型 5.1.5 交换机制 5.1.6 虚拟内存的优点 5.1.7 虚拟内存的硬件要求 5.2 FreeBSD虚拟内存系统概述 5.3 内核的存储管理 5.3.1 内核映射和子映射 5.3.2 内核地址空间的分配 5.3.3 内核的存储分配程序 5.3.4 内核的区域存储分配程序 5.4 进程独立拥有的资源 5.4.1 FreeBSD的进程虚拟地址空间 5.4.2 缺页处理 5.4.3 映射到对象 5.4.4 对象 5.4.5 对象到页面 5.5 共享存储 5.5.1 mmap模型 5.5.2 共享映射 5.5.3 私有映射 5.5.4 压缩影子链 5.5.5 私有快照 5.6 创建新进程 5.6.1 保留内核资源 5.6.2 复制用户地址空间 5.6.3 不通过复制创建新进程 5.7 执行一个文件 5.8 进程地址空间的操作 5.8.1 改变进程大小 5.8.2 文件映射 5.8.3 改变保护权限 5.9 终止进程 5.10 调页器接口 5.10.1 vnode调页器 5.10.2 设备调页器 5.10.3 物理内存调页器 5.10.4 交换调页器 5.11 调页机制 5.11.1 硬件高速缓存的设计 5.11.2 页面填色 5.12 页面替换 5.12.1 调页参数 5.12.2 pageout守护进程 5.12.3 交换机制 5.12.4 换入进程 5.13 可移植性 5.13.1 pmap模块的作用 5.13.2 初始化和启动 5.13.3 分配和释放映射 5.13.4 改变映射的访问和固定属性 5.13.5 管理页表的使用信息 5.13.6 初始化物理页面 5.13.7 管理内部数据结构 5.14 复习题 5.15 参考文献 第三部分 I/O系统 第6章 I/O系统概述 6.1 从用户到设备的I/O映射 6.1.1 设备驱动程序 6.1.2 I/O队列 6.1.3 中断处理 6.2 字符设备 6.2.1 原始设备和物理I/O 6.2.2 面向字符的设备 6.2.3 字符设备驱动程序的入口点 6.3 磁盘设备 6.3.1 块设备驱动程序的入口点 6.3.2 磁盘I/O请求的排序 6.3.3 磁盘标签 6.4 描述符的管理和服务 6.4.1 打开文件项 6.4.2 管理描述符 6.4.3 异步I/O 6.4.4 文件描述符的上锁机制 6.4.5 描述符上的多路I/O操作 6.4.6 select调用的实现 6.4.7 数据在内核中的转移 6.5 虚拟文件系统的接口 6.5.1 vnode的内容 6.5.2 对vnode的操作 6.5.3 路径名转换 6.5.4 文件系统的导出服务 6.6 与文件系统无关的服务 6.6.1 名字缓存 6.6.2 缓冲区管理 6.6.3 缓冲区管理的实现 6.7 可叠加的文件系统 6.7.1 简单的文件系统层 6.7.2 联合安装的文件系统 6.7.3 其他文件系统 6.8 复习题 6.9 参考文献 第7章 设备 7.1 设备概述 7.1.1 PC的I/O体系结构 7.1.2 FreeBSD海量存储I/O子系统的结构 7.1.3 设备的命名和访问 7.2 GEOM层 7.2.1 术语和拓扑规则 7.2.2 改变拓扑 7.2.3 运行 7.2.4 拓扑的灵活性 7.3 CAM层 7.3.1 SCSI子系统 7.3.2 I/O请求通过CAM子系统的路径 7.4 ATA层 7.5 配置设备 7.5.1 识别设备 7.5.2 自动配置数据结构 7.5.3 资源管理 7.6 复习题 7.7 参考文献 第8章 本地文件系统 8.1 文件系统的分层管理 8.2 inode的结构 8.2.1 inode格式的变化 8.2.2 扩展属性 8.2.3 文件系统的新功能 8.2.4 文件标志 8.2.5 动态的inode 8.2.6 管理inode 8.3 命名 8.3.1 目录 8.3.2 在目录中查找名字 8.3.3 路径名转换 8.3.4 链接 8.4 配额 8.5 文件上锁 8.6 软更新 8.6.1 文件系统中的更新依赖 8.6.2 依赖关系的数据结构 8.6.3 跟踪位映射表的依赖关系 8.6.4 跟踪inode的依赖关系 8.6.5 跟踪直接块的依赖关系 8.6.6 跟踪间接块的依赖关系 8.6.7 跟踪新间接块的依赖关系 8.6.8 跟踪新目录项的依赖关系 8.6.9 跟踪新目录的依赖关系 8.6.10 跟踪删除目录项时的依赖关系 8.6.11 截短文件 8.6.12 回收文件和目录的inode节点 8.6.13 跟踪目录项重命名时的依赖关系 8.6.14 跟踪删除文件时的依赖关系 8.6.15 fsync对软更新的要求 8.6.16 删除文件时对软更新的要求 8.6.17 fsck对软更新的要求 8.6.18 软更新的性能 8.7 文件系统的快照 8.7.1 创建文件系统快照 8.7.2 维护文件系统快照 8.7.3 大型文件系统的快照 8.7.4 快照性能 8.7.5 后台fsck 8.7.6 用户可见的快照 8.7.7 动态的转储 8.8 本地文件库 8.8.1 文件库概述 8.8.2 用户的文件I/O 8.9 伯克利快速文件系统 8.9.1 伯克利快速文件系统的组成 8.9.2 引导块 8.9.3 优化存储空间利用率 8.9.4 读写文件 8.9.5 布局策略 8.9.6 分配机制 8.9.7 将块组成簇 8.9.8 基于扩展的分配 8.10 复习题 8.11 参考文献 第9章 网络文件系统 9.1 历史和概述 9.2 NFS的结构和操作 9.2.1 NFS协议 9.2.2 FreeBSD的NFS实现 9.2.3 客户机/服务器的交互操作 9.2.4 RPC的传输问题 9.2.5 安全问题 9.3 提高性能的技术 9.3.1 租约 9.3.2 崩溃恢复 9.4 复习题 9.5 参考文献 第10章 终端处理 10.1 终端处理模式 10.2 行规程 10.3 用户接口 10.4 tty结构 10.5 进程组、会话和终端控制 10.6 C-list 10.7 RS-232和调制解调器控制 10.8 终端操作 10.8.1 打开终端 10.8.2 输出到行规程 10.8.3 终端的输出 10.8.4 终端的输入 10.8.5 ioctl例程 10.8.6 调制解调器转换 10.8.7 关闭终端设备 10.9 其他行规程 10.10 复习题 10.11 参考文献 第四部分 进程间通信 第11章 进程间通信 11.1 进程间通信的模型 11.2 实现的结构和概述 11.3 内存管理 11.3.1 mbuf 11.3.2 存储管理算法 11.3.3 mbuf工具例程 11.4 数据结构 11.4.1 通信域 11.4.2 套接口 11.4.3 套接口地址 11.4.4 锁 11.5 建立连接 11.6 传送数据 11.6.1 发送数据 11.6.2 接收数据 11.7 关闭套接口 11.8 本地进程间通信 11.8.1 信号量 11.8.2 消息队列 11.8.3 共享内存 11.9 复习题 11.10 参考文献 第12章 网络通信 12.1 内部结构 12.1.1 数据流 12.1.2 通信协议 12.1.3 网络接口 12.2 套接口到协议的接口 12.2.1 协议的用户请求例程 12.2.2 协议的控制输出例程 12.3 协议到协议的接口 12.3.1 pr_output 12.3.2 pr_input 12.3.3 pr_ctlinput 12.4 协议和网络的接口 12.4.1 发送数据包 12.4.2 接收数据包 12.5 路由选择 12.5.1 内核路由选择表 12.5.2 路由选择查找 12.5.3 路由选择重定向 12.5.4 路由选择表接口 12.5.5 用户级的路由选择策略 12.5.6 用户级路由选择接口:路由选择套接口 12.6 缓冲和拥塞控制 12.6.1 协议缓冲策略 12.6.2 队列限制 12.7 原始套接口 12.7.1 控制块 12.7.2 输入处理 12.7.3 输出处理 12.8 网络子系统的其他主题 12.8.1 带外数据 12.8.2 地址解析协议 12.9 复习题 12.10 参考文献 第13章 网络协议 13.1 IPv4网络协议 13.1.1 IPv4地址 13.1.2 广播地址 13.1.3 组播 13.1.4 端口与关联 13.1.5 协议控制块 13.2 UDP协议 13.2.1 初始化 13.2.2 输出 13.2.3 输入 13.2.4 控制操作 13.3 Internet协议(IP) 13.3.1 输出 13.3.2 输入 13.3.3 转发 13.4 TCP协议 13.4.1 TCP连接状态 13.4.2 序号变量 13.5 TCP算法 13.5.1 定时器 13.5.2 往返时间的估计 13.5.3 建立连接 13.5.4 SYN缓存 13.5.5 关闭连接 13.6 TCP输入处理 13.7 TCP输出处理 13.7.1 发送数据 13.7.2 避免糊涂窗口综合症 13.7.3 避免小数据包 13.7.4 确认延迟和窗口更新 13.7.5 重发状态 13.7.6 慢启动 13.7.7 源拥塞的处理 13.7.8 缓冲与窗口大小分配 13.7.9 使用慢启动避免拥塞 13.7.10 快速重发 13.8 ICMP协议 13.9 IPv6 13.9.1 IPv6地址 13.9.2 IPv6数据包格式 13.9.3 套接口API的调整 13.9.4 自动配置 13.10 安全 13.10.1 IPSec概述 13.10.2 安全协议 13.10.3 密钥管理 13.10.4 IPSec实现 13.10.5 密码子系统 13.11 复习题 13.12 参考文献 第五部分 系统运行 第14章 启动和关机 14.1 概述 14.2 引导 14.3 初始化内核 14.4 初始化内核模块 14.4.1 基本服务 14.4.2 初始化内核线程 14.4.3 初始化设备模块 14.4.4 内核的可加载模块 14.4.5 启动进程间通信 14.4.6 启动内核线程 14.5 用户级初始化 14.5.1 /sbin/init 14.5.2 系统的启动脚本 14.5.3 /usr/libexec/getty 14.5.4 /usr/bin/login 14.6 系统运行 14.6.1 内核的配置 14.6.2 系统关机与自动重启 14.6.3 系统调试 14.6.4 同内核传递信息 14.7 复习题 14.8 参考文献 术语表
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值