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

来源:互联网 发布:梦想世界跨服传输数据 编辑:程序博客网 时间:2024/06/05 02:14

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

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

include/linux/netdevice.hstruct net_device {    ...    char    name[IFNAMSIZ]; //设备名, 如"eth0", "enp3s0"    unsigned long       last_rx; //上次接收数据包的时间,用jiffies    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.1ARPING 192.168.88.1 from 192.168.88.88 mynetUnicast reply from 192.168.88.1 [11:22:33:44:55:66]  0.540msUnicast reply from 192.168.88.1 [11:22:33:44:55:66]  0.534msUnicast reply from 192.168.88.1 [11:22:33:44:55:66]  0.535msUnicast reply from 192.168.88.1 [11:22:33:44:55:66]  0.533msUnicast reply from 192.168.88.1 [11:22:33:44:55:66]  0.533ms
原创粉丝点击