linux内核虚拟网卡驱动

❓ 问题描述:

编写一个内核虚拟网卡驱动程序

MAC(Media Access Control):一般集成在 CPU 或 SoC 中,负责以太网帧的封装 / 解封装、地址过滤、流量控制等。
PHY(Physical Layer Transceiver):独立芯片(如 RTL8211F),负责电信号转换、网线连接检测(如 Auto-Negotiation,自动协商速率和双工模式)、信号放大等。
MII/RMII 接口:连接 MAC 和 PHY 的标准化接口,定义了数据、时钟、控制信号的电气特性和时序

网卡驱动是操作系统内核与网卡硬件之间的桥梁,主要负责协调软件(内核协议栈)与硬件(MAC、PHY 等)的交互,实现数据的发送、接收及网卡的管理控制。其核心操作围绕MAC 层、PHY 层以及硬件寄存器展开

初始化 MAC 控制器
配置 PHY 芯片
注册网络设备

发送数据:
当上层协议(如 TCP/IP)有数据要发送时,驱动需将数据从内核缓冲区传递到网卡硬件
接收数据:
当网卡收到数据时,驱动需将数据从硬件缓冲区传递到内核协议栈

网络模型

linux内核虚拟网卡驱动

MAC帧

linux内核虚拟网卡驱动

IP数据包

linux内核虚拟网卡驱动

TCP数据包

linux内核虚拟网卡驱动

UDP数据包

linux内核虚拟网卡驱动

ICMP数据包

linux内核虚拟网卡驱动

ARP数据包

linux内核虚拟网卡驱动

TCP抓包

linux内核虚拟网卡驱动

UDP抓包

linux内核虚拟网卡驱动

ICMP抓包

linux内核虚拟网卡驱动

ARP抓包

linux内核虚拟网卡驱动

常见以太网帧协议类型

linux内核虚拟网卡驱动

常见IP层协议类型

日志

添加打印日志信息

分析步骤

第1步:
第2步:
...

代码片段

/*
 * @Author: your name
 * @Date: 2025-09-28 17:05:40
 * @LastEditTime: 2025-09-28 17:05:41
 * @LastEditors: your name
 * @Description: In User Settings Edit
 * @FilePath: project	echnologyswlinux_drivers内核网卡srcvirtual-netdev.c
 */
//https://blog.csdn.net/mike8825/article/details/103939330
//https://segmentfault.com/a/1190000023409283

#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/udp.h>  // 添加UDP头文件
#include <linux/netdev_features.h>
 
static struct net_device *virt_net;

void print_skb_data(struct sk_buff *skb)
{
    int i;
    printk("sk_buff->data: ");
    for (i = 0; i < skb->len; i++) {
        printk("%02x ", skb->data[i]);
    }
    printk("
");
}
  
/*
 * 当 "ifconfig veth0 up" 时调用
 */
static int virt_open(struct net_device *dev)
{
    pr_info("%s: device opened
", dev->name);
    // 允许协议栈开始发送数据
    netif_start_queue(dev);

    return 0;
}

/*
 * 当 "ifconfig veth0 down" 时调用
 */
static int virt_stop(struct net_device *dev)
{
    pr_info("%s: device closed
", dev->name);
    // 停止协议栈发送数据
    netif_stop_queue(dev);
    return 0;
}

static void virt_rs_packet(struct sk_buff *skb, struct net_device *dev) {

    unsigned char *type;
    struct iphdr *ih;
    struct udphdr *uh;
    __be32 *saddr, *daddr, tmp;
    __be16 tmp_port;
    unsigned char tmp_dev_addr[ETH_ALEN];
    struct ethhdr *ethhdr;
    struct sk_buff *rx_skb;
    int ret;
 
    //以太网头
    ethhdr = (struct ethhdr *)skb->data;
    // 保存原始目的MAC地址
    memcpy(tmp_dev_addr, ethhdr->h_dest, ETH_ALEN);
    // 交换源和目的MAC地址
    memcpy(ethhdr->h_dest, ethhdr->h_source, ETH_ALEN);
    memcpy(ethhdr->h_source, tmp_dev_addr, ETH_ALEN);

    //IP头
    ih = (struct iphdr *)(skb->data + sizeof(struct ethhdr));
    
    // 检查是否为UDP协议
    if (ih->protocol == IPPROTO_UDP) {
        printk("UDP packet received
");
        // 处理UDP协议
        uh = (struct udphdr *)((char *)ih + (ih->ihl * 4));
        
        // 交换源和目的IP地址
        saddr = &ih->saddr;
        daddr = &ih->daddr;
        tmp = *saddr;
        *saddr = *daddr;
        *daddr = tmp;
        
        // 交换源和目的端口
        tmp_port = uh->source;
        uh->source = uh->dest;
        uh->dest = tmp_port;
        
        // 重新计算IP头部校验和
        ih->check = 0;
        ih->check = ip_fast_csum((unsigned char *)ih, ih->ihl);
        
        // UDP校验和处理(设置为0表明不使用校验和)
        uh->check = 0;
    } else if (ih->protocol == IPPROTO_ICMP) { 	// 原有的ICMP处理逻辑,ping请求和回复
        printk("ICMP packet received
");
    
        saddr = &ih->saddr;
        daddr = &ih->daddr;
        tmp = *saddr;
        *saddr = *daddr;
        *daddr = tmp;
        
        ih->check = 0;
        ih->check = ip_fast_csum((unsigned char *)ih, ih->ihl);
        
        type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr);
        *type = 0; // ICMP Echo Reply
    } else {
        // 其他协议暂不处理
        printk("Other protocol packet received
");
        return;
    }

    /*
    - 1.第一行: rx_skb = dev_alloc_skb(skb->len + 2);
    - 这行代码分配一个新的套接字缓冲区(socket buffer,简称skb)用于接收数据包
    - dev_alloc_skb 是网络设备驱动中常用的分配skb的函数
    - 分配的大小是原始skb长度加2字节,这2字节是为了对齐目的,确保IP头在16字节边界上对齐,提高内存访问效率(以太网帧头为14字节)
    
    - 2.第二行: skb_reserve(rx_skb, 2);
    - 这行代码在接收skb的头部预留2字节空间
    - skb_reserve 函数用于在skb的数据区域前保留指定大小的空间
    - 这样做是为了确保后续添加的协议头(如以太网头)能够正确对齐,提高处理效率
    
    - 3.第三行: memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);
    - 这行代码将原始skb的数据复制到新分配的接收skb中
    - skb_put 函数扩展skb的数据区域,并返回指向新扩展区域的指针
    - memcpy 将原始skb的数据复制到这个新区域
    - 这样处理后,rx_skb就包含了完整的数据包内容,可以交给网络协议栈进一步处理
    */
    rx_skb = dev_alloc_skb(skb->len + 2);
    skb_reserve(rx_skb, 2);
    memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);
    rx_skb->dev = dev;	
    rx_skb->ip_summed = CHECKSUM_UNNECESSARY;
    rx_skb->protocol = eth_type_trans(rx_skb, dev);
    printk("rx_skb->protocol=%x
", rx_skb->protocol);

    //从驱动中往上层发送数据
    ret = netif_rx(rx_skb);
    
    dev->stats.rx_packets++;        
    dev->stats.rx_bytes += skb->len;
    pr_info("rx_packets=%ld rx_bytes=%ld ret=%d
", dev->stats.rx_packets, dev->stats.rx_bytes, ret);
}

static int virt_send_packet(struct sk_buff *skb, struct net_device *dev)
{
    netif_stop_queue(dev);
    print_skb_data(skb);
    virt_rs_packet(skb,dev);
    dev_kfree_skb(skb);

    dev->stats.tx_packets++; 
    dev->stats.tx_bytes+=skb->len; 
    pr_info("tx_packets=%ld tx_bytes=%ld
",dev->stats.tx_packets,dev->stats.tx_bytes);
    
    netif_wake_queue(dev); 
    return NETDEV_TX_OK;
}
 
static int set_mac_address(struct net_device *dev,void *p) {

    struct sockaddr *addr = p;
    pr_info("set_mac_address: %pM
", addr->sa_data);

    if (netif_running(dev))
        return -EBUSY;
    memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);
    return 0;
}

/*
 * 获取统计信息,例如 ifconfig 命令会调用
 */
static struct net_device_stats *virt_get_stats(struct net_device *dev)
{
    pr_info("vnet_get_stats: rx_packets=%lu, tx_packets=%lu
",
             dev->stats.rx_packets, dev->stats.tx_packets);
    return &dev->stats;
}

void virt_tx_timeout(struct net_device *net) 
{
    pr_info("virt_tx_timeout
");
}



static const struct net_device_ops net_ops = {
    .ndo_open               = virt_open,                //打开网卡
    .ndo_stop               = virt_stop,                //停止网卡
    .ndo_start_xmit		    = virt_send_packet,         //发送数据
//	.ndo_set_mac_address    = set_mac_address,          //设置mac地址,eth_mac_addr,使用内核通用的 MAC 地址设置函数
    .ndo_set_mac_address    = eth_mac_addr,
    .ndo_get_stats          = virt_get_stats,           //获取包状态
    .ndo_tx_timeout		    = virt_tx_timeout,          //发送超时
    .ndo_validate_addr = eth_validate_addr, // 使用内核通用的 MAC 地址验证函数
};
 
static int virt_net_init(void)
{
    virt_net = alloc_netdev(sizeof(struct net_device), "virt_net", NET_NAME_UNKNOWN,ether_setup);
    
    virt_net->netdev_ops = &net_ops;
    virt_net->flags |= IFF_NOARP; // 禁用ARP协议
 
    //这里可以手动设置MAC地址
    // virt_net->dev_addr[0] = 0x88;
    // virt_net->dev_addr[1] = 0x88;
    // virt_net->dev_addr[2] = 0x88;
    // virt_net->dev_addr[3] = 0x88;
    // virt_net->dev_addr[4] = 0x88;
    // virt_net->dev_addr[5] = 0x88;
 
    // 随机生成MAC地址
    eth_random_addr(virt_net->dev_addr);

    printk("virt_net_init: %pM
", virt_net->dev_addr);

    register_netdev(virt_net);

    return 0;
}
 
static void virt_net_exit(void)
{
    unregister_netdev(virt_net);
    free_netdev(virt_net);   
}
 
module_init(virt_net_init);
module_exit(virt_net_exit);
 
MODULE_LICENSE("GPL");
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>

#define BUFFER_SIZE 1024

int main()
{
    int sockfd;
    struct sockaddr_in servaddr;
    char *data = "Hello";
    char buffer[BUFFER_SIZE];
    int recv_len;

    {
        int i = 0;
        for(i = 0;i < 5;i++) {
                printf("data[%x] = %x,",i,data[i]);
        }
    }
    printf("
");

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket");
        return 1;
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("192.168.100.2");
    servaddr.sin_port = htons(12345);

    if (sendto(sockfd, data, strlen(data), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("sendto");
        close(sockfd);
        return 1;
    }

    // 接收从驱动程序返回的数据
    recv_len = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, NULL, NULL);
    if (recv_len < 0) {
        perror("recvfrom");
        close(sockfd);
        return 1;
    }

    printf("....Received data: %s
", buffer);

    close(sockfd);
    return 0;
}
//启动网卡
/ko # ifconfig virt_net 192.168.100.1 up
virt_net: device opened
vnet_get_stats: rx_packets=0, tx_packets=0

//查看网卡
/ko # ifconfig
vnet_get_stats: rx_packets=2, tx_packets=2
virt_net  Link encap:Ethernet  HWaddr 2A:BB:F6:F6:5E:25
          inet addr:192.168.100.1  Bcast:192.168.100.255  Mask:255.255.255.0
          UP BROADCAST RUNNING NOARP MULTICAST  MTU:1500  Metric:1
          RX packets:2 errors:0 dropped:0 overruns:0 frame:0
          TX packets:2 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:145 (145.0 B)  TX bytes:145 (145.0 B)
          

//Ping 网卡
/ko # ping 192.168.100.2 -c 1
PING 192.168.100.2 (192.168.100.2): 56 data bytes
sk_buff->data: 2a bb f6 f6 5e 25 2a bb f6 f6 5e 25 08 00 45 00 00 54 2a 86 40 00 40 01 c6 ce c0 a8 64 01 c0 a8 64 02 08 00 e3 27 03 3c 00 00 b9 e6 57 b5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ICMP packet received
rx_skb->protocol=8
rx_packets=1 rx_bytes=98 ret=0
tx_packets=1 tx_bytes=98
64 bytes from 192.168.100.2: seq=0 ttl=64 time=55.893 ms

--- 192.168.100.2 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 55.893/55.893/55.893 ms

//发送udp包
/ko # ./udp
data[0] = 48,data[1] = 65,data[2] = 6c,data[3] = 6c,data[4] = 6f,
sk_buff->data: 2a bb f6 f6 5e 25 2a bb f6 f6 5e 25 08 00 45 00 00 21 f2 ef 40 00 40 11 fe 87 c0 a8 64 01 c0 a8 64 02 99 7b 30 39 00 0d c8 f8 48 65 6c 6c 6f
UDP packet received
rx_skb->protocol=8
rx_packets=2 rx_bytes=145 ret=0
tx_packets=2 tx_bytes=145
....Received data: Hello

//手动设置mac地址
ifconfig virt_net down
//设置网卡mac地址,在设置之前需要关闭网卡
ifconfig virt_net hw ether 2A:BB:F6:F6:5E:25

图片

✅ 结论

输出结论

待查资料问题

  • ❓ 问题 1:?
  • ❓ 问题 2:?

参考链接

  • 官方文档
  • 官方文档
© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容