☰
虚拟网卡驱动源代码解析:从tun.c到NDIS的编译与排错指南
2026/10/8 15:11:48 网站建设 项目流程

简介:虚拟网卡驱动源代码(snull)出自《Linux设备驱动程序》一书,是经典网络驱动教学示例,面向Linux驱动开发入门与进阶学习者,也适合内核网络子系统研究者。代码展示了从设备注册、打开/关闭、数据包收发、统计信息到中断处理的完整网络驱动骨架,并包含普通中断与NAPI两种接收路径,方便对照学习内核网络接口的典型实现。资源共7个文件,压缩包仅14KB,主体为snull.c与snull.h源码,另附snull_load、snull_unload加载卸载脚本、Makefile及两个bak备份文件,便于直接编译、加载与修改实验。内容还涉及sk_buff操作、环回发送机制、发送超时模拟、数据包池管理等细节,对理解net_device结构、netif_rx流程和NAPI轮询有直接帮助。目前已有1576人学习浏览,适合配合内核源码或原书章节边读边练。

1. 虚拟网卡驱动源代码:先搞清楚你在找哪一层

如果你搜过“虚拟网卡驱动源代码(原版)”,大概率已经踩过一圈坑:下载到一个几十 MB 的压缩包,解压后源码和脚本混在一起,装上后设备管理器挂黄叹号,甚至直接被系统安全模块拦掉。先说一个反直觉的结论——Linux 下真正的原版虚拟网卡驱动,就躺在内核源码树里,路径是 drivers/net/tun.c,你不需要去任何第三方论坛求下载;Windows 下则是另一套 NDIS 框架的工程组织。这篇笔记要解决的是三件事:确认你找到的代码是不是原版、怎么编译安装、装上不生效时怎么排错。适合内核驱动初学者、虚拟化平台维护者,以及要做网络功能二次开发的从业者。

2. 判断“原版”与源码完整性:下载后的第一件事

2.1 先分清内核态驱动与用户态工具

这个标题里最容易混淆的一点是“驱动”和“工具”的边界。Linux 的 tun 驱动加载后,你日常用的ip、ifconfig并不是驱动,它们是用户态工具,通过 ioctl 和/dev/net/tun这个字符设备打交道。驱动本身只负责在内核里创建虚拟网卡、维护收发队列,把用户态写入的数据包挂到网络协议栈上。

所以你搜“虚拟网卡驱动源代码”时,先问自己一句:我要的是内核里那个创建虚拟网卡的模块,还是上层管理工具?如果指驱动,“原版源代码”在 Linux 下就是内核源码树里的drivers/net/tun.c(以及相邻的小部分头文件)。如果指 Windows 下的虚拟网卡驱动,那是一个完整的 NDIS 驱动工程,包含 .inf、.sys、C 源码、签名文件,结构完全不同。

把这两层混在一起找,是新手最常见的翻车点。拿着 Linux 内核模块的编译方式去套 Windows 驱动工程,或者反过来找“能用就行”的 exe 安装包,最后都会卡在加载阶段。先定位你在哪一层,再谈原版。

2.2 用 md5sum 与 git 状态验证源码未被改动

拿到一份自称“原版”的源码,我一般先做三件机械但有效的事:算哈希、看 git 状态、做一次 diff。哈希一致性是证明文件未被修改的最直接手段,前提是你拿到的对比基准本身可靠。对驱动这类源代码管理比较严格的项目,git 仓库里的 tag 和 commit 历史本身就是原版的指纹。

# 1) 对驱动文件算哈希 md5sum drivers/net/tun.c # 2) 如果源码来自 git 仓库,查看当前版本和最近提交 git describe --tags git log --oneline -3 -- drivers/net/tun.c # 3) 和当前内核自带的驱动文件做 diff diff -u /usr/src/linux-headers-$(uname -r)/drivers/net/tun.c drivers/net/tun.c

逻辑说明:md5sum 一旦一致,基本能证明这份文件和官方发布一致。git describe 能看到源码树还剩多少版本信息,原版应该能对应到一个正式发布的 tag,如果输出一串提交号而没有 tag,说明源码树是被人手工拼出来的。diff 是最后的兜底,能直观看到本地被改过哪些行。

参数说明:uname -r会展开成当前内核版本号,所以 diff 命令里的源路径必须指向同版本的源码树;否则两个不同版本的文件做 diff,输出结果没有参考意义。

除了哈希,还可以看源码里的模块元信息。原版tun.c的MODULE_LICENSE是 "GPL",带完整的MODULE_AUTHOR和MODULE_DESCRIPTION。第三方魔改版为了过查杀,经常把这些信息抹掉或替换。

grep -n "MODULE_LICENSE\|MODULE_AUTHOR\|MODULE_DESCRIPTION" drivers/net/tun.c

这一步是血泪经验:网上流传的某些“原版驱动”压缩包,文件哈希对不上就算了,连模块描述都被改成了网站上自己的推广文案。哈希、git 状态、元信息三项都过,才值得你继续往下做编译和安装。

2.3 内核版本对齐:驱动不是越新越好

内核驱动和用户态程序最大的区别在于:模块编译时打进了当前内核的版本信息,加载时内核会做严格校验,版本不一致直接拒绝加载。网上找的“最新版”tun 模块,很可能在你的旧内核上根本 insmod 不进去。

# 查看当前内核版本 uname -r # 查看系统里已有的 tun 模块信息 modinfo tun | head -20

逻辑说明:modinfo输出里的 vermagic 字段记录了模块编译时对应的内核版本。把 vermagic 和uname -r对照,一眼就能看出这个模块是给哪个内核用的。如果你手里是一份源码而不是编译好的 .ko,那同样要先确认源码版本和运行内核匹配。

正确做法是:先uname -r确认内核版本,再拿对应版本的完整内核源码,从里面取 tun.c 来编译,或者直接安装发行版自带的 kernel-modules-extra 类软件包。不要从第三方站点下载一个孤零零的驱动文件。这一步想省,后面所有编译安装都可能白做。

3. 在 Linux 下编译并安装 tun 虚拟网卡驱动:最小可运行流程

3.1 编译准备:内核头文件与工具链

编译内核模块的前提是头文件版本和当前内核一致。很多人装完 gcc 就把源码扔进去编译,结果报一堆找不到头文件的错误。先确认编译工具和内核头文件齐全:

# Debian / Ubuntu 系 sudo apt install build-essential linux-headers-$(uname -r) # RHEL / CentOS / Fedora 系 sudo dnf install gcc make kernel-devel-$(uname -r)

逻辑说明:build-essential 提供 gcc 和 make;linux-headers 提供当前内核的编译配置和生成的头文件。模块编译时需要include/generated/autoconf.h这些文件,它们只随内核头文件一起发布,普通用户态开发环境不会带。kernel-devel 在 RHEL 系里是等价物。

参数说明:uname -r自动展开成当前版本,所以升级内核后必须重装对应版本的头文件。这是换内核后模块编译失败最常见的原因——头文件还是旧版本,编译出来的模块 vermagic 对不上新内核。

3.2 用 menuconfig 与 .config 确认 CONFIG_TUN

tun 驱动不是一个独立工程,它是内核源码树里的一个模块。所以“编译 tun 驱动”实际是在内核构建系统里单独编译一个目标。先定位配置项:

cd /usr/src/linux-source-$(uname -r) # 或你自己解压的内核源码目录 make menuconfig

在 Device Drivers → Network device support 里找到 Universal TUN/TAP device driver support,对应配置项是 CONFIG_TUN。把它设成 M(模块),保存退出后确认:

grep CONFIG_TUN .config

参数说明:CONFIG_TUN=y表示编进内核镜像,=m表示编成独立模块,=n表示不编译。大多数发行版默认是=m或=y。如果你改过配置,注意不要随便动其他选项,菜单里一次错误的保存可能让整个内核编译失败。

我一般建议编成模块而不是编进内核。模块方式便于升级和卸载,出问题时rmmod即可回滚,编进内核反而没有后悔药。生产环境里为了减少重启次数,有人选择=y,但代价是驱动行为异常时只能重启系统恢复。

3.3 只编译 tun.ko 的最小命令:M= 方式与产物检查

全量编译内核动辄一两个小时,只为了一个模块不划算。用M=参数把构建系统限定到单一目录,几秒钟就能出产物:

# 先准备模块编译所需的最小生成文件 make prepare make modules_prepare # 只编译 drivers/net 目录下的模块 make M=drivers/net modules # 查看产物 ls -l drivers/net/tun.ko

逻辑说明:make prepare和make modules_prepare会生成模块编译所需的头文件和符号表。M=drivers/net告诉内核构建系统只进入该目录编译目标,耗时短,适合反复改代码的场景。tun.ko就是最终的内核模块文件。

参数说明:M= 后面接的是相对源码根的目录路径,也可以用绝对路径写M=/path/to/source/drivers/net。如果在外部建了输出目录,用O=指定,但初次调试不建议这么做,输出目录和目标目录分开后符号依赖更容易出问题。

如果你不想手动跑 menuconfig,可以跳过配置步骤,直接用当前内核的配置开始编译:

cp /boot/config-$(uname -r) .config make olddefconfig

然后确认grep CONFIG_TUN .config的值,再进入上面的编译流程。这种方式适合只想快速编一个模块,不想碰内核配置菜单的情况。

3.4 安装模块并设置开机加载

编译出 tun.ko 后,安装到系统模块目录:

sudo make M=drivers/net modules_install sudo depmod -a sudo modprobe tun lsmod | grep tun

逻辑说明:modules_install把 tun.ko 复制到/lib/modules/$(uname -r)/kernel/drivers/net/下,depmod -a重建模块依赖,modprobe tun按依赖自动加载。lsmod看到 tun 说明加载成功。

参数说明:安装后可以用modinfo tun查看模块路径和 vermagic,确认它是刚从你源码编译出来的版本,而不是系统旧版本。这一步很重要,因为发行版如果已经内置了 tun 模块,modprobe会优先加载系统模块,你编译的版本根本没生效。

开机加载有两种方式,任选其一:

# 方式 A:写入 modules-load.d echo "tun" | sudo tee /etc/modules-load.d/tun.conf # 方式 B:只对本次会话加载 sudo modprobe tun

方式 A 适合服务器重启后要自动恢复虚拟网卡的场景,比如 KVM 宿主机。方式 B 只对当前启动周期有效,适合调试。还要检查设备节点是否存在:

ls -l /dev/net/tun

如果没有这个设备文件,加载后一般由 udev 自动创建。创建失败时的处理,放到第 4 章排查部分单独讲。

3.5 和 tun 强相关的几个配置项

编译 tun 驱动时,除了 CONFIG_TUN 本身,还有几个相邻选项会影响虚拟网卡的行为和性能。

配置项作用建议
CONFIG_TUN启用 tun/tap 驱动必选,设为 m
CONFIG_TUN_VNET_HDR支持 virtio net header虚拟化场景建议开启
CONFIG_NET_UDP_TUNNELUDP 隧道支持搭建 overlay 网络时开启

逻辑说明:CONFIG_TUN_VNET_HDR 打开后,用户态程序可以通过TUNSETVNETHDRSZioctl 设置 virtio 头部大小,这是 KVM 虚拟机半虚拟化网卡的关键。不是内置网卡方案的话,这一项对最终性能影响很大。

参数说明:这些选项通常在 menuconfig 的同级菜单里,设好之后.config中会以独立的CONFIG_*形式出现。只改 CONFIG_TUN 而忽略 VNET_HDR,tun 驱动也能跑,但虚拟机网络吞吐会明显偏低。

4. 驱动装上不生效:从内核日志定位的 4 个高频排查点

4.1 现象:虚拟网卡不存在或被禁用,lsmod 里也没有 tun

很多人在 KVM 宿主机上装完系统,发现虚拟机没有虚拟网卡,第一反应是去装驱动。先别急着装,执行lsmod和modprobe:

lsmod | grep tun sudo modprobe tun

如果 modprobe 不报错但lsmod里还是看不到,可能模块已经编进内核(=y),此时不会有独立模块名。用配置确认:

grep CONFIG_TUN /boot/config-$(uname -r)

原因:发行版默认把 tun 编进内核或模块,但设备节点没建、容器没映射权限、或 BIOS 里虚拟化没开,都会表现为“虚拟网卡不存在或被禁用”。解决:先确认 CONFIG_TUN 取值,再检查/dev/net/tun是否存在。

4.2 现象:insmod 报 version magic mismatch

自己编译的 tun.ko 用 insmod 加载时,最常见报错是version magic mismatch。原因很简单:源码版本与当前内核版本不一致,或者编译时用的配置和运行内核配置不同。解决:

# 查看编译出的模块期望的版本 modinfo tun.ko | grep vermagic # 查看当前内核版本 uname -r

两者不一致就直接重编,拿对应的源码树重走第 3 章的流程。如果你是从第三方网站下载的所谓“原版”模块,这是它最常见的翻车点——下载的模块根本不认识你的内核。用发行版自带的模块最稳妥,自己编译只做二次开发时才需要。

4.3 现象:open /dev/net/tun 失败,提示 No such file or directory

驱动加载成功但设备节点缺失,在容器和精简系统里很常见。原因:udev 的静态设备规则缺失,或者/dev/net目录本身没建。解决:

sudo mkdir -p /dev/net sudo mknod /dev/net/tun c 10 200 sudo chmod 666 /dev/net/tun

注意:字符设备主设备号 10、次设备号 200 是内核固定分配,不能随意改。这是在 udev 失效时的临时兜底。正常系统里应该让 udev 管理节点,临时创建只用于验证驱动是否可用。

4.4 现象:设备节点在,但用户态程序打不开,报 Permission denied

非 root 用户调用open()失败。原因:设备文件权限不允许当前用户读写。解决:

sudo chmod 666 /dev/net/tun # 或者用 udev 规则固定权限 echo 'KERNEL=="tun", MODE="0666"' | sudo tee /etc/udev/rules.d/50-tun.rules

参数说明:0666 让所有用户可读写,适合开发机;生产环境应限定用户组,把普通用户加进组,不要全局开放。

4.5 现象:Windows 虚拟机找不到网络,添加 virt-io 驱动无效

原因:Windows guest 需要 virtio-net 驱动,而不是 Linux 的 tun 驱动;宿主侧的 tun 只是后端通道。很多人把宿主机侧和客户机侧重合了,实际上宿主用 tun,guest 用 virtio-net,两者配合才有一条完整链路。解决:在 Windows guest 里安装对应虚拟化平台的 virtio 驱动包,确认设备管理器里出现 virtio net 且无黄叹号。

这条“玄学”般的错位其实有迹可循:你在宿主侧看到tun设备已加载,就以为虚拟机侧也搞定了,但虚拟机的网卡型号如果是 virtio,它不认识宿主的 tun,必须有自己的驱动。

4.6 现象:升级内核后 tun 功能消失

升级内核后虚拟网卡功能消失,多半是/lib/modules/新版本号/kernel/drivers/net/下没有 tun.ko。原因:旧内核的模块不会自动复制到新内核目录,tun 如果没编进新内核镜像,加载就会失败。解决:重新安装匹配新内核版本的模块包,或者用新源码重新编译一次。

5. Windows 虚拟网卡驱动怎么落地:从 NDIS 工程到设备管理器

5.1 Windows 虚拟网卡驱动的三种来源

Windows 下没有 Linux 那种统一的内核源码树,虚拟网卡驱动的“源代码”分三类:

来源典型形态适用场景
微软官方开源驱动Wintun,提供二进制和接口只装不用改
社区维护的开源 TAP 驱动完整 C 工程 + INF二次开发、学习
自研 NDIS 驱动WDK 工程,全套自建深度定制

对只想“拿到原版代码”的人来说,社区开源 TAP 驱动是最现实的路径;对要深度定制的人来说,自研 NDIS 驱动才是正路。选择前先明确你的目标:是要一个能装上的成品,还是要一个能改的工程。目标不同,取舍完全不同。

5.2 一个最小 NDIS 驱动工程里应该有哪些文件

不管选哪条路,Windows 驱动工程的骨架是固定的。文件清单如下:

文件作用必需程度
.inf描述硬件 ID、驱动版本、安装服务名必需
.sys编译出的驱动二进制必需
.c/.h入口例程、回调函数、数据结构定义必需
.vcxproj工程文件,WDK 用 MSBuild 编译必需
.cat数字签名目录文件安装时必需

写代码的最小入口是DriverEntry函数,注册各种分派回调,这和 Linux 模块的module_init是同一思想,但 Windows 把 PnP、电源、I/O 都绑在 WDF 框架上,代码量不是一个量级。真正的虚拟网卡驱动还要实现发送路径、接收路径和中断处理,工程规模通常在几千行以上。

5.3 用 WDK 编译与测试签名

Windows 驱动必须签名,64 位系统上未签名驱动默认拒绝加载。测试签名步骤:

# 打开测试签名模式(管理员 PowerShell) bcdedit /set testsigning on # 重启后,用 signtool 对驱动做测试签名 signtool sign /v /s TestCertStore /n "MyTestCert" driver.sys

逻辑说明:bcdedit 打开测试签名模式后,系统才允许加载带有测试签名的驱动。signtool 是 WDK 自带的工具,签名后再安装驱动,设备管理器才不会报错。参数说明:TestCertStore 是证书存储名,MyTestCert 是证书名称,需要先用 Windows SDK 生成一个测试证书。

测试完要恢复:

bcdedit /set testsigning off

注意:这个命令要在测试完、确认驱动不依赖测试签名时执行。长期开着测试签名模式,系统安全水位会下降。

5.4 设备管理器 Code 43 的常见原因

Windows 下虚拟网卡驱动最常见的故障就是设备管理器里显示“该设备无法启动(代码 43)”。原因大多是四个:

  1. .inf 的硬件 ID 和你实际安装的设备不匹配
  2. 驱动二进制与系统版本不符,本质是 vermagic 的 Windows 版
  3. 缺少或过期签名
  4. 和已有虚拟网卡驱动冲突

解决顺序:先卸载旧驱动并清理(设备管理器 → 卸载设备 → 勾选删除驱动程序软件),再安装新签名驱动。仍报 43 就看系统事件日志里的 net 类错误,不要一上来就重装系统。

“装完驱动显示 43”这类问题,九成是驱动签名或版本对不上,纯玄学的场景很少。事件日志里会写明失败模块路径,先看日志比盲试快得多。

5.5 卸载与回滚:驱动没有后悔药,但有快照

Windows 驱动安装没有像 Linuxrmmod那样干净的卸载机制。驱动的 .sys 文件被系统进程占用时,直接删是删不掉的。做驱动测试前,先在系统里建一个还原点,或者用快照工具留底。手动清理时,禁用设备再卸载,比直接卸载更彻底。

6. 验证驱动是否干净:三个命令看穿内核模块

文件名写着“原版”不算数,加载前先验证模块本身。第一个命令是modinfo:

modinfo tun.ko

重点看描述、作者、vermagic 和依赖。如果作者和描述被篡改过,大概率是被二次打包的版本。

第二个命令是strings:

strings tun.ko | grep -i "tun\|tap\|net"

正常模块的字符串围绕 tun/tap 展开。如果输出里出现和驱动无关的域名、关键词或者明显异常的路径,说明里面夹带私货。这个方法对 Windows 的 .sys 文件一样有效,用 strings 分析 PE 文件同样能看出异常引用。

第三个命令是运行时行为观察:

cat /sys/class/misc/tun/dev ip link add dev tap-test type tun ip link show tap-test

能创建出虚拟网卡并显示出来,说明驱动数据通路是通的,代码是按标准接口实现的。对 Windows,对应观察设备管理器和网络适配器列表。

我以前拿到驱动也习惯直接装,直到一次线上加网卡遇到系统不稳定,才养成先 modinfo、再 strings、最后小流量验证的习惯。真正值得信任的不是文件名里的“原版”二字,而是你自己跑过的验证命令。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询