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
*/
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); /* 引用計數減1 */
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->match中match欄位在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;
//大於0 說明發生了重疊
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_rcv和igmp_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_ALERT的sockets.因為這些sockets對設定了Router Alert option的ip包感興趣.如果資料包為分片的,則將分片資料包組裝好後發給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);
}