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

目录

一、网卡设备简介

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

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()函数将上层传递来的待发送数据进行了源地址和目的地址的交换,又返回给了系统上层,实现了类似于回环接口的功能。

                

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux操作系统中的网络接口可以分为三种类型:本机网卡、回环网卡和虚拟网卡。 1. 本机网卡: 本机网卡是指物理连接到计算机的网卡设备,它通过电缆连接到网络中的其他设备,并负责接收和发送网络数据包。在Linux中,本机网卡通常被称为实体网卡,每个实体网卡都有唯一的MAC地址,有可能是有线网卡、无线网卡或者其他类型的物理接口。Linux操作系统使用驱动程序与本机网卡进行通信,使其能够进行网络通信。 2. 回环网卡: 回环网卡是Linux系统内置的虚拟网卡,它模拟了一个虚拟的网络接口。回环网卡不需要物理设备支持,数据包在发送和接收时都会在回环网卡上进行转发处理。回环网卡的IP地址通常设置为"127.0.0.1",也被称为"本地回环地址"。通过回环网卡,我们可以在本机上进行网络连接测试、网络服务测试等,而无需与其他设备进行实际的网络通信。 3. 虚拟网卡虚拟网卡是一种软件实现的网络接口,它并不对应物理设备。在Linux系统中,虚拟网卡可以通过网络命令进行创建和配置。虚拟网卡可以用于各种不同的场景,比如网络隔离、虚拟化环境中的网络互联等。虚拟网卡在操作系统中以网络设备驱动程序的形式存在,通过驱动程序与操作系统进行通信,实现网络数据的发送和接收。 总结来说,Linux操作系统中的网络接口分为本机网卡、回环网卡和虚拟网卡。本机网卡是物理连接计算机的网卡设备,回环网卡是模拟的虚拟网卡用于本机测试,而虚拟网卡是一种软件实现的网络接口,用于虚拟化环境中的网络互联等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值