UDP详细理解(实现部分基于linux5.12.12版本内核)
包的格式
源端口
发送方进程所使用的端口号(1-65535)
RFC768中规定:是否指定源端口可选,未指定是为0
linux中规定:如果未指定端口号,自动赋予一个非0的端口号
目的端口
目标系统中负责接收UDP包的那个应用端口
长度
包括包头和有效负荷
最小的udp包头8字节
最大UDP有效负荷为65535-8=65527字节
linux源码中udp发送函数udp_sendmsg中声明完变量后,首先就是判断长度是否符合规范
if (len > 0xFFFF)
return -EMSGSIZE;
校验和
包括包头、有效负荷、伪负荷。
对ip源地址目标地址、UDP协议标识符、UDP包长度取1的补码和,然后对结果取11位1的补码和,得到校验和。
如果字节非偶,后面会补零。仅计算,不传输这个零
如果校验和为零,会传输比特位均为1的值,这等价于1的补码算术。
如果传输的校验和位零,表示发送方没有计算校验和。
传递有效负荷
1.有效负荷被当作msghdr结构传递给套接字接口处的sendmsg()系统调用。
2.套接字会检查msghdr结构,在复制它到内核中(除了那些最初驻留在用户地址空间中的实际有效负荷外)。
综上,UDP发包期间,msghdr结构会原封不动的传递给udp_sendmsg()
//include/linux/socket.h
struct msghdr {
void *msg_name; /* ptr to socket address structure */
int msg_namelen; /* size of socket address structure */
struct iov_iter msg_iter; /* data */
/*
* Ancillary data. msg_control_user is the user buffer used for the
* recv* side when msg_control_is_user is set, msg_control is the kernel
* buffer used for all other cases.
*/
union {
void *msg_control;
void __user *msg_control_user;
};
bool msg_control_is_user : 1;
__kernel_size_t msg_controllen; /* ancillary data buffer length */
unsigned int msg_flags; /* flags on received message */
struct kiocb *msg_iocb; /* ptr to iocb for async requests */
};
msg_name:不是消息名,他是指向sockaddr_in结构的指针。(sockaddr_in包含IP地址和端口)
msg_namelen:指出了sockaddr_in结构的长度。
msg_iter:有效负荷。
msg_control:msg_control是用于所有其他情况的内核缓冲区;
msg_control_user:在设置msg_control_is_user时用于recv*端的用户缓冲区。
msg_control_is_user:(这里为1,暂时未debug,后面补上)
msg_controllen:辅助数据缓冲区长度。
以上四个字段规定的缓冲区可以用于传递特定协议的控制消息,具体的控制消息参见recv()。
msg_flags:接收到的消息上的标志。
内核会考虑如下标志位:
MSG_DONTROUTE:目标地址再局域网内,表示规定了不能通过路由。
MSG_DONTWAIT:防止系统调用阻塞。
MSG_ERRQUEUE:套接字无法得到包,只会收到一条详细的出错消息。
内核返回给用户进程的标志位:
MSG_TRUNC:表示用于接收的缓冲空间不足,因此会丢失一些包数据。
这四个不是所有的标志位,更详细的参考系统调用手册
msg_iocb:PTR到iocb的异步请求。(这里暂不拓展讲解,本文以了解udp协议特性为主)
UDP数据报
udphdr
struct udphdr {
__be16 source;
__be16 dest;
__be16 len;
__sum16 check;
};
四个unsigned short类型一共8字节
可以在net/ipv4/udp.c中看到相关校验和的操作
校验和相关的安全措施较多,最为典型的校验和计算入口为
static inline u16 udp_csum(u32 saddr, u32 daddr, u32 len,u8 proto, u16 *udp_pkt);
UDP至网络体系结构的集成
UDP有两个接口:
1.上行到应用层的接口,PF_INET协议族的套接字所构造。
2.下行到网络层的接口,
到应用层的接口
net/ipv4/udp.c中定义了一个proto
struct proto udp_prot = {
.name = "UDP",
.owner = THIS_MODULE,
.close = udp_lib_close,
.pre_connect = udp_pre_connect,
.connect = ip4_datagram_connect,
.disconnect = udp_disconnect,
.ioctl = udp_ioctl,
.init = udp_init_sock,
.destroy = udp_destroy_sock,
.setsockopt = udp_setsockopt,
.getsockopt = udp_getsockopt,
.sendmsg = udp_sendmsg,
.recvmsg = udp_recvmsg,
.sendpage = udp_sendpage,
.release_cb = ip4_datagram_release_cb,
.hash = udp_lib_hash,
.unhash = udp_lib_unhash,
.rehash = udp_v4_rehash,
.get_port = udp_v4_get_port,
.memory_allocated = &udp_memory_allocated,
.sysctl_mem = sysctl_udp_mem,
.sysctl_wmem_offset = offsetof(struct net, ipv4.sysctl_udp_wmem_min),
.sysctl_rmem_offset = offsetof(struct net, ipv4.sysctl_udp_rmem_min),
.obj_size = sizeof(struct udp_sock),
.h.udp_table = &udp_table,
.diag_destroy = udp_abort,
};
EXPORT_SYMBOL(udp_prot);
到ip层的接口
net/ipv4/af_inet.中定义了一个含有用于UDP的 net_protocol结构
static struct net_protocol udp_protocol = {
.early_demux = udp_v4_early_demux,
.early_demux_handler = udp_v4_early_demux,
.handler = udp_rcv,
.err_handler = udp_err,
.no_policy = 1,
.netns_ok = 1,
};
UDP数据报收发
UDP发送包:从套接字接口处的系统调用将包添加到网络接口的输出队列中。
UDP接收包:将UDP包放入套接字的接收队列中,用户进程通过系统调用从队列中收取包。
UDP数据报的发送
int udp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len);
sk:带有PF_INET发送方套接字状态的sock接口
msg:传递有效负荷
len:发送包的长度
UDP数据报的接受
udp_rcv()