分用单播数据报
如果程序执行到这里,说明程序并没有执行多播操作,那么大概率是单播。
维护缓存指针
udp_last_inpcb是上一次接收数据报的端口的控制块指针,维护该指针的依据是许多程序往往具有时间局部性,也就是:经常运行的程序下一次往往也还运行,不经常运行的程序下一次大概率不会运行。
迭代全部端口
判断缓存指针指向的是不是现在要找的控制块,如果不是,就调用in_pcblookup函数迭代全部端口。
生成ICMP不可达错误
如果inp为NULL,说明没有找到端口,那么需要生成ICMP错误报告消息。
小结
虽然据TCP/IP卷二中描述,udp_last_inpcb其实没什么用,但笔者看来这里的程序相当巧妙,能从中感受到作者对时间局部性与空间局部性的考虑。inp被复用进行程序流控制,例如inp为NULL时,这说明肯定是进行了寻找操作且没有找到,因为赋值缓存指针必定使inp不为NULL,如果inp匹配缓存指针或者是找到符合的端口,那么生成ICMP错误的代码就直接跳过。
inp这个变量使用register关键字修饰,并被反复使用。可以想象它的值第一次被保存在寄存器后,后面的程序只需要反复操作该寄存器,而不用频繁对其他变量取地址并引用,这对于指令预取和流水线机制都是十分有利的,因为寄存器本身就是速度最快的内存。
(整个UDP协议的代码都写得非常巧妙,精简凝练,对变量的复用不仅能体现全部的控制流,还能体现时间局部性)
/*
* Locate pcb for datagram.
*/
inp = udp_last_inpcb;
if (inp->inp_lport != uh->uh_dport ||
inp->inp_fport != uh->uh_sport ||
inp->inp_faddr.s_addr != ip->ip_src.s_addr ||
inp->inp_laddr.s_addr != ip->ip_dst.s_addr) {
inp = in_pcblookup(&udb, ip->ip_src, uh->uh_sport,
ip->ip_dst, uh->uh_dport, INPLOOKUP_WILDCARD);
if (inp)
udp_last_inpcb = inp;
udpstat.udpps_pcbcachemiss++;
}
if (inp == 0) {
udpstat.udps_noport++;
if (m->m_flags & (M_BCAST | M_MCAST)) {
udpstat.udps_noportbcast++;
goto bad;
}
*ip = save_ip;
ip->ip_len += iphlen;
icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_PORT, 0, 0);
return;
}
单播提交数据报给对应端口
程序执行到这里都没有返回,说明并不是多播,但肯定是匹配了上一次的控制块或者是找到了控制块,那么是单播。
返回源站IP地址和端口
还记得我们在应用层定义的sockaddr_in变量吗?在这里会把IP地址和端口保存到这个变量中。
返回控制信息
如果定义了控制选项,那么可以使用udp_saveopt分配一个mbuf缓存控制信息(在这里是IP地址)并返回。
提交数据报到socket的接收队列
这一步可以使用sbappendaddr完成,但是在执行之前,需要修正第一个mbuf,忽略UDP首部和IP首部。提交数据报后,需要使用sorwakeup函数唤醒所有阻塞在接收队列的进程。
/*
* Construct sockaddr format source address.
* Stuff source address and datagram in user buffer.
*/
udp_in.sin_port = uh->uh_sport;
udp_in.sin_addr = ip->ip_src;
if (inp->inp_flags & INP_CONTROLOPTS) {
struct mbuf **mp = &opts;
if (inp->inp_flags & INP_RECVDSTADDR) {
*mp = udp_saveopt((caddr_t) &ip->ip_dst,
sizeof(struct in_addr), IP_RECVDSTADDR);
if (*mp)
mp = &(*mp)->m_next;
}
下面宏条件编译的代码还没有实现,暂时忽略
#ifdef notyet
/* options were tossed above */
if (inp->inp_flags & INP_RECVOPTS) {
*mp = udp_saveopt((caddr_t) opts_deleted_above,
sizeof(struct in_addr), IP_RECVOPTS);
if (*mp)
mp = &(*mp)->m_next;
}
/* ip_srcroute doesn't do what we want here, need to fix */
if (inp->inp_flags & INP_RECVRETOPTS) {
*mp = udp_saveopt((caddr_t) ip_srcroute(),
sizeof(struct in_addr), IP_RECVRETOPTS);
if (*mp)
mp = &(*mp)->m_next;
}
#endif
}
iphlen += sizeof(struct udphdr);
m->m_len -= iphlen;
m->m_pkthdr.len -= iphlen;
m->m_data += iphlen;
if (sbappendaddr(&inp->inp_socket->so_rcv, (struct sockaddr *)&udp_in,
m, opts) == 0) {
udpstat.udps_fullsock++;
goto bad;
}
sorwakeup(inp->inp_socket);
return;
bad:
m_freem(m);
if (opts)
m_freem(opts);
}