linux内核中TCP接收的实现入口函数是tcp_v4_rcv

1. 数据包检查处理

一开始做一些数据包详细检查处理,一旦出错,可能导致内核挂掉

int tcp_v4_rcv(struct sk_buff *skb)
{const struct iphdr *iph;struct tcphdr *th;struct sock *sk;int ret;struct net *net = dev_net(skb->dev);/****************************1. 数据包正确性检查**************************************//*非本机数据包丢弃*/if (skb->pkt_type != PACKET_HOST)goto discard_it;/* Count it even if it's bad */TCP_INC_STATS_BH(net, TCP_MIB_INSEGS);/*检测TCP协议头正确性*/if (!pskb_may_pull(skb, sizeof(struct tcphdr)))goto discard_it;th = tcp_hdr(skb);/*检查doff数据域正确性*/if (th->doff < sizeof(struct tcphdr) / 4)goto bad_packet;if (!pskb_may_pull(skb, th->doff * 4))goto discard_it;/* An explanation is required here, I think.* Packet length and doff are validated by header prediction,* provided case of th->doff==0 is eliminated.* So, we defer the checks. *//*检查csum正确性*/if (!skb_csum_unnecessary(skb) && tcp_v4_checksum_init(skb))goto bad_packet;/****************************2. 保存协议头信息**************************************/th = tcp_hdr(skb);iph = ip_hdr(skb);TCP_SKB_CB(skb)->seq = ntohl(th->seq);TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +skb->len - th->doff * 4);TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);TCP_SKB_CB(skb)->when    = 0;TCP_SKB_CB(skb)->flags  = iph->tos;TCP_SKB_CB(skb)->sacked   = 0;/*.....*/
}

2. 数据包处理流程

int tcp_v4_rcv(struct sk_buff *skb)
{/*...*//****************************3. 查看数据段是否属于某个套接字***************************************//*_inet_lookup_skb函数来查看接收到的数据包是否属于某个打开的套接字,查看的依据是接收数据包的网络接口、源端口号和目的端口号。如果数据段属于某个套接字,则将sk变量设置为指向打开的套接字的数据结构,接着继续处理数据段。*/sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);if (!sk)goto no_tcp_socket;/****************************4. 数据段的处理***************************************/
process:/*如果套接字的连接状态为TIME_WAIT,以特殊的方式立即处理数据包*/if (sk->sk_state == TCP_TIME_WAIT)goto do_time_wait;if (unlikely(iph->ttl < inet_sk(sk)->min_ttl)) {NET_INC_STATS_BH(net, LINUX_MIB_TCPMINTTLDROP);goto discard_and_relse;}/* IPsec策略检查和网络过滤。如果内核配置使用了IPsec协议栈,则对数据包进行IPsec策略检查,此项检查由网络过滤子系统完成。如果未通过检查,则扔掉数据包。*/if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))goto discard_and_relse;nf_reset(skb);if (sk_filter(sk, skb))goto discard_and_relse;skb->dev = NULL;/*在开始将数据向套接字传送前,首先要获取防止并发访问套接字的锁。*/bh_lock_sock_nested(sk);ret = 0;if (!sock_owned_by_user(sk)) { /*如果锁定套接字成功,则将数据段放入prequeue队列中。一旦数据段放入prequeue队列后,就由用户程序来处理数据,而不是由内核进程来处理数据。这样作为TCP提供最高的执行效率,能将核进程和用户进程之间执行现场的切换最小化。*/
#ifdef CONFIG_NET_DMAstruct tcp_sock *tp = tcp_sk(sk);if (!tp->ucopy.dma_chan && tp->ucopy.pinned_list)tp->ucopy.dma_chan = dma_find_channel(DMA_MEMCPY);if (tp->ucopy.dma_chan)ret = tcp_v4_do_rcv(sk, skb);else
#endif{if (!tcp_prequeue(sk, skb)) /*数据段放入prequeue队列,走fast path*/ret = tcp_v4_do_rcv(sk, skb); /*如果放入不成功,tcp_v4_do_rcv,走slow path*/}} else if (unlikely(sk_add_backlog(sk, skb))) {    /*如果获取套接字的保护锁不成功,说明有其他进程锁定了套接字,这时套接字不能接收其他数据段,则调用sk_add_backlog函数将输入段放入backlog queue队列中。*/bh_unlock_sock(sk);NET_INC_STATS_BH(net, LINUX_MIB_TCPBACKLOGDROP);goto discard_and_relse;}bh_unlock_sock(sk);sock_put(sk);return ret;/*未打开的套接字,做RST处理*/
no_tcp_socket:if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))goto discard_it;if (skb->len < (th->doff << 2) || tcp_checksum_complete(skb)) {bad_packet:TCP_INC_STATS_BH(net, TCP_MIB_INERRS);} else {tcp_v4_send_reset(NULL, skb);}discard_it:/* Discard frame. */kfree_skb(skb);return 0;discard_and_relse:sock_put(sk);goto discard_it;/*如果套接字的状态是TIME_WAIT, 做特殊处理*/
do_time_wait:if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {inet_twsk_put(inet_twsk(sk));goto discard_it;}if (skb->len < (th->doff << 2) || tcp_checksum_complete(skb)) {TCP_INC_STATS_BH(net, TCP_MIB_INERRS);inet_twsk_put(inet_twsk(sk));goto discard_it;}switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {case TCP_TW_SYN: {struct sock *sk2 = inet_lookup_listener(dev_net(skb->dev),&tcp_hashinfo,iph->daddr, th->dest,inet_iif(skb));if (sk2) {inet_twsk_deschedule(inet_twsk(sk), &tcp_death_row);inet_twsk_put(inet_twsk(sk));sk = sk2;goto process;}/* Fall through to ACK */}case TCP_TW_ACK:tcp_v4_timewait_ack(sk, skb);break;case TCP_TW_RST:goto no_tcp_socket;case TCP_TW_SUCCESS:;}goto discard_it;
}

3. Fast Path和prequeue队列的处理

Linux TCP/IP协议栈中,在TCP层有两条路径处理输入数据包:“Fast Path” 和“SlowPath”。

“Fast Path”是内核优化TCP处理输入数据包的方式。当TCP协议实例收到一个数据包后,它首先通过协议头来预定向数据包的去处:“Fast Path”或“Slow Path”。

如果将数据包放入“Fast Path”处理,则需要满足以下条件:

  • 收到的数据段中包含的是数据,而不是ACK。
  • 数据段是顺序传送数据中的一个完整数据段,接收顺序正确

满足以上条件的数据段会放入prequeue队列中,这时用户进程被唤醒,在prequeue队列中的数据段就由用户层的进程来处理,这个过程省略很多“Slow Path”处理中的步骤,从而加大了数据吞吐量。

tcp_prequeue函数完成将Socket Buffer放入prequeue队列的功能。

static inline int tcp_prequeue(struct sock *sk, struct sk_buff *skb)
{struct tcp_sock *tp = tcp_sk(sk);/*如果用户空间有进程在等待接收数据段,ucopy.task非空*/if (sysctl_tcp_low_latency || !tp->ucopy.task)return 0;__skb_queue_tail(&tp->ucopy.prequeue, skb);tp->ucopy.memory += skb->truesize;/*如果prequeue队列的长度>套接字接收缓冲区的长度,将skb放入backlog queue走slow path*/if (tp->ucopy.memory > sk->sk_rcvbuf) {struct sk_buff *skb1;BUG_ON(sock_owned_by_user(sk));while ((skb1 = __skb_dequeue(&tp->ucopy.prequeue)) != NULL) {sk_backlog_rcv(sk, skb1);NET_INC_STATS_BH(sock_net(sk),LINUX_MIB_TCPPREQUEUEDROPPED);}tp->ucopy.memory = 0;} else if (skb_queue_len(&tp->ucopy.prequeue) == 1) {/*如果prequeue队列上有一个skb,就唤醒用户进程接收数据*/wake_up_interruptible_sync_poll(sk_sleep(sk),POLLIN | POLLRDNORM | POLLRDBAND);if (!inet_csk_ack_scheduled(sk))inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,(3 * tcp_rto_min(sk)) / 4,TCP_RTO_MAX);}return 1;
}

4. slow path和Backlog队列

相对于“Fast Path”处理过程,TCP的Backlog队列处理,即“Slow Path”是常规输入数据包的处理方式。
将输入数据包放入backlog queue队列的前提条件是:

  • 输入数据包中包含的是数据段,不是ACK段。
  • 数据段完好无损。
  • 套接字缓冲区已满或套接字被别的用户进程占用。

这个过程需要的处理步骤较多,一旦数据包缓冲区放入队列,套接字就被唤醒,进程调度器(scheduler)调度用户进程,开始从Backlog queue队列中读取数据包缓冲区。

“SlowPath”的处理过程由tcp_v4_do_rcv函数完成。

int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{struct sock *rsk;/*如果套接字是established状态,可能可以走fast path,由tcp_rcv_established完成*/if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */sock_rps_save_rxhash(sk, skb->rxhash);TCP_CHECK_TIMER(sk);if (tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len)) {rsk = sk;goto reset;}TCP_CHECK_TIMER(sk);return 0;}/*检查数据包正确性*/if (skb->len < tcp_hdrlen(skb) || tcp_checksum_complete(skb))goto csum_err;/*如果收到的是一个有效的SYN,且处于坚挺状态,tcp_v4_hnd_req创建一个新的套接字*/if (sk->sk_state == TCP_LISTEN) {struct sock *nsk = tcp_v4_hnd_req(sk, skb);if (!nsk)goto discard;/*原套接字sk继续侦听,调用tcp_child_process函数在子套接字nsk上处理接收*/if (nsk != sk) {if (tcp_child_process(sk, nsk, skb)) {rsk = nsk;goto reset;}return 0;}} elsesock_rps_save_rxhash(sk, skb->rxhash);/*tcp_rcv_state_process函数处理套接字的常规状态切换。*/TCP_CHECK_TIMER(sk);if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {rsk = sk;goto reset;}TCP_CHECK_TIMER(sk);return 0;reset:tcp_v4_send_reset(rsk, skb);
discard:kfree_skb(skb);/* Be careful here. If this function gets more complicated and* gcc suffers from register pressure on the x86, sk (in %ebx)* might be destroyed here. This current version compiles correctly,* but you have been warned.*/return 0;csum_err:TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);goto discard;
}

5. 用户态接收数据

当用户进程通过获得信号得知在打开的套接字上有数据等待用户进程来接收时,用户进程调用receive或read系统调用来读取套接字缓冲区中的数据。

当这些系统调用将读取的数据传送到套接字层时,转而会调用tcp_recvmsg函数来执行具体的传送操作。

tcp_recvmsg函数从打开的套接字上将数据复制到用户缓冲区。

数据包接收的顺序可以假设为3个队列:backlog队列,prequeue队列,常规接收队列。每个队列,只有在前面的队列数据包处理完后才能处理

linux内核中TCP接收的实现相关推荐

  1. linux做预警机制,预警通告:Linux内核中TCP SACK机制远程DoS

    漏洞描述 2019年6月18日,RedHat官网发布报告:安全研究人员在Linux内核处理TCP SACK数据包模块中发现了三个漏洞,CVE编号为CVE-2019-11477.CVE-2019-114 ...

  2. Linux内核中TCP协议实现的关键数据结构

    1. TCP协议头tcphdr TCP协议头描述了TCP数据段发送的源地址.目标地址.数据段传送管理和连接管理的信息,是TCP协议实现的重要数据结构之一. struct tcphdr {__be16 ...

  3. linux内核中TCP发送的实现

    TCP发送功能是指将从应用层通过打开的套接字写入的数据移入内核,通过TCP/IP协议栈,最终通过网络设备发送到远端接收主机. TCP传送的特点如下: 异步传送:TCP的实际传送独立于应用层. 汇集从套 ...

  4. linux程序获取透传参数,Linux内核中TCP SACK处理流程分析

    frankzfz2014-07-27 17:32 demo121:frankzfz您好: 我想请教一个问题,就是将写好的GenericApp项目(没有配置工具),我加入zigbee协议栈的配置工具后还 ...

  5. TCP三次握手在linux内核中的实现

    TCP三次握手在linux内核中的实现 以下基于linux内核2.4.0源码(转自www.yuanma.org/) 以前一直使用的网络通讯的函数都是工作在阻塞模式.在看connect实现源码时,突然想 ...

  6. TCP/IP协议栈在Linux内核中的运行时序分析

    本文主要是讲解TCP/IP协议栈在Linux内核中的运行时序,文章较长,里面有配套的视频讲解,建议收藏观看. 1 Linux概述 1.1 Linux操作系统架构简介 Linux操作系统总体上由Linu ...

  7. linux下IPROTO_TCP,TCP/IP协议栈在Linux内核中的运行时序分析

    可选题目三:TCP/IP协议栈在Linux内核中的运行时序分析 在深入理解Linux内核任务调度(中断处理.softirg.tasklet.wq.内核线程等)机制的基础上,分析梳理send和recv过 ...

  8. TCP/IP网络协议栈在Linux内核中的如何使用丨内核开发丨驱动开发丨操作系统丨内核源码

    TCP/IP网络协议栈在Linux内核中的如何使用 视频讲解如下,点击观看: TCP/IP网络协议栈在Linux内核中的如何使用丨内核开发丨驱动开发丨操作系统丨内核源码 C/C++Linux服务器开发 ...

  9. 简单谈一点linux内核中套接字的bind机制--数据结构以及端口确定

    众所周知,创建一个套接字可以bind到一个特定的ip地址和端口,实际上套接字这一概念代表了TCP/IP协议栈的应用层标识,协议栈中的应用层就是通过一个ip地址和一个端口号标识的,当然这仅仅是对于TCP ...

最新文章

  1. 《C++游戏编程入门(第4版)》——2.4 使用带else子句的if语句序列
  2. Vector反向迭代器使用
  3. CentOS6.5下用yum安装 git .
  4. 《虚拟化技术原理与实现》读书笔记之前序
  5. NPOI 设置合并后的单元格的边框的解决方法
  6. 中国剩余定理 —— 入门
  7. 中小型研发团队架构实践:应用监控怎么做?
  8. 精通CSS:高级Web标准解决方案(中文电子书下载)
  9. python的shutil模块
  10. python_统计数组中指定范围的数据占的比例
  11. 获取并反编译微信小程序源码(仅供学习)
  12. python拆分excel列_python自动化办公:实现按照一列内容拆分excel
  13. CentOS安装打字游戏,typespeed
  14. 持续测试(Continuous Testing)
  15. 完美解决:“已损坏,无法打开。 您应该将它移到废纸篓。”
  16. Pyrene-PEG3400-NHS,琥珀酰亚胺酯(NHS)官能化的芘-PEG
  17. 孤尽31天-day02
  18. matlab教程 for循环,Matlab for循环使用操作教程分享
  19. 【Hawk】高级教程——post参数采集万方医学网论文
  20. Python中的切片

热门文章

  1. 多线程是并行还是并发_并发,并行,线程,进程,异步和同步有相关性吗?
  2. java冒泡排序经典代码6_经典排序算法之冒泡排序
  3. 2 亚马逊_索泰称仅亚马逊渠道就收到2万块RTX 3080显卡订单,无法按时发货
  4. ifound Android wifi,方正新品记录仪iFound V1号称黑夜变白天,真的假的?
  5. 替换掉(取消掉)pip freeze 生成的@ file:///格式,变为正常的==版本号
  6. python取两个列表的并集、交集、差集
  7. R语言快速学习第一部分(有其他语言基础)
  8. pandas标记一列为时间序列
  9. python程序代码图片_完整的图片去噪代码(python)
  10. .net Core 介绍