对于不了解内核的,特别是内核网络的人来说,内核的网路处理就像一个巧克力盒子。 不打开就不会知道里面是什么,打开了就会觉得里面是丰富多彩的。
本文试图从一个原始数据包处理流程的角度,结合源代码(相应的函数)简单扼要地 分析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,层层封装的,不要仅仅是停留在书本上。
|