Linux核心分析 - 網路:netif_receive_skb平臺報文入口函式詳解
網路收包流程從網絡卡驅動開始,一直往上,涉及NAPI、GRO、RPS等特性,但是一般最後都會呼叫__netif_receive_skb函式:
函式主要有幾個處理:
1、vlan報文的處理,主要是迴圈把vlan頭剝掉,如果qinq場景,兩個vlan都會被剝掉;
2、交給rx_handler處理,例如OVS、linux bridge等;
3、ptype_all處理,例如抓包程式、raw socket等;
4、ptype_base處理,交給協議棧處理,例如ip、arp、rarp等;
- static int __netif_receive_skb(
- {
- struct packet_type *ptype, *pt_prev;
- rx_handler_func_t *rx_handler;
- struct net_device *orig_dev;
- struct net_device *
- bool deliver_exact = false;
- int ret = NET_RX_DROP;
- __be16 type;
-
- if (!netdev_tstamp_prequeue)
- net_timestamp_check(
-
- trace_netif_receive_skb(skb);
-
- if (netpoll_receive_skb(skb))
- return NET_RX_DROP;
-
- if (!skb->skb_iif)
- skb->skb_iif = skb->dev->ifindex;
- orig_dev = skb->dev;
-
- skb_reset_network_header(skb); //把L3、L4的頭都指向data資料結構,到這裡的時候skb已經處理完L2層的頭了
- skb_reset_transport_header(skb);
- skb_reset_mac_len(skb);
-
- pt_prev = NULL;
-
- rcu_read_lock();
-
- another_round:
-
- __this_cpu_inc(softnet_data.processed);
-
- if (skb->protocol == cpu_to_be16(ETH_P_8021Q)) {
- skb = vlan_untag(skb);
- if (unlikely(!skb))
- goto out;
- }
-
- #ifdef CONFIG_NET_CLS_ACT
- if (skb->tc_verd & TC_NCLS) {
- skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
- goto ncls;
- }
- #endif
-
- list_for_each_entry_rcu(ptype, &ptype_all, list) { //把包交給特定協議相關的處理函式前,先呼叫ptype_all中註冊的函式
- if (!ptype->dev || ptype->dev == skb->dev) { //最常見的為tcpdump,該工具就是從這裡拿到所有收到的包的
- if (pt_prev)
- ret = deliver_skb(skb, pt_prev, orig_dev);
- pt_prev = ptype; //pt_prev的加入是為了優化,只有當找到下一個匹配的時候,才執行這一次的回撥函式
- }
- }
-
- #ifdef CONFIG_NET_CLS_ACT
- skb = handle_ing(skb, &pt_prev, &ret, orig_dev);
- if (!skb)
- goto out;
- ncls:
- #endif
- rx_handler = rcu_dereference(skb->dev->rx_handler); //由具體驅動決定
- if (rx_handler) {
- if (pt_prev) {
- ret = deliver_skb(skb, pt_prev, orig_dev);
- pt_prev = NULL;
- }
- switch (rx_handler(&skb)) {
- case RX_HANDLER_CONSUMED:
- goto out;
- case RX_HANDLER_ANOTHER:
- goto another_round;
- case RX_HANDLER_EXACT:
- deliver_exact = true;
- case RX_HANDLER_PASS:
- break;
- default:
- BUG();
- }
- }
-
- if (vlan_tx_tag_present(skb)) {
- if (pt_prev) {
- ret = deliver_skb(skb, pt_prev, orig_dev);
- pt_prev = NULL;
- }
- if (vlan_do_receive(&skb)) {
- ret = __netif_receive_skb(skb);
- goto out;
- } else if (unlikely(!skb))
- goto out;
- }
-
- /* deliver only exact match when indicated */
- null_or_dev = deliver_exact ? skb->dev : NULL;
-
- type = skb->protocol;
- list_for_each_entry_rcu(ptype,
- &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
- if (ptype->type == type &&
- (ptype->dev == null_or_dev || ptype->dev == skb->dev ||
- ptype->dev == orig_dev)) {
- if (pt_prev)
- ret = deliver_skb(skb, pt_prev, orig_dev); //atomic_inc(&skb->users);
- pt_prev = ptype;
- }
- }
-
- if (pt_prev) {
- ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev); //一般的最後這一次沒有引用計數的增加,直接呼叫函式
- } else {
- atomic_long_inc(&skb->dev->rx_dropped);
- kfree_skb(skb);
- /* Jamal, now you will not able to escape explaining
- * me how you were going to use this. :-)
- */
- ret = NET_RX_DROP;
- }
-
- out:
- rcu_read_unlock();
- return ret;
- }
- static struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly;
- static struct list_head ptype_all __read_mostly
- static struct packet_type ip_packet_type __read_mostly = {
- .type = cpu_to_be16(ETH_P_IP),
- .func = ip_rcv,
- .gso_send_check = inet_gso_send_check,
- .gso_segment = inet_gso_segment,
- .gro_receive = inet_gro_receive,
- .gro_complete = inet_gro_complete,
- };
-
- static struct packet_type arp_packet_type __read_mostly = {
- .type = cpu_to_be16(ETH_P_ARP),
- .func = arp_rcv,
- }
- 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) //驅動根據MAC地址設定的,如果MAC地址不是本機的話,在這裡丟棄。
- 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
- */
-
- 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))) //校驗ip頭是否正確
- goto inhdr_error;
- len = ntohs(iph->tot_len); //iph中的大小是真正的大小,skb中len的大小是驅動中設定的,當包很小的時候,會進行填充,因此會比iph中的大
- if (skb->len < len) {//以r8169為例,如果收到udp的包負載為1,則iph中的大小為20+8+1=29。但是此時skb->len=46=64(min)-14-4(vlan)
- 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);
-
- 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;
- }
- 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.
- */
- if (skb_dst(skb) == NULL) {
- int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,//路由尋找,根據目的地址判斷是本地接收還是轉發(使能forward的話)
- iph->tos, skb->dev);
- if (unlikely(err)) {
- if (err == -EHOSTUNREACH)
- IP_INC_STATS_BH(dev_net(skb->dev),
- IPSTATS_MIB_INADDRERRORS);
- else if (err == -ENETUNREACH)
- IP_INC_STATS_BH(dev_net(skb->dev),
- IPSTATS_MIB_INNOROUTES);
- else if (err == -EXDEV)
- NET_INC_STATS_BH(dev_net(skb->dev),
- LINUX_MIB_IPRPFILTER);
- goto drop;
- }
- }
-
-
相關推薦
Linux核心分析 - 網路:netif_receive_skb平臺報文入口函式詳解
網路收包流程從網絡卡驅動開始,一直往上,涉及NAPI、GRO、RPS等特性,但是一般最後都會呼叫__netif_receive_skb函式: 函式主要有幾個處理: 1、vlan報文的處理,主要是迴圈把vlan頭剝掉,如果qinq場景,兩個vlan都會被剝掉; 2、交給rx_h
Linux核心分析 - 網路:網橋原理分析
網橋資料包的處理流程 網橋處理包遵循以下幾條原則: 1. 在一個介面上接收的包不會再在那個介面上傳送這個資料包; 2. 每個接收到的資料包都要學習其源地址; &nbs
linux網路程式設計之shutdown() 與 close()函式詳解
1.close()函式 #include<unistd.h> int close(int sockfd); //返回成功為0,出錯為-1. close 一個套接字的預設行為是把套接字標記為已關閉,然後立即返回到呼叫程序,該套接字描述符不能再由呼叫
Linux核心讀取檔案流程原始碼及阻塞點超詳解
以linux核心3.13版本為例,首先核心通過系統呼叫read(),執行sys_read()函式,在檔案linux/fs/read_write.c中: //linux/fs/read_write.c SYSCALL_DEFINE3(read, unsigne
Opencv基礎: Mat類裡setTo函式詳解
https://blog.csdn.net/oMoDao1/article/details/80324360 函式原型: /** @brief Sets all or some of the array elements to the specified value. &n
網路位元組序之間的轉換函式詳解
接下來介紹兩組地址轉換函式,它們在ASCII字串和網路位元組序的二進位制值之間轉換網際地址。 (1).inet_aton,inet_addr和inet_ntoa在點分十進位制數串與它長度為32的網路位元組序二進位制值間轉換IPV4地址。你可能會在許多現有程式碼中見到這些函式
Linux核心移植 part2:uboot裝置樹--生成過程分析
本文從裝置樹軟體控制相關程式碼進行分析,進而理清裝置樹相關的知識。 先放一個裝置樹在記憶體中的結構圖: 分析來源為$(tree)/lib/fdtdec_test.c 一、資料結構 1.1 檔案頭 每個dtb都包含如下結構的檔案頭,用來表示裝
Linux核心分析(六):程序的描述和程序的建立
一、Linux中的程序簡析 程序是具有多道程式設計的作業系統的基本概念,關於程序的定義就是程式執行的一個例項,也是系統資源排程的最小單位。如果同一個程式被多個使用者同時執行,那麼這個程式就有多個相對獨立的程序,與此同時他們又共享相同的執行程式碼。在Li
Linux核心分析:Linux核心如何裝載和啟動一個可執行程式
1.編譯連結的過程和ELF可執行檔案格式 從一個原始碼檔案到一個可執行程式檔案大概要經歷如下過程: 以C程式碼為例子,有如下程式碼的一個hello.c檔案 //hello.c #include <stdio.h> int ma
分析Linux核心啟動過程:從start_kernel到init
cd ~/LinuxKernel/ wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.18.6.tar.xz xz -d linux-3.18.6.tar.xz tar -xvf l
《linux核心分析》作業一:反彙編一個C語言程式並分析彙編程式碼執行過程
楊新峰原創作品轉載請註明出處 《Linux核心分析》 MOOC課程http://mooc.study.163.com/course/USTC-1000029000 實驗環境:實驗樓網站64位linux虛擬機器 原始碼如下: int g(int x){ re
Linux核心分析:實驗一
計算機體系結構與程式執行過程 現代計算機大都採用的是“馮.諾依曼”體系結構,它的核心思想是:程式儲存,指令和資料不加區分的放在一個儲存器中。由指令指標暫存器儲存著下一條將要執行指令的地址,這個暫存器在32位系統中叫eip,64位系統中叫rip。
【轉載】Android Bug分析系列:第三方平臺安裝app啟動後,home鍵回到桌面後點擊app啟動時會再次啟動入口類bug的原因剖析
特殊 返回 androidm android系統 圖片 管理 相關 OS 簡便 前言 前些天,測試MM發現了一個比較奇怪的bug。 具體表現是: 1、將app包通過電腦QQ傳送到手機QQ上面,點擊安裝,安裝後選擇打開app (此間的應用邏輯應該是要觸發 【閃屏頁
Linux核心分析第二次作業
這周學習了《庖丁解牛Linux核心分析》並且學習了實驗樓的相關知識。 在實驗樓的虛擬環境下編寫程式碼: 通過gcc編譯後,使用檢視檔案命令:cat -n 20189223.c 在vim中,通過“g/\.s
Linux 核心分析及應用
編輯推薦 本書分模組介紹了 Linux 作業系統的核心設計和實現,針對關鍵概念、演算法和資料結構做了重點的講解。同時,對諸多經典應用程式進行了剖析,如 Nginx、Memcached、Redis、LVS 等,講解如何利用作業系統提供的底層支援進行合理的應用設計和實現。 內容簡介 本書由架構師親
讀書筆記:LINUX核心完全剖析:基於0.12核心
讀書筆記:LINUX核心完全剖析 IBM PC及其相容機主要使用 獨立編址方式,採用獨立的I/O地址空間對控制裝置中的暫存器進行定址和訪問,IBM PC也部分地使用統一編址。對於使用EISA、PCI等匯流排結構的PC,有64KB的I/O地址空間可供使用。在普通Li
Linux核心分析第六次作業
分析system_call中斷處理過程 一、先在實驗樓的虛擬機器中MenuOs增加utsname和utsname-asm指令。 具體實現如下: 1、克隆最新新版本的menu,之後進入menu 2、進入test.c,完成之後make rootfs,使系統自動編譯自動執行 3.設定分割點,用gdb追
ifconfig---配置和顯示Linux核心中網路介面
ifconfig命令被用於配置和顯示Linux核心中網路介面的網路引數。用ifconfig命令配置的網絡卡資訊,在網絡卡重啟後機器重啟後,配置就不存在。要想將上述的配置資訊永遠的存的電腦裡,那就要修改網絡卡的配置檔案了。 語法 ifconfig(引數) 引數 add<地址>:設定網路裝置I
Linux核心分析第七次作業
分析Linux核心建立一個新程序的過程 Linux中建立程序一共有三個函式: 1. fork,建立子程序 2. vfork,與fork類似,但是父子程序共享地址空間,而且子程序先於父程序執行。 3. clone,主要用於建立執行緒 程序建立的大概過程 通過之前的學習,我們
讀書筆記《Linux核心完全剖析:基於0.12核心》——第三章 核心程式語言和環境
3.1 as86彙編器 linux 0.1x系統中使用了兩種彙編器(Assembler)。一種是能產生16位程式碼的as86彙編器,配套ld86連結器;另一種是GNU的彙編器gas(as),使用GNU ld連結器。 編譯器和連結器的原始碼可以從FTP伺服器ftp