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