tcp/ip 协议栈Linux内核源码分析13 udp套接字发送流程二

内核版本:3.4.39

继续UDP套接字发送,上一篇讲到了sock_sendmsg,这里继续,下面是sock_sendmsg的相关代码

int sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
{
	/* kiocb为内核通用的IO请求结构 */
	struct kiocb iocb;
	struct sock_iocb siocb;
	int ret;

	/* 初始化同步的内核IO请求结构 */
	init_sync_kiocb(&iocb, NULL);
	iocb.private = &siocb;
	/* 发送消息 */
	ret = __sock_sendmsg(&iocb, sock, msg, size);
	/* 返回结果表明该消息已经加入队列,要等待完成事件 */
	if (-EIOCBQUEUED == ret)
		ret = wait_on_sync_kiocb(&iocb);
	return ret;
}
EXPORT_SYMBOL(sock_sendmsg)

这里__sock_sendmsg只是做了安全性检查,然后就调用了__sock_sendmsg_nosec函数。

static inline int __sock_sendmsg(struct kiocb *iocb, struct socket *sock,
				 struct msghdr *msg, size_t size)
{
	int err = security_socket_sendmsg(sock, msg, size);

	return err ?: __sock_sendmsg_nosec(iocb, sock, msg, size);
}

再继续看__sock_sendmsg_nosec,代码如下:

static inline int __sock_sendmsg_nosec(struct kiocb *iocb, struct socket *sock,
				       struct msghdr *msg, size_t size)
{
	/* 获得套接字在sock_sendmsg中设置的IO请求, */
	struct sock_iocb *si = kiocb_to_siocb(iocb);

	sock_update_classid(sock->sk);

	sock_update_netprioidx(sock->sk);
	
	/* 初始化套接字的IO请求字段 */
	si->sock = sock;
	si->scm = NULL;
	si->msg = msg;
	si->size = size;

	/* 根据不同的套接字类型,调用其发送数据函数 */
	return sock->ops->sendmsg(iocb, sock, msg, size);
}

 到此,我们完成了数据包从用户空间到内核空间的流程跟踪。接下来的数据包发送过程,将根据不同的协议,走不同的流程。

我们分析UDP的发送,UDP的sendmsg操作函数为udp_sendmsg,代码如下:

int udp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
		size_t len)
{
	/* 从inet通用套接字得到inet套接字 */
	struct inet_sock *inet = inet_sk(sk);

	/* 从inet通用套接字得到UDP套接字 */
	struct udp_sock *up = udp_sk(sk);
	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;
	int err, is_udplite = IS_UDPLITE(sk);
	/* 是否有数据包聚合:或者UDP套接字设置了聚合选项,或者数据包消息指明了还有更多数据
	UDP_CORK 或者 MSG_MORE,表示使用单个数据包发送多个数据
	*/
	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;

	/* 数据包长度检查 */	
	if (len > 0xFFFF)
		return -EMSGSIZE;

	/*
	 *	Check the flags.
	 */
	/* 检查消息标志,UDP不支持带外数据 *
	if (msg->msg_flags & MSG_OOB) /* Mirror BSD error message compatibility */
		return -EOPNOTSUPP;

	ipc.opt = NULL;
	ipc.tx_flags = 0;
	
	/* 设置正确的分片函数 */
	getfrag = is_udplite ? udplite_getfrag : ip_generic_getfrag;

	fl4 = &inet->cork.fl.u.ip4;
	if (up->pending) {
		/*
		 * There are pending frames.
		 * The socket lock must be held while it's corked.
		 */
		/* 该UDP套接字还有待发的数据包 */ 
		lock_sock(sk);
		 /*  常见的上锁双重检查机制 */
		if (likely(up->pending)) {
			/* 若待发的数据不是INET数据,则报错返回 */
			if (unlikely(up->pending != AF_INET)) {
				release_sock(sk);
				return -EINVAL;
			}
			/* 调到追加数据处 */
			goto do_append_data;
		}
		release_sock(sk);
	}
	ulen += sizeof(struct udphdr);

	/*
	 *	Get and verify the address.
	 */
	if (msg->msg_name) {
		/* 若指定了目标地址,则对其进行校验 */
		struct sockaddr_in * usin = (struct sockaddr_in *)msg->msg_name;
		/* 检查长度 */
		if (msg->msg_namelen < sizeof(*usin))
			return -EINVAL;
		/* 检查协议族。目前只支持AF_INET和AF_UNSPEC协议族 */
		if (usin->sin_family != AF_INET) {
			if (usin->sin_family != AF_UNSPEC)
				return -EAFNOSUPPORT;
		}
		/* 若通过了检查,则设置目的地址与目的端口 */
		daddr = usin->sin_addr.s_addr;
		dport = usin->sin_port;
		/* 目的端口不能为0 */
		if (dport == 0)
			return -EINVAL;
	} else {
		/* 如果没有指定目的地址和目的端口,则当前套接字的状态必须是已连接,即已经调用过connect设置了目的地址 */
		if (sk->sk_state != TCP_ESTABLISHED)
			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;
	}
	ipc.addr = inet->inet_saddr;

	ipc.oif = sk->sk_bound_dev_if;
	/* 设置时间戳标志 */
	err = sock_tx_timestamp(sk, &ipc.tx_flags);
	if (err)
		return err;
	/* 发送的消息包含控制数据 */
	if (msg->msg_controllen) {
	 	/* 虽然这个函数的名字叫作send,其实并没有任何发送动作,而只是将控制消息设置到ipc中 */
		err = ip_cmsg_send(sock_net(sk), msg, &ipc);
		if (err)
			return err;
		/* 设置释放ipc.opt的标志 */
		if (ipc.opt)
			free = 1;
		connected = 0;
	}
	if (!ipc.opt) {
		/* 如果没有使用控制消息指定IP选项,则检查套接字的IP选项设置。如果有,则使用套接字的IP选项 */
		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();
	}

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

	if (ipc.opt && ipc.opt->opt.srr) {
		/* 设置了严格路由 */
		if (!daddr)
			return -EINVAL;
		faddr = ipc.opt->opt.faddr;
		connected = 0;
	}
	/*
    若有下列情况之一的:
    1)套接字设置了本地路由标志。
    2)发送消息时,指明了不做路由。
    3)设置了IP严格路由选项。
    则设置不查找路由标志
    */
	tos = RT_TOS(inet->tos);
	if (sock_flag(sk, SOCK_LOCALROUTE) ||
	    (msg->msg_flags & MSG_DONTROUTE) ||
	    (ipc.opt && ipc.opt->opt.is_strictroute)) {
		tos |= RTO_ONLINK;
		connected = 0;
	}

	/* 如果目的地址是多播地址 */
	if (ipv4_is_multicast(daddr)) {
		/* 若未指定出口接口,则使用套接字的多播接口索引 */
		if (!ipc.oif)
			ipc.oif = inet->mc_index;
		/* 若源地址为0,则使用套接字的多播地址 */	
		if (!saddr)
			saddr = inet->mc_addr;
		connected = 0;
	} else if (!ipc.oif)
		ipc.oif = inet->uc_index;

	/* 连接标志为真,即此次发送的数据包与上次的地址相同,则判断保存的路由缓存是否还可用。*/
	if (connected)
		/* 从套接字检查并获得保存的路由缓存 */
		rt = (struct rtable *)sk_dst_check(sk, 0);

	/* 若目前路由缓存为空,则需要查找路由 */
	if (rt == NULL) {
		struct net *net = sock_net(sk);

		fl4 = &fl4_stack;
		/* 根据套接字和数据包的信息,初始化flowi4—这是查找路由的key */
		flowi4_init_output(fl4, ipc.oif, sk->sk_mark, tos,
				   RT_SCOPE_UNIVERSE, sk->sk_protocol,
				   inet_sk_flowi_flags(sk)|FLOWI_FLAG_CAN_SLEEP,
				   faddr, saddr, dport, inet->inet_sport,
				   sock_i_uid(sk));
		/* 查找出口路由 */
		security_sk_classify_flow(sk, flowi4_to_flowi(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_BH(net, IPSTATS_MIB_OUTNOROUTES);
			goto out;
		}

		err = -EACCES;
		/* 若路由是广播路由,并且套接字非广播套接字 */
		if ((rt->rt_flags & RTCF_BROADCAST) &&
		    !sock_flag(sk, SOCK_BROADCAST))
			goto out;

		/* 若该UDP为已连接状态,则保存这个路由缓存 */	
		if (connected)
			sk_dst_set(sk, dst_clone(&rt->dst));
	}
	
	/* 如果数据包设置了MSG_CONFIRM标志,则是要告诉链路层,对端是可达的。调到do_confrim处,可
		  以发现其实现方法是在有neibour信息的情况下,直接更新neibour确认时间戳为当前时间。 */
	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. */
	/* 没有使用cork选项或MSG_MORE标志。这也是最常见的情况。 */
	if (!corkreq) {
		/* 每次都生成一个UDP数据包 */
		skb = ip_make_skb(sk, fl4, getfrag, msg->msg_iov, ulen,
				  sizeof(struct udphdr), &ipc, &rt,
				  msg->msg_flags);
		err = PTR_ERR(skb);
		/* 成功生成了数据包 */
		if (skb && !IS_ERR(skb))
			/* 发送UDP数据包 */
			err = udp_send_skb(skb, fl4);
		goto out;
	}

	lock_sock(sk);
	if (unlikely(up->pending)) {
		/* The socket is already corked while preparing it. */
		/* ... which is an evident application bug. --ANK */
		 /*
        现在马上要做cork处理,但发现套接字已经cork了。
        因此这是一个应用程序bug。释放套接字锁,并返回错误。
        */
		release_sock(sk);

		LIMIT_NETDEBUG(KERN_DEBUG pr_fmt("cork app bug 2\n"));
		err = -EINVAL;
		goto out;
	}
	/*
	 *	Now cork the socket to pend data.
	 */
	/* 设置cork中的流信息 */ 
	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:
	/* 增加UDP数据长度 */
	up->len += ulen;
	 /* 向IP数据包中追加新的数据 */
	err = ip_append_data(sk, fl4, getfrag, msg->msg_iov, 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)// 若不在cork即阻塞,则发送所有未决的数据包
		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);
	if (free)
		kfree(ipc.opt);
	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.
	 */
	if (err == -ENOBUFS || test_bit(SOCK_NOSPACE, &sk->sk_socket->flags)) {
		UDP_INC_STATS_USER(sock_net(sk),
				UDP_MIB_SNDBUFERRORS, is_udplite);
	}
	return err;

do_confirm:
	dst_confirm(&rt->dst);
	if (!(msg->msg_flags&MSG_PROBE) || len)
		goto back_from_confirm;
	err = 0;
	goto out;
}
EXPORT_SYMBOL(udp_sendmsg);

一般情况下,在使用UDP发送数据包时很少会使用CORK或MSG_MORE标志,因为我们希望在每次调用发送接口时,就发送一次UDP数据包。因此可以不必考虑CORK和MSG_MORE的情况,而继续追踪udp_send_skb函数。 

static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4)
{
	struct sock *sk = skb->sk;
	struct inet_sock *inet = inet_sk(sk);
	struct udphdr *uh;
	int err = 0;
	int is_udplite = IS_UDPLITE(sk);
	int offset = skb_transport_offset(skb);
	int len = skb->len - offset;
	__wsum csum = 0;

	/*
	 * Create a UDP header
	 */
	/* 创建UDP报文头部 */ 
	uh = udp_hdr(skb);
	uh->source = inet->inet_sport;
	uh->dest = fl4->fl4_dport;
	uh->len = htons(len);
	uh->check = 0;

	/*如果是轻量级UDP协议,则调用相应的校验和计算函数。
	* 轻量级UDP协议简单说就是可以校验指定长度的数据长度而不是全部
	* 减少数据丢弃分线。具体google下
	  */
	if (is_udplite)  				 /*     UDP-Lite      */
		csum = udplite_csum(skb);
	/* 禁止了UDP校验和 */
	else if (sk->sk_no_check == UDP_CSUM_NOXMIT) {   /* UDP csum disabled */

		skb->ip_summed = CHECKSUM_NONE;
		goto send;

	} else if (skb->ip_summed == CHECKSUM_PARTIAL) { /* UDP hardware csum */
		/* 硬件支持校验和的计算 */

		udp4_hwcsum(skb, fl4->saddr, fl4->daddr);
		goto send;

	} else
		/* 一般情况下的校验和计算 */
		csum = udp_csum(skb);

	/* add protocol-dependent pseudo-header */
	/* 计算UDP的校验和,需要考虑伪首部 */
	uh->check = csum_tcpudp_magic(fl4->saddr, fl4->daddr, len,
				      sk->sk_protocol, csum);
	/* 如果校验和为0,则需要将其设置为0xFFFF。因为UDP的零校验和,有特殊的含义,表示没有校验和。*/
	if (uh->check == 0)
		uh->check = CSUM_MANGLED_0;

send:
	/* 发送IP数据包 */
	err = ip_send_skb(skb);
	if (err) {
		if (err == -ENOBUFS && !inet->recverr) {
			UDP_INC_STATS_USER(sock_net(sk),
					   UDP_MIB_SNDBUFERRORS, is_udplite);
			err = 0;
		}
	} else
		UDP_INC_STATS_USER(sock_net(sk),
				   UDP_MIB_OUTDATAGRAMS, is_udplite);
	return err;
}

至此,UDP已经完成了自己的工作,后面的发送工作将交由IP层来负责。 

参考文档:

1. 《Linux环境编程:从应用到内核》

2.  浅析Linux网络子系统(一) 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值