zoukankan      html  css  js  c++  java
  • 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
    ");
    		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.发包中没有包含校验和的操作,只含有数据拼接和发送数据前的准备工作。

  • 相关阅读:
    树莓派基于scratch2控制GPIO
    一次修复linux的efi引导的集中方法总结记录
    linux(deepin) 下隐藏firefox标题栏
    log4j 1.2 配置和使用简述
    在非gnome系桌面环境下运行deepin-wine tim的错误解决
    manjaro AwesomeWM 上使用双显示器
    linux 关闭主板上的蜂鸣器声音
    anki的使用以及anki server的配置
    阅读《人类简史》-- 1.认知革命
    java生成zip包兼容Linux
  • 原文地址:https://www.cnblogs.com/still-smile/p/14929937.html
Copyright © 2011-2022 走看看