文章目录
UDP数据报的接收过程要分两部分来看:
- 网络层将数据报递交给UDP后,UDP的处理过程。该过程中,UDP需要接收数据包并对其进行校验,校验成功后将其放入接收队列中等待用户空间程序来读取;
- 用户空间程序调用read()等系统调用读取已经放入接收队列中的数据。
这篇笔记先来介绍第一部分。
从IP层接收数据包: udp_rcv()
在AF_INET协议族初始化时,由UDP注册给网络层的接收回调函数。
int udp_rcv(struct sk_buff *skb)
{
return __udp4_lib_rcv(skb, &udp_table, IPPROTO_UDP);
}
@skb: 输入数据包
@udptable:已绑定端口的UDP传输控制块哈希表,将从该哈希表查找该skb属于哪个套接字
@proto:L4协议号,到这里可能是IPPROTO_UDP或者IPPROTO_UDPLITE
int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable, int proto)
{
struct sock *sk;
struct udphdr *uh;
unsigned short ulen;
struct rtable *rt = skb_rtable(skb);
__be32 saddr, daddr;
struct net *net = dev_net(skb->dev);
// 保证skb的数据区至少包含udp首部
if (!pskb_may_pull(skb, sizeof(struct udphdr)))
goto drop; /* No space for header. */
uh = udp_hdr(skb);
ulen = ntohs(uh->len);
// skb中的数据长度不能小于UDP首部指示的数据包长度,该检查确保数据包是完整的
if (ulen > skb->len)
goto short_packet;
if (proto == IPPROTO_UDP) {
// 1. UDP数据包长度必须大于首部长度
// 2. pskb_trim_rcum()会去掉可能的填充(UDP数据包过小,IP可能会填充),然后重新计算校验和
if (ulen < sizeof(*uh) || pskb_trim_rcsum(skb, ulen))
goto short_packet;
uh = udp_hdr(skb);
}
// 校验和检查
if (udp4_csum_init(skb, uh, proto))
goto csum_error;
// 获取数据包中的源IP和目的IP地址
saddr = ip_hdr(skb)->saddr;
daddr = ip_hdr(skb)->daddr;
// 对于多播或者广播报文的处理
if (rt->rt_flags & (RTCF_BROADCAST|RTCF_MULTICAST))
return __udp4_lib_mcast_deliver(net, skb, uh, saddr, daddr, udptable);
// 根据报文的源端口号和目的端口号查询udptable,寻找应该接收该数据包的传输控制块
sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable);
if (sk != NULL) {
// 找到了处理该数据包的传输控制块,调用udp_queue_rcv_skb()接收数据包
int ret = udp_queue_rcv_skb(sk, skb);
sock_put(sk);
/* a return value > 0 means to resubmit the input, but
* it wants the return to be -protocol, or 0
*/
if (ret > 0)
return -ret;
return 0;
}
// 到这里,说明没有传输控制块接收该数据包,做些统计然后丢弃该数据包
// IPSec相关
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
goto drop;
nf_reset(skb);
/* No socket. Drop packet silently, if checksum is wrong */
if (udp_lib_checksum_complete(skb))
goto csum_error;
// 累计输入数据包错误统计值,并且回复端口不可达ICMP报文
UDP_INC_STATS_BH(net, UDP_MIB_NOPORTS, proto == IPPROTO_UDPLITE);
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
/*
* Hmm. We got an UDP packet to a port to which we
* don't wanna listen. Ignore it.
*/
kfree_skb(<