udp_sendmsg源码完整分析(基于linux5.12.13版本内核)

源码分析

int udp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
{
	/*套接字的网络层表示转换成INET套接字的表示*/
	struct inet_sock *inet = inet_sk(sk);
	/*套接字的网络层表示转换成UDP套接字的表示*/
	struct udp_sock *up = udp_sk(sk);
	/*struct sockaddr_in * usin = ({ do { } while (0); (struct sockaddr_in *) msg->msg_name; })获取ip地址和端口*/
	DECLARE_SOCKADDR(struct sockaddr_in *, usin, msg->msg_name);
	struct flowi4 fl4_stack;
	struct flowi4 *fl4;
	int ulen = len;
	struct ipcm_cookie ipc;
	struct rtable *rt = NULL;
	int free = 0;
	int connected = 0;
	__be32 daddr, faddr, saddr;
	__be16 dport;
	u8  tos;
	/*获取pcflag标志确定该套接字是普通的UDP套接字还是UDP轻量级套接字*/
	int err, is_udplite = IS_UDPLITE(sk);
	/*如果 up->corkflag> 0,则将套接字标记为 UDP-Lite,后半段判断是否还需要发送更多消息*/
	int corkreq = up->corkflag || msg->msg_flags&MSG_MORE;
	int (*getfrag)(void *, char *, int, int, int, struct sk_buff *);
	struct sk_buff *skb;
	struct ip_options_data opt_copy;
	/*UDP数据报最长为64KB*/
	if (len > 0xFFFF)
		return -EMSGSIZE;

	/*
	 *	Check the flags.
	 */

	/*UDP不支持发送带外数据,如果发送标志中设置了MSG_OOB,则返回*/
	if (msg->msg_flags & MSG_OOB) /* Mirror BSD error message compatibility */
		return -EOPNOTSUPP;

	/*udp和udplite使用不同的getfrag*/
	getfrag = is_udplite ? udplite_getfrag : ip_generic_getfrag;

	fl4 = &inet->cork.fl.u.ip4;/*struct flowi4类型,*/
	if (up->pending) {
		/*
		 * There are pending frames.
		 * The socket lock must be held while it's corked.
		 */
		 /*当前的sock有等待发送的数据,直接将数据追加*/
		lock_sock(sk);
		/*likely和unlikely对程序逻辑没影响,likely提示编译器括号内的内容为真的概率更大,unlikely相反*/
		if (likely(up->pending)) {
			if (unlikely(up->pending != AF_INET)) {
				/* 和lock_sock成对出现,一个加锁,一个解锁*/
				release_sock(sk);
				return -EINVAL;
			}
			/*up->pending为AF_INET时候,直接跳转到数据发送,这里进行的了第一次数据发送后的数据发送*/
			goto do_append_data;
		}
		release_sock(sk);
	}
	
	/*接下来的都是第一次发包时的操作,UDP数据报长度,包括UDP data + UDP header*/
	ulen += sizeof(struct udphdr);

	/*
	 *	Get and verify the address.
	 */
	 /*获取目的IP地址和端口:目的地址和端口有两个可能的来源:
	1. 如果之前socket已经建立,那socket本身就存储了目标地址;
	2. 地址通过msghdr传入,通常为调用sendto发送UDP数据*/
	if (usin) {
		if (msg->msg_namelen < sizeof(*usin))
			return -EINVAL;
		if (usin->sin_family != AF_INET) {
			if (usin->sin_family != AF_UNSPEC)
				return -EAFNOSUPPORT;
		}

		daddr = usin->sin_addr.s_addr;
		dport = usin->sin_port;
		/*目的端口不能为零*/
		if (dport == 0)
			return -EINVAL;
	} else {
		/*msg没有目的地址的情况:通常为先调用了connect,然后调用send发送UDP数据,
		UDP套接字调用connetc之后,UDP传输控制块状态为TCP_ESTABLISHED*/
		if (sk->sk_state != TCP_ESTABLISHED)/*即没有指明目的地址,又没有建立connect连接,则返错。*/
			return -EDESTADDRREQ;
		daddr = inet->inet_daddr;
		dport = inet->inet_dport;
		/* Open fast path for connected socket.
		   Route will not be used, if at least one option is set.
		 */
		 /*为连接的套接字打开快速路径。如果至少设置了一个选项,则不会使用路由。*/
		connected = 1;
	}
	
	/*获取存储在 socket 上的源地址、发送网络设备索引(device index)和时间戳选项*/
	ipcm_init_sk(&ipc, inet);
	/*保留了当套接字被解锁时创建 UDP 标头的信息。*/
	ipc.gso_size = up->gso_size;
	/*msg中控制信息处理*/
	if (msg->msg_controllen) {
		/*如果msg_controllen辅助缓冲区中有数据,则消息发送*/
		err = udp_cmsg_send(sk, msg, &ipc.gso_size);
		if (err > 0)
			/*调用ip_cmsg_send处理控制信息,包括IP选项等...*/
			err = ip_cmsg_send(sk, msg, &ipc,
					   sk->sk_family == AF_INET6);
		if (unlikely(err < 0)) {
			kfree(ipc.opt);
			return err;
		}
		if (ipc.opt)
			free = 1;
		/*这里表示不进行路由*/
		connected = 0;
	}
	/*如果发送的数据中没有IP选项控制信息,则从正在使用的socket中获取IP选项信息*/
	if (!ipc.opt) {
		struct ip_options_rcu *inet_opt;

		rcu_read_lock();
		inet_opt = rcu_dereference(inet->inet_opt);
		if (inet_opt) {
			memcpy(&opt_copy, inet_opt,
			       sizeof(*inet_opt) + inet_opt->opt.optlen);
			ipc.opt = &opt_copy.opt;
		}
		rcu_read_unlock();
	}

	if (cgroup_bpf_enabled(BPF_CGROUP_UDP4_SENDMSG) && !connected) {
		err = BPF_CGROUP_RUN_PROG_UDP4_SENDMSG_LOCK(sk,
					    (struct sockaddr *)usin, &ipc.addr);
		if (err)
			goto out_free;
		if (usin) {
			if (usin->sin_port == 0) {
				/* BPF program set invalid port. Reject it. */
				/*linux中不允许目的端口为0*/
				err = -EINVAL;
				goto out_free;
			}
			daddr = usin->sin_addr.s_addr;
			dport = usin->sin_port;
		}
	}

	saddr = ipc.addr;
	ipc.addr = faddr = daddr;

	if (ipc.opt && ipc.opt->opt.srr) {
		if (!daddr) {
			err = -EINVAL;
			goto out_free;
		}
		faddr = ipc.opt->opt.faddr;
		/*这里表示不进行路由,进行本地路由*/
		connected = 0;
	}
	tos = get_rttos(&ipc, inet);
	if (sock_flag(sk, SOCK_LOCALROUTE) ||
	    (msg->msg_flags & MSG_DONTROUTE) ||
	    (ipc.opt && ipc.opt->opt.is_strictroute)) {
		tos |= RTO_ONLINK;
		//SOCK_LOCALROUTE, /* route locally only, %SO_DONTROUTE setting */
		//MSG_DONTROUTE,顾名思义,消息不路由
		//ipc.opt && ipc.opt->opt.is_strictroute同时不为零
		/*这里表示不进行路由,以上3种情况只进行本地路由*/
		connected = 0;
	}

	//ip地址为224.*.*.*时
	if (ipv4_is_multicast(daddr)) {
		if (!ipc.oif || netif_index_is_l3_master(sock_net(sk), ipc.oif))
			ipc.oif = inet->mc_index;
		if (!saddr)
			saddr = inet->mc_addr;
		connected = 0;
	} else if (!ipc.oif) {
		ipc.oif = inet->uc_index;
	} else if (ipv4_is_lbcast(daddr) && inet->uc_index) {/*ip地址如果为255.255.255.255*/
		/* oif is set, packet is to local broadcast and
		 * uc_index is set. oif is most likely set
		 * by sk_bound_dev_if. If uc_index != oif check if the
		 * oif is an L3 master and uc_index is an L3 slave.
		 * If so, we want to allow the send using the uc_index.
		 */
		 /*oif 设置,数据包到本地广播并设置 uc_index。 oif 很可能由 sk_bound_dev_if 设置。 如果 uc_index != oif 检查 oif 是否是 L3 master 并且 uc_index 是否是 L3 slave。 如果是这样,我们希望允许使用 uc_index 发送。*/
		if (ipc.oif != inet->uc_index &&
		    ipc.oif == l3mdev_master_ifindex_by_index(sock_net(sk),
							      inet->uc_index)) {
			ipc.oif = inet->uc_index;
		}
	}
	if (connected)
		/*目标路由检查*/
		rt = (struct rtable *)sk_dst_check(sk, 0);
	//如果没有路由,建立一次路由
	if (!rt) {
		struct net *net = sock_net(sk);
		__u8 flow_flags = inet_sk_flowi_flags(sk);

		fl4 = &fl4_stack;

		flowi4_init_output(fl4, ipc.oif, ipc.sockc.mark, tos,
				   RT_SCOPE_UNIVERSE, sk->sk_protocol,
				   flow_flags,
				   faddr, saddr, dport, inet->inet_sport,
				   sk->sk_uid);

		security_sk_classify_flow(sk, flowi4_to_flowi_common(fl4));
		rt = ip_route_output_flow(net, fl4, sk);
		if (IS_ERR(rt)) {
			err = PTR_ERR(rt);
			rt = NULL;
			if (err == -ENETUNREACH)
				IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);
			goto out;
		}

		err = -EACCES;
		if ((rt->rt_flags & RTCF_BROADCAST) &&
		    !sock_flag(sk, SOCK_BROADCAST))
			goto out;
		if (connected)
			sk_dst_set(sk, dst_clone(&rt->dst));
	}

	//msg_flags为确认路径有效性的话,跳转到do_confirm
	if (msg->msg_flags&MSG_CONFIRM)
		goto do_confirm;
back_from_confirm:

	saddr = fl4->saddr;
	if (!ipc.addr)
		daddr = ipc.addr = fl4->daddr;

	/* Lockless fast path for the non-corking case. */
	if (!corkreq) {
		struct inet_cork cork;

		skb = ip_make_skb(sk, fl4, getfrag, msg, ulen,
				  sizeof(struct udphdr), &ipc, &rt,
				  &cork, msg->msg_flags);
		err = PTR_ERR(skb);
		if (!IS_ERR_OR_NULL(skb))
			err = udp_send_skb(skb, fl4, &cork);
		goto out;
	}

	lock_sock(sk);
	if (unlikely(up->pending)) {
		/* The socket is already corked while preparing it. */
		/* ... which is an evident application bug. --ANK */
		release_sock(sk);
		//这里表示程序错误导致阻塞,将不会发送数据
		net_dbg_ratelimited("socket already corked\n");
		err = -EINVAL;
		goto out;
	}
	/*
	 *	Now cork the socket to pend data.
	 */
	fl4 = &inet->cork.fl.u.ip4;
	fl4->daddr = daddr;
	fl4->saddr = saddr;
	fl4->fl4_dport = dport;
	fl4->fl4_sport = inet->inet_sport;
	up->pending = AF_INET;

do_append_data:
	up->len += ulen;
	/*udp组ip包*/
	err = ip_append_data(sk, fl4, getfrag, msg, ulen,
			     sizeof(struct udphdr), &ipc, &rt,
			     corkreq ? msg->msg_flags|MSG_MORE : msg->msg_flags);
	if (err)
		udp_flush_pending_frames(sk);
	else if (!corkreq)
		err = udp_push_pending_frames(sk);
	else if (unlikely(skb_queue_empty(&sk->sk_write_queue)))
		up->pending = 0;
	release_sock(sk);

out:
	ip_rt_put(rt);//等效release_sock(sk);
out_free:
	if (free)
		kfree(ipc.opt);//释放内存,内部将ipc.opt==NULL
	if (!err)
		return len;//处理过程没有错误,返回已发送的字节数
	/*
	 * ENOBUFS = no kernel mem, SOCK_NOSPACE = no sndbuf space.  Reporting
	 * ENOBUFS might not be good (it's not tunable per se), but otherwise
	 * we don't have a good statistic (IpOutDiscards but it can be too many
	 * things).  We could add another new stat but at least for now that
	 * seems like overkill.
	 */
	 /*ENOBUFS = 无内核内存,SOCK_NOSPACE = 无 sndbuf 空间。 报告 ENOBUFS 可能不好(它本身不可调),但除此之外我们没有很好的统计数据(IpOutDiscards,但它可能太多了)。 我们可以添加另一个新的统计数据,但至少现在这似乎有点矫枉过正。*/
	if (err == -ENOBUFS || test_bit(SOCK_NOSPACE, &sk->sk_socket->flags)) {
		UDP_INC_STATS(sock_net(sk),
			      UDP_MIB_SNDBUFERRORS, is_udplite);
	}
	return err;

do_confirm:
	if (msg->msg_flags & MSG_PROBE)//MSG_PROBE表示不发送,仅探测路径
		dst_confirm_neigh(&rt->dst, &fl4->daddr);
	if (!(msg->msg_flags&MSG_PROBE) || len)
		goto back_from_confirm;
	err = 0;
	goto out;
}
EXPORT_SYMBOL(udp_sendmsg);

总结

1.参数sk:套接字的网络层表示,msg:传递有效负荷,len:数据字节长度不包含udphdr
2.首先获取到目标地址和端口,原地址和端口,目的端口不能为0,目标地址为广播地址或者开头为224操作不同。
3.虽然是面向无连接的,但是需要起始点到目的地之间的链路,发送数据必须有一条路由缓存。如果目的地址改变,则重新建立一条路由并获取缓存。
4.数据包过大时分包,第一次发送数据和第二次发送数据的逻辑不同
5.发包中没有包含校验和的操作,只含有数据拼接和发送数据前的准备工作。

posted @ 2021-06-25 10:47  Smah  阅读(934)  评论(0编辑  收藏  举报