本文档的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。