1. 程式人生 > >Linux網卡驅動框架及制作虛擬網卡

Linux網卡驅動框架及制作虛擬網卡

模式 繼續 receiver 適配器 流量 ssa source 內核 init

1.概述

網卡驅動與硬件相關,主要負責收發網絡的數據包,將上層協議傳遞下來的數據包以特定的媒介訪問控制方式進行發送,並將接收到的數據包傳遞給上層協議。

網卡設備與字符設備,塊設備不同,網絡設備驅動程序不依賴與 /dev 或 /sys 來與用戶空間通信,應用程序是通過網絡接口(如作為第一個網絡接口的eth0)與網卡驅動程序互相操作的。

網卡設備存放在 /sys/class/net 目錄下

2.Linux 系統網絡協議的處理框架

網絡設備驅動的框架層次分為四層:

技術分享圖片

1)網絡設備與媒介層:

用來負責完成數據包發送和接收的物理實體, 設備驅動功能層的函數都在這物理上驅動的,即DM9000網絡處理芯片。

2)設備驅動功能層:

用來負責驅動網絡設備硬件來完成各個功能, 如通過hard_start_xmit() 函數啟動發送操作, 並通過網絡設備上的中斷觸發接收操作,即DM9000網卡驅動,實現文件在 linux/driver/net

3)網絡設備接口層:

是整個網絡接口的關鍵部位,它為網絡協議提供統一的發送接口,屏蔽各種物理介質,同時又負責把來自下層的包向合適的協議配送。

通過net_device結構體來描述一個具體的網絡設備的信息,實現不同的硬件的統一。實現文件在linux/net/core 下,其中dev.c 為主要實現文件。

4)網絡協議接口層:

此部分實現了各種具體協議,Linux支持 TCP/IP, IPX, X.25, AppleTalk 等協議,實現源碼在Linux / net 目錄下有對應名稱。

此處主要討論 TCP/IP (IPv4) 協議,實現源碼在 linux/net/ipv4 下,其中 af_inet.c為主要實現文件。

實現統一的數據包收發的協議,該層主要負責調用dev_queue_xmit()函數發送數據, netif_rx()函數接收數據

-------------------------

在網絡協議接口層之上還有一層為 網絡接口socket層, 主要為用戶提供網絡服務的編程接口,方便用戶進行網絡應用程序開發,源碼在 linux/net/socket.c

技術分享圖片

--------------------------

了解網絡設備驅動的框架層次後,下面來分析網卡驅動的初始化,數據發送和接收的處理流程。

3.網卡驅動初始化

此次的網卡驅動程序,只需要編寫網絡設備接口層,填充net_device數據結構的內容並將net_device註冊入內核,設置硬件相關操作,使能中斷處理等

3.1其中net_device結構體的重要成員:

 1 struct net_device
 2 {
 3     char             name[IFNAMSIZ];   //網卡設備名稱
 4     unsigned long    mem_end;          //該設備的內存結束地址
 5     unsigned long    mem_start;        //該設備的內存起始地址
 6     unsigned long    base_addr;        //該設備的內存I/O基地址
 7     unsigned int     irq;              //該設備的中斷號
 8     unsigned char    if_port;          //多端口設備使用的端口類型
 9    unsigned char    dma;              //該設備的DMA通道
10     unsigned long    state;            //網絡設備和網絡適配器的狀態信息
11     ......
12     struct net_device_stats* (*get_stats)(struct net_device *dev); //獲取流量的統計信息
13   //運行ifconfig便會調用該成員函數,並返回一個net_device_stats結構體獲取信息
14 
15     struct net_device_stats  stats;     //用來保存統計信息的net_device_stats結構體
16 
17  
18     unsigned long   features;            //接口特征,     
19     unsigned int    flags;                 //flags指網絡接口標誌,以IFF_(Interface Flags)開頭
20     //當flags =IFF_UP( 當設備被激活並可以開始發送數據包時, 內核設置該標誌)、 IFF_AUTOMEDIA(設置設備可在多種媒介間切換)、
21     //IFF_BROADCAST( 允許廣播)、IFF_DEBUG( 調試模式, 可用於控制printk調用的詳細程度) 、 IFF_LOOPBACK( 回環)、
22     //IFF_MULTICAST( 允許組播) 、 IFF_NOARP( 接口不能執行ARP,點對點接口就不需要運行 ARP) 和IFF_POINTOPOINT( 接口連接到點到點鏈路) 等。
23 
24     unsigned        mtu;        //最大傳輸單元,也叫最大數據包
25 
26     unsigned short  type;     //接口的硬件類型
27 
28     unsigned short  hard_header_len;         //硬件幀頭長度,一般被賦為ETH_HLEN,即14
29 
30   unsigned char   dev_addr[MAX_ADDR_LEN];   //存放設備的MAC地址
31 
32     unsigned long   last_rx;    //接收數據包的時間戳,調用netif_rx()後賦上jiffies即可
33 
34     unsigned long   trans_start; //發送數據包的時間戳,當要發送的時候賦上jiffies即可
35 
36     unsigned char   dev_addr[MAX_ADDR_LEN];    //MAC地址
37  
38     int  (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);
39      //數據包發送函數, sk_buff就是用來收發數據包的結構體
40 
41 
42   void  (*tx_timeout) (struct net_device *dev); //發包超時處理函數
43   ... ...
44 }

上面的統計信息net_device_stats結構體,其中重要成員如下所示:

 1 struct net_device_stats
 2 {
 3        unsigned long       rx_packets;            /*收到的數據包數*/
 4        unsigned long       tx_packets;            /*發送的數據包數    */
 5        unsigned long       rx_bytes;               /*收到的字節數,可以通過sk_buff結構體的成員len來獲取*/
 6        unsigned long       tx_bytes;               /*發送的字節數,可以通過sk_buff結構體的成員len來獲取*/
 7        unsigned long       rx_errors;              /*收到的錯誤數據包數*/
 8        unsigned long       tx_errors;              /*發送的錯誤數據包數*/
 9        ... ...
10 }

3.2 所以init函數,初始化網卡步驟如下所示:

  • 1)使用alloc_netdev()來分配一個net_device結構體
  • 2)設置網卡硬件相關的寄存器
  • 3)設置net_device結構體的成員
  • 4)使用register_netdev()來註冊net_device結構體

4.網卡驅動發包過程

在內核中,當上層要發送一個數據包時, 就會調用網絡設備層裏net_device數據結構的成員hard_start_xmit()將數據包發送出去。

hard_start_xmit()發包函數需要我們自己構建,該函數原型如下所示:

int    (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);

在這個函數中需要涉及到sk_buff結構體,含義為(socket buffer)套接字緩沖區,用來網絡各個層次之間傳遞數據.

4.1 sk_buff結構體是一個雙向鏈表,其中重要成員如下所示:

 1 struct sk_buff {
 2        /* These two members must be first. */
 3        struct sk_buff        *next;     //指向下一個sk_buff結構體
 4        struct sk_buff        *prev;     //指向前一個sk_buff結構體
 5     ... ...
 6        unsigned int         len,        //數據包的總長度,包括線性數據和非線性數據
 7                             data_len,      //非線性的數據長度
 8                             mac_len;       //mac包頭長度
 9 
10     __u32         priority;           //該sk_buff結構體的優先級   
12     __be16        protocol;           //存放上層的協議類型,可以通過eth_type_trans()來獲取
13        ... ...
14 
15       sk_buff_data_t              transport_header;    //傳輸層頭部的偏移值
16       sk_buff_data_t              network_header;     //網絡層頭部的偏移值
17       sk_buff_data_t              mac_header;          //MAC數據鏈路層頭部的偏移值
18 
19     sk_buff_data_t              tail;                    //指向緩沖區的數據包末尾
20       sk_buff_data_t              end;                     //指向緩沖區的末尾
21       unsigned char            *head,                   //指向緩沖區的協議頭開始位置
22                                   *data;                   //指向緩沖區的數據包開始位置
23        ... ...
24 }

其中sk_buff結構體的空間,如下圖所示:

技術分享圖片

其中sk_buff-> data數據包格式如下圖所示:

技術分享圖片

4.2 所以,hard_start_xmit()發包函數處理步驟如下所示:

1)把數據包發出去之前,需要使用netif_stop_queue()來停止上層傳下來的數據包,

2)設置寄存器,通過網絡設備硬件,來發送數據

3)當數據包發出去後, 再調用dev_kfree_skb()函數來釋放sk_buff,該函數原型如下:

void dev_kfree_skb(struct sk_buff *skb);           

 4)當數據包發出成功,就會進入TX中斷函數,然後更新統計信息,調用netif_wake_queue()來喚醒,啟動上層繼續發包下來.

 5)若數據包發出去超時,一直進不到TX中斷函數,就會調用net_device結構體的(*tx_timeout)超時成員函數,在該函數中更新統計信息, 調用netif_wake_queue()來喚醒

其中netif_wake_queue()和netif_stop_queue()函數原型如下所示:

1 void netif_wake_queue(struct net_device *dev);  //喚醒被阻塞的上層,啟動繼續向網絡設備驅動層發送數據包
2 
3 void netif_stop_queue(struct net_device *dev); //阻止上層向網絡設備驅動層發送數據包

5.網卡驅動收包過程

接收數據包主要是通過中斷函數處理,來判斷中斷類型,如果等於 ISQ_RECEIVER_EVENT, 表示為接收中斷,然後進入接收數據函數,通過 netif_rx() 將數據上交給上層

例如下圖所示,參考的內核中自帶的網卡驅動: /drivers/net/cs89x0.c

 1 static irqreturn_t net_interrupt(int irq, void *dev_id)
 2 {
 3     struct net_device *dev = dev_id;
 4     struct net_local *lp;
 5     int ioaddr, status;
 6      int handled = 0;
 7 
 8     ioaddr = dev->base_addr;
 9     lp = netdev_priv(dev);
10 
11     /* we MUST read all the events out of the ISQ, otherwise we‘ll never
12            get interrupted again.  As a consequence, we can‘t have any limit
13            on the number of times we loop in the interrupt handler.  The
14            hardware guarantees that eventually we‘ll run out of events.  Of
15            course, if you‘re on a slow machine, and packets are arriving
16            faster than you can read them off, you‘re screwed.  Hasta la
17            vista, baby!  */
18     while ((status = readword(dev->base_addr, ISQ_PORT))) {
19         if (net_debug > 4)printk("%s: event=%04x\n", dev->name, status);
20         handled = 1;
21         switch(status & ISQ_EVENT_MASK) {
22         case ISQ_RECEIVER_EVENT:     /* 判斷是否為接收中斷 */
23             /* Got a packet(s). */
24             net_rx(dev);  /* 進入net_rx(dev)函數,將接收的數據交給上層 */
25             break;
26         case ISQ_TRANSMITTER_EVENT:  /* 判斷是否為發送中斷 */
27             lp->stats.tx_packets++;
28             netif_wake_queue(dev);    /* Inform upper layers. */
29             if ((status & (    TX_OK |
30                     TX_LOST_CRS |
31                     TX_SQE_ERROR |
.......................................................

如上圖所示,通過獲取的status標誌來判斷是什麽中斷,如果是接收中斷,就進入net_rx()

5.1 其中net_rx()收包函數處理步驟如下所示:

  • 1)使用dev_alloc_skb()來構造一個新的sk_buff
  • 2)使用skb_reserve(rx_skb, 2); 將sk_buff緩沖區裏的數據包先後位移2字節,來騰出sk_buff緩沖區裏的頭部空間
  • 3)讀取網絡設備硬件上接收到的數據
  • 4)使用memcpy()將數據復制到新的sk_buff裏的data成員指向的地址處,可以使用skb_put()來動態擴大sk_buff結構體裏中的數據區
  • 5)使用eth_type_trans()來獲取上層協議,將返回值賦給sk_buff的protocol成員裏
  • 6)然後更新統計信息,最後使用netif_rx( )來將sk_fuffer傳遞給上層協議中

其中skb_put()函數原型如下所示:

static inline unsigned char *skb_put(struct sk_buff *skb, unsigned int len);
//len:將數據區向下擴大len字節

使用skb_put()函數後,其中sk_buff緩沖區變化如下圖:

技術分享圖片

6.寫虛擬網卡驅動

本節便開始來寫一個簡單的虛擬網卡驅動,也就是說不需要硬件相關操作,所以就沒有中斷函數,我們通過linux的ping命令來實現發包,然後在發包函數中偽造一個收的ping包函數,實現能ping通任何ip地址

在init初始函數中:

  • 1)使用alloc_netdev()來分配一個net_device結構體
  • 2)設置net_device結構體的成員
  • 3)使用register_netdev()來註冊net_device結構體

在發包函數中:

  • 1)使用netif_stop_queue()來阻止上層向網絡設備驅動層發送數據包
  • 2)調用收包函數,並代入發送的sk_buff緩沖區, 裏面來偽造一個收的ping包函數
  • 3)使用dev_kfree_skb()函數來釋放發送的sk_buff緩存區
  • 4)更新發送的統計信息
  • 5)使用netif_wake_queue()來喚醒被阻塞的上層,

在收包函數中:

首先修改發送的sk_buff裏數據包的數據,使它變為一個接收的sk_buff,其中數據包結構如下圖所示:

技術分享圖片

  • 1)需要對調上圖的ethhdr結構體 ”源/目的”MAC地址
  • 2)需要對調上圖的iphdr結構體”源/目的” IP地址
  • 3)使用ip_fast_csum()來重新獲取iphdr結構體的校驗碼
  • 4)設置上圖數據包的數據類型,之前是發送ping包0x08,需要改為0x00,表示接收ping包
  • 5)使用dev_alloc_skb()來構造一個新的sk_buff
  • 6)使用skb_reserve(rx_skb, 2);將sk_buff緩沖區裏的數據包先後位移2字節,來騰出sk_buff緩沖區裏的頭部空間
  • 7)使用memcpy()將之前修改好的sk_buff->data復制到新的sk_buff裏的data成員指向的地址處:
memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);
// skb_put():來動態擴大sk_buff結構體裏中的數據區,避免溢出
  • 8)設置新的sk_buff 其它成員
  • 9)使用eth_type_trans()來獲取上層協議,將返回值賦給sk_buff的protocol成員裏
  • 10)然後更新接收統計信息,最後使用netif_rx( )來將sk_fuffer傳遞給上層協議中

7.驅動具體代碼如下:

技術分享圖片
  1 /*
  2  *參考linux-2.6.22.6\drivers\net\Cs89x0.c
  3  */
  4 
  5 #include <linux/module.h>
  6 #include <linux/errno.h>
  7 #include <linux/netdevice.h>
  8 #include <linux/etherdevice.h>
  9 #include <linux/kernel.h>
 10 #include <linux/types.h>
 11 #include <linux/fcntl.h>
 12 #include <linux/interrupt.h>
 13 #include <linux/ioport.h>
 14 #include <linux/in.h>
 15 #include <linux/skbuff.h>
 16 #include <linux/slab.h>
 17 #include <linux/spinlock.h>
 18 #include <linux/string.h>
 19 #include <linux/init.h>
 20 #include <linux/bitops.h>
 21 #include <linux/delay.h>
 22 #include <linux/ip.h>
 23 
 24 
 25 #include <asm/system.h>
 26 #include <asm/io.h>
 27 #include <asm/irq.h>
 28 
 29 
 30 static struct net_device *vnet_dev;
 31 
 32 static void emulator_rx_packet(struct sk_buff *skb, struct net_device *dev)
 33 {
 34     /* 參考LDD3 */
 35     unsigned char *type;
 36     struct iphdr *ih;
 37     __be32 *saddr, *daddr, tmp;
 38     unsigned char    tmp_dev_addr[ETH_ALEN];
 39     struct ethhdr *ethhdr;
 40     
 41     struct sk_buff *rx_skb;
 42         
 43     // 從硬件讀出/保存數據
 44     /* 對調"源/目的"的mac地址 */
 45     ethhdr = (struct ethhdr *)skb->data;
 46     memcpy(tmp_dev_addr, ethhdr->h_dest, ETH_ALEN);
 47     memcpy(ethhdr->h_dest, ethhdr->h_source, ETH_ALEN);
 48     memcpy(ethhdr->h_source, tmp_dev_addr, ETH_ALEN);
 49 
 50     /* 對調"源/目的"的ip地址 */    
 51     ih = (struct iphdr *)(skb->data + sizeof(struct ethhdr));
 52     saddr = &ih->saddr;
 53     daddr = &ih->daddr;
 54 
 55     tmp = *saddr;
 56     *saddr = *daddr;
 57     *daddr = tmp;
 58     
 59     //((u8 *)saddr)[2] ^= 1; /* change the third octet (class C) */
 60     //((u8 *)daddr)[2] ^= 1;
 61     type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr);
 62     //printk("tx package type = %02x\n", *type);
 63     // 修改類型, 原來0x8表示ping
 64     *type = 0; /* 0表示reply */
 65     
 66     ih->check = 0;           /* and rebuild the checksum (ip needs it) */
 67     ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);
 68     
 69     // 構造一個sk_buff
 70     rx_skb = dev_alloc_skb(skb->len + 2);
 71     skb_reserve(rx_skb, 2); /* align IP on 16B boundary */    
 72     memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);
 73 
 74     /* Write metadata, and then pass to the receive level */
 75     rx_skb->dev = dev;
 76     rx_skb->protocol = eth_type_trans(rx_skb, dev);
 77     rx_skb->ip_summed = CHECKSUM_UNNECESSARY; /* don‘t check it */
 78     dev->stats.rx_packets++;
 79     dev->stats.rx_bytes += skb->len;
 80 
 81     // 提交sk_buff
 82     netif_rx(rx_skb);
 83 }
 84 
 85 static int    virt_net_send_packet(struct sk_buff *skb, struct net_device *dev)
 86 {
 87     static int cnt = 0; 
 88     printk("virt_net_send_packet = %d\n", ++cnt);
 89 
 90     /*對於真實的網卡,會把skb裏的數據發送出去*/
 91     netif_stop_queue(dev);  /* 停止該網卡的隊列 */
 92     /* -------------- */    /* 把skb的數據寫入網卡 */
 93 
 94     /* 構造一個假的sk_buff,上報 */
 95     emulator_rx_packet(skb, dev);
 96     
 97     dev_kfree_skb (skb);    /* 釋放skb */
 98     netif_wake_queue(dev);    /* 數據全部發送出去後,中斷喚醒隊列 */
 99     /*更新統計信息*/
100     dev->stats.tx_packets++;
101     dev->stats.tx_bytes += skb->len;
102 
103     /*構造一個假的sk_buff上報*/
104     emulator_rx_packet(skb, dev);    
105     
106     return 0;
107 }
108 
109 
110 static int  virt_net_init(void)
111 {
112     /* 1.分配一個net_device結構體 */
113     vnet_dev = alloc_netdev(0, "vnet%d", ether_setup);  /*alloc_etherdev*/
114     
115     /* 2.設置 */
116     vnet_dev->hard_start_xmit = virt_net_send_packet;
117 
118     vnet_dev->dev_addr[0] = 0x08;
119     vnet_dev->dev_addr[1] = 0x08;
120     vnet_dev->dev_addr[2] = 0x89;
121     vnet_dev->dev_addr[3] = 0x08;
122     vnet_dev->dev_addr[4] = 0x08;
123     vnet_dev->dev_addr[5] = 0x11;
124 
125     /* 設置一下兩項才能ping通 */
126     vnet_dev->flags           |= IFF_NOARP;
127     vnet_dev->features        |= NETIF_F_NO_CSUM;    
128 
129     /* 3.註冊 */
130     register_netdev(vnet_dev);
131     
132     return 0;
133 }
134 
135 static void  virt_net_exit(void)
136 {
137     unregister_netdev(vnet_dev);
138     free_netdev(vnet_dev);
139 
140 }
141 
142 module_init(virt_net_init);
143 module_exit(virt_net_exit);
144 MODULE_LICENSE("GPL");
virt_net.c

以上內容基本摘自:

Linux網卡驅動框架及制作虛擬網卡