1. 程式人生 > >linux協議棧ip層分析

linux協議棧ip層分析

學習目標:

熟悉ip層的職責?

熟練資料包如何通過ip層?

熟練ip資料重組的設計思路?

熟悉ip路由的思路?

熟悉netfilter的鉤子函式處理?

1資料流路徑

 

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

}