/*linux-5.10.x\net\ipv4\udp.c
*负责接收UDP数据包,并进行数据处理、复制和相关的控制消息处理。也处理接收过程中的错误情况,并提供时间戳和丢弃计数等统计信息
1、参数解析:函数接收struct sock类型的套接字指针sk和struct msghdr类型的消息头指针msg作为参数
2、数据接收:函数通过调用sk_recv_datagram函数从套接字的接收队列中获取一个数据包,
该函数会处理接收缓冲区的相关逻辑,包括数据包的复制和消耗等
3、数据处理:函数根据套接字的类型和配置,对接收到的数据包进行处理,例如校验和验证、解析IP头部、解析UDP头部等
4、数据复制:函数将接收到的数据复制到msg结构体中的缓冲区,并更新复制的数据长度
5、控制消息处理:函数根据套接字的配置,处理控制消息,例如获取源地址信息、处理IP层的控制消息等
6、错误处理:函数处理接收过程中可能出现的错误情况,例如校验和错误、无法放入接收队列等,同时更新相关的统计信息
7、时间戳和丢弃计数:函数根据套接字的类型和配置,获取接收数据包的时间戳和丢弃的数据包计数,
并将其存储在msg结构体中的msg_control字段中
8、返回值:函数返回已经复制的数据长度
*/
int udp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int noblock,
int flags, int *addr_len)
{
struct inet_sock *inet = inet_sk(sk); // 获取套接字的底层数据结构 inet_sock 的指针
DECLARE_SOCKADDR(struct sockaddr_in *, sin, msg->msg_name); // 将消息头部的 msg_name 强制转换为 sockaddr_in 类型
struct sk_buff *skb; // 用于处理网络数据包
unsigned int ulen, copied; // UDP 数据包长度和拷贝长度
int off, err, peeking = flags & MSG_PEEK; // 偏移量 off、错误码 err 和标志位 peeking,标志是否进行 MSG_PEEK 操作
int is_udplite = IS_UDPLITE(sk); // 判断是否为 UDPLite 协议(轻型 UDP)
bool checksum_valid = false; // 标志校验和是否有效
if (flags & MSG_ERRQUEUE) // 如果flags参数中包含了MSG_ERRQUEUE标志
return ip_recv_error(sk, msg, len, addr_len); // 处理接收错误队列中的数据包,并返回结果
try_again: /* 设置标签try_again,在需要重新尝试接收数据包时跳转到该标签 */
off = sk_peek_offset(sk, flags); // 获取接收缓冲区的偏移量
skb = __skb_recv_udp(sk, flags, noblock, &off, &err); // 从套接字的接收队列中获取一个UDP数据包的skb
if (!skb)
return err; // 如果获取数据包失败,则返回错误码
ulen = udp_skb_len(skb); // 获取接收到的 UDP 数据包的总长度ulen
copied = len; // 将拷贝长度 copied 设置为用户请求的数据长度len
if (copied > ulen - off)
copied = ulen - off; // 若拷贝长度超过数据包剩余长度(ulen - off),将拷贝长度设置为数据包剩余长度
else if (copied < ulen)
msg->msg_flags |= MSG_TRUNC; // 如果拷贝长度小于数据包总长度,则设置消息头部的 msg_flags 标志位为 MSG_TRUNC,表示数据被截断
if (copied < ulen || peeking || // 如果已拷贝的数据长度小于期望接收的数据长度(ulen)或是 peeking 操作(即只查看数据而不拷贝)
(is_udplite && UDP_SKB_CB(skb)->partial_cov)) { // 或者是UDPLite协议并且该数据包是部分覆盖的
checksum_valid = udp_skb_csum_unnecessary(skb) || // 检查校验和是否是不必要的
!__udp_lib_checksum_complete(skb); // 检查校验和是否完整
if (!checksum_valid) // 如果校验和无效
goto csum_copy_err; // 跳转到错误处理标签csum_copy_err
}
if (checksum_valid || udp_skb_csum_unnecessary(skb)) { // 检查校验和是否有效,如果有效或者校验和不必要,则进行数据包拷贝操作
if (udp_skb_is_linear(skb)) // 如果数据包的缓冲区是线性存储的(连续存储)
err = copy_linear_skb(skb, copied, off, &msg->msg_iter);// 在线性缓冲区内拷贝 skb 数据包的一部分到用户空间
/*
skb: 指向 sk_buff 数据包的指针
copied:已经拷贝到用户空间的数据长度
off: 从数据包的偏移量开始拷贝数据到用户空间
msg->msg_iter:指向消息迭代器的指针(用于记录当前拷贝位置和剩余长度)
*/
else // 如果数据包的缓冲区不是线性存储的
err = skb_copy_datagram_msg(skb, off, msg, copied); // 将相关的散列缓冲区数据逐个复制到用户空间消息结构体中,直到拷贝完成或者到达指定的拷贝长度
} else { // 如果校验和无效
err = skb_copy_and_csum_datagram_msg(skb, off, msg); // 将 skb(sk_buff)数据包的一部分拷贝到用户空间,并在拷贝过程中计算校验和
if (err == -EINVAL)
goto csum_copy_err; // 如果拷贝时出现无效参数错误,则跳转到 csum_copy_err 标签处处理
}
if (unlikely(err)) { // 如果拷贝过程中发生错误,则进行相应处理
if (!peeking) {
atomic_inc(&sk->sk_drops); // 如果非 MSG_PEEK 操作,则增加套接字对象的 sk_drops 计数
UDP_INC_STATS(sock_net(sk),
UDP_MIB_INERRORS, is_udplite); // 增加 UDP 错误统计信息:输入错误的数据报数量
}
kfree_skb(skb); // 释放 sk_buff 对象的内存空间
return err; // 返回错误码给调用函数
}
if (!peeking) // 如果拷贝成功且非 MSG_PEEK 操作,则增加套接字对象的相应统计信息
UDP_INC_STATS(sock_net(sk),
UDP_MIB_INDATAGRAMS, is_udplite);
sock_recv_ts_and_drops(msg, sk, skb); // 在接收数据包时获取时间戳和丢弃的数据包计数
/* 复制地址信息 */
if (sin) { // 如果sin不为空指针
sin->sin_family = AF_INET; // 复制协议族,存储到sin结构体中
sin->sin_port = udp_hdr(skb)->source; // 复制源端口号,存储到sin结构体中
sin->sin_addr.s_addr = ip_hdr(skb)->saddr; // 复制源 IP 地址,存储到sin结构体中
memset(sin->sin_zero, 0, sizeof(sin->sin_zero)); // 设置其他字段为0
*addr_len = sizeof(*sin); // 将addr_len设置为sin结构体的大小
if (cgroup_bpf_enabled) // 如果启用了cgroup BPF,则在接收数据包之前运行相关的BPF程序
BPF_CGROUP_RUN_PROG_UDP4_RECVMSG_LOCK(sk,
(struct sockaddr *)sin);
}
if (udp_sk(sk)->gro_enabled) // 如果套接字启用了 GRO(Generic Receive Offload)
udp_cmsg_recv(msg, sk, skb); // 处理相关的控制消息
if (inet->cmsg_flags) // 如果 inet 结构体的 cmsg_flags 不为零
ip_cmsg_recv_offset(msg, sk, skb, sizeof(struct udphdr), off); // 处理IP层的控制消息,偏移量为 sizeof(struct udphdr)
err = copied; // 将err设置为已经复制的数据长度copied
if (flags & MSG_TRUNC) // 如果数据是被截断的
err = ulen; // 将err设置为UDP数据包的总长度ulen
skb_consume_udp(sk, skb, peeking ? -err : err); // 消耗已经复制的数据,并更新套接字的内部状态
return err; // 返回复制的数据长度err
csum_copy_err: /* 进行错误处理 */
if (!__sk_queue_drop_skb(sk, &udp_sk(sk)->reader_queue, skb, flags,
udp_skb_destructor)) { // 如果不能将数据包skb放入套接字的读队列中
UDP_INC_STATS(sock_net(sk), UDP_MIB_CSUMERRORS, is_udplite); // 增加UDP的校验和错误统计
UDP_INC_STATS(sock_net(sk), UDP_MIB_INERRORS, is_udplite); // 增加UDP的接收错误统计
}
kfree_skb(skb); // 释放数据包的内存空间
cond_resched(); // 重新开始处理新的数据包,但如果需要让出 CPU,则进行调度
msg->msg_flags &= ~MSG_TRUNC; // 将msg的msg_flags字段的MSG_TRUNC标志位清除
goto try_again; // 返回 try_again 标签处重新尝试接收数据包
}
linux内核代码-注释详解:udp_recvmsg
最新推荐文章于 2024-03-30 14:02:26 发布