内核版本: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;}} elseUDP_INC_STATS_USER(sock_net(sk),UDP_MIB_OUTDATAGRAMS, is_udplite);return err;
}

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

参考文档:

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

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

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

  1. tcp/ip 协议栈Linux内核源码分析15 udp套接字接收流程二

    内核版本:3.4.39 上篇我们分析了UDP套接字如何接收数据的流程,最终它是在内核套接字的接收队列里取出报文,剩下的问题就是谁会去写入这个队列,当然,这部分工作由内核来完成,本篇剩下的文章主要分析内 ...

  2. tcp/ip 协议栈Linux内核源码分析12 udp套接字发送流程一

    内核版本:3.4.39 因为过往的开发工作中既包括内核网络层模块的开发,又包括应用层程序的开发,所以对于网络数据的通信有那么一些了解.但是对于网络通信过程中,内核和应用层之间接口是如何运作的不是很清楚 ...

  3. tcp/ip 协议栈Linux内核源码分析14 udp套接字接收流程一

    内核版本:3.4.39 前面两篇文章分析了UDP套接字从应用层发送数据到内核层的处理流程,这里继续分析相反的流程,看看数据是怎么从内核送到应用层的. 与发送类似,内核也提供了多个接收数据的系统调用接口 ...

  4. tcp/ip 协议栈Linux内核源码分析九 IPv6分片ip6_fragment 分析

    内核版本:3.4.39 IPv6的分片流程和IPv4基本一致,这一点内核源码作者也说了.流程比较简单,分片的时候判断是否满足快速分片,满足的话直接一个接一个加上分片扩展选项发送出去,不满足的话就只能走 ...

  5. tcp/ip 协议栈Linux内核源码分析十 邻居子系统分析一 概述通用邻居框架

    内核版本:3.4.39 为什么需要邻居子系统呢?因为在网络上发送报文的时候除了需要知道目的IP地址还需要知道邻居的L2 mac地址,为什么是邻居的L2地址而不是目的地的L2地址呢,这是因为目的地网络可 ...

  6. tcp/ip 协议栈Linux内核源码分析11 邻居子系统分析二 arp协议的实现处理

    内核版本:3.4.39 内核邻居子系统定义了一个基本的框架,使得不同的邻居协议可以共用一套代码.比起其它的内核模块,邻居子系统框架代码还是比较简单易懂的.邻居子系统位于网络层和流量控制子系统中间,它提 ...

  7. tcp/ip 协议栈Linux内核源码分析六 路由子系统分析一路由缓存

    内核版本:3.4.39 收到报文或者发送报文的时候都需要查找路由表,频繁的路由表查找操作时需要耗费一部分CPU的,Linux提供了路由缓存来减少路由表的查询,路由缓存由hash表组织而成,路由缓存的初 ...

  8. tcp/ip 协议栈Linux内核源码分析八 路由子系统分析三 路由表

    内核版本:3.4.39 Linux路由子系统代码量虽说不是很多,但是难度还是有的,最近在分析路由子系统这一块,对它的框架有了基本的了解,如果要想掌握的话估计还得再花点时间阅读代码,先把框架记录下来.路 ...

  9. tcp/ip 协议栈Linux内核源码分析七 路由子系统分析二 策略路由

    内核版本:3.4.39 策略路由就是根据配置策略查找路由表,早期的Linux版本是不支持策略路由的,默认的查找策略就是先查找local路由表,找不到再继续查找main表,当支持策略路由功能时,内核最多 ...

最新文章

  1. 中国发展研究基金会联合百度发布智能经济白皮书:新基建是助燃剂,其势已成...
  2. 做自适应网站专业乐云seo_乐云分享新站SEO优化实践经验,收录和排名持续稳定上升方...
  3. 常用的函数式接口_Function接口_默认方法andThen
  4. 线程池参数详解_java中常见的六种线程池详解
  5. 关于内存的划分和传引用传参数的区别
  6. assistant字体_如何使用Google Assistant设置和致电家庭联系人
  7. AQS(AbstractQuenedSynchronizer)详解
  8. 【吴恩达课后编程作业pytorch实现】Keras入门与残差网络的搭建【1】
  9. Redhat(Linux)上的JBoss管理配置
  10. python locals_Python locals()
  11. JAVA----数组(一)
  12. Object-C——三大特性之多态
  13. ios 设置字体家族
  14. c语言程序ppt课件,c语言ppt课件
  15. 【深度相机系列二】深度相机原理揭秘--飞行时间(TOF)
  16. 华为交换机导入配置_华为交换机配置导入和导出
  17. 加油,我看好你 本题由擂主Wfox提供 -flag{bc57380e-9f8d-4b1e-8432-794b54b5625f}
  18. RT-Thread学习笔记【网络设备与BSD套接字组件】
  19. [前端案例]百行代码实现炫酷时钟
  20. win7回收站右键没有清空回收站选项

热门文章

  1. 缺陷漏测分析:测试过程改进
  2. CentOS 6网络配置
  3. wpf展开树节点_【转】WPF TreeView如何展开到某个节点
  4. CUDA,C++,Java,Python,Fortran运行速度比较
  5. 2.18 Logistic 损失函数的解释-深度学习-Stanford吴恩达教授
  6. 二 DeepinV20版本安装
  7. 计算机专业好的211大学6,计算机专业好的985大学有哪些?附985211计算机大学名单排名...
  8. 基于MATLAB的波速形成仿真
  9. 利用腾讯云为你的域名申请并配置免费SSL一年
  10. 做小程序费用太高?帮你选一个最省钱的方案