Linux虚拟网卡实现

2019-07-13 03:35发布

本文档的Copyleft归rosetta所有,使用GPL发布,可以自由拷贝、转载,转载时请保持文档的完整性。
参考资料:《Linux设备驱动程序 第三版》,snull源码,linux-2.6.10

    曾经一段时间在看openswan源码,有一个问题迷惑我很久,就是它的那个ipsec虚拟网卡接口是怎么实现的?当时没有思路、没有想法,因为不知道从何着手去解决这个问题,最近接触到了内核模块的编写,又接触到openswan的klips模块,原来这一切全属于网络驱动程序编写范畴。现在我迫不及待的想去了解下它的实现,然后就有了这篇学习笔记……
    本文只是初步讲解虚拟网卡实现的过程,最终实现一个虚拟网卡,对于具体体细节和数据包的发送和传送等等问题没有涉及。对于klips的ipsec0的实现大体上类似这个过程。
    本文档注重实际实现过程,缺少理论知识。
    本文档以《Linux设备驱动程序 第三版》为理论知识;以snull源码为学习对象。为贪图省力,所帖源码来至snull源码和linux-2.6.10内核源码。

一、最终的效果,实现了一个名为sn0的虚拟网卡接口
  [root@xxx snull]# cat /proc/net/dev
  Inter-|   Receive                       
   face |bytes    packets errs drop fifo
      lo:    6528      76    0    0    0  
    eth0:148681882  216304    0    0    0
    eth1:       0       0    0    0    0  
    eth2:       0       0    0    0    0  
    sit0:       0       0    0    0    0  
     sn0:       0       0    0    0    0  
     sn1:     210       3    0    0    0

  [root@xxx snull]# ifconfig sn0  up
  [root@xxx snull]# ifconfig sn0
  sn0       Link encap:Ethernet  HWaddr 00:53:4E:55:4C:30  
            inet6 addr: fe80::253:4eff:fe55:4c30/64 Scope:Link
            UP BROADCAST RUNNING NOARP MULTICAST  MTU:1500  Metric:1
            RX packets:0 errors:0 dropped:0 overruns:0 frame:0
            TX packets:1 errors:0 dropped:0 overruns:0 carrier:0
            collisions:0 txqueuelen:1000
            RX bytes:0 (0.0 b)  TX bytes:70 (70.0 b)

二、总体过程
    首先需要内核分配一个net_device结构(以下简称dev结构),并初始化dev结构。
      struct net_device *snull_devs[2]; //snull 实现的是两个虚拟网卡,用一个两个大小的结构体数组保存net_device结构,我只关注一个。
      snull_devs[0] = alloc_netdev(sizeof(struct snull_priv), "sn%d",
            snull_init);//内核分配一个dev,此dev由snull_init初始化。然后由alloc_netdev函数返回此dev指针。
 
      for (i = 0; i < 2;  i++)
        if ((result = register_netdev(snull_devs[i])))//dev结构初始化后调用register_netdev向内核注册,注册后就可以对设备(虚拟网卡)操作了。
            printk("snull: error %i registering device "%s" ",
                    result, snull_devs[i]->name);
        else
            ret = 0;


三、初始化过程
    为了便于理解初始化过程,帖上一段alloc_netdev的内核实现:
      struct net_device *alloc_netdev(int sizeof_priv, const char *mask,
                           void (*setup)(struct net_device *))//最后一个参数为函数指针,即指向传进来的snull_init
    {
        void *p;
        struct net_device *dev;
        int alloc_size;
    
        /* ensure 32-byte alignment of both the device and private area */
    
        alloc_size = (sizeof(struct net_device) + NETDEV_ALIGN_CONST)
                & ~NETDEV_ALIGN_CONST;
        alloc_size += sizeof_priv + NETDEV_ALIGN_CONST;
    
        p = kmalloc (alloc_size, GFP_KERNEL);
        if (!p) {
            printk(KERN_ERR "alloc_dev: Unable to allocate device. ");
            return NULL;
        }
    
        memset(p, 0, alloc_size);
    
        dev = (struct net_device *)(((long)p + NETDEV_ALIGN_CONST)
                    & ~NETDEV_ALIGN_CONST);//为dev结构分配内存空间
        dev->padded = (char *)dev - (char *)p;
    
        if (sizeof_priv)
            dev->priv = netdev_priv(dev);
    
        setup(dev);//实现调用了snull_init(dev)对dev结构初始化。
        strcpy(dev->name, mask);
    
        return dev;//返回已初始化的dev
    }

    下面看下这个重要的初始化函数snull_init(),dev的一些重要函数指针均在此赋值
    void snull_init(struct net_device *dev)
    {
        struct snull_priv *priv;
        /*
         * Then, assign other fields in dev, using ether_setup() and some
         * hand assignments
         */
        ether_setup(dev); /* assign some of the fields *///这个函数初始化了dev的许多成员
    
        dev->open            = snull_open; //打开设备
        dev->stop            = snull_release;
        dev->set_config      = snull_config;
        dev->hard_start_xmit = snull_tx;  //数据发送函数
        dev->do_ioctl        = snull_ioctl;
        dev->get_stats       = snull_stats;
        dev->change_mtu      = snull_change_mtu;
        dev->rebuild_header  = snull_rebuild_header;
        dev->hard_header     = snull_header;//建立硬件头,包含源和目的mac地址
        dev->tx_timeout      = snull_tx_timeout;//数发送超时处理。
        dev->watchdog_timeo = timeout;
        if (use_napi) {
            dev->poll        = snull_poll;
            dev->weight      = 2;
        }
        /* keep the default flags, just add NOARP */
        dev->flags           |= IFF_NOARP;
        dev->features        |= NETIF_F_NO_CSUM;
        dev->hard_header_cache = NULL;      /* Disable caching */
    
        /*
         * Then, initialize the priv field. This encloses the statistics
         * and a few private fields.
         */
        priv = netdev_priv(dev);
        memset(priv, 0, sizeof(struct snull_priv));
        spin_lock_init(&priv->lock);
        snull_rx_ints(dev, 1);      /* enable receive interrupts */
        snull_setup_pool(dev);
    }

    以上内容已经把框架搭出来了,至于net结构成员的理解和细节、数据包的发送的接收、超时传输中断处理及sk_buff数据结构等
    信息可参考给出的参考资料。
    类似的实现网络驱动程序的源码还有内核源码中的loopback.c、plip.c、e100.c。