深入理解Linux网络技术内幕——IPv4 报文的传输发送

来源:互联网 发布:软件测试基础视频 编辑:程序博客网 时间:2024/05/16 18:50

报文传输,指的是报文离开本机,发往其他系统的过程。

传输可以由L4层协议发起,也可以由报文转发发起。

在深入理解Linux网络技术内幕——IPv4 报文的接收(转发与本地传递)一文中,我们可以看到,报文转发最后会调用dst_output与邻居子系统进行交互,然后传给设备驱动程序。 这里,我们从L4层协议发起的传输,最后也会经历这一过程(调用dst_output)。本文讨论的是L4层协议发起的传输,在IPv4协议处理(IP层)中的一些环节。

大蓝图

我们先看下传输环节的大蓝图,以便对传输这一过程有大概的过程。

我们看到L4层协议(如TCP、UDP),以及一些特殊的三层协议(ICMP,RAW IP等)最终都会调用dst_output来将报文传给驱动程序。
调用dst_output之前的处理可以分为图中四种情形(不考虑报文转发发起的传输)。
case1a 和case1b主要针对(UDP、ICMP、RAWIP),分别调用ip_append_data和ip_append_page(其实是ip_append_data的变种),来将报文保存在缓冲区中(先不传输),待到缓冲区需要刷新时,才通过ip_push_pending_frames末尾间接调用dst_output来完成传输工作。
case2 面对TCP和SCTP,会直接调用ip_queue_xmit处理报文,然后调用dst_output。
case3 针对RAWIP和IGMP,直接调用dst_output。

上面的分类知识针对一般情况,也有一些特殊情形,比如TCP在需要发送ACK和RESET报文,会使用ip_send_reply,并间接调用ip_append_data和ip_push_pending_frames。TCP在传输ACK SYN时,也会调用ip_build_and_send_pkt。

传输环节-内核的主要任务

1.查询下一跳点 ——涉及路由子系统
2.初始化IP报头 ——填写一些字段
3.处理选项 ——设置一些需要的选项(博主其它博文会进行介绍)
4.分段 ——IP包太大时,传输前必须分段
5.检验和 ——
6.Netfilter检查 ——
7.更新统计数据 ——

ip_queue_xmit情形

ip_queue_xmit是TCP和SCTP所使用的函数。


//由tcp、sctp使用//skb:封包描述符                                                                                                                                           //ipfragok: sctp使用的标志,指明是否可以分段int ip_queue_xmit(struct sk_buff *skb, int ipfragok){    struct sock *sk = skb->sk;    struct inet_sock *inet = inet_sk(sk); //要通过的套接字    struct ip_options_rcu *inet_opt = NULL;    struct rtable *rt;     struct iphdr *iph;    int res;     /* Skip all of this if the packet is already routed,     * f.e. by something like SCTP.     */    rcu_read_lock();    rt = skb_rtable(skb); //如果缓冲区已经设置了正确的路由信息,就不需要查找路由表了    if (rt != NULL)        goto packet_routed;    /* Make sure we can route this packet. */    rt = (struct rtable *)__sk_dst_check(sk, 0);    inet_opt = rcu_dereference(inet->inet_opt); //选项初始化    if (rt == NULL) {        __be32 daddr;        /* Use correct destination address if we have options. */        daddr = inet->daddr;        if (inet_opt && inet_opt->opt.srr)            daddr = inet_opt->opt.faddr;        {            struct flowi fl = { .oif = sk->sk_bound_dev_if,                        .mark = sk->sk_mark,                        .nl_u = { .ip4_u =                              { .daddr = daddr,                            .saddr = inet->saddr,                            .tos = RT_CONN_FLAGS(sk) } },                        .proto = sk->sk_protocol,                        .flags = inet_sk_flowi_flags(sk),                        .uli_u = { .ports =                               { .sport = inet->sport,                             .dport = inet->dport } } };            /* If this fails, retransmit mechanism of transport layer will             * keep trying until route appears or the connection times             * itself out.             */            security_sk_classify_flow(sk, &fl);            if (ip_route_output_flow(sock_net(sk), &rt, &fl, sk, 0))                goto no_route;        }        sk_setup_caps(sk, &rt->u.dst);    }    skb_dst_set(skb, dst_clone(&rt->u.dst));packet_routed:    if (inet_opt && inet_opt->opt.is_strictroute && rt->rt_dst != rt->rt_gateway)        goto no_route;    /* OK, we know where to send it, allocate and build IP header. */    //把skb-》data往回移动,使其指向ip报头(而不是数据段)    skb_push(skb, sizeof(struct iphdr) + (inet_opt ? inet_opt->opt.optlen : 0));    skb_reset_network_header(skb);    /* 构建ip报头*/    iph = ip_hdr(skb);     *((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));    if (ip_dont_fragment(sk, &rt->u.dst) && !ipfragok)        iph->frag_off = htons(IP_DF);    else        iph->frag_off = 0;    iph->ttl      = ip_select_ttl(inet, &rt->u.dst);    iph->protocol = sk->sk_protocol;    iph->saddr    = rt->rt_src;    iph->daddr    = rt->rt_dst;    /* Transport layer set skb->h.foo itself. */    if (inet_opt && inet_opt->opt.optlen) {        iph->ihl += inet_opt->opt.optlen >> 2;        ip_options_build(skb, &inet_opt->opt, inet->daddr, rt, 0);    }    ip_select_ident_more(iph, &rt->u.dst, sk,                 (skb_shinfo(skb)->gso_segs ?: 1) - 1);    skb->priority = sk->sk_priority;    skb->mark = sk->sk_mark;    res = ip_local_out(skb);                                                                                                                                    rcu_read_unlock();    return res;no_route:    rcu_read_unlock();    IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);    kfree_skb(skb);    return -EHOSTUNREACH;}


ip_push_pending_frames的情形

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


ip_append_data

ip_append_data主要有以下几项任务:
1. 组织缓冲区。把L4层的报文数据组织到缓冲区,使这些缓冲区能够更好的处理分段。也能让L2、L3在稍后能够更容易添加报头。
2. 优化内存分配 。这里要考虑到上层协议信息,以及设备出口的传输能力。
3. 处理L4检验和。



ip_append_data 这部分的内容还没完全搞明白,最近没时间细看了,以后有空了再来更新,先Mark下。



IPv4报文的传输最后调用dst_output,然后简介调用ip_finish_output2与邻居子系统进行交互。最终调用dev_queue_xmit把数据报传递给设备驱动程序。



























0 0