虚拟网卡实验笔记

网络协议方面的东东,我现在一点也不懂,本文只涉及网络驱动最底层网络设备结构体
net_device的一些简单操作而已,甚至网卡的硬件操作也不非常清楚,学点大概,能移植芯
片厂家的驱动就好。


    协议层与网卡设备驱动层的通信,是通过net_device类型结构的hard_start_xmit()成员函数和协
议层提供的netif_rx()函数来进行,如分配的一个net_device类型结构体是net_dev,则:
    1、协议层想通过hard_start_xmit()函数把skb发送出去,则硬件驱动层需提供
    net_dev->hard_start_xmit()函数针对具体网卡硬件的具体实现,把协议层通过hard_start_xmit()
    传递来的skb写入网卡。
    2、硬件驱动层通过调用协议层提供的netif_rx()函数把skb上传。


最简单的驱动:

static struct net_device *vnet_dev;

static int virt_net_init(void)
{
	/* 1. 分配一个net_device结构体 */
	vnet_dev = alloc_netdev(0, "vnet%d", ether_setup);;  /* alloc_etherdev */

	/* 2. 设置 */

	/* 3. 注册 */
	//register_netdevice(vnet_dev);
	register_netdev(vnet_dev);
	
	return 0;
}

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

module_init(virt_net_init);
module_exit(virt_net_exit);
分配net_device结构体后,硬件相关的什么都没有。编译,装载驱动后,
任意设置ip,设为3.3.3.3,ping自己,发现竟然能ping得通。如下:


#ifconfig vnet0 3.3.3.3
#ping 3.3.3.3
PING 3.3.3.3(3.3.3.3):56 data bytes
64 bytes from 3.3.3.3: seq=0 tt1=64 time=0.785 ms
64 bytes from 3.3.3.3: seq=1 tt1=64 time=0.523 ms
64 bytes from 3.3.3.3: seq=2 tt1=64 time=0.653 ms
64 bytes from 3.3.3.3: seq=3 tt1=64 time=0.523 ms
64 bytes from 3.3.3.3: seq=4 tt1=64 time=0.425 ms


--- 3.3.3.3 ping statistics ---
5 packets transmitted,5 packets received. 0% packet loss


这样ping并不经过硬件,说明ip是纯软件的东西
ping3.3.3.4,则是要把包发送到其他主机上去,硬件驱动部分还没有提供处理这个的能力,则不行,死机。


提供一个并不处理硬件的hard_start_xmit,即设置vnet_dev->hard_start_xmit = virt_net_send_packet;
并简单实现virt_net_send_packet()函数:

static int virt_net_send_packet(struct sk_buff *skb, struct net_device *dev)
{
	static int cnt = 0;
	printk("virt_net_send_packet cnt = %d\n", ++cnt);
	return 0;
}
再次实验:
#ifconfig vnet0 3.3.3.3
#ping 3.3.3.3
PING 3.3.3.3(3.3.3.3):56 data bytes
64 bytes from 3.3.3.3: seq=0 tt1=64 time=0.784 ms
64 bytes from 3.3.3.3: seq=1 tt1=64 time=0.526 ms
64 bytes from 3.3.3.3: seq=2 tt1=64 time=0.654 ms
64 bytes from 3.3.3.3: seq=3 tt1=64 time=0.523 ms
64 bytes from 3.3.3.3: seq=4 tt1=64 time=0.424 ms


--- 3.3.3.3 ping statistics ---
5 packets transmitted,5 packets received. 0% packet loss


结果和之前一样,也没有调用到virt_net_send_packet()函数,所以,ping自己的话,协议层上应
该不会调用vnet_dev->hard_start_xmit函数。


#ping 3.3.3.4
PING 3.3.3.4(3.3.3.4):56 data bytes
virt_net_send_packet cnt = 1
virt_net_send_packet cnt = 2
virt_net_send_packet cnt = 3
virt_net_send_packet cnt = 4
virt_net_send_packet cnt = 5
virt_net_send_packet cnt = 6


--- 3.3.3.4 ping statistics ---
6 packets transmitted,0 packets received. 100% packet loss


调用了virt_net_send_packet()函数,但virt_net_send_packet()函数并没有将包实际发送出去,
在真实的网卡驱动中,应该在virt_net_send_packet()函数中把skb写进网卡


实现虚拟网卡:
要想ping通(当然不是真真正正的ping通) 3.3.3.4,需要实现两点:
1、硬件驱动层提供hard_start_xmit()函数的实现,即使像前面一样不处理skb也行。
2、硬件驱动层调用netif_rx()函数上传一个正确结构的skb,这个skb来自哪里?
它本来应该来自3.3.3.4主机,但这里是在硬件驱动层自己构造一个合适的skb上传给协议层,欺骗协
议层,使协议层以为这个skb来自3.3.3.4主机,以达ping通目的。


为什么说用一个正确结构的skb?skb包含了目的mac,源mac,目的ip,源ip,type等信息。这些信息都关
系着是否能ping通。


相比上面那个最简单的驱动,主要写了个构造skb的函数emulator_rx_packet(),和hard_start_xmit的
实现:virt_net_send_pack()函数,搞笑的是在发包函数virt_net_send_pack()里面调用
emulator_rx_packet()函数把一个要发送的ping包 skb构造为一个接收到的reply包rx_skb,然后调用
netif_rx(rx_skb)上传此包给协议层。当然,要想在virt_net_send_pack()函数中尝试把包真正发送
出去则应把包写入网卡,实现对网卡的硬件操作。


下面是东山老师虚拟网卡驱动程序源码:

/*
 * 参考 drivers\net\cs89x0.c
 */

#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 *vnet_dev;


/*
 *这个函数的作用是把协议层通过hard_start_xmit()传过来的包转换为reply 类型的包,
 *同时要对换源包的 源MAC/目的MAC 、源IP/目的IP才能正确ping通
 *构造好包后调用netif_rx()上传
 */
static void emulator_rx_packet(struct sk_buff *skb, struct net_device *dev)
{
	/* 参考LDD3 */
	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;
		
	// 从硬件读出/保存数据
	/* 对调"源/目的"的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);

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

	tmp = *saddr;
	*saddr = *daddr;
	*daddr = tmp;
	
	//((u8 *)saddr)[2] ^= 1; /* change the third octet (class C) */
	//((u8 *)daddr)[2] ^= 1;
	type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr);
	//printk("tx package type = %02x\n", *type);
	// 修改类型, 原来0x8表示ping
	*type = 0; /* 0表示reply */
	
	ih->check = 0;		   /* and rebuild the checksum (ip needs it) */
	ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);
	
	// 构造一个sk_buff
	rx_skb = dev_alloc_skb(skb->len + 2);
	skb_reserve(rx_skb, 2); /* align IP on 16B boundary */	
	memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);

	/* Write metadata, and then pass to the receive level */ //填充这个skb的一些成员
	rx_skb->dev = dev;
	rx_skb->protocol = eth_type_trans(rx_skb, dev);
	rx_skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
	dev->stats.rx_packets++;
	dev->stats.rx_bytes += skb->len;

	// 提交sk_buff
	netif_rx(rx_skb);
}

static int virt_net_send_packet(struct sk_buff *skb, struct net_device *dev)
{
	static int cnt = 0;
	printk("virt_net_send_packet cnt = %d\n", ++cnt);

	/* 对于真实的网卡, 把skb里的数据通过网卡发送出去 */
	netif_stop_queue(dev); /* 停止该网卡的队列 */
    /* ...... */           /* 把skb的数据写入网卡 */

	/* 构造一个假的sk_buff,上报 */
	emulator_rx_packet(skb, dev);

	dev_kfree_skb (skb);   /* 释放skb */
	netif_wake_queue(dev); /* 数据全部发送出去后,唤醒网卡的队列 */

	/* 更新统计信息 */
	dev->stats.tx_packets++;
	dev->stats.tx_bytes += skb->len;
	
	return 0;
}


static int virt_net_init(void)
{
	/* 1. 分配一个net_device结构体 */
	vnet_dev = alloc_netdev(0, "vnet%d", ether_setup);;  /* alloc_etherdev */

	/* 2. 设置 */
	vnet_dev->hard_start_xmit = virt_net_send_packet;

	/* 设置MAC地址 */
    vnet_dev->dev_addr[0] = 0x08;
    vnet_dev->dev_addr[1] = 0x89;
    vnet_dev->dev_addr[2] = 0x89;
    vnet_dev->dev_addr[3] = 0x89;
    vnet_dev->dev_addr[4] = 0x89;
    vnet_dev->dev_addr[5] = 0x11;

    /* 设置下面两项才能ping通 */
	vnet_dev->flags           |= IFF_NOARP;	//无ARP协议,没有设置第二层目的地址
	vnet_dev->features        |= NETIF_F_NO_CSUM;	//does not require checksum

	/* 3. 注册 */
	//register_netdevice(vnet_dev);
	register_netdev(vnet_dev);
	
	return 0;
}

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

module_init(virt_net_init);
module_exit(virt_net_exit);

MODULE_AUTHOR("thisway.diy@163.com,17653039@qq.com");
MODULE_LICENSE("GPL");
总结:网络驱动程序不同字符设备和块设备,它使用socket编程,没有设备节点。这个实验,我们
只知道ping命令后,会调用hard_start_xmit(),我们要实现这个hard_start_xmit()成员函数把skb写
入网卡,接收到skb后,应该调用协议层提供的netif_rx()把它上传。对于时序,中断等方面的细节,
希望下节移植DM9000驱动时能再有所收获,和大家分享。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值