L4层的协议会把数据通过ip_append_data或ip_append_page把数据线放在缓冲区,然后再显示调用ip_push_pending_frames传送数据。
把数据放在缓冲区有两个优点,一方面,缓冲区的数据可以被后续的一些函数使用,构成一些片段;另一方面,把数据放缓冲区,等缓冲区满了(达到PMTU)再传送数据,可以更有效率。
如果在一些情况下,L4层希望去放在缓冲区的数据立即被传输,那么在调用ip_append_data把数据放缓冲区后,立即调用ip_push_pending_frames进行传输。

ip_push_pending_frames:其作用为将输出队列上的多个片段合成一个完整的ip数据报文,并通过ip_output输出;此函数相当于一个notify函数,当4层决定传输帧到ip层的时候,他就需要调用这个函数.通过前面我们知道此时所有的数据(如果不支持Scatter/Gather I/O),都在sk_write_queue中;

根据是否使用分散聚集I/O 封包数据的组织和在sk_buf结构中的组织会有所不同:

int ip_push_pending_frames(struct sock *sk, struct flowi4 *fl4)
{
struct sk_buff *skb; skb = ip_finish_skb(sk, fl4);
if (!skb)
return 0; /* Netfilter gets whole the not fragmented skb. */
return ip_send_skb(sock_net(sk), skb);
}
static inline struct sk_buff *ip_finish_skb(struct sock *sk, struct flowi4 *fl4)
{
return __ip_make_skb(sk, fl4, &sk->sk_write_queue, &inet_sk(sk)->cork.base);
}

函数__ip_make_skb除了填充L3头外,还对skb的组织进行了修改:

所有的大小都会在第一个skb中体现,即本例中skb->len=load+L3+L4
后续调用ip_finish_output进行发送

__ip_make_skb 简析

如果路径rt开启了PMTU 且没有被锁定ip_dont_fragment
 IP_PMTUDISC_DO  路径mtu发现开启   则禁止分片

/*
* Combined all pending IP fragments on the socket as one IP datagram
* and push them out.
*/
struct sk_buff *__ip_make_skb(struct sock *sk,
struct flowi4 *fl4,
struct sk_buff_head *queue,
struct inet_cork *cork)
{
struct sk_buff *skb, *tmp_skb;
struct sk_buff **tail_skb;
struct inet_sock *inet = inet_sk(sk);
struct net *net = sock_net(sk);
struct ip_options *opt = NULL;
struct rtable *rt = (struct rtable *)cork->dst;
struct iphdr *iph;
__be16 df = 0;
__u8 ttl; skb = __skb_dequeue(queue);/* 发送队列可能为空 */
if (!skb)
goto out;
tail_skb = &(skb_shinfo(skb)->frag_list); /* 获得分片链表 */ /* move skb->data to ip header from ext header */
if (skb->data < skb_network_header(skb)) /* 调整data指针位置 */
__skb_pull(skb, skb_network_offset(skb));
while ((tmp_skb = __skb_dequeue(queue)) != NULL) { /* 调整所有发送缓冲中的sk_buff的data指针位置,并更新第一个sk_buff的数据长度 */
__skb_pull(tmp_skb, skb_network_header_len(skb));
*tail_skb = tmp_skb;
tail_skb = &(tmp_skb->next);
skb->len += tmp_skb->len;
skb->data_len += tmp_skb->len;
skb->truesize += tmp_skb->truesize;
tmp_skb->destructor = NULL;
tmp_skb->sk = NULL;
} /* Unless user demanded real pmtu discovery (IP_PMTUDISC_DO), we allow
* to fragment the frame generated here. No matter, what transforms
* how transforms change size of the packet, it will come out.
*/
skb->ignore_df = ip_sk_ignore_df(sk); /* DF bit is set when we want to see DF on outgoing frames.
* If ignore_df is set too, we still allow to fragment this frame
* locally. */
if (inet->pmtudisc == IP_PMTUDISC_DO ||
inet->pmtudisc == IP_PMTUDISC_PROBE ||
(skb->len <= dst_mtu(&rt->dst) &&
ip_dont_fragment(sk, &rt->dst))) /* 不允许分片,或者不需要分片 */
df = htons(IP_DF); if (cork->flags & IPCORK_OPT)/* ip option 保存在cork中, 则使用cork中的option */
opt = cork->opt; if (cork->ttl != 0)/* 选择合适的TTL值 */
ttl = cork->ttl;
else if (rt->rt_type == RTN_MULTICAST)
ttl = inet->mc_ttl;
else
ttl = ip_select_ttl(inet, &rt->dst); iph = ip_hdr(skb);/* 得到IP报文头的地址 */
iph->version = 4;
iph->ihl = 5;
iph->tos = (cork->tos != -1) ? cork->tos : inet->tos;
iph->frag_off = df;
iph->ttl = ttl;
iph->protocol = sk->sk_protocol;
ip_copy_addrs(iph, fl4);
ip_select_ident(net, skb, sk); if (opt) { /*
填充IP option
看到这里,可以发现opt只可能从cork中获得
*/
iph->ihl += opt->optlen>>2;
ip_options_build(skb, opt, cork->addr, rt, 0);
} skb->priority = (cork->tos != -1) ? cork->priority: sk->sk_priority;
skb->mark = sk->sk_mark;
/*
* Steal rt from cork.dst to avoid a pair of atomic_inc/atomic_dec
* on dst refcount
*/
cork->dst = NULL;
skb_dst_set(skb, &rt->dst); if (iph->protocol == IPPROTO_ICMP)
icmp_out_count(net, ((struct icmphdr *)
skb_transport_header(skb))->type); ip_cork_release(cork);
out:
return skb;
}
/* Output packet to network from transport.  */
static inline int dst_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{
   /*
     * 如果是单播数据包,设置的是ip_output(),
     * 如果是组播数据包,设置的是ip_mc_output().dev_queue_xmit
     */
    return skb_dst(skb)->output(net, sk, skb);
}

int __ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)
{
struct iphdr *iph = ip_hdr(skb); iph->tot_len = htons(skb->len);
ip_send_check(iph);
return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT,
net, sk, skb, NULL, skb_dst(skb)->dev,
dst_output);
} int ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)
{
int err; err = __ip_local_out(net, sk, skb);
if (likely(err == 1))
err = dst_output(net, sk, skb); return err;
}

IP 层收发报文简要剖析6--ip报文输出3 ip_push_pending_frames的更多相关文章

  1. IP 层收发报文简要剖析3--ip输入报文分片重组

    在ip_local_deliver中,如果检测到是分片包,则需要将报文进行重组.其所有的分片被重新组合后才能提交到上层协议,每一个被重新组合的数据包文用ipq结构实例来表示 struct ipq { ...

  2. IP 层收发报文简要剖析5--ip报文发送2

    udp 发送ip段报文接口ip_append_data ip_append_data 函数主要用来udp 套接字以及raw套接字发送报文的接口.在tcp中发送ack 以及rest段的ip_send_u ...

  3. IP 层收发报文简要剖析2--ip报文的输入ip_local_deliver

    ip报文根据路由结果:如果发往本地则调用ip_local_deliver处理报文:如果是转发出去,则调用ip_forward 处理报文. 一.ip报文转发到本地: /* * Deliver IP Pa ...

  4. IP 层收发报文简要剖析1-ip报文的输入

    ip层数据包处理场景如下: 网络层处理数据包文时需要和路由表以及邻居系统打交道.输入数据时,提供输入接口给链路层调用,并调用传输层的输入接口将数据输入到传输层. 在输出数据时,提供输出接口给传输层,并 ...

  5. IP 层收发报文简要剖析4--ip 报文发送

    无论是从本地输出的数据还是转发的数据报文,经过路由后都要输出到网络设备,而输出到网络设备的接口就是dst_output(output)函数 路由的时候,dst_output函数设置为ip_output ...

  6. IP 层收发报文简要剖析6--ip_forward 报文转发

    //在函数ip_route_input_slow->ip_mkroute_input注册, /* * IP数据包的转发是由ip_forward()处理,该函数在ip_rcv_finish() * ...

  7. TCP层的分段和IP层的分片之间的关系 & MTU和MSS之间的关系 (转载)

    首先说明:数据报的分段和分片确实发生,分段发生在传输层,分片发生在网络层.但是对于分段来说,这是经常发生在UDP传输层协议上的情况,对于传输层使用TCP协议的通道来说,这种事情很少发生. 1,MTU( ...

  8. 原 TCP层的分段和IP层的分片之间的关系 & MTU和MSS之间的关系

    首先说明:数据报的分段和分片确实发生,分段发生在传输层,分片发生在网络层.但是对于分段来说,这是经常发生在UDP传输层协议上的情况,对于传输层使用TCP协议的通道来说,这种事情很少发生. 1,MTU( ...

  9. Linux 网卡驱动学习(六)(应用层、tcp 层、ip 层、设备层和驱动层作用解析)

    本文将介绍网络连接建立的过程.收发包流程,以及当中应用层.tcp层.ip层.设备层和驱动层各层发挥的作用. 1.应用层 对于使用socket进行网络连接的server端程序.我们会先调用socket函 ...

随机推荐

  1. nginx的脚本引擎(二)rewrite

    其实rewrite指令和上一篇说的if/set/return/break之类的没多大差别,但是rewrite用起来相对复杂,我就把他单独放到了这里.想要弄懂nginx的脚本引擎需要先明白处理reque ...

  2. 分析如何直接绕过超时代VPY视频播放器的播放密码

    声明:仅技术交流和学习! 前言: 你有没试过在网上下载一套视频,因网盘限速整整开机下载好几晚,下完后打开发现加密了,又找不到卖家注册.心里是几万只草泥马飞奔啊. 于是不甘心和好奇下,偿试自己破解. 目 ...

  3. pytest文档44-allure.dynamic动态生成用例标题

    前言 pytest 结合 allure 描述用例的时候我们一般使用 @allure.title 和 @allure.description 描述测试用例的标题和详情. 在用例里面也可以动态更新标题和详 ...

  4. RDS 事务型数据库sql

    -- 替换json中数据 select SUBSTRING_INDEX(SUBSTRING_INDEX('[{"channelCode":"MOBIL",&qu ...

  5. logback.xml demo

    如何关闭 org.apache.zookeeper.clientcnxn 的(控制台大量输出)debug 日志 1.在项目resources路径下新建 logback.xml 2.然后把下面的代码co ...

  6. jmeter_04_常用取样器

    目录 常用取样器详解 http取样器 1.1 基本配置 1.2 高级配置 jdbc取样器 2.1 JDBC Connection Configuration 2.1.1 **Variable Name ...

  7. spring boot:spring security给用户登录增加自动登录及图形验证码功能(spring boot 2.3.1)

    一,图形验证码的用途? 1,什么是图形验证码? 验证码(CAPTCHA)是"Completely Automated Public Turing test to tell Computers ...

  8. 对接天猫精灵X1 (https 的申请)

    1 起因 公司是做智能家居的,最近公司要求对接天猫精灵的智能家居功能,所以就来对接天猫精灵X1 了. 新产品,大家都懂的,坑是有不少的,正常事. 1 首先,语言是 c#,不要和我讲 php 是世界最好 ...

  9. ValidatorException-异常

    Java在发送Https请求的时候,不可避免的会发生SSL证书认证失败 错误信息:sun.security.validator.ValidatorException: PKIX path buildi ...

  10. Mac下使用GitHub+Hexo搭建个人博客

    首发链接 开始之前需要在电脑上安装好Git和node.js,Mac上可以使用Homebrew命令行工具来安装Git和node.js 安装Homebrew 在命令行工具输入以下命令,如果已经安装过Hom ...