FreeBSD内核网络处理流程介绍
来源:www.45fan.com 2016-09-03 18:24:14
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,层层封装的,不要仅仅是停留在书本上。
对于不了解内核的,特别是内核网络的人来说,内核的网路处理就像一个巧克力盒子。
|
|