目录
2.1 init初始函数编写( vir_dev_register() )
2.3 收包函数编写( vir_dev_rcpacket() )
一、网卡设备简介
网卡设备主要负责对网络数据包进行收发操作。网卡主要有两个功能:接收数据和发送数据。它将上层协议传递下来的数据包传递给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;
}
三、运行测试
3.1 编译加载
3.2 网卡启动及ip地址配置
3.3 ping测试
四、总结
本章编写了一个简单的网卡驱动程序,该驱动程序只实现了ndo_start_xmit操作方法。驱动程序中vir_dev_rcpacket()函数将上层传递来的待发送数据进行了源地址和目的地址的交换,又返回给了系统上层,实现了类似于回环接口的功能。