TCP/IP协议栈-ipv4

ipv4协议是当今基于标准的Internet中的核心协议之一,大部分的流量都是靠它传输的。本章记录IPv4数据包的接收、发送和转发,以及IPv4选项的处理。

IP报头

IPv4数据包的开头都是一个IP报头,其长度不少于20字节。如果使用了IP选项,IPv4报头最长可达到60字节。由上图可看出IP报头分为两部分,第一部分为IPv4报头,长20字节;第二部分为IP选项,长度为0-40字节

  • ihl:表示Internet报头长度,最短20字节,最长60字节,且必须为4字节的整数倍
  • version:必须为4
  • tos:表示服务类型。
  • id:IPv4报头标识。对SKB进行分段时,所有分段的id值必须相同
  • ttl:存活时间。每经历一个转发节点,ttl将会减1,当ttl值为0时,丢弃数据包,并发回一条ICMPv4超时消息,以避免数据包因某种原因而被无休止转发
  • protocol:数据包第四层协议,IPPROTO_TCP表示TCP,IPPROTO_UDP表示UDP流量
  • check:校验和,长16位
  • saddr:源IPv4地址,长32位
  • daddr:目标IPv4地址,长32位

IPv4初始化

static struct packet_type ip_packet_type __read_mostly = {
    .type = cpu_to_be16(ETH_P_IP),
    .func = ip_rcv,
};

static int __init inet_init(void)
{
    struct inet_protosw *q;
    struct list_head *r;
    int rc = -EINVAL;

    BUILD_BUG_ON(sizeof(struct inet_skb_parm) > FIELD_SIZEOF(struct sk_buff, cb));

    sysctl_local_reserved_ports = kzalloc(65536 / 8, GFP_KERNEL);
    if (!sysctl_local_reserved_ports)
        goto out;

    rc = proto_register(&tcp_prot, 1);
    if (rc)
        goto out_free_reserved_ports;

    rc = proto_register(&udp_prot, 1);
    if (rc)
        goto out_unregister_tcp_proto;

    rc = proto_register(&raw_prot, 1);
    if (rc)
        goto out_unregister_udp_proto;

    rc = proto_register(&ping_prot, 1);
    if (rc)
        goto out_unregister_raw_proto;
    /*
     *  Tell SOCKET that we are alive...
     */
    (void)sock_register(&inet_family_ops);

#ifdef CONFIG_SYSCTL
    ip_static_sysctl_init();
#endif
    tcp_prot.sysctl_mem = init_net.ipv4.sysctl_tcp_mem;
    /*
     *  Add all the base protocols.
     */
    if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)
        pr_crit("%s: Cannot add ICMP protocoln", __func__);
    if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
        pr_crit("%s: Cannot add UDP protocoln", __func__);
    if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)
        pr_crit("%s: Cannot add TCP protocoln", __func__);
#ifdef CONFIG_IP_MULTICAST
    if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0)
        pr_crit("%s: Cannot add IGMP protocoln", __func__);
#endif

    /* Register the socket-side information for inet_create. */
    for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
        INIT_LIST_HEAD(r);

    for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
        inet_register_protosw(q);
    /*
     *  Set the ARP module up
     */
    arp_init();
    /*
     *  Set the IP module up
     */
    ip_init();
    tcp_v4_init();
    /* Setup TCP slab cache for open requests. */
    tcp_init();
    /* Setup UDP memory threshold */
    udp_init();
    /* Add UDP-Lite (RFC 3828) */
    udplite4_register();
    ping_init();
    /*
     *  Set the ICMP layer up
     */

    if (icmp_init() < 0)
        panic("Failed to create the ICMP control socket.n");

    /*
     *  Initialise the multicast router
     */
#if defined(CONFIG_IP_MROUTE)
    if (ip_mr_init())
        pr_crit("%s: Cannot init ipv4 mrouten", __func__);
#endif
    /*
     *  Initialise per-cpu ipv4 mibs
     */
    if (init_ipv4_mibs())
        pr_crit("%s: Cannot init ipv4 mibsn", __func__);
    ipv4_proc_init();
    ipfrag_init();

    dev_add_pack(&ip_packet_type);

    rc = 0;
out:
    return rc;
out_unregister_raw_proto:
    proto_unregister(&raw_prot);
out_unregister_udp_proto:
    proto_unregister(&udp_prot);
out_unregister_tcp_proto:
    proto_unregister(&tcp_prot);
out_free_reserved_ports:
    kfree(sysctl_local_reserved_ports);
    goto out;
}

方法dev_add_pack()将方法ip_rev指定为IPv4数据包的协议处理程序。这些数据包的以太类型为0x0800(ETH_P_IP),inet_init()执行各种IPv4初始化工作,在引导阶段被占用。

Netfilter

在看ip_rcv之前,先看一下这个Netfilter模块。

linux内核网络之Netfilter

接收IPv4数据包

ip_rcv

以上两个图都是一个意思,可以对比着看。

ip_rcv() 函数验证 IP 分组,比如目的地址是否本机地址,校验和是否正确等。若正确,则交给 netfilter 的NF_IP_PRE_ROUTING 钩子。

到了 ip_rcv_finish() 函数,数据包就要根据 skb 结构的目的或路由信息各奔东西了

  • 判断数据包的去向, ip_local_deliver() 处理到本机的数据分组、 ip_forward() 处理需要转发的数据分组、 ip_mr_input() 转发组播数据包。如果是转发的数据包,还需要找出出口设备和下一跳
  • 分析和处理 IP 选项。(并不是处理所有的 IP 选项)
/*
 *  Main IP Receive routine.
 */
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
    const struct iphdr *iph;
    u32 len;

    /* When the interface is in promisc. mode, drop all the crap
     * that it receives, do not try to analyse it.
     */
    if (skb->pkt_type == PACKET_OTHERHOST)
        goto drop;


    IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len);

    if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
        IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
        goto out;
    }

    if (!pskb_may_pull(skb, sizeof(struct iphdr)))
        goto inhdr_error;

    iph = ip_hdr(skb);

    /*
     *  RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum.
     *
     *  Is the datagram acceptable?
     *
     *  1.  Length at least the size of an ip header
     *  2.  Version of 4
     *  3.  Checksums correctly. [Speed optimisation for later, skip loopback checksums]
     *  4.  Doesn't have a bogus length
     */
//这边判断报头长度以4字节为单位,最短20字节,故ihl最小为5,version必须为4
    if (iph->ihl < 5 || iph->version != 4)
        goto inhdr_error;

    if (!pskb_may_pull(skb, iph->ihl*4))
        goto inhdr_error;

    iph = ip_hdr(skb);
//检查校验和,如果不正确,丢弃数据包
    if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
        goto inhdr_error;

    len = ntohs(iph->tot_len);
    if (skb->len < len) {
        IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
        goto drop;
    } else if (len < (iph->ihl*4))
        goto inhdr_error;

    /* Our transport medium may have padded the buffer out. Now we know it
     * is IP we can trim to the true length of the frame.
     * Note this now means skb->len holds ntohs(iph->tot_len).
     */
    if (pskb_trim_rcsum(skb, len)) {
        IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
        goto drop;
    }

    /* Remove any debris in the socket control block */
    memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));

    /* Must drop socket now because of tproxy. */
    skb_orphan(skb);
//调用NF_HOOK宏,数据包由Netfilter子系统接管,调用方法ip_rcv_finish
    return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
               ip_rcv_finish);

inhdr_error:
    IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);
drop:
    kfree_skb(skb);
out:
    return NET_RX_DROP;
}

ip_rcv_finish

static int ip_rcv_finish(struct sk_buff *skb)
{
    const struct iphdr *iph = ip_hdr(skb);
    struct rtable *rt;

    if (sysctl_ip_early_demux && !skb_dst(skb)) {
        const struct net_protocol *ipprot;
        int protocol = iph->protocol;

        ipprot = rcu_dereference(inet_protos[protocol]);
        if (ipprot && ipprot->early_demux) {
            ipprot->early_demux(skb);
            /* must reload iph, skb->head might have changed */
            iph = ip_hdr(skb);
        }
    }

    /*
     *  Initialise the virtual path cache for the packet. It describes
     *  how the packet travels inside Linux networking.
     */
    //skb->dst 包含了数据分组到达目的地的路由信息,如果没有,则需要查找路由,如果最后结果显示目的地不可达,那么就丢弃该数据包
    if (!skb_dst(skb)) {
        //在路由选择子系统中进行查找
        int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
                           iph->tos, skb->dev);
        if (unlikely(err)) {
            if (err == -EXDEV)
                NET_INC_STATS_BH(dev_net(skb->dev),
                         LINUX_MIB_IPRPFILTER);
            goto drop;
        }
    }

#ifdef CONFIG_IP_ROUTE_CLASSID
    if (unlikely(skb_dst(skb)->tclassid)) {
        struct ip_rt_acct *st = this_cpu_ptr(ip_rt_acct);
        u32 idx = skb_dst(skb)->tclassid;
        st[idx&0xFF].o_packets++;
        st[idx&0xFF].o_bytes += skb->len;
        st[(idx>>16)&0xFF].i_packets++;
        st[(idx>>16)&0xFF].i_bytes += skb->len;
    }
#endif
//检查报头长度,如果大于5说明含有选项,调用ip_rcv_options进行处理
    if (iph->ihl > 5 && ip_rcv_options(skb))
        goto drop;

    rt = skb_rtable(skb);
    //如果路由选择的类型是组播或广播,将更新IPSTATS_MIB_INMCAST或IPSTATS_MIB_INBCAST
    if (rt->rt_type == RTN_MULTICAST) {
        IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INMCAST,
                skb->len);
    } else if (rt->rt_type == RTN_BROADCAST)
        IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INBCAST,
                skb->len);

    return dst_input(skb);

drop:
    kfree_skb(skb);
    return NET_RX_DROP;
}

/* Input packet from network to transport.  */
static inline int dst_input(struct sk_buff *skb)
{
    return skb_dst(skb)->input(skb);
}

下面就开始介绍这个skb_dst(skb)->input函数是怎么注册的。

方法ip_rcv也是组播数据包的处理程序。在完成一些完整性检查后,它会调用方法ip_rcv_finish,后者调用ip_route_input_noref执行路由选择子系统查找。首先调用ip_check_mc_rcu来检查当前机器是否属于目标组播地址指定的组播组。如果是这样的组播组或当前机器为组播路由器(设置了CONFIG_IP_MROUTE),就调用ip_route_input_mc

ip_route_input_noref

int ip_route_input_noref(struct sk_buff *skb, __be32 daddr, __be32 saddr,
             u8 tos, struct net_device *dev)
{
    //...
    if (ipv4_is_multicast(daddr)) {
        struct in_device *in_dev = __in_dev_get_rcu(dev);

        if (in_dev) {
            //检查当前机器是否属于目标组播地址指定的组播组
            int our = ip_check_mc_rcu(in_dev, daddr, saddr,
                          ip_hdr(skb)->protocol);
            if (our
#ifdef CONFIG_IP_MROUTE
                ||
                (!ipv4_is_local_multicast(daddr) &&
                 IN_DEV_MFORWARD(in_dev))
#endif
               ) {
                //our==1,说明当前机器是目标组播组
                int res = ip_route_input_mc(skb, daddr, saddr,
                                tos, dev, our);
                rcu_read_unlock();
                return res;
            }
        }
        rcu_read_unlock();
        return -EINVAL;
    }
    res = ip_route_input_slow(skb, daddr, saddr, tos, dev);
    rcu_read_unlock();
    return res;
}

ip_route_input_mc

static int ip_route_input_mc(struct sk_buff *skb, __be32 daddr, __be32 saddr,
                u8 tos, struct net_device *dev, int our)
{
    struct rtable *rth;
    struct in_device *in_dev = __in_dev_get_rcu(dev);
    //...
    rth = rt_dst_alloc(dev_net(dev)->loopback_dev,
               IN_DEV_CONF_GET(in_dev, NOPOLICY), false, false);
    if (!rth)
        goto e_nobufs;

#ifdef CONFIG_IP_ROUTE_CLASSID
    rth->dst.tclassid = itag;
#endif
    //设置dst.output函数
    rth->dst.output = ip_rt_bug;

    rth->rt_genid   = rt_genid(dev_net(dev));
    rth->rt_flags   = RTCF_MULTICAST;
    rth->rt_type    = RTN_MULTICAST;
    rth->rt_is_input= 1;
    rth->rt_iif = 0;
    rth->rt_pmtu    = 0;
    rth->rt_gateway = 0;
    rth->rt_uses_gateway = 0;
    INIT_LIST_HEAD(&rth->rt_uncached);
    if (our) {
        //设置dst.input函数为ip_local_deliver
        rth->dst.input= ip_local_deliver;
        rth->rt_flags |= RTCF_LOCAL;
    }
//此宏会检查procfs组播转发条目
#ifdef CONFIG_IP_MROUTE
    if (!ipv4_is_local_multicast(daddr) && IN_DEV_MFORWARD(in_dev))
        //将dst.input函数设置为ip_mr_input
        rth->dst.input = ip_mr_input;
#endif
    RT_CACHE_STAT_INC(in_slow_mc);

    skb_dst_set(skb, &rth->dst);
    return 0;
}

所以如果是组播数据包就会调用ip_mr_input。然后调用ip_mr_forward。此函数执行一些检查并最终调用ipmr_queue_xmit。在方法ipmr_queue_xmit中,调用方法ip_decrease_ttl将ttl减1,并更新校验和。最后调用NF_HOOKNF_INET_FORWARD来调用方法ipmr_forward_finish

ip_route_input_slow

介绍几个关键的数据结构:

struct flowi4

struct flowi4 {
    struct flowi_common __fl_common;
#define flowi4_oif     __fl_common.flowic_oif
#define flowi4_iif     __fl_common.flowic_iif
#define flowi4_mark        __fl_common.flowic_mark
#define flowi4_tos     __fl_common.flowic_tos
#define flowi4_scope       __fl_common.flowic_scope
#define flowi4_proto       __fl_common.flowic_proto
#define flowi4_flags       __fl_common.flowic_flags
#define flowi4_secid       __fl_common.flowic_secid

    /* (saddr,daddr) must be grouped, same order as in IP header */
    __be32          saddr;
    __be32          daddr;

    union flowi_uli     uli;
#define fl4_sport      uli.ports.sport
#define fl4_dport      uli.ports.dport
#define fl4_icmp_type      uli.icmpt.type
#define fl4_icmp_code      uli.icmpt.code
#define fl4_ipsec_spi      uli.spi
#define fl4_mh_type        uli.mht.type
#define fl4_gre_key        uli.gre_key
} __attribute__((__aligned__(BITS_PER_LONG/8)));

flowi4对象包含了对IPv4路由选择查找过程中至关重要的字段,比如目标地址dst、原地址src、服务类型tos等。在fib_lookup中执行查找前对其进行初始化。

struct rtable

struct rtable {
    struct dst_entry    dst;

    int         rt_genid;
    unsigned int        rt_flags;//rtable的标志
    __u16           rt_type;
    __u8            rt_is_input;//对于输入路由为1
    __u8            rt_uses_gateway;//下一条为网关为1,直连路由为0

    int         rt_iif;
    /* Info on neighbour */
    __be32          rt_gateway;

    /* Miscellaneous cached information */
    u32         rt_pmtu;

    struct list_head    rt_uncached;
};
struct dst_entry {
    //...
    int         (*input)(struct sk_buff *);
    int         (*output)(struct sk_buff *);
    //...
}

rtable结构中内嵌了一个dst_entry结构体,dst_entry中有着两个相当重要的函数。也就是dst.input以及dst.output。在进行路由选择查找时,会根据路由选择查找结果将这两个函数设置为合适的处理程序。

  1. 对于目的地为当前主机的单播数据包:dst.input=ip_local_deliver
  2. 对于需要转发的单播数据包:dst.input=ip_forward
  3. 对于当前主机生成并要向外发送的数据包:dst.output=ip_output
  4. 对于组播数据包:dst.input=ip_mr_input
  5. 有些情况:dst.input=ip_error

发送IPv4数据包

IPv4为它上面的层(传输层L4)提供了将数据包交给数据链路层L2的发送方式。从L4发送数据包的方法有2个:一个是ip_queue_xmit(),另一个是ip_append_data()。有时候还直接调用dst_output(),例如在使用套接字选项IP_HDRINCL的原始套接字发送数据包时,不需要准备IPv4的报头。

ip_queue_xmit

rtable对象为路有选择子系统查找结果。如果rtable实例为NULL,需要执行路由选择子系统查找的情形。接下去使用函数ip_route_output_ports()在路由子系统中执行查找。如果查找失败,丢弃该数据包。

在L3报头添加后,就可以通过ip_local_out()进行发送了

上面就是NF_INET_LOCAL_OUT这个hook点的挂载,我们可以搜一下dst.output在哪些地方被赋值!

无论走的那一条,他们都调用的同一个NF_HOOK_COND宏,来挂载NF_INET_POST_ROUTING.

回调函数output是要使用的传输方法。从ip_finish_output调用方法ip_fragment时,被设置为ip_finish_output2
ip_fragment是数据包分段的函数。下面就开始介绍分段。

分段

发送长于出栈网卡MTU的数据包时,需要将其划分为较小的片段,这是通过ip_fragment完成的。而当收数据包时,需要将这些小数据包重组成一个数据包,这是通过ip_defrag完成的。

这个之前也提到过ip_options_build的最后一个参数就是用来判断是否可分段,如果不可分段,就向发送方返回一个ICMPv4``项目不可达的消息

快速路径

  1. 调用skb_has_frag_list判断是否采用快速路径处理
  2. 为第一个分段创建IPv4报头。这个报头的frag_off被设置为htons(IP_MF),frag_off字段长16位,其中后13位为分段偏移量,前3位为标志。第一个分段偏移量为0,标志位IP_MF。除最后一个分段外,标志位都为IP_MF,最后一个分段,不设置标志位
    if (skb_has_frag_list(skb)) {
        struct sk_buff *frag, *frag2;
        int first_len = skb_pagelen(skb);
        //...

        /* Everything is OK. Generate! */

        err = 0;
        offset = 0;
        frag = skb_shinfo(skb)->frag_list;
        //调用skb_frag_list_init将skb_fshinfo(skb)->frag_list设置为NULL
         skb_frag_list_init(skb);
        skb->data_len = first_len - skb_headlen(skb);
        skb->len = first_len;
        iph->tot_len = htons(first_len);
        //设置第一个分段标志位
        iph->frag_off = htons(IP_MF);
        //重新计算校验和
        ip_send_check(iph);

        for (;;) {
            /* Prepare header of the next frame,
             * before previous one went down. */
            if (frag) {
                frag->ip_summed = CHECKSUM_NONE;
                skb_reset_transport_header(frag);
                __skb_push(frag, hlen);
                //设置L3报头,使其只想skb->data
                skb_reset_network_header(frag);
                //将这个报头复制到L3报头中
                memcpy(skb_network_header(frag), iph, hlen);
                //初始化下一个分段的报头
                iph = ip_hdr(frag);
                iph->tot_len = htons(frag->len);
                //将各个SKB字段(pkt_type、priority、protocol)复制到frag中
                ip_copy_metadata(frag, skb);
                if (offset == 0)
                    ip_options_fragment(frag);
                offset += skb->len - hlen;
                //frag_off字段必须以8字节为单位,所以除以8
                iph->frag_off = htons(offset>>3);
                //这边就是循环设置frag_off标志位为IP_MF,除最后一个分段外
                if (frag->next != NULL)
                    iph->frag_off |= htons(IP_MF);
                /* Ready, complete checksum */
                ip_send_check(iph);
            }
            //调用output回调函数发送分段
            err = output(skb);

            if (!err)
                IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGCREATES);
            if (err || !frag)
                break;

            skb = frag;
            frag = skb->next;
            skb->next = NULL;
        }

        if (err == 0) {
            IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGOKS);
            return 0;
        }
        //如果有一次调用output时失败,就释放所有的skb
        while (frag) {
            skb = frag->next;
            kfree_skb(frag);
            frag = skb;
        }
        IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGFAILS);
        return err;

慢速路径

slow_path:
    /* for offloaded checksums cleanup checksum before fragmentation */
    if ((skb->ip_summed == CHECKSUM_PARTIAL) && skb_checksum_help(skb))
        goto fail;
    iph = ip_hdr(skb);

    left = skb->len - hlen;     /* Space per frame */
    ptr = hlen;     /* Where to start from */

    /* for bridged IP traffic encapsulated inside f.e. a vlan header,
     * we need to make room for the encapsulating header
     */
    ll_rs = LL_RESERVED_SPACE_EXTRA(rt->dst.dev, nf_bridge_pad(skb));

    /*
     *  Fragment the datagram.
     */

    offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3;
    not_last_frag = iph->frag_off & htons(IP_MF);

    /*
     *  Keep copying data until we run out.
     */

    while (left > 0) {
        len = left;
        /* IF: it doesn't fit, use 'mtu' - the data space left */
        if (len > mtu)
            len = mtu;
        /* IF: we are not sending up to and including the packet end
           then align the next start on an eight byte boundary */
        if (len < left) {
            len &= ~7;
        }
        /*
         *  Allocate buffer.
         */

        if ((skb2 = alloc_skb(len+hlen+ll_rs, GFP_ATOMIC)) == NULL) {
            NETDEBUG(KERN_INFO "IP: frag: no memory for new fragment!n");
            err = -ENOMEM;
            goto fail;
        }

        /*
         *  Set up data on packet
         */
        //生成数据包数据
        ip_copy_metadata(skb2, skb);
        skb_reserve(skb2, ll_rs);
        skb_put(skb2, len + hlen);
        skb_reset_network_header(skb2);
        skb2->transport_header = skb2->network_header + hlen;

        /*
         *  Charge the memory for the fragment to any owner
         *  it might possess
         */
        //为分段分配的内存归占有者占用
        if (skb->sk)
            skb_set_owner_w(skb2, skb->sk);

        /*
         *  Copy the packet header into the new buffer.
         */
        //拷贝数据包报头复制到新缓冲区
        skb_copy_from_linear_data(skb, skb_network_header(skb2), hlen);

        /*
         *  Copy a block of the IP datagram.
         */
        //复制IP数据块
        if (skb_copy_bits(skb, ptr, skb_transport_header(skb2), len))
            BUG();
        left -= len;

        /*
         *  Fill in the new header fields.
         */
        //填充新的报头字段
        iph = ip_hdr(skb2);
        iph->frag_off = htons((offset >> 3));

        /* ANK: dirty, but effective trick. Upgrade options only if
         * the segment to be fragmented was THE FIRST (otherwise,
         * options are already fixed) and make it ONCE
         * on the initial skb, so that all the following fragments
         * will inherit fixed options.
         */
        if (offset == 0)
            ip_options_fragment(skb);

        /*
         *  Added AC : If we are fragmenting a fragment that's not the
         *         last fragment then keep MF on each bit
         */
        if (left > 0 || not_last_frag)
            iph->frag_off |= htons(IP_MF);
        ptr += len;
        offset += len;

        /*
         *  Put this fragment into the sending queue.
         */
        iph->tot_len = htons(len + hlen);

        ip_send_check(iph);

        err = output(skb2);
        if (err)
            goto fail;

        IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGCREATES);
    }
    consume_skb(skb);
    IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGOKS);
    return err;

fail:
    kfree_skb(skb);
    IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGFAILS);
    return err;
}

重组

重组指的是将数据包的所有分段重组为一个缓冲区,这些分段的IPv4报头的id都相同。在接收路径中,处理重组的主方法为ip_defrag。在ip_local_deliver中就调用了ip_is_fragment来检查数据包是否经过分段。如果是就调ip_defrag

ip_is_fragment返回true的条件:

  1. 设置IP_MF标志
  2. 分段偏移量不为0

ip_defrag

ip_frag_queue

接下来就是找出分段的添加位置。方法是:查找分段偏移量后面的第一个位置(因为分段链表是按偏移量排序的)

插入所有的分段到分段链表

最后调用ip_frag_reasm进行重组

总结

本篇介绍了IPv4的相关linux网络协议栈,包括IPv4数据包如何创建、IPv4报头的结构和IP选项以及如何处理IPv4报头。还有数据包的发送和接收。数据包的分段以及重组。

剑气纵横三万里

“为什么要努力?” “想去的地方很远,想要的东西很贵,喜欢的人很优秀,父母的白发,朋友的约定,周围人的嘲笑,以及,天生傲骨。”

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>

相关推荐