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()
原文地址:https://www.cnblogs.com/still-smile/p/14925569.html