IP 层收发报文简要剖析6--ip报文输出3 ip_push_pending_frames

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;
}

原文地址:https://www.cnblogs.com/codestack/p/9265886.html

时间: 2024-08-30 05:52:21

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

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

在ip_local_deliver中,如果检测到是分片包,则需要将报文进行重组.其所有的分片被重新组合后才能提交到上层协议,每一个被重新组合的数据包文用ipq结构实例来表示 struct ipq { struct inet_frag_queue q; u32 user;//分片来源 __be32 saddr;//原地址 __be32 daddr;//目的地址 __be16 id;//ip报文序列号 u8 protocol;//上层协议号 //这四个字段来自ip首部是为了确定来自哪个ip数据报文

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

ip报文根据路由结果:如果发往本地则调用ip_local_deliver处理报文:如果是转发出去,则调用ip_forward 处理报文. 一.ip报文转发到本地: /* * Deliver IP Packets to the higher protocol layers. */ /* * 在ip_route_input_noref进行路由选择后,如果接收的包 * 是发送给本机,则调用ip_local_deliver来传递给上层协议 */ int ip_local_deliver(struct s

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

ip层数据包处理场景如下: 网络层处理数据包文时需要和路由表以及邻居系统打交道.输入数据时,提供输入接口给链路层调用,并调用传输层的输入接口将数据输入到传输层. 在输出数据时,提供输出接口给传输层,并调用链路层的输出接口将数据输出到链路层.在输入输出数据时,需要查找路由表 通过netfiler处理等操作. 一.ip数据报文输入 ip_rcv &ip_rcv_finish &ip_rcv_finish2 1.在inet_init中注册了类型为ETH_P_IP协议的数据包的回调ip_rcv 2

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

无论是从本地输出的数据还是转发的数据报文,经过路由后都要输出到网络设备,而输出到网络设备的接口就是dst_output(output)函数 路由的时候,dst_output函数设置为ip_output ip_mc_output等 1.TCP输出接口 L4 层在发送数据时会根据协议的不同调用上面提到的几个辅助函数之一,tcp协议打包成ip数据包文的方法根据tcp段的不同而选择不同的接口, 其中ip_queue_xmit为常用接口,ip_build_and_send_pkt.ip_send_repl

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

//在函数ip_route_input_slow->ip_mkroute_input注册, /* * IP数据包的转发是由ip_forward()处理,该函数在ip_rcv_finish() * 通过输入路由缓存被调用. */ int ip_forward(struct sk_buff *skb) { u32 mtu; struct iphdr *iph; /* Our header */ struct rtable *rt; /* Route we use */ struct ip_optio

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

本文将介绍网络连接建立的过程.收发包流程,以及当中应用层.tcp层.ip层.设备层和驱动层各层发挥的作用. 1.应用层 对于使用socket进行网络连接的server端程序.我们会先调用socket函数创建一个套接字: fd = socket(AF_INET, SOCK_STREAM, 0); 以上指定了连接协议,socket调用返回一个文件句柄,与socket文件相应的inode不在磁盘上,而是存在于内存. 之后我们指定监听的port.同意与哪些ip建立连接,并调用bind完毕port绑定:

老斜两宗事-七层代理模式还是IP层VPN

1.七层代理模式还是IP层VPN 很多人会问,我到底是使用代理模式呢,还是使用VPN模式,如果我想数据在中间不安全的链路上实现加密保护的话.这个问题有一个背景,那就是,你想保护你的数据,可以使用VPN,但是有时候,第七层的代理模式或许更好,比如SSL卸载器,比如内置SSL处理的代理,分为正向代理和反向代理.正向代理:代理的是访问者.一般位于访问者一端,访问者能意识到代理的存在,直接访问代理,由代理向服务器发起访问.反向代理:反向代理代理的是被访问者.位于被访问者一端,访问者意识不到代理的存在,访

第二十二章 TCP/IP层的实现

                      第二十二章    TCP/IP层的实现        我比较喜欢先难后易,如果把GPU显示管理.和网络管理拿下后:我会从头整理.改写一遍APO操作系统.这样,就会形成APO操作系统的锥形.也获得了全局观.内核CPU线路.和用户CPU线路,你可以将它们看成是独立的2个32位CPU核:内核CPU主要任务是实时处理.硬件中断,256个实时线程包含了一些中断程序的后半部.用户CPU主要是动态优先级进程.线程调度,各种应用程序的运行:2个核之间是通过消息交互.句

用Netcat,SSH构建的IP层加密隧道搭建VPN

[关于题外话在最后] 写作本文主要基于两点,首先是因为我前段时间写了几篇关于VPN的新解,收到了很多的邮件反馈,我也思考了很多,另一个方面是因为很多人问我怎么用QQ,P2P搭建一个IP层的VPN,我的回答是"我也不知道".我确实不知道,根本就没有试过,只是有个这样那样的想法...我主要是没有能力去Hack这些非Linux上的东西...所以说,我写这篇文章,用UNIX的方法"将多个小工具结合起来"实现我的那些没有实现的想法,抛砖引玉一下. 声明: 本文没有技术含量,甚