从计网实验课思考,尝试手撕一个简易的二层 VPN
Simple L2-VPN
让位于世界两端的计算机,以为自己连在同一根网线上。
这是一个基于 Linux TAP 虚拟网络设备和 UDP 隧道的学习项目。通过在云端实现一个简单的 MAC 地址学习交换机,构建一个跨越物理边界的虚拟二层局域网,让不同物理位置的主机可以通过虚拟二层网络相互通信,以此解决复杂网络环境下的内网穿透与 IP 冲突问题。这个项目的目的是理解网络虚拟化的基本原理,而非生产级别的解决方案。
项目背景
经典实验
这个项目源于计算机网络课程中的经典实验——《VLAN 划分与三层交换》。在实验中,我们通过配置三层交换机的路由表和 VLAN 网关,让不同网段的 PC 实现了互联。

学习动机
我试图将这些原理应用到真实互联网时,遇到了无法逾越的物理障碍。首先是 IP 地址冲突的问题。绝大多数家庭和企业局域网都默认使用 192.168.1.x 网段。如果想把两个地点的局域网连起来,IP 冲突会导致路由表彻底瘫痪。其次是 NAT 高墙。由于 IPv4 地址枯竭,几乎所有终端都躲在 NAT 防火墙后面,外网无法主动发起连接。最后是三层路由限制,公网路由器只负责转发公网 IP,它看不懂也不关心你内网的以太网帧。
虽然现实中已有很多成熟的 VPN 解决方案,如 OpenVPN、WireGuard 等,但通过手动实现一个简单的二层隧道,不仅能解决这些实际问题,还能更深入地理解网络虚拟化的原理。具体来说,可以学到 Linux 虚拟网络设备 TAP 的工作原理、以太网帧的结构和 MAC 地址学习机制、UDP 隧道如何封装和转发数据,以及多线程编程在网络应用中的应用。
核心特性
这个项目通过 Overlay Network 覆盖网络技术实现了几个核心特性:
• 二层透明传输:不仅能转发 IP 数据包,还能转发 ARP、DHCP 等非 IP 协议的以太网帧
• 零配置 IP 漫游:即使 Client A 和 Client B 在物理上位于不同城市,且都拥有完全相同的本地私有 IP,它们依然可以通过本系统通信
• 穿透 NAT:利用 UDP 协议的无连接特性,穿透常见的 NAT 设备,无需配置复杂的端口映射
• 自学习交换机:服务端实现了标准交换机的 MAC 地址学习算法,维护 MAC 表,根据目标地址决策是单播转发还是广播泛洪
系统架构
技术栈
客户端采用 C 语言 + Linux TAP 设备 + UDP Socket,服务端采用 Python 3 + Socket 编程。
工作流程
当 Client A 想要给 Client B 发送数据时,流程如下:
• 打包:Client A 的内核将数据生成以太网帧,写入虚拟网卡,C 程序读取该帧并套上 UDP 头部
• 运输:UDP 包通过物理网卡发出,经过互联网路由到达云端 Server
• 分拣:Python 服务端拆开 UDP 包,查看内部以太网帧的目标 MAC。如果是新地址则记录源 MAC 和来源 IP,如果是已知地址则查找 MAC 表找到 Client B 的隧道信息
• 投递:Server 将包转发给 Client B,Client B 的 C 程序收到后剥去 UDP 头部,将原始帧写入虚拟网卡
• 收货:Client B 的内核以为刚才那个数据包是从网线上过来的,正常处理
核心代码解析
tap.c - 虚拟设备构建
封装了 Linux TAP 设备的底层交互逻辑。机制是打开 /dev/net/tun 并通过 ioctl(TUNSETIFF) 注册设备。关键标志包括 IFF_TAP 指定设备工作在二层处理 Ethernet Frame,以及 IFF_NO_PI 禁用内核附加的 Packet Info 头部,确保读写的是纯净以太网帧。作用是建立用户态程序与内核网络栈的秘密通道。
client.c - 多线程数据泵
采用 pthread 实现全双工通信,核心结构体 struct Vport 统一管理 TAP 句柄与 UDP Socket。上行线程 upstream_thread 阻塞读取 TAP 设备数据捕获内核发出的帧,通过 sendto 将原始帧作为 UDP 载荷发往 Server。下行线程 downstream_thread 监听 UDP 端口接收云端数据,通过 write 将帧注入 TAP 设备,模拟物理网卡收包,欺骗内核进行协议栈处理。
vswitch.py - 云端交换逻辑
Python 实现的轻量级 UDP 交换机,手动解析前 14 字节以太网头部。MAC 学习动态维护 MAC -> (Client_IP, Port) 映射表,实时更新主机位置。转发策略中已知单播查表命中精确转发,广播 ff:ff:ff:ff:ff:ff 向表中除源地址外的所有活跃客户端泛洪,未知单播为防止网络风暴,对于表中不存在的目标 MAC,策略为直接丢弃。
快速开始
事实上我们可以使用 GNS3 搭建一个拓扑图进行抓包来验证程序。

前置准备:Server-VSwitch 和 PC 机均为 Ubuntu Server,需要将代码拷入。
Server-VSwitch 在服务器端运行脚本监听端口:
python3 vswitch.py 9999
Ubuntu-PC 编译 C 客户端,并运行配置脚本:
# 编译
gcc -o client tap.c client.c common.c -I. -lpthread
# 运行,需确保 run.sh 已配置正确 IP
sudo sh run.sh
验证:Ping 对方虚拟 IP,并使用 tcpdump 抓包验证二层连通性。
有趣的玩法
如果你和你的小伙伴拥有 Linux 环境以及一台云服务器,可以将这个底层项目应用起来,做一些有意思的实验。项目中附带了一个 chat.py,这是一个基于 Python tkinter 库编写的简陋对话窗口。配置修改代码中的 ROLE 角色以及服务器 IP,在两台通过 VPN 连通的主机上直接运行 python3 chat.py。数据包将通过我们的 UDP 隧道透明传输,你可以体验一下在这个自己亲手搭建的虚拟局域网中聊天的感觉。
友情链接
参考资源
Linux TAP/TUN 设备文档、以太网帧格式规范、交换机 MAC 地址学习原理、UDP 套接字编程。
原文地址: https://www.cveoy.top/t/topic/qFTy 著作权归作者所有。请勿转载和采集!