❓ 问题描述:
编写一个内核虚拟网卡驱动程序
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)有数据要发送时,驱动需将数据从内核缓冲区传递到网卡硬件
接收数据:
当网卡收到数据时,驱动需将数据从硬件缓冲区传递到内核协议栈
网络模型

MAC帧

IP数据包

TCP数据包

UDP数据包

ICMP数据包

ARP数据包

TCP抓包

UDP抓包

ICMP抓包

ARP抓包

常见以太网帧协议类型

常见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





![[C++探索之旅] 第一部分第十一课:小练习,猜单词 - 鹿快](https://img.lukuai.com/blogimg/20251015/da217e2245754101b3d2ef80869e9de2.jpg)










暂无评论内容