昨晚搞到快两点,今天9.30才起床,希望11点半之前能搞完去吃饭😭
参照上图,有几个基本概念(本例中的传输层协议为UDP):
需注意的是IP数据报结构存储不受机器大小端的影响,均按照下面地址结构存储
IP数据报结构如上图所示,上面的个部分含义已在图中标出
可能需注意的地方:
ip_hl
常用4字节度量,即当ip_hl == 5
时,实际首部长度为5*4=20。其中选项的部分为0~40不等,所以ip_hl
的最大值为:(20+40)/4 = 15,最小值为:20/4=5。另外,由于ip_hl为4的倍数,所以选项的长度一般也为4的倍数。
void ipintr ()
{
struct ip *ip;
struct mbuf *m;
struct ipq *fp;
struct in_ifaddr *ia;
int hlen, s;
next:
/*
Get next datagram off input queue and get IP header
in first mbuf.
*/
s = splimp() ;
IF_DEQUEUE (&ipintrq, m) ;
sp1x(s);
if(m==0) return;
/*
很多很多代码😭
*/
goto next;
bad:
m_freem(m);
goto next;
}
该函数的基本框架如上,大致就是依靠两个goto实现对分组处理的循环,直到队列为空结束。中间省略的代码主要做下面几件事(实在太长了):
ipintrq
中取出,验证它们的内容,损坏和有差错的分组自动被丢弃。ip_dooptions
来处理IP选项,如果没到达最终目的地就尝试转发,反之交给对应传输层协议到达非目的地的分组需要被转发。只有当ipforwarding非零或当分组中包含源路由时,ipintr才调用该函数ip_forward
实现转发。ip_forward依靠route结构体与路由表接口
struct route{
struct rtentry *ro_rt;
struct sockaddr ro_dst;
}
ro_rt
指向与路由表相关的rtentry结构体ro_dst
指向存放目的地址信息的sockaddr结构体ip_forward()
的基本流程分为两部分:
ip_output
进行转发流程中可能需注意的点:
- IP转发算法把最近的路由缓存在全局route结构的ipforward_rt中
- 当转发错误时,由且仅由路由器向源主机返回ICMP重定向报文,以此更新主机路由表
ip_output
为IP输出代码,主要为三部分:
整体框架如上图所示,从上到下分别是:
用户进程(应用层)–>UDP(传输层)–>IP(网络层)–>数据链路层–>硬件网卡(物理层)
UDP首部如上图所示,对应域的含义也有标记。需要注意的是IP首部和UDP首部一般是紧挨在一起,我们在CH1-CH3中分析mbuf时已经可以注意到这种现象。而实际上在udp内相关函数(如udp_input
,udp_output
)处理时也会将两者放在一起使用,他们共同构成了结构体udpiphdr
,如下所示:
struct udpiphdr{
struct ippvly ui_i; /* overlaid ip structure*/
struct udphdr ui_u; /* udp header*/
}
20字节的IP首部被定义为 ippvly结构体,如下所示,可自行对照IP首部结构图:
struct ipov1y {
caddr_t ih_next, ih_prev; /* for protocol sequence q's */
u_char ih_x1; /* (unused) */
u_char ih_pr; /* protocol */
short ih_len; /* protocol length */
struct in_addr ih_src; /* source internet address */
struct in_addr ih_dst; /* destination internet address */
};
初始化函数,用来构建PCB双向链表的起始,让头部PCB的next指针和prev指针都指向自己。需注意UDP临时端口号由1024开始分配
前0~1023端口为常用端口,一般都有固定对应服务,想要监听这些端口时也需要sudo权限
PCB是22章的概念:PCB协议控制块存放各UDP和TCP插口所要求的多个信息片,UDP所需要的所有信息都可以在Internet PCB中找到,不存在UDP控制块
当程序调用send,sendto,sendmsg,write或writev时会发生UDP输出。基本流程如下图:
udp_output
主要干这几件事:
我们在前三章分析mbuf时说到过,udp会填写一部分首部,ip会填写一部分首部,这里就是分别调用udp_output
,ip_output
实现的
下图中阴影部分由ip_output()
填写,其余浅色部分交由udp_output()
填写
UDP在检验和时会包含三部分内容:12字节的伪首部,8字节的UDP首部,UDP数据
伪首部和UDP首部如上图所示,下图是填充首部和检验和的整个示意图
这部分机组的时候学过,自行适当复习一下,课件例题如下
当IP在它的协议字段指定为UDP的输入队列上收到一个IP数据报时,才发生UDP的输入。IP通过协议交换表中的pr_input
函数调用函数udp_input
。因为IP的输入是在软件中断级,所以udp_input
也在这一级上执行。udp_input
的目标是把UDP数据报放置到合适的插口的缓存内,唤醒该插口上因输入阻塞的所有进程。udp_input
函数的主要分三个部分:
UDP对收到的数据报完成一般性的确认;
处理目的地是单播地址的UDP数据报:找到合适的PCB,把数据报放到插口的缓存内,
处理目的地是广播或多播地址的UDP数据报:必须把数据报提交给多个插口。
确认部分可以继续复习前三章mbuf分析部分,个人感觉这本章和前三章关系还是比较紧密的,可以多对照看看