虚拟网卡驱动源代码解析:从tun.c到NDIS的编译与排错指南

发布时间:2026/10/8 15:11:51
虚拟网卡驱动源代码解析:从tun.c到NDIS的编译与排错指南
简介虚拟网卡驱动源代码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 和 makelinux-headers 提供当前内核的编译配置和生成的头文件。模块编译时需要include/generated/autoconf.h这些文件它们只随内核头文件一起发布普通用户态开发环境不会带。kernel-devel 在 RHEL 系里是等价物。参数说明uname -r自动展开成当前版本所以升级内核后必须重装对应版本的头文件。这是换内核后模块编译失败最常见的原因——头文件还是旧版本编译出来的模块 vermagic 对不上新内核。3.2 用 menuconfig 与 .config 确认 CONFIG_TUNtun 驱动不是一个独立工程它是内核源码树里的一个模块。所以“编译 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_TUNy表示编进内核镜像m表示编成独立模块n表示不编译。大多数发行版默认是m或y。如果你改过配置注意不要随便动其他选项菜单里一次错误的保存可能让整个内核编译失败。我一般建议编成模块而不是编进内核。模块方式便于升级和卸载出问题时rmmod即可回滚编进内核反而没有后悔药。生产环境里为了减少重启次数有人选择y但代价是驱动行为异常时只能重启系统恢复。3.3 只编译 tun.ko 的最小命令M 方式与产物检查全量编译内核动辄一两个小时只为了一个模块不划算。用M参数把构建系统限定到单一目录几秒钟就能出产物# 先准备模块编译所需的最小生成文件 make prepare make modules_prepare # 只编译 drivers/net 目录下的模块 make Mdrivers/net modules # 查看产物 ls -l drivers/net/tun.ko逻辑说明make prepare和make modules_prepare会生成模块编译所需的头文件和符号表。Mdrivers/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 Mdrivers/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 驱动必选设为 mCONFIG_TUN_VNET_HDR支持 virtio net header虚拟化场景建议开启CONFIG_NET_UDP_TUNNELUDP 隧道支持搭建 overlay 网络时开启逻辑说明CONFIG_TUN_VNET_HDR 打开后用户态程序可以通过TUNSETVNETHDRSZioctl 设置 virtio 头部大小这是 KVM 虚拟机半虚拟化网卡的关键。不是内置网卡方案的话这一项对最终性能影响很大。参数说明这些选项通常在 menuconfig 的同级菜单里设好之后.config中会以独立的CONFIG_*形式出现。只改 CONFIG_TUN 而忽略 VNET_HDRtun 驱动也能跑但虚拟机网络吞吐会明显偏低。4. 驱动装上不生效从内核日志定位的 4 个高频排查点4.1 现象虚拟网卡不存在或被禁用lsmod 里也没有 tun很多人在 KVM 宿主机上装完系统发现虚拟机没有虚拟网卡第一反应是去装驱动。先别急着装执行lsmod和modprobelsmod | 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 KERNELtun, MODE0666 | sudo tee /etc/udev/rules.d/50-tun.rules参数说明0666 让所有用户可读写适合开发机生产环境应限定用户组把普通用户加进组不要全局开放。4.5 现象Windows 虚拟机找不到网络添加 virt-io 驱动无效原因Windows guest 需要 virtio-net 驱动而不是 Linux 的 tun 驱动宿主侧的 tun 只是后端通道。很多人把宿主机侧和客户机侧重合了实际上宿主用 tunguest 用 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”。原因大多是四个.inf 的硬件 ID 和你实际安装的设备不匹配驱动二进制与系统版本不符本质是 vermagic 的 Windows 版缺少或过期签名和已有虚拟网卡驱动冲突解决顺序先卸载旧驱动并清理设备管理器 → 卸载设备 → 勾选删除驱动程序软件再安装新签名驱动。仍报 43 就看系统事件日志里的 net 类错误不要一上来就重装系统。“装完驱动显示 43”这类问题九成是驱动签名或版本对不上纯玄学的场景很少。事件日志里会写明失败模块路径先看日志比盲试快得多。5.5 卸载与回滚驱动没有后悔药但有快照Windows 驱动安装没有像 Linuxrmmod那样干净的卸载机制。驱动的 .sys 文件被系统进程占用时直接删是删不掉的。做驱动测试前先在系统里建一个还原点或者用快照工具留底。手动清理时禁用设备再卸载比直接卸载更彻底。6. 验证驱动是否干净三个命令看穿内核模块文件名写着“原版”不算数加载前先验证模块本身。第一个命令是modinfomodinfo tun.ko重点看描述、作者、vermagic 和依赖。如果作者和描述被篡改过大概率是被二次打包的版本。第二个命令是stringsstrings 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、最后小流量验证的习惯。真正值得信任的不是文件名里的“原版”二字而是你自己跑过的验证命令。希望帮到你。本文还有配套的精品资源点击获取