1. 程式人生 > >Linux核心分析 - 網路:netif_receive_skb平臺報文入口函式詳解

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


  1. static int __netif_receive_skb(
    struct sk_buff *skb)
  2. {
  3.     struct packet_type *ptype, *pt_prev;
  4.     rx_handler_func_t *rx_handler;
  5.     struct net_device *orig_dev;
  6.     struct net_device *
    null_or_dev;
  7.     bool deliver_exact = false;
  8.     int ret = NET_RX_DROP;
  9.     __be16 type;

  10.     if (!netdev_tstamp_prequeue)
  11.         net_timestamp_check(
    skb);

  12.     trace_netif_receive_skb(skb);

  13.     if (netpoll_receive_skb(skb))
  14.         return NET_RX_DROP;

  15.     if (!skb->skb_iif)
  16.         skb->skb_iif = skb->dev->ifindex;
  17.     orig_dev = skb->dev;

  18.     skb_reset_network_header(skb);  //把L3、L4的頭都指向data資料結構,到這裡的時候skb已經處理完L2層的頭了
  19.     skb_reset_transport_header(skb);
  20.     skb_reset_mac_len(skb);

  21.     pt_prev = NULL;

  22.     rcu_read_lock();

  23. another_round:

  24.     __this_cpu_inc(softnet_data.processed);

  25.     if (skb->protocol == cpu_to_be16(ETH_P_8021Q)) {
  26.         skb = vlan_untag(skb);
  27.         if (unlikely(!skb))
  28.             goto out;
  29.     }

  30. #ifdef CONFIG_NET_CLS_ACT
  31.     if (skb->tc_verd & TC_NCLS) {
  32.         skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
  33.         goto ncls;
  34.     }
  35. #endif

  36.     list_for_each_entry_rcu(ptype, &ptype_all, list) {  //把包交給特定協議相關的處理函式前,先呼叫ptype_all中註冊的函式
  37.         if (!ptype->dev || ptype->dev == skb->dev) {    //最常見的為tcpdump,該工具就是從這裡拿到所有收到的包的
  38.             if (pt_prev)
  39.                 ret = deliver_skb(skb, pt_prev, orig_dev);
  40.             pt_prev = ptype;  //pt_prev的加入是為了優化,只有當找到下一個匹配的時候,才執行這一次的回撥函式
  41.         }                     
  42.     }

  43. #ifdef CONFIG_NET_CLS_ACT
  44.     skb = handle_ing(skb, &pt_prev, &ret, orig_dev);
  45.     if (!skb)
  46.         goto out;
  47. ncls:
  48. #endif
  49.     rx_handler = rcu_dereference(skb->dev->rx_handler);  //由具體驅動決定
  50.     if (rx_handler) {
  51.         if (pt_prev) {
  52.             ret = deliver_skb(skb, pt_prev, orig_dev);
  53.             pt_prev = NULL;
  54.         }
  55.         switch (rx_handler(&skb)) {
  56.         case RX_HANDLER_CONSUMED:
  57.             goto out;
  58.         case RX_HANDLER_ANOTHER:
  59.             goto another_round;
  60.         case RX_HANDLER_EXACT:
  61.             deliver_exact = true;
  62.         case RX_HANDLER_PASS:
  63.             break;
  64.         default:
  65.             BUG();
  66.         }
  67.     }

  68.     if (vlan_tx_tag_present(skb)) {
  69.         if (pt_prev) {
  70.             ret = deliver_skb(skb, pt_prev, orig_dev);
  71.             pt_prev = NULL;
  72.         }
  73.         if (vlan_do_receive(&skb)) {
  74.             ret = __netif_receive_skb(skb);
  75.             goto out;
  76.         } else if (unlikely(!skb))
  77.             goto out;
  78.     }

  79.     /* deliver only exact match when indicated */
  80.     null_or_dev = deliver_exact ? skb->dev : NULL;

  81.     type = skb->protocol;
  82.     list_for_each_entry_rcu(ptype,
  83.             &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
  84.         if (ptype->type == type &&
  85.             (ptype->dev == null_or_dev || ptype->dev == skb->dev ||
  86.              ptype->dev == orig_dev)) {
  87.             if (pt_prev)
  88.                 ret = deliver_skb(skb, pt_prev, orig_dev); //atomic_inc(&skb->users);
  89.             pt_prev = ptype;
  90.         }
  91.     }

  92.     if (pt_prev) {
  93.         ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);  //一般的最後這一次沒有引用計數的增加,直接呼叫函式
  94.     } else {
  95.         atomic_long_inc(&skb->dev->rx_dropped);
  96.         kfree_skb(skb);
  97.         /* Jamal, now you will not able to escape explaining
  98.          * me how you were going to use this. :-)
  99.          */
  100.         ret = NET_RX_DROP;
  101.     }

  102. out:
  103.     rcu_read_unlock();
  104.     return ret;
  105. }
該函式涉及兩個全域性變數:
  1. static struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly;
  2. static struct list_head ptype_all __read_mostly
看幾個常見的packet_type,這些都在相應的協議初始化的時候呼叫dev_add_pack加入到特性的連結串列中:
  1. static struct packet_type ip_packet_type __read_mostly = {
  2.     .type = cpu_to_be16(ETH_P_IP),
  3.     .func = ip_rcv,
  4.     .gso_send_check = inet_gso_send_check,
  5.     .gso_segment = inet_gso_segment,
  6.     .gro_receive = inet_gro_receive,
  7.     .gro_complete = inet_gro_complete,
  8. };

  9. static struct packet_type arp_packet_type __read_mostly = {
  10.     .type = cpu_to_be16(ETH_P_ARP),
  11.     .func = arp_rcv,
  12. }
在ip_rcv函式中會對L3頭做一些有效性檢測:
  1. int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
  2. {
  3.     const struct iphdr *iph;
  4.     u32 len;

  5.     /* When the interface is in promisc. mode, drop all the crap
  6.      * that it receives, do not try to analyse it.
  7.      */
  8.     if (skb->pkt_type == PACKET_OTHERHOST)  //驅動根據MAC地址設定的,如果MAC地址不是本機的話,在這裡丟棄。
  9.         goto drop;


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

  11.     if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
  12.         IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
  13.         goto out;
  14.     }

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

  17.     iph = ip_hdr(skb);

  18.     /*
  19.      * RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum.
  20.      *
  21.      * Is the datagram acceptable?
  22.      *
  23.      * 1. Length at least the size of an ip header
  24.      * 2. Version of 4
  25.      * 3. Checksums correctly. [Speed optimisation for later, skip loopback checksums]
  26.      * 4. Doesn't have a bogus length
  27.      */

  28.     if (iph->ihl < 5 || iph->version != 4)
  29.         goto inhdr_error;

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

  32.     iph = ip_hdr(skb);

  33.     if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))  //校驗ip頭是否正確
  34.         goto inhdr_error;
  35.     len = ntohs(iph->tot_len);  //iph中的大小是真正的大小,skb中len的大小是驅動中設定的,當包很小的時候,會進行填充,因此會比iph中的大
  36.     if (skb->len < len) {//以r8169為例,如果收到udp的包負載為1,則iph中的大小為20+8+1=29。但是此時skb->len=46=64(min)-14-4(vlan)
  37.         IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
  38.         goto drop;
  39.     } else if (len < (iph->ihl*4))
  40.         goto inhdr_error;

  41.     /* Our transport medium may have padded the buffer out. Now we know it
  42.      * is IP we can trim to the true length of the frame.
  43.      * Note this now means skb->len holds ntohs(iph->tot_len).
  44.      */
  45.     if (pskb_trim_rcsum(skb, len)) {  //去除填充的資料
  46.         IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
  47.         goto drop;
  48.     }

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

  51.     /* Must drop socket now because of tproxy. */
  52.     skb_orphan(skb);

  53.     return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
  54.                ip_rcv_finish);

  55. inhdr_error:
  56.     IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);
  57. drop:
  58.     kfree_skb(skb);
  59. out:
  60.     return NET_RX_DROP;
  61. }
然後呼叫ip_rcv_finish:

  1. static int ip_rcv_finish(struct sk_buff *skb)
  2. {
  3.     const struct iphdr *iph = ip_hdr(skb);
  4.     struct rtable *rt;

  5.     /* 
  6.      * Initialise the virtual path cache for the packet. It describes
  7.      * how the packet travels inside Linux networking.
  8.      */
  9.     if (skb_dst(skb) == NULL) {
  10.         int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,//路由尋找,根據目的地址判斷是本地接收還是轉發(使能forward的話)
  11.                            iph->tos, skb->dev);
  12.         if (unlikely(err)) {
  13.             if (err == -EHOSTUNREACH)
  14.                 IP_INC_STATS_BH(dev_net(skb->dev),
  15.                         IPSTATS_MIB_INADDRERRORS);
  16.             else if (err == -ENETUNREACH)
  17.                 IP_INC_STATS_BH(dev_net(skb->dev),
  18.                         IPSTATS_MIB_INNOROUTES);
  19.             else if (err == -EXDEV)
  20.                 NET_INC_STATS_BH(dev_net(skb->dev),
  21.                          LINUX_MIB_IPRPFILTER);
  22.             goto drop;
  23.         }
  24.     } 

  25. 相關推薦

    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核心移植 part2uboot裝置樹--生成過程分析

    本文從裝置樹軟體控制相關程式碼進行分析,進而理清裝置樹相關的知識。 先放一個裝置樹在記憶體中的結構圖: 分析來源為$(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