linux协议栈ip层分析

时间:2022-03-09 01:20:45

学习目标:

熟悉ip层的职责?

熟练数据包如何通过ip层?

熟练ip数据重组的设计思路?

熟悉ip路由的思路?

熟悉netfilter的钩子函数处理?

 

1数据流路径

 linux协议栈ip层分析

 

 

2职责

ip层的主要任务有下面5个方面: 

1、ip数据包的校验(包括数据包的完整性、格式正确性、校验和等)。

2、防火墙的处理(也就是netfilter子系统)。

3、处理options(这里的options包含了一些可选的信息。比如时间戳或者源路由option)。

4、分片和重组(由于mtu的存在,因此我们需要切包和组包)。

5、接收,输出和转发操作。

3分析各自函数的实现(直接上源码分析)

3.1ip_rcv

该函数主要功能是对ip格式合法性进行检查,然后将报文交给一下流程处理。如ip_rcv_finish。

/*

 *  Main IP Receive routine.

 */

int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)

{

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.

 *当网络结构设定为混杂模式时。当网卡处于混杂模式时,收到不是发往该主机的数据包,由net_rx_action()设置。在调用ip_rcv之前,内核会将该数据包交给嗅探器,所以该函数仅丢弃该包。

 */

if (skb->pkt_type == PACKET_OTHERHOST)

goto drop;

 

/* SNMP所需要的统计数据,暂不进行分析*/

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

 

/* skb是否被其他模块共享进行检查。

 *ip_rcv是由netif_receive_skb函数调用,如果嗅探器或者其他的用户对数据包需要进

 *进行处理,则在调用ip_rcv之前,netif_receive_skb会增加skb的引用计数,既该引

 *用计数会大于1。若如此次,则skb_share_check会创建sk_buff的一份拷贝。

 */

if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {

IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);

goto out;

}

 

/*

 *pskb_may_pull确保skb->data指向的内存包含的数据至少为IP头部大小,由于每个

 *IP数据包包括IP分片必须包含一个完整的IP头部。如果小于IP头部大小,则缺失

 *的部分将从数据分片中拷贝。这些分片保存在skb_shinfo(skb)->frags[]中。

 */

if (!pskb_may_pull(skb, sizeof(struct iphdr)))

goto inhdr_error;

 

/* 返回一个iph结构体,数据已经进行填充 */

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 至少一个20自己的ip

 * 2. Version of 4  ipv4

 * 3. Checksums correctly. [Speed optimisation for later, skip loopback checksums]

校验和正确

 * 4. Doesn't have a bogus length

 */

 

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;

 

/*确保skb的数据长度大于等于IP头部中指示的IP数据包总长度及数据包总长度必须

 *大于等于IP头部长度。

 */

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).

 */

/*传输媒介可能写数据超过了缓冲区,我们现在已经知道是ip格式的数据包,根据长度提取真实的数据。*/

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

/* netfilter子系统交互,如果防火墙未开,使用默认ip_rcv_finish*/

return NF_HOOK(PF_INET, 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;

}

红色处,参考:http://blog.csdn.net/qy532846454/article/details/6605592

NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL,ip_rcv_finish)
执行顺序:

NF_HOOK()->NF_HOOK_THRESH()->nf_hook_thresh()->nf_hook_slow();

NF_ACCEPT:表示允许报文完成后续操作,执行ip_rcv_finish。

3.2ip_rcv_finish

主要工作是完成路由表的查询,决定报文经过IP层处理后,是继续向上传递,还是进行转发,还是丢弃。

static int ip_rcv_finish(struct sk_buff *skb)

{

    const struct iphdr *iph = ip_hdr(skb);

    struct rtable *rt;

 

    /*

     *    Initialise the virtual path cache for the packet. It describes

     *    how the packet travels inside Linux networking.

     */

    /*

     通常从外界接收的数据包,skb->dst不会包含路由信息,刚开始没有进行路由表查询,所以还没有相应的路由表项:skb_dst(skb) == NULL ip_route_input函数会根据路由表设置路由信息,暂时不分析路由系统。

     */

    if (skb->dst == NULL) {

        int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos,

                     skb->dev);

        if (unlikely(err)) {

            if (err == -EHOSTUNREACH)

                IP_INC_STATS_BH(IPSTATS_MIB_INADDRERRORS);

            else if (err == -ENETUNREACH)

                IP_INC_STATS_BH(IPSTATS_MIB_INNOROUTES);

            goto drop;

        }

    }

/* 更新流量控制所需要的统计数据,暂不考虑 */

#ifdef  CONFIG_NET_CLS_ROUTE

    if (unlikely(skb->dst->tclassid)) {

        struct ip_rt_acct *st = ip_rt_acct + 256*smp_processor_id();

        u32 idx = skb->dst->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

    /* 如果IP头部大于20字节,则表示IP头部包含IP选项,需要进行选项处理.不分析,毕竟很少用 */

    if (iph->ihl > 5 && ip_rcv_options(skb))

        goto drop;

 

    /* skb->dst上面已经查找了路由表,所以有了路由信息。根据路由类型更新SNMP统计数据*/

    rt = (struct rtable*)skb->dst;

    if (rt->rt_type == RTN_MULTICAST)

        IP_INC_STATS_BH(IPSTATS_MIB_INMCASTPKTS);

    else if (rt->rt_type == RTN_BROADCAST)

        IP_INC_STATS_BH(IPSTATS_MIB_INBCASTPKTS);

    /*

     * dst_input实际上会调用skb->dst->input(skb).input函数会根据路由信息设置为合适的函数指针,如果是递交到本地的则为ip_local_deliver,若是转发则为ip_forward.暂时仅先考虑ip_local_deliver

     */

    return dst_input(skb);

drop:

    kfree_skb(skb);

    return NET_RX_DROP;

}

3.3ip_local_deliver发往本机

主要功能:收集IP分片,然后调用ip_local_deliver_finish将一个完整的数据包传送给上层协议。

/*

 *     Deliver IP Packets to the higher protocol layers.

 */

int ip_local_deliver(struct sk_buff *skb)

{

    /*

     * 判断该IP数据包是否是一个分片,如果IP_MF置位,则表示该包是分片之一,其

     * 后还有更多分片,最后一个IP分片未置位IP_MF但是其offset是非0

     * 如果是一个IP分片,则调用ip_defrag重新组织IP数据包。

     */

    if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {

        if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))

            return 0;

    }

    /* 调用ip_local_deliver_finish(skb) */

    return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,

         ip_local_deliver_finish);

}

3.3.1int ip_defrag(struct sk_buff *skb, u32 user)

Ip分片包进行重组,看看linux是如何对ip分片进行处理的,可以为以后一些开发提供好的思维。如dpdk中样例就是仿照linux中进行编写。

参考:http://blog.csdn.net/qy532846454/article/details/6744252

基本思想:

(1) 当内核接收到本地的IP包, 在传递给上层协议处理之前,先进行碎片重组。IP包片段之间的标识号(id)是相同的.当IP包片偏量(frag_off)第14位(IP_MF)为1时, 表示该IP包有后继片段。片偏量的低13位则为该片段在完整数据包中的偏移量, 以8字节为单位.。当IP_MF位为0时,表示IP包是最后一块碎片。

(2) 碎片重组由重组队列完成, 每一重组队列对应于(daddr,saddr,protocol,id)构成的键值,它们存在于ipq结构构成的散列链之中. 重组队列将IP包按照将片段偏移量的顺序进行排列,当所有的片段都到齐后, 就可以将队列中的包碎片按顺序拼合成一个完整的IP包.

(3) 如果30秒后重组队列内包未到齐, 则重组过程失败, 重组队列被释放,同时向发送方以ICMP协议通知失败信息.重组队列的内存消耗不得大于256k(sysctl_ipfrag_high_thresh),否则将会调用(ip_evictor)释放每支散列尾端的重组队列。、

几个问题:

1、hash值是否唯一?

jhash_3word 采用了一种hash算法,可以避免类似发生。

2、分片重叠和分片丢失、重发如何处理?

丢失:如果分片包不会到来,则删除整个队列和清楚hash表。

重叠/重发:重叠可能发生两种情况(与前一片重叠,后一片重叠)

 

在收到IP分片时,会暂时存储到一个哈希表ip4_frags中,它在IP协议模块加载时初始化,inet_init() -> ipfrag_init()。要留意的是ip4_frag_match用于匹配IP分片是否属于同一个报文;ip_expire用于在IP分片超时时进行处理。

初始化过程:

void __init ipfrag_init(void)

{

 ip4_frags_ctl_register();

 register_pernet_subsys(&ip4_frags_ops);

 ip4_frags.hashfn = ip4_hashfn;

 ip4_frags.constructor = ip4_frag_init;

 ip4_frags.destructor = ip4_frag_free;

 ip4_frags.skb_free = NULL;

 ip4_frags.qsize = sizeof(struct ipq);

 ip4_frags.match = ip4_frag_match

 ip4_frags.frag_expire = ip_expire;

 ip4_frags.secret_interval = 10 * 60 * HZ;

 inet_frags_init(&ip4_frags);

}

 

/* Process an incoming IP datagram fragment. */

int ip_defrag(struct sk_buff *skb, u32 user)

{

    struct ipq *qp;

 

    IP_INC_STATS_BH(IPSTATS_MIB_REASMREQDS);

 

    /* Start by cleaning up the memory. */

    /*

     * 首先检查所有IP分片所消耗的内存是否大于系统允许的最高阀值,如果是,则调用ip_evictor()丢弃未完全到达的IP分片,从最旧的分片开始释放。此举一来是为了节约内存,二来是未了防止黑客的恶意攻击。使分片在系统中累计,降低系统性能。

     */

    if (atomic_read(&ip4_frags.mem) > ip4_frags_ctl.high_thresh)

        ip_evictor();

 

    /* Lookup (or create) queue header */

    /* 如果该分片是数据报的第一个分片,则ip_find返回一个新的队列来搜集分片,否则返回其所属于的分片队列。*/

    if ((qp = ip_find(ip_hdr(skb), user)) != NULL) {

        int ret;

 

        spin_lock(&qp->q.lock);

    /* 将该分片加入到队列中,重组分片队列,如果所有的包都收到了,则该函数负责重组IP*/

        ret = ip_frag_queue(qp, skb);

 

        spin_unlock(&qp->q.lock);

        ipq_put(qp);    /* 引用计数减*/

        return ret;

    }

 

    IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);

    kfree_skb(skb);

    return -ENOMEM;

}

 

/* Find the correct entry in the "incomplete datagrams" queue for

 * this IP datagram, and create new one, if nothing is found.

 */

/* u32 user这个参数有点迷惑,其表示以何种理由需要对数据包进行重组,在ip_local_deliver的调用序列当中,这个值是IP_DEFRAG_LOCAL_DELIVER*/

static inline struct ipq *ip_find(struct iphdr *iph, u32 user)

{

    struct inet_frag_queue *q;

    struct ip4_create_arg arg;

    unsigned int hash;

 

    arg.iph = iph;

    arg.user = user;

    /* hash值为:(识,源IP,目的IP,协议号)

     * hash算法,该算法除了使用所给的这四个参数之外,还使用了一个随机值

     * ip4_frags.rnd,,其初始化为

     * (u32) ((num_physpages ^ (num_physpages>>7)) ^ (jiffies ^ (jiffies >> 6)));

     * 这是为了防止黑客根据固定的hash算法,通过设置ip头部的这些字段,生成同样

     * HASH值,从而使某一HASH队列长度急剧增大而影响性能。

     */

    hash = ipqhashfn(iph->id, iph->saddr, iph->daddr, iph->protocol);

    /* 若存在该分片所属的分片队列则返回这个队列,否则创建一个新的队列 */

    q = inet_frag_find(&ip4_frags, &arg, hash);

    if (q == NULL)

        goto out_nomem;

 

    return container_of(q, struct ipq, q);

 

out_nomem:

    LIMIT_NETDEBUG(KERN_ERR "ip_frag_create: no memory left !\n");

    return NULL;

}

inet_frag_find根据hash值取ip4_frag->hash[hash]项 – inet_frag_queue,它是一个队列,然后遍历该队列,当net, id, saddr, daddr, protocol, user相匹配时,就是要找的IP分片。如果没有匹配的,则调用inet_frag_create创建它。

struct inet_frag_queue *inet_frag_find(struct inet_frags *f, void *key,

        unsigned int hash)

{

    struct inet_frag_queue *q;

    struct hlist_node *n;

 

    /* f->lock是读写锁,先搜索是否存在该IP分段所属的队列 */

    read_lock(&f->lock);

    hlist_for_each_entry(q, n, &f->hash[hash], list) { 

/* 扫描该HASH槽中所有节点 f->matchmatch字段在ipfrag_init中初始化为ip4_frag_match函数。对比分片队列中的散列字段和user是否和key相等,key指向的是struct ip4_create_arg结构,包含IP头部和user字段。 */

        if (f->match(q, key)) {

            atomic_inc(&q->refcnt);     /* 若找到,则增加该队列引用计数。 */

            read_unlock(&f->lock);

            return q;                /* 返回该队列 */

        }

    }

    read_unlock(&f->lock);

    /* 该分片是第一个IP分片,创建一个新的分片队列并添加到合适的HASH队列 */

    return inet_frag_create(f, key, hash);

}

ip_frag_queue将到来的分片进行重组:

/* Add new segment to existing queue. */

ip_frag_queue()函数将新来的skb包插入队列节点中,这个函数是防御各种碎片攻击的关键,要能处理各种异常的重组过程:

static int ip_frag_queue(struct ipq *qp, struct sk_buff *skb)

{

struct sk_buff *prev, *next;

struct net_device *dev;

int flags, offset;

int ihl, end;

int err = -ENOENT;

/* 对已经有INET_FRAG_COMPLETE标志的的队列节点,后续来的数据包都丢弃。*/

if (qp->q.last_in & INET_FRAG_COMPLETE)

goto err;

 

if (!(IPCB(skb)->flags & IPSKB_FRAG_COMPLETE) &&

    unlikely(ip_frag_too_far(qp)) &&

    unlikely(err = ip_frag_reinit(qp))) {

ipq_kill(qp);

goto err;

}

/* 计算当前包的偏移值,IP头中的偏移值只有13,但表示的是8字节的倍数*/

offset = ntohs(ip_hdr(skb)->frag_off);

flags = offset & ~IP_OFFSET;

offset &= IP_OFFSET;

offset <<= 3; /* offset is in 8-byte chunks */

ihl = ip_hdrlen(skb);

 

/* Determine the position of this fragment. --计算片段的位置 */

end = offset + skb->len - ihl;

err = -EINVAL;

 

/* Is this the final fragment?  -- 是最后一个分片吗?*/

if ((flags & IP_MF) == 0) {

/* If we already have some bits beyond end

 * or have different end, the segment is corrrupted.

如果我们有一些位已经超过了结束end,或者有不同的结束,这个分片时被破坏。

 */

if (end < qp->q.len ||

    ((qp->q.last_in & INET_FRAG_LAST_IN) && end != qp->q.len))

goto err;

qp->q.last_in |= INET_FRAG_LAST_IN; //最后一个分包

qp->q.len = end;

} else {

//仍然存在后续的分片包,检查数据长度是否是8字节对齐的

if (end&7) {

end &= ~7;

if (skb->ip_summed != CHECKSUM_UNNECESSARY)

skb->ip_summed = CHECKSUM_NONE;

}

//度超过当前记录的长度

if (end > qp->q.len) {

/* Some bits beyond end -> corruption. */

if (qp->q.last_in & INET_FRAG_LAST_IN)

goto err;

qp->q.len = end;

}

}

if (end == offset)

goto err;

 

err = -ENOMEM;

//去掉IP头部分,只保留数据部分

if (pskb_pull(skb, ihl) == NULL)

goto err;

//skb包长度调整为end-offset, 该值为该skb包中的实际有效数据长度

err = pskb_trim_rcsum(skb, end - offset);

if (err)

goto err;

 

/* Find out which fragments are in front and at the back of us

 * in the chain of fragments so far. We must know where to put

 * this fragment, right?

 */

//确定当前包在完整包中的位置,分片包不一定是顺序到达目的端的,有可能是杂乱顺序的,因此需要调整包的顺序.

prev = NULL;

for (next = qp->q.fragments; next != NULL; next = next->next) {

if (FRAG_CB(next)->offset >= offset)

break; /* bingo! */

prev = next;

}

 

/* We found where to put this one.  Check for overlap with

 * preceding fragment, and, if needed, align things so that

 * any overlaps are eliminated.

 */

//检查偏移是否有重叠,重叠是允许的,只要是正确的

if (prev) {

int i = (FRAG_CB(prev)->offset + prev->len) - offset;

//大于说明发生了重叠

if (i > 0) {

offset += i;

err = -EINVAL;

if (end <= offset)

goto err;

err = -ENOMEM;

if (!pskb_pull(skb, i))

goto err;

if (skb->ip_summed != CHECKSUM_UNNECESSARY)

skb->ip_summed = CHECKSUM_NONE;

}

}

 

err = -ENOMEM;

//如果重叠,则队列后面的所有包的偏移值都要调整,数据包长度的累加值也要相应减小

while (next && FRAG_CB(next)->offset < end) {

int i = end - FRAG_CB(next)->offset; /* overlap is 'i' bytes */

 

if (i < next->len) {

/* Eat head of the next overlapped fragment

 * and leave the loop. The next ones cannot overlap.

 */

if (!pskb_pull(next, i))

goto err;

FRAG_CB(next)->offset += i;

qp->q.meat -= i;

if (next->ip_summed != CHECKSUM_UNNECESSARY)

next->ip_summed = CHECKSUM_NONE;

break;

} else {

struct sk_buff *free_it = next;

 

/* Old fragment is completely overridden with

 * new one drop it.

 */

next = next->next;

 

if (prev)

prev->next = next;

else

qp->q.fragments = next;

 

qp->q.meat -= free_it->len;

frag_kfree_skb(qp->q.net, free_it, NULL);

}

}

// skb记录自己的偏移值

FRAG_CB(skb)->offset = offset;

 

/* Insert this fragment in the chain of fragments. */

skb->next = next;

if (prev)

prev->next = skb;

else

qp->q.fragments = skb;

 

dev = skb->dev;

if (dev) {

qp->iif = dev->ifindex;

skb->dev = NULL;

}

qp->q.stamp = skb->tstamp;

qp->q.meat += skb->len;

atomic_add(skb->truesize, &qp->q.net->mem);

if (offset == 0)

qp->q.last_in |= INET_FRAG_FIRST_IN;

 

if (qp->q.last_in == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&

    qp->q.meat == qp->q.len)

return ip_frag_reasm(qp, prev, dev);

 

write_lock(&ip4_frags.lock);

list_move_tail(&qp->q.lru_list, &qp->q.net->lru_list);

write_unlock(&ip4_frags.lock);

return -EINPROGRESS;

 

err:

kfree_skb(skb);

return err;

}

3.3.2ip_local_deliver_finish

如果忽略掉原始套接字和IPSec,则该函数仅仅是根据IP头部中的协议字段选择上层L4协议,并交给它来处理。

static int ip_local_deliver_finish(struct sk_buff *skb)

{

    /* 跳过IP头部 */

    __skb_pull(skb, ip_hdrlen(skb));

 

    /* Point into the IP datagram, just past the header. */

    /* 设置传输层头部位置 */

    skb_reset_transport_header(skb);

 

    rcu_read_lock();

    {

        /* Note: See raw.c and net/raw.h, RAWV4_HTABLE_SIZE==MAX_INET_PROTOS */

        int protocol = ip_hdr(skb)->protocol;

        int hash;

        struct sock *raw_sk;

        struct net_protocol *ipprot;

 

    resubmit:

    /* 这个hash根本不是哈希值,仅仅只是inet_protos数组中的下表而已 */

        hash = protocol & (MAX_INET_PROTOS - 1);

        raw_sk = sk_head(&raw_v4_htable[hash]);

 

        /* If there maybe a raw socket we must check - if not we

         * don't care less

         */

    /* 原始套接字?? 忽略... */

        if (raw_sk && !raw_v4_input(skb, ip_hdr(skb), hash))

            raw_sk = NULL;

    /* 查找注册的L4层协议处理结构。 */

        if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL) {

            int ret;

    /* 启用了安全策略,则交给IPSec */

            if (!ipprot->no_policy) {

                if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {

                    kfree_skb(skb);

                    goto out;

                }

                nf_reset(skb);

            }

    /* 调用L4层协议处理函数 */

    /* 通常会是tcp_v4_rcv, udp_rcv, icmp_rcvigmp_rcv */

    /* 如果注册了其他的L4层协议处理,则会进行相应的调用。 系统启动的时候回初始化相应的四层函数*/

            ret = ipprot->handler(skb);

            if (ret < 0) {

                protocol = -ret;

                goto resubmit;

            }

            IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);

        } else {

            if (!raw_sk) {    /* 无原始套接字,提交给IPSec */

                if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {

                    IP_INC_STATS_BH(IPSTATS_MIB_INUNKNOWNPROTOS);

                    icmp_send(skb, ICMP_DEST_UNREACH,

                         ICMP_PROT_UNREACH, 0);

                }

            } else

                IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);

            kfree_skb(skb);

        }

    }

 out:

    rcu_read_unlock();

 

    return 0;

}

3.4转发ip_forward

主要做一些路由转发的功能,一般PC上不会使用转发功能(PC如果不是自己处理的包,肯定就不会要),只有那些需要开启转发业务的数据包才有用。

int ip_forward(struct sk_buff *skb)

{

struct iphdr *iph; /* Our header */

struct rtable *rt; /* Route we use */

struct ip_options * opt = &(IPCB(skb)->opt);

/* gso相关设置,对gso不熟悉 */

if (skb_warn_if_lro(skb))

goto drop;

/* 策略检查 */

if (!xfrm4_policy_check(NULL, XFRM_POLICY_FWD, skb))

goto drop;

/*

 *如果router alert option被设置了,则立即交由ip_call_ra_chain处理数据包.ip_call_ra_chain函数轮询一个全局链表ip_ra_chain.此全局链表中是一些设置了IP_ROUTER_ALERTsockets.因为这些sockets对设置了Router Alert optionip包感兴趣.如果数据包为分片的,则将分片数据包组装好后发给ip_ra_chain链表中的原始套接口中的相关接收函数.(raw sockets)

 */

if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb))

return NET_RX_SUCCESS;

 

if (skb->pkt_type != PACKET_HOST)

goto drop;

 

skb_forward_csum(skb);

 

/*

 * According to the RFC, we must first decrease the TTL field. If

 * that reaches zero, we must reply an ICMP control message telling

 * that the packet's lifetime expired.

 */

if (ip_hdr(skb)->ttl <= 1)

goto too_many_hops;

 

if (!xfrm4_route_forward(skb))

goto drop;

 

rt = skb_rtable(skb);

/*设置了严格ip源站选路选项(必须按发送者指定的路线走),目的地址不是网关*/

if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway)

goto sr_failed;

 

if (unlikely(skb->len > dst_mtu(&rt->u.dst) && !skb_is_gso(skb) &&

     (ip_hdr(skb)->frag_off & htons(IP_DF))) && !skb->local_df) {

IP_INC_STATS(dev_net(rt->u.dst.dev), IPSTATS_MIB_FRAGFAILS);

icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,

  htonl(dst_mtu(&rt->u.dst)));

goto drop;

}

 

/* We are about to mangle packet. Copy it! */

if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+rt->u.dst.header_len))

goto drop;

iph = ip_hdr(skb);

 

/* Decrease ttl after skb cow done */

ip_decrease_ttl(iph);

 

/*

 * We now generate an ICMP HOST REDIRECT giving the route

 * we calculated.

 */

if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr && !skb_sec_path(skb))

ip_rt_send_redirect(skb);

 

skb->priority = rt_tos2priority(iph->tos);

 

return NF_HOOK(PF_INET, NF_INET_FORWARD, skb, skb->dev, rt->u.dst.dev,

       ip_forward_finish);

 

sr_failed:

/*

 * Strict routing permits no gatewaying

 */

 icmp_send(skb, ICMP_DEST_UNREACH, ICMP_SR_FAILED, 0);

 goto drop;

 

too_many_hops:

/* Tell the sender its packet died... */

IP_INC_STATS_BH(dev_net(skb_dst(skb)->dev), IPSTATS_MIB_INHDRERRORS);

icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);

drop:

kfree_skb(skb);

return NET_RX_DROP;

}

3.4.1转发 ip_forward_finish

static int ip_forward_finish(struct sk_buff *skb)

{

struct ip_options * opt = &(IPCB(skb)->opt);

 

IP_INC_STATS_BH(dev_net(skb_dst(skb)->dev), IPSTATS_MIB_OUTFORWDATAGRAMS);

 

if (unlikely(opt->optlen))

ip_forward_options(skb);

 

return dst_output(skb);

}