8 IP 处理
lwIP仅实现了最为基本的IP功能。它能发送,接收和转发网络包,但不能接收和发送分段的IP包,或者处理有IP选项的网络包。对大多数应用来说不会产生任何问题。
8.1 接收网络包
对到来的IP包,处理开始于网络设备驱动调用ip_input()函数。这里,初步全面(initial sanity)的IP版本域和头部长度的检查,加上计算和检查头部检验和。它假定协议栈将不会接收到任何的IP分片,因为它假定代理会收集热合的分片包,因此所有的IP分片都会被丢掉。包含IP选项的包也假定由代理处理因此同样被丢掉。
接下来,函数用该网络接口的IP地址检查目的地址,看该包是否是传送给该主机的。网络接口被顺序链接到一个链表中,被线性搜索。网络接口的数量预期很少所以没有用比线性搜索更好的搜索策略。
如果到来的包被认定是发给本主机的,则协议域值被用来决定该包被传递给哪个高层协议。
8.2 发送网络包
一个发送的包由函数ip_output()处理,该函数使用ip_route()来找到合适的网络接口以传输该包。当决定了发送该包的网络接口后,该包被传递给ip_output_if()函数,同时决定发送该包的网络接口也是该函数的一个参数被传递。这时,所有的IP头部字段都被填写,并且计算了IP头部校验和。该IP包的源和目的地址都被作为一个参数传递给ip_output_if()函数。另外可以省略传递该IP包的源地址,但这种情况下,发送该包的网络接口的IP地址将作为该包的源IP地址。
Ip_route()函数线性搜索网络接口列表,以找到合适的网络接口。搜索过程中,IP包的目的IP地址由该网络接口的子网掩码所掩盖(masked)。假如该被掩盖的目的地址相等于一个网络接口的掩盖后的IP地址,那么该网络接口就被选择,否则将使用一个默认的网络接口。该默认网络接口可以在系统引导时设置,或者运行时由人工配置。如果默认网络接口的网络地址和目的IP地址不品配,则网络接口结构(图5)中的gw字段值将被作为链路帧的目的IP地址。(注意:在这种情况下,IP包的目的地址和链路帧的目的地址将会不同)。这种原始形式的路由假象下的事实是一个网络可能有多个路由器相连。对于大多数的基本情况,如一个本地网络只有一台路由器,这工作的很好。
由于传输层协议UDP和TCP需要目的IP地址以计算传输层校验和,在某种情况下,在该包被传递给IP层之前就应该确定发送该包的网络接口。解决的办法是让传输层函数直接调用ip_route()。由于在该包到达IP层时已经知道了发送该包的网络接口,因此无需再搜索网络接口列表。相反,这些协议直接调用ip_output_if()。因为该函数把该网络接口作为一个参数没,因此就避免了搜索该网路接口。
8.3 转发网络包
如果没有一个网路接口的IP地址和一个到来的包的目的地址相匹配,该包就应该被转发。这由ip_forward()函数处理。这时,TTL字段的值被减少,并且如果该值为0,一个ICMP错误消息将被发送到该包的原始发送者,同时丢弃该包。因为IP头部已改变了,IP头部校验和必须重新校正。因为可以应用[MK90,Rij94]简单的算法来校正原始IP校验和,因此没必要重新计算整个校验和。最后,该包被转发给一个合适的网路接口。用于找到一个合适的网络接口的算法和发送一个IP包一样.
8.4 ICMP 处理
ICMP处理过程相当简单.有ip_input()收到的ICMP包被转交给icmp_input()函数,该函数对ICMP头部进行解码并作出相应的响应.一些ICMP消息被传递给高层协议,以及一些特殊的传输层协议可能会关注这些消息.ICMP目的不可达消息可由传输层协议发送,特别是UDP.icmp_dest_unreach()被用来处理此种情况.
网络中,广泛使用ICMP Echo消息来探测网络,因此ICMP Echo在性能上尽量的优化.实际的处理发生在icmp_input()中,包含的处理有:对到来的包的源和目的IP地址进行交换,改变Echo回复的ICMP类型并校正ICMP校验和.此时,该包被传递回IP层以进一步发送出去.
9.UDP处理
UDP是用来在不同进程间多路转发(demultiplexing)包的简单协议.每个UDP会话的状态被保存在一个PCB结构体中(如图7所示).UDP控制块被保存在一个链表中.当一个UDP数据报到来时将搜索该链表以找到一个相比配的控制块(pcb).
在全局UDP控制块链表中,每个UDP控制块都包含有指向下一个控制块的指针.一个UDP会话由两终端的IP地址和端口号决定,而这些信息分别被保存在local_ip、dest_ip、local_port、dest_port字段中.flags字段指示将对当前会话应用何种UDP校验和.这允许关闭UDP检验和,或者使用UDP Lite来计算部分数据报的校验和.当使用UDP Lite时,chesum_len字段指明将计算多少的数据报.
最后的两个参数recv和recv_arg将在一个数据报到达时被使用.
由于UDP的简单性,输入和输出处理也十分简单,完全以直线方式流程处理(如图8).为了发送数据,应用程序调用udp_send()函数,而该函数则接着调用udp_output()函数.其中将计算必要的校验和,以及填充UDP头部字段的值.由于检验和包含IP包的源地址,因此在某种情况下将调用ip_route()来找出发送该IP包的网络接口.该网络接口的IP地址被作为该包的源IP地址.最后,该包被转交给ip_output_if()进行传输.
当一个UDP数据报到来时,IP层调用udp_input()函数.这时,如果该会话应该使用检验和,则检查该数据报的UDP校验和,接着多路转发该包.当找到相应的UDP控制后,recv函数就被调用.
10.TCP 处理
TCP是一个为应用层提供可靠的字节流服务的传输层协议.TCP必任何其它在这里描述的协议都要复杂,其实现代码占总代码量的50%左右.
10.1 概要
基本的TCP处理(如图9)被分为6个函数.tcp_input()、tcp_process()和tcp_receive()三个函数与TCP接收处理相关,而tcp_write()、tcp_enqueue()和tcp_output()三个函数则与TCP发送处理有关.
应用程序调用tcp_write()来发送TCP数据.tcp_write()函数传递控制给tcp_enqueue()函数.如果需要,tcp_enqueue()将把数据分段成合适大小的TCP报文段,最终把TCP报文段放到连接的传输队列中.tcp_output()函数将检查是否可以发送该数据,比如,接收窗口是否有足够的空间并且拥塞窗口是否足够大等等,假如可以的话,该函数将调用ip_route()和ip_output_if()发送该数据.
当ip_input()确认完IP头部并转交TCP报文段给tcp_input()函数时,接收处理便开始了.tcp_input()函数做好初步完整的检查(如校验和以及TCP选项解析等),同时确认该报文段属于哪个TCP连接.接着,该数据报就被转交给tcp_process(),其中实现了TCP状态机和任何必要的状态转换.加入该连接正处于从网络中接收数据的状态,则将调用tcp_receive()函数.这样的话,tcp_reveive()将传递给报文给上层的应用程序.假如该报文包含一个未确认数据(当然是当前缓冲的数据)的ACK应答,那么该数据将从缓冲区中移出,其所占空间则可以重新使用了.同时,当受到一个ACK应答时,接收者可能想要接收更多的数据,因此将调用tcp_output()函数.
(待续)