Linux-网卡驱动介绍以及制作虚拟网卡驱动

描述
网卡的驱动还是与硬件有关,注意是负责收发网络的数据包,它将上层协议传递下来的数据包,以特定的媒介访问控制方式进行发送,并将接收到的数据包传递给上层协议。

网卡设备与字符设备和块设备不同,网卡设备并不对应与/dev目录下的文件,不过会放在/sys/class/net目录下。


linux系统对网络设备驱动定义了4个层次,这四个层次为:

  • 网络协议接口层
    实现统一的数据包收发的协议,该层主要负责调用dev_queue_xmit()函数发送数据, netif_rx()函数接收数据。
  • 网络设备接口层
    通过net_device结构体来描述一个具体的网络设备的信息,实现不同的硬件的统一。
  • 设备驱动功能层
    用来负责驱动网络设备硬件来完成各个功能, 它通过hard_start_xmit() 函数启动发送操作, 并通过网络设备上的中断触发接收操作。
  • 网络设备与媒介层

    用来负责完成数据包发送和接收的物理实体, 设备驱动功能层的函数都在这物理上驱动的。

网卡驱动初始化
网卡驱动程序,只需要编写网络设备接口层,填充net_device数据结构的内容并将net_device注册入内核,设置硬件相关操作,使能中断处理等。
其中net_device结构体的重要成员

struct net_device
{
   char               name[IFNAMSIZ];              //网卡设备名称
   unsigned long              mem_end;             //该设备的内存结束地址
   unsigned long              mem_start;            //该设备的内存起始地址
   unsigned long              base_addr;            //该设备的内存I/O基地址
   unsigned int          irq;                       //该设备的中断号

   unsigned char        if_port;                  //多端口设备使用的端口类型
   unsigned char        dma;                     //该设备的DMA通道

   unsigned long              state;                    //网络设备和网络适配器的状态信息

        
                                    
   struct net_device_stats* (*get_stats)(struct net_device *dev); //获取流量的统计信息
                                         
  //运行ifconfig便会调用该成员函数,并返回一个net_device_stats结构体获取信息

   struct net_device_stats  stats;      //用来保存统计信息的net_device_stats结构体


   unsigned long              features;        //接口特征,     
   unsigned int          flags; //flags指网络接口标志,以IFF_(Interface Flags)开头
   //当flags =IFF_UP( 当设备被激活并可以开始发送数据包时, 内核设置该标志)、 IFF_AUTOMEDIA(设置设备可在多种媒介间切换)、
  IFF_BROADCAST( 允许广播)、IFF_DEBUG( 调试模式, 可用于控制printk调用的详细程度) 、 IFF_LOOPBACK( 回环)、
 IFF_MULTICAST( 允许组播) 、 IFF_NOARP( 接口不能执行ARP,点对点接口就不需要运行 ARP) 和IFF_POINTOPOINT( 接口连接到点到点链路) 等。


   unsigned        mtu;        //最大传输单元,也叫最大数据包

   unsigned short  type;     //接口的硬件类型

   unsigned short   hard_header_len;     //硬件帧头长度,一般被赋为ETH_HLEN,即14
                          
   unsigned char   dev_addr[MAX_ADDR_LEN];      //存放设备的MAC地址

   unsigned long              last_rx;    //接收数据包的时间戳,调用netif_rx()后赋上jiffies即可

   unsigned long              trans_start;     //发送数据包的时间戳,当要发送的时候赋上jiffies即可

   unsigned char        dev_addr[MAX_ADDR_LEN];                //MAC地址


   int                 (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);
                               //数据包发送函数, sk_buff就是用来收发数据包的结
   void  (*tx_timeout) (struct net_device *dev); //发包超时处理函数

上面讲到的统计信息net_device_stats结构体,其中重要成员如下所示:

 struct net_device_stats
{
     unsigned long       rx_packets;            /*收到的数据包数*/
     unsigned long       tx_packets;            /*发送的数据包数    */
     unsigned long       rx_bytes;               /*收到的字节数,可以通过sk_buff结构体的成员len来获取*/
     unsigned long       tx_bytes;               /*发送的字节数,可以通过sk_buff结构体的成员len来获取*/
     unsigned long       rx_errors;              /*收到的错误数据包数*/
     unsigned long       tx_errors;              /*发送的错误数据包数*/
   ... ...
}

所以init函数,初始化网卡步骤如下所示

  • 使用alloc_netdev()来分配一个net_device结构体
  • 设置网卡硬件相关的寄存器
  • 设置net_device结构体的成员
  • 使用register_netdev()来注册net_device结构体

网卡驱动发包过程

在内核中,当上层发送一个数据包时,就会调用网络设备层里net_device数据结构的成员hard_start_xmit()将数据包发送出去。

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

这个函数中需要涉及到sk_buff结构体,含义为(socket buffer)套接字缓冲区,用来网络各个层次之间传递数据。

struct sk_buff {
   /* These two members must be first. */
   struct sk_buff        *next;      //指向下一个sk_buff结构体
   struct sk_buff        *prev;     //指向前一个sk_buff结构体
  ... ...
   unsigned int          len,         //数据包的总长度,包括线性数据和非线性数据
                        data_len,        //非线性的数据长度
                        mac_len;         //mac包头长度
     __u32          priority;          //该sk_buff结构体的优先级   
     __be16        protocol;           //存放上层的协议类型,可以通过eth_type_trans()来获取
   ... ...

  sk_buff_data_t              transport_header;    //传输层头部的偏移值
  sk_buff_data_t              network_header;     //网络层头部的偏移值
  sk_buff_data_t              mac_header;          //MAC数据链路层头部的偏移值     
  sk_buff_data_t              tail;                    //指向缓冲区的数据包末尾
  sk_buff_data_t              end;                     //指向缓冲区的末尾
   unsigned char            *head,                   //指向缓冲区的协议头开始位置
                              *data;                   //指向缓冲区的数据包开始位置
   ... ...

其中sk_buff结构体的空间,如下图所示:


其中sk_buff-> data数据包格式如下图所示:

所以,hard_start_xmit()发包函数处理步骤如下所示:

  • 把数据包发出去之前,需要使用netif_stop_queue()来停止上层传下来的数据包。
  • 设置寄存器,通过网络设备硬件,来发送数据。
  • 当数据包发出去后, 再调用dev_kfree_skb()函数来释放sk_buff,该函数原型如下:
  • 当数据包发出成功,就会进入TX中断函数,然后更新统计信息,调用netif_wake_queue()来唤醒,启动上层继续发包下来.
  • 若数据包发出去超时,一直进不到TX中断函数,就会调用net_device结构体的(*tx_timeout)超时成员函数,在该函数中更新统计信息, 调用netif_wake_queue()来唤醒

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

void netif_wake_queue(struct net_device *dev);  //唤醒被阻塞的上层,启动继续向网络设备驱动层发送数据包

void netif_stop_queue(struct net_device *dev); //阻止上层向网络设备驱动层发送数据包

网卡驱动收包过程
接收数据包主要通过中断函数处理,来判断中断类型,如果等于ISQ_RECEIVER_EVENT,表示为接收中断,然后进入接收数据函数,通过netif_xr()将数据上交给上层。
内核中自带的网卡驱动:/drivers/net/cs89x0.c


通过获取的status标志来判断是什么中断,如果是接收中断,就进入net_rx()
其中net_rx()收包函数处理步骤如下所示:

  • 用dev_alloc_skb()来构造一个新的sk_buff
  • 使用skb_reserve(rx_skb, 2); 将sk_buff缓冲区里的数据包先后位移2字节,来腾出sk_buff缓冲区里的头部空间
  • 读取网络设备硬件上接收到的数据
  • 使用memcpy()将数据复制到新的sk_buff里的data成员指向的地址处,可以使用skb_put()来动态扩大sk_buff结构体里中的数据区
  • 使用eth_type_trans()来获取上层协议,将返回值赋给sk_buff的protocol成员里
  • 然后更新统计信息,最后使用netif_rx( )来将sk_fuffer传递给上层协议中

    使用skb_put()函数后,其中sk_buff缓冲区变化如下图:

写虚拟网卡驱动
不需要硬件相关操作,所以就没有中断函数,我们通过linux的ping命令来实现发包,然后在发包函数中伪造一个收的ping包函数,实现能ping通任何ip地址.
在init初始函数中

  • 使用alloc_netdev()来分配一个net_device结构体
  • 设置net_device结构体的成员
  • 使用register_netdev()来注册net_device结构体

在发包函数中:

  • 使用netif_stop_queue()来阻止上层向网络设备驱动层发送数据包
  • 调用收包函数,并代入发送的sk_buff缓冲区, 里面来伪造一个收的ping包函数
  • 使用dev_kfree_skb()函数来释放发送的sk_buff缓存区
  • 更新发送的统计信息
  • 使用netif_wake_queue()来唤醒被阻塞的上层,

在收包函数中:
首先修改发送的sk_buff里数据包的数据,使它变为一个接收的sk_buff,其中数据包结构如下图所示:

  • 需要对调上图的ethhdr结构体 ”源/目的”MAC地址
  • 需要对调上图的iphdr结构体”源/目的” IP地址
  • 使用ip_fast_csum()来重新获取iphdr结构体的校验码
  • 设置上图数据包的数据类型,之前是发送ping包0x08,需要改为0x00,表示接收ping包
  • 使用dev_alloc_skb()来构造一个新的sk_buff
  • 使用skb_reserve(rx_skb, 2);将sk_buff缓冲区里的数据包先后位移2字节,来腾出sk_buff缓冲区里的头部空间
  • 使用memcpy()将之前修改好的sk_buff->data复制到新的sk_buff里的data成员指向的地址处:
  • 设置新的sk_buff 其它成员
  • 使用eth_type_trans()来获取上层协议,将返回值赋给sk_buff的protocol成员里
  • 然后更新接收统计信息,最后使用netif_rx( )来将sk_fuffer传递给上层协议中

驱动具体代码

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/in.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/ip.h> 

#include <asm/system.h>
#include <asm/io.h>
#include <asm/irq.h>


static struct net_device    *virt_net;


static void virt_rs_packet(struct sk_buff *skb, struct net_device *dev)
{
   unsigned char *type;
   struct iphdr *ih;
   __be32 *saddr, *daddr, tmp;
   unsigned char tmp_dev_addr[ETH_ALEN];
   struct ethhdr *ethhdr;
   struct sk_buff *rx_skb;

/*1) 对调ethhdr结构体 "源/目的"MAC地址*/
   ethhdr = (struct ethhdr *)skb->data;
   memcpy(tmp_dev_addr, ethhdr->h_dest, ETH_ALEN);
   memcpy(ethhdr->h_dest, ethhdr->h_source, ETH_ALEN);
   memcpy(ethhdr->h_source, tmp_dev_addr, ETH_ALEN);


 /*2)对调 iphdr结构体"源/目的" IP地址*/
   ih = (struct iphdr *)(skb->data + sizeof(struct ethhdr));
   saddr = &ih->saddr;
   daddr = &ih->daddr;

   tmp = *saddr;
   *saddr = *daddr;
   *daddr = tmp;



/*3)使用ip_fast_csum()来重新获取iphdr结构体的校验码*/
    ih->check = 0;               
    ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);


/*4)设置数据类型*/
   type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr);
   *type = 0;      //之前是发送ping包0x08,需要改为0x00,表示接收ping包


/*5)使用dev_alloc_skb()来构造一个新的sk_buff   */
   rx_skb = dev_alloc_skb(skb->len + 2);

/*6)使用skb_reserve()来腾出2字节头部空间  */
   skb_reserve(rx_skb, 2);


/*7)使用memcpy()将之前修改好的sk_buff->data复制到新的sk_buff里*/
memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len); // skb_put():来动态扩大sk_buff结构体里中的数据区,避免溢出

/*8)设置新的sk_buff 其它成员*/
rx_skb->dev = dev;
rx_skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */


/*9)使用eth_type_trans()来获取上层协议 */
rx_skb->protocol = eth_type_trans(rx_skb, dev);


/*10) 更新接收统计信息,并使用netif_rx( )来 传递sk_fuffer收包 */
   dev->stats.rx_packets++;                     
   dev->stats.rx_bytes += skb->len;
   dev->last_rx= jiffies;                       //收包时间戳

   netif_rx(rx_skb);

}


static int virt_send_packet(struct sk_buff *skb, struct net_device *dev)
{
  /*1)使用netif_stop_queue()来阻止上层向网络设备驱动层发送数据包*/
netif_stop_queue(dev);

  //期间设置硬件发送数据包

  /*2)调用收包函数,里面来伪造一个收的ping包函数*/
virt_rs_packet(skb,dev);

 /*3)使用dev_kfree_skb()函数来释放发送的sk_buff缓存区*/
 dev_kfree_skb(skb);

/*4)更新发送的统计信息*/
dev->stats.tx_packets++;            //成功发送一个包
dev->stats.tx_bytes+=skb->len;  //成功发送len长字节
dev->trans_start = jiffies;            //发送时间戳

/*5)使用netif_wake_queue()来唤醒被阻塞的上层*/
netif_wake_queue(dev); 

return 0;
}


static int virt_net_init(void)
{
/*1)使用alloc_netdev()来分配一个net_device结构体*/
virt_net= alloc_netdev(sizeof(struct net_device), "virt_eth0", ether_setup);

/*2)设置net_device结构体的成员 */
virt_net->hard_start_xmit      = virt_send_packet;

virt_net->dev_addr[0] = 0x08;     
virt_net->dev_addr[1] = 0x89;
virt_net->dev_addr[2] = 0x89;
virt_net->dev_addr[3] = 0x89;
virt_net->dev_addr[4] = 0x89;
virt_net->dev_addr[5] = 0x89;

virt_net->flags           |= IFF_NOARP;
virt_net->features        |= NETIF_F_NO_CSUM;


   /*3)使用register_netdev()来注册net_device结构体 */
register_netdev(virt_net);

return 0;
}

static void virt_net_exit(void)
{
   unregister_netdev(virt_net);
   free_netdev(virt_net);   
}

module_init(virt_net_init);
module_exit(virt_net_exit);![1182576-20171031191155154-1353223217.png](https://upload-images.jianshu.io/upload_images/2878032-af431a7b9ed529c5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


MODULE_LICENSE("GPL");
MODULE_AUTHOR("by:zhang");

测试运行
挂载驱动,如下图所示,可以看到net类下就有了这个网卡设备
insmod 16_th_virt_net.ko
ls /sys/class/net/
开始试验,首先设置这个网卡设备的ip,然后去ping一下其它的ip,如下图所示:


上图的ping,之所以成功,是因为我们在发包函数中,伪造了一个来收包,通过netif_rx()来将收包上传给上层
使用ifconfig,可以看到这个网卡设备的统计信息共收发了6个包,以及收发的总数据

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容