70 linux网络设备驱动之虚拟网卡及arp应答的实现

网络设备驱动的调用不像字符设备驱动,不是通过打开设备文件或属性文件的方式来操作的.
通常情况下用socket编程来实现网络通信, 当使用tcp/udp协议收发网络数据时,无需处理网络协议的包头。
因为网络设备驱动应用程序是无法直接调用, socket编程时是经过网络协议栈处理包头信息后, 再由网络协议栈调用网络设备来收发数据的.

在linux内核里以结构体net_device的对象来描述一个网络设备.

include/linux/netdevice.h

struct net_device {
	...
	char	name[IFNAMSIZ]; //设备名, 如"eth0", "enp3s0"
	unsigned char		dev_addr[MAX_ADDR_LEN]; //mac地址
	unsigned long       last_rx; //记录上一次接收网络数据包的时间,以jiffies时间为准
	unsigned long       trans_start; //记录上一次发送网络数据包的时间,以jiffies时间为准
	const struct net_device_ops *netdev_ops;  //网络设备的操作对象,如发送数据包的功能函数
	...
};

struct net_device_ops {
    int         (*ndo_init)(struct net_device *dev); //网络设备初始化时自动调用的, 只调用一次
    void         (*ndo_uninit)(struct net_device *dev); //网络设备退出工作时调用
    int         (*ndo_open)(struct net_device *dev); //在ifconfig 设备名 up时调用
    int         (*ndo_stop)(struct net_device *dev); //在ifconfig 设备名 down时调用
    int         (*ndo_set_mac_address)(struct net_device *dev, void *addr); //设置mac地址触发 如ifconfig enp3s0 hw ether "00:0e:4c:1d:55:66"
    netdev_tx_t     (*ndo_start_xmit) (struct sk_buff *skb, struct net_device *dev);
	//此网络设备发送数据包的接口, 也就是让硬件发出数据包的代码实现. 这个接口是由网络协议栈需要发出网络数据时调用.
		//网络设备接收到数据时,需调用全局函数netif_rx(..)把网络数据提交到网络协议栈去.
	...
};

///
一个系统里可有多个网络设备,socket通信时,数据从哪个网络设备发出?
首先每个网络设备有不同的IP地址, 而且不能在同一网段. 如网络掩码是255.255.255.0, 有网络设备A(192.168.0.11), 网络设备B(192.168.250.250). 当用户进程需要连接或发数据到192.168.0.250时,则从同网段的IP地址的网络设备发出.
当用户进程需与都不属于这两个网段的IP地址通信时, 则从默认网关指定的IP网段的网络设备发出.
以上的工作都是由网络协议栈来完成的。

这里写图片描述

网络数据的发送:
socket —> 网络协议栈 ----> net_device —> net_device的函数成员hard_start_xmit —> 硬件发出数据

网络数据的接收:
socket <— 网络协议栈 <— 全局函数netif_rx(struct sk_buff *skb) <— 硬件收到数据后

网络协议栈与网络设备之间的数据交互是以struct sk_buff来描述的.

struct sk_buff 表示一个套接字的缓冲区
{
	unsigned int		len,      //数据总长
				data_len;
	__u16			mac_len,
				hdr_len;	
	unsigned char		*data;	 //指向用户的数据包缓冲区地址
};

/

网络设备驱动里常用的函数:

alloc_etherdev(sizeof_priv);
alloc_netdev(sizeof_priv, name, setup)//用来动态创建net_device的对象还创建对象里的私有数据, sizeof_priv指创建priv成员指向的空间大小, name为设备名, setup指定网络设备的初始化函数   

struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name,
		void (*setup)(struct net_device *), unsigned int queue_count)

void ether_setup(struct net_device *dev); //给网络设备对象填入通用的值

extern int register_netdev(struct net_device *dev);//注册一个网络设备
extern void unregister_netdev(struct net_device *dev);//反注册网络设备

void netif_start_queue(struct net_device *dev);//开启网络数据接收发送队列
void netif_stop_queue(struct net_device *dev); //停止接收发送网络数据队列

int netif_rx(struct sk_buff *skb); //把收到的数据递给网络协议层

//关于sk_buff的操作函数:
struct sk_buff *dev_alloc_skb(unsigned int length); //动态申请套接字缓冲区
dev_kfree_skb(struct sk_buff *skb); //把skb指向的套接字缓冲区释放掉

unsigned char *skb_put(struct sk_buff *skb, unsigned int len);//动态申请套接字缓冲区里的data, 返回首地址

__be16 eth_type_trans(struct sk_buff *skb, struct net_device
*dev);//检查skb数据包的协议, 返回协议id

代码:
test.c


#include <linux/init.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/netdevice.h>

struct net_device *vnet;
u8 mymac[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; //本网络设备用的mac地址

int my_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
	printk("in my hard_start_xmit\n");

	//enc28j60: 网络协议栈 --> 此函数 --> spi控制器  --> spi设备 ---> 网络数据

	return 0;
}

int myopen(struct net_device *dev)
{
	printk("in myopen\n");
	return 0;
}

int mystop(struct net_device *dev)
{
	printk("in mystop\n");
	return 0;
}

int my_set_mac_address(struct net_device *dev, void *addr)
{
	printk("in my set mac addr\n");
	return 0;
}

void vnet_init(struct net_device *vnet) //网络设备初始时调用
{
	ether_setup(vnet); //因net_device里成员多, 这里就给其它成员设通用的值
	
	printk("vnet init ...\n");
}

struct net_device_ops nops = {
	.ndo_open = myopen,
	.ndo_stop = mystop,
	.ndo_set_mac_address = my_set_mac_address,
	.ndo_start_xmit = my_hard_start_xmit,
};

int __init test_init(void)
{
	int ret;

	vnet = alloc_netdev(0, "mynet", vnet_init); //动态创建net_device对象, 并指定vnet_init函数作为网络设备的初始函数, 则vnet->netdev_ops->ndo_init = vnet_init.   网络设备名为mynet
	if (!vnet)
		goto err0;
		
	memcpy(vnet->dev_addr, mymac, 6); //设置网络设备的mac地址

	vnet->netdev_ops = &nops; //指定网络设备的操作对象
	ret = register_netdev(vnet); //注册网络设备
	if (ret < 0)
		goto err1;	

	return 0;
err1:
	free_netdev(vnet);
	return ret;
err0:
	return -EINVAL;
}

void __exit test_exit(void)
{
	unregister_netdev(vnet);
	free_netdev(vnet);
}

module_init(test_init);
module_exit(test_exit);

MODULE_LICENSE("GPL");

加载驱动模块后, ifconfig -a查看到mynet网络设备
ifconfig mynet up 触发函数myopen
ifconfig mynet down 触发函数mystop
ifconfig mynet hw ehter “11:22:33:44:55:66” 触发函数my_set_mac_address

 ifconfig mynet 192.168.88.88 设置ip地址
 ping 192.168.88.11  会触发函数my_hard_start_xmit

/
实现一个虚拟网络设备,只要通过它发出arp请求指定ip地址的pc的mac地址时,都会回arp应答包,表示要请求的mac地址是"11:22:33:44:55:66"

test.c


#include <linux/init.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>

#pragma pack(1)

typedef struct {
	u8 dst_mac[6];
	u8 src_mac[6];
	u16 type;
}eth_t;

typedef struct {
	u16 hw_type;
	u16 pro_type;
	u8  hw_addr_len;
	u8  pro_addr_len;

	u16 op; // 1请求   2应答
	u8  src_mac[6];
	u32 src_ip;
	u8  dst_mac[6];
	u32 dst_ip;
}arp_t;

#pragma pack()

struct net_device *vnet;
u8 mymac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; //本网络设备用的mac地址
u8 mac_rx[6] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; //用于arp应答包的mac地址

int my_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
	eth_t *eth = (eth_t *)(skb->data), *eth_rx;
	arp_t *arp = (arp_t *)(skb->data + sizeof(eth_t)), *arp_rx;
	struct sk_buff *skb_rx;	

	if (eth->type != htons(0x0806)) //arp协议号: 0x0806. 协议栈发出的不是arp相关的数据包,则丢弃.
		goto exit;
	
	if (arp->op != htons(1)) //如果不是arp请求包的话,则进行丢弃处理
		goto exit;

	//这里回伪数据包,不管请求什么IP地址的mac地址,都回mac地址是"11:22:33:44:55:66". arp应答包
	skb_rx = dev_alloc_skb(sizeof(eth_t) + sizeof(arp_t));
	skb_put(skb_rx, sizeof(eth_t) + sizeof(arp_t)); //分配skb_rx->data的空间

	eth_rx = (eth_t *)(skb_rx->data);
	arp_rx = (arp_t *)(skb_rx->data + sizeof(eth_t));

//以太网包头
	memcpy(eth_rx->dst_mac, mymac, 6);
	memcpy(eth_rx->src_mac, mac_rx, 6);
	eth_rx->type = htons(0x0806);

//arp包头
	arp_rx->hw_type = htons(1);
	arp_rx->pro_type = htons(0x0800);
	arp_rx->hw_addr_len = 6;
	arp_rx->pro_addr_len = 4;
	
	arp_rx->op = htons(2); //应答
	memcpy(arp_rx->src_mac, mac_rx, 6);
	memcpy(arp_rx->dst_mac, mymac, 6);
	arp_rx->src_ip = arp->dst_ip;
	arp_rx->dst_ip = arp->src_ip;
	

	skb_rx->protocol = eth_type_trans(skb_rx, vnet); //
	netif_rx(skb_rx); //提交伪数据包到网络协议栈
exit:
	dev_kfree_skb(skb); //需要回收协议栈传过来的缓冲区
	return 0;
}

int myopen(struct net_device *dev)
{
	printk("in myopen\n");
	return 0;
}

int mystop(struct net_device *dev)
{
	printk("in mystop\n");
	return 0;
}

int my_set_mac_address(struct net_device *dev, void *addr)
{
	printk("in my set mac addr\n");
	return 0;
}

void vnet_init(struct net_device *vnet) //网络设备初始时调用
{
	ether_setup(vnet); //因net_device里成员多, 这里就给其它成员设通用的值
	
	printk("vnet init ...\n");
}

struct net_device_ops nops = {
	.ndo_open = myopen,
	.ndo_stop = mystop,
	.ndo_set_mac_address = my_set_mac_address,
	.ndo_start_xmit = my_hard_start_xmit,
};

int __init test_init(void)
{
	int ret;

	vnet = alloc_netdev(0, "mynet", vnet_init); //动态创建net_device对象, 并指定vnet_init函数作为网络设备的初始函数, 则vnet->netdev_ops->ndo_init = vnet_init.   网络设备名为mynet
	if (!vnet)
		goto err0;

	strcpy(vnet->name, "mynet");
	memcpy(vnet->dev_addr, mymac, 6); //设置网络设备的mac地址
	vnet->netdev_ops = &nops; //指定网络设备的操作对象
	ret = register_netdev(vnet); //注册网络设备
	if (ret < 0)
		goto err1;	

	return 0;
err1:
	free_netdev(vnet);
	return ret;
err0:
	return -EINVAL;
}

void __exit test_exit(void)
{
	unregister_netdev(vnet);
	free_netdev(vnet);
}

module_init(test_init);
module_exit(test_exit);

MODULE_LICENSE("GPL");

加载驱动模块后:

  1. ifconfig mynet 192.168.88.88 //随便设置一个ip地址

  2. arping -I mynet 192.168.88.1 //指定从mynet网络设置发出一个arp请求,用于获取ip地址为192.168.88.1的网络设备的mac地址。

[root@jk 02]# ifconfig mynet 192.168.88.88
[root@jk 02]# arping -I mynet 192.168.88.1
ARPING 192.168.88.1 from 192.168.88.88 mynet
Unicast reply from 192.168.88.1 [11:22:33:44:55:66]  0.540ms
Unicast reply from 192.168.88.1 [11:22:33:44:55:66]  0.534ms
Unicast reply from 192.168.88.1 [11:22:33:44:55:66]  0.535ms
Unicast reply from 192.168.88.1 [11:22:33:44:55:66]  0.533ms
Unicast reply from 192.168.88.1 [11:22:33:44:55:66]  0.533ms

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值