[Linux驱动开发九] 简单虚拟网卡制作

您所在的位置:网站首页 tcpdump查看mac地址 [Linux驱动开发九] 简单虚拟网卡制作

[Linux驱动开发九] 简单虚拟网卡制作

2023-06-12 23:14| 来源: 网络整理| 查看: 265

目录

一、网卡设备简介

二、虚拟网卡驱动程序编写

2.1 init初始函数编写( vir_dev_register() )

 2.2 发包函数编写( vir_dev_xmit() )

2.3  收包函数编写( vir_dev_rcpacket() )

三、运行测试

3.1 编译加载

3.2 网卡启动及ip地址配置

3.3 ping测试

 四、总结

一、网卡设备简介

        网卡设备主要负责对网络数据包进行收发操作。网卡主要有两个功能:接收数据和发送数据。它将上层协议传递下来的数据包传递给Linux内核,并且将Linux内核的数据发送出去。

        与字符设备不同,网卡设备在/dev目录下没有对应文件,但在/sys/class/net目录下可以查看网卡。接下来,我们简单介绍一下网卡的收发包流程。

(1)网卡收包

        step1:网卡芯片获取网线上的物理帧,并检查物理帧的CRC,保证完整性;

        step2:网卡芯片去除物理帧头,得到MAC数据包;

        step3:网卡芯片检查MAC包中的目的MAC地址,如果和本网卡MAC不一致则丢弃(混杂模式除外);

        step4:网卡芯片将MAC帧拷贝到内部缓冲区,触发硬件中断;

        step5:网卡驱动程序通过中断处理函数,构造sk_buff,将其拷贝到内存中,交付内核处理。

(2)网卡发包

        step1:网卡驱动程序将上层协议传递下来的数据包(IP数据包)构造成MAC包;

        step2:网卡驱动程序将MAC包拷贝至网卡芯片内部缓冲区;

        step3:网卡芯片将MAC包封装成物理帧,添加同步信息和CRC校验,通过网线发送出去(网线上所有网卡都能收到该帧)。

二、虚拟网卡驱动程序编写

        本章节编写一个虚拟网卡驱动程序,利用ping命令发包,然后构造一个发包函数伪造一个收的ping包函数,实现该虚拟网卡能ping通任何ip地址。

2.1 init初始函数编写( vir_dev_register() )

        (1)使用alloc_netdev()函数分配一个net_device结构体;

        (2)设置net_device结构体成员;

        (3)使用register_netdev()来注册net_device结构体;

static const struct net_device_ops vnet_ops = { .ndo_open = vnet_open, .ndo_stop = vnet_stop, .ndo_start_xmit = vnet_tx, }; static int vir_dev_register(void) { int ret = 0; vir_dev = alloc_netdev(sizeof(struct net_device), "eth_xzx", NET_NAME_UNKNOWN, ether_setup); if (IS_ERR(vir_dev)) { return -ENOMEM; } /* 初始化MAC地址 */ vir_dev->dev_addr[0] = 0x00; vir_dev->dev_addr[1] = 0x01; vir_dev->dev_addr[2] = 0x02; vir_dev->dev_addr[3] = 0x03; vir_dev->dev_addr[4] = 0x04; vir_dev->dev_addr[5] = 0x05; /* 设置操作函数 */ vir_dev->netdev_ops = &vir_dev_ops; vir_dev->flags |= IFF_NOARP; vir_dev->features |= NETIF_F_HW_CSUM; /* 注册net_device结构体 */ ret = register_netdev(vir_dev); if (ret) { free_netdev(vir_dev); return ret; } return ret; }  2.2 发包函数编写( vir_dev_xmit() )

        (1)使用netif_stop_queue()来阻止上层向网络设备驱动层发送数据包;

        (2)使用收包函数( vir_dev_rcpacket() )伪造收的ping函数;

        (3)使用dev_kfree_skb()函数释放发送的sk_buff缓冲区;

        (4)更新发送统计信息;

        (5)使用netif_wake_queue()唤醒被阻塞的上层;

static int vir_dev_xmit(struct sk_buff *skb, struct net_device *dev) { printk("Running vir_dev_xmit\n"); /* 调用netif_stop_queue()阻止上层向网络设备驱动层发送数据包 */ netif_stop_queue(dev); /* 调用收包函数,伪造接收ping包 */ vir_dev_rcpacket(skb, dev); /* 调用dev_kfree_skb()函数释放发送的sk_buff */ dev_kfree_skb(skb); /* 更新发送的统计信息 */ dev->stats.tx_packets ++; dev->stats.tx_bytes += skb->len; dev->trans_start = jiffies; /* 调用netif_wake_queue()唤醒被阻塞的上层 */ netif_wake_queue(dev); return 0; } 2.3  收包函数编写( vir_dev_rcpacket() )

        (1)交换ethhdr结构体的“源/目”MAC地址;

        (2)交换iphdr结构体的“源/目” IP地址;

        (3)使用ip_fast_csum()来重新获取iphdr结构体的校验码;

        (4)修改数据类型(发送ping包为0x08,接收ping包类型为0x00);

        (5)使用dev_alloc_skb()构造一个新的sk_buff;

        (6)调用skb_reserve()预留2字节的头部空间,16字节对齐,(以太网的协议头长度是14个字节);

        (7)将skb_buff->data复制到新的sk_buff中 利用skb_put动态扩大数据区,避免溢出;

        (8)设置新的sk_buff的其他成员;

        (9)调用eth_type_trans()获取上层协议;

        (10)更新接收的统计信息,调用netif_rx()来传递sk_buff数据包。

static int vir_dev_rcpacket(struct sk_buff *skb, struct net_device *dev) { struct ethhdr *ethhdr; struct iphdr *ih; unsigned char *type; unsigned char tmp_dev_addr[ETH_ALEN]; __be32 *saddr, *daddr, tmp; struct sk_buff *rx_skb; /* 1、对调 源/目 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); /* 2、对调 源/目 IP地址 */ ih = (struct iphdr *)(skb->data + sizeof(struct ethhdr)); saddr = &ih->saddr; daddr = &ih->daddr; tmp = *saddr; *saddr = *daddr; *daddr = tmp; /* 3、调用ip_fast_csum()获取iphr结构体的校验码 */ ih->check = 0; ih->check = ip_fast_csum((unsigned char *)ih, ih->ihl); /* 4、设置数据类型为0,表示接收ping包 */ type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr); *type = 0; /* 5、调用dev_alloc_skb()构造新的接收sk_buff */ rx_skb = dev_alloc_skb(skb->len + 2); /* * skb_reserve()增加头部空间 * skb_put()增加数据区长度 */ /* 6、调用skb_reserve()预留2字节的头部空间,16字节对齐,(以太网的协议头长度是14个字节) */ skb_reserve(rx_skb, 2); /* 7、将skb_buff->data复制到新的sk_buff中 利用skb_put动态扩大数据区,避免溢出*/ memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len); /* 8、设置新的sk_buff的其他成员 */ rx_skb->dev = dev; rx_skb->ip_summed = CHECKSUM_UNNECESSARY; /* 9、调用eth_type_trans()获取上层协议 */ rx_skb->protocol = eth_type_trans(rx_skb, dev); /* 10、更新接收的统计信息,调用netif_rx()来传递sk_buff数据包 */ dev->stats.rx_packets ++; dev->stats.rx_bytes += skb->len; dev->last_rx = jiffies; netif_rx(rx_skb); return 0; }

【完整源代码及Makefile下载链接】

三、运行测试 3.1 编译加载

3.2 网卡启动及ip地址配置

3.3 ping测试

 四、总结

        本章编写了一个简单的网卡驱动程序,该驱动程序只实现了ndo_start_xmit操作方法。驱动程序中vir_dev_rcpacket()函数将上层传递来的待发送数据进行了源地址和目的地址的交换,又返回给了系统上层,实现了类似于回环接口的功能。

                



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3