外观
VPN隧道封装与企业组网实战
约 12320 字大约 41 分钟
网络安全技术VPN隧道技术IPSec
2026-05-17
本课核心
提起 VPN,很多人的第一反应是"翻墙用的"或者"换 IP 的"。如果你学完了本课还是这个理解,那你就白学了。
VPN 真正的价值在于:它让一家公司分散在不同城市、不同国家的办公室和员工,像是在同一张局域网里一样安全地通信——而这整套感觉,是建立在加密、认证、封装和路由这四根柱子上的。
本课我们不做纸上谈兵。你会在自己的一台机器上,用 Linux network namespace 造出一个"总部"和一个"分支",中间用 OpenVPN 建一条真实的加密隧道,然后用 tcpdump 在两个位置同时抓包。你会亲眼看到:外层链路上只有加密的 UDP 包在飞,内层接口里才是你熟悉的 HTTP 请求——这就是"隧道"二字最直观的证据。
课堂红线
本实验只在本机 Kali WSL 与临时 namespace 中模拟企业内网,不连接公网,不提供真实 VPN 接入服务。禁止把课堂配置暴露到公网,禁止用于绕过网络管理策略,禁止为他人提供匿名代理或隧道服务。
一、 先忘掉"翻墙",我们从一个真实的公司场景出发
假设你所在的公司在全国有三个办公室:北京总部、上海研发中心、广州销售办公室。每个办公室都有自己的内部系统——北京的财务系统、上海的代码仓库、广州的客户管理系统。这些都是只允许内网访问的,没有公网 IP。
现在问题来了:上海的工程师需要访问北京的代码仓库,广州的销售需要查北京的客户数据,总部的 CFO 出差在酒店要批上海的报销单——这些人都不在同一个物理局域网里,怎么安全地访问?
你可能会说:"那还不简单,把那些系统都配上公网 IP,加上密码不就行了?"
这个想法有几个致命缺陷:
第一,把内网系统暴露到公网上,就等于全世界的人都能尝试连接它。"加个密码"挡不住暴力破解、漏洞扫描和 0day 攻击。安全圈有一句话:不被发现的漏洞才是安全的。一旦暴露到公网,你的系统就成了全天候的靶子。
第二,数据在公网上传输的过程中,经过的每一段链路——酒店 WiFi、咖啡馆路由器、运营商骨干网——都可以被监听。如果你传的是财务报表或客户身份证号,而且没有加密,那每一段链路上的监听者都能看到。
第三,密码只能解决"你是谁"的问题,解决不了"数据有没有被改"的问题。如果把公网比作一条拥挤的街道,你从街头喊一句话到街尾,中间任何人都能听到,而且可以捏造你的声音传假话——只靠一个口令阻止不了这个。
VPN 做的就是这么一件事:在这条拥挤、危险、不可靠的公共街道下面,挖一条带警卫、有密码锁、墙壁坚不可摧的专用隧道。你在隧道里说什么,外面的人只能听到沉闷的回响,完全看不懂。而隧道两端的警卫(VPN 网关)会互相认证身份,确保对面确实是自家人。
拆开来看,VPN 要同时解决四个问题:
第一,你是谁?(身份认证) 不能随便来个人就能接进来。VPN 网关必须确认连接方是合法员工、合法分支还是合法合作伙伴。常用的手段包括证书(我们上一课学的 PKI)、账号密码、短信验证码的组合。
第二,你说的话会不会被人听到?(机密性) 所有穿过公网的数据必须加密。我们在上一课学的 AES-GCM,在这一课就要派上用场了。
第三,你说的话有没有被人改过?(完整性) 加密只保证"别人看不懂",但如果攻击者胡乱修改密文,接收方解密出来的可能是垃圾数据。所以 VPN 的加密通道还必须做完整性校验——这个前面学的 AEAD(带认证的加密)模式的价值就体现出来了。
第四,我怎么找到你?(可路由) 公司内网用的都是私有 IP(比如 10.x.x.x、192.168.x.x),这些地址在公网上是无法路由的。VPN 通过在公网上建立隧道,把内网 IP 的流量包在公网报文里面传输,让远端的电脑感觉"你就在我隔壁"。
这就是 VPN 的四个基本能力。记住了这四个能力,你就不会再把 VPN 简单理解成"换 IP 的工具"了——换 IP 只是表象,认证、加密、校验、路由才是它真正的骨架。
VPN Encryption Boundary
同一个 HTTP 请求在隧道内外看到的内容完全不同
分支终端10.88.0.2
公共链路172.31.14.2:43929 -> 172.31.14.1:11940
总部网关10.88.0.1
内层明文GET / HTTP/1.1intranet payroll=classified
加密与认证Data Channel: AES-256-GCM证书认证 + TLS 控制信道
外层封装UDP 11940 length 158outer capture: no HTTP plaintext
plain capture: GET / HTTP/1.1 and intranet payroll=classified are visible
不走 VPN 时,公共链路能直接看到业务内容。
明文 HTTP 的请求行和响应内容会出现在抓包载荷里,旁路监听者可以读到路径、Cookie 或业务数据。
二、 三种 VPN,三种信任边界
不是所有的 VPN 连接都应该享有相同的权限。这听起来像废话,但现实中很多公司的做法就是"连上 VPN 就等于接入整个内网"。这种粗暴的设计导致了无数次安全事件——一个在家办公的员工电脑中了勒索病毒,病毒顺着 VPN 隧道把总部的文件服务器也加密了。
所以我们在学具体技术之前,先把 VPN 分成三类,理清每一类的信任假设:
Access VPN:员工从外面连回公司
场景就是你在家、在咖啡馆、在酒店,通过 VPN 客户端连回公司内网。这类 VPN 最大的风险在于终端——你永远不知道员工的个人电脑上装了什么。可能是带毒的游戏外挂,可能是被黑的浏览器插件,也可能是小孩下载的恶意软件。所以 Access VPN 的安全策略必须是"零信任"心态:连上来不等于信任你,还要检查你的终端有没有打补丁、有没有装杀毒、连接的设备是不是公司配的那台。而且接入后只能访问你工作需要的几个系统,不是整个内网。
Intranet VPN:总部和分支之间的永久通道
两个办公室之间拉一根 VPN 隧道,让两边内网像一张网一样互通。这类 VPN 的风险在于"一个分支沦陷,全网遭殃"。上海办公室被攻破了,攻击者可以顺着 VPN 隧道横向移动到北京总部的核心系统。所以 Intranet VPN 的访问控制必须是双向的:不仅限制外网进来,也要限制分支之间的互访。
Extranet VPN:向合作伙伴开放特定的资源
比如你的公司是汽车制造商,需要让零件供应商访问你的零件图纸系统。但供应商只能看到自己供应的那几个零件,不能看到全部,更不能访问你的财务系统和人事系统。Extranet VPN 的安全策略是最精细的——最小权限原则在这类场景里是生死线。
这三种分类的差别不在协议上,而在信任假设上。理解了这个差别,你将来在面试中被问到"怎么设计 VPN 的访问控制",就不会只回答"加上防火墙规则"这种万金油答案了。
三、 隧道到底是怎么"包"出来的
很多人学 VPN,卡就卡在"隧道"这个概念上。什么叫"把一层包放进另一层包里面"?
我换一个生活中的类比。你寄快递的时候,你要寄的东西(比如一本书)是"内层货物"。你把书放进一个快递纸箱,在纸箱上写了发件人和收件人,交给快递公司。快递公司拿到后,把这个纸箱放进他们的货车里,货车的车牌号就是"外层运输标识"。货车在高速公路上跑,路上的摄像头只能看到货车的车牌,看不到你纸箱上写的收件人。等货车到了目的城市的中转站,工作人员拆掉外层的货车,取出你的纸箱,按照纸箱上的地址送给你。
VPN 的隧道封装和这个几乎一模一样:
你的电脑想访问内网服务器
10.88.0.1,发了一个数据包,源地址是你自己10.88.0.2。这是"内层货物"。VPN 客户端判断:
10.88.0.x是内网地址,不能直接走公网,得先进隧道。于是它把这个数据包整个加密,变成一段不可读的字节流。VPN 客户端给这段加密字节流套上一个新的外层包头:源地址是你的公网 IP,目的地址是 VPN 网关的公网 IP,端口是 VPN 协议端口(比如 UDP 11940)。
这个"外层包"在公网上传输。沿途的路由器、交换机只能看到外层包头——源和目的都是公网 IP,内容是一段加密的字节流。
包到达 VPN 网关。网关剥掉外层包头,取出加密字节流,用会话密钥解密,还原出原始的内层包——就是最开始你的电脑发出的那个"去
10.88.0.1"的包。网关把还原后的内层包转发到内网目标服务器。服务器回复的包走同样的流程反向回去。
这个过程的精髓在于:公网只应该看到外层封装,VPN 端点才应该看到内层明文。这就是为什么本课的实验不止是"配一个 VPN 看看能不能连上",而是要用 tcpdump 同时在外层接口和内层接口抓包——让你亲眼验证这个"外层只有密文、内层才有明文"的事实。
四、 IPSec:三层安全怎么做到的
IPSec 是 IETF 制定的一套 IP 层安全协议族。它工作的位置很有意思——不依赖具体应用,而是在 IP 层对包做保护。这意味着任何基于 IP 的应用(HTTP、FTP、SMTP、DNS……),都可以被 IPSec 保护,不需要修改应用程序本身。
IPSec 的核心组件有四个:
AH(Authentication Header,认证头) 只做认证,不加密。它在原始 IP 包后面加一个头,里面放着对整个包的校验值。接收方一算,如果校验值对不上,就知道包被改了。但 AH 不加密载荷,所以明文照样是明文——公网上的监听者能看到内容,只是改不了。
ESP(Encapsulating Security Payload,封装安全载荷) 既加密又认证。这是实际部署中最常用的 IPsec 协议。ESP 把原始载荷加密了,还附带认证信息。公网上的监听者只能看到加密字节,看不到载荷内容,也改不了。你在真实的企业 VPN 中看到的 IPsec 部署,几乎都是 ESP。
SA(Security Association,安全关联)SA 描述的是"单向通信的安全参数"——比如 A 发给 B 的数据用什么算法加密、什么算法认证、密钥是什么、密钥有效期多久。注意"单向"——A 到 B 是一个 SA,B 到 A 是另一个 SA,双向通信至少需要两个 SA。SA 就像一份合同,规定了这条通道上的所有安全规则。
IKE(Internet Key Exchange,互联网密钥交换) 负责自动帮通信双方协商 SA——包括互相认证身份(用证书或预共享密钥)、协商使用什么加密算法、生成会话密钥。前面学 Diffie-Hellman 的时候你知道了密钥协商的概念,IKE 就是把这件事自动化、标准化了的协议。
为什么 OpenVPN 外层走 UDP 而不是 TCP?
你可能会想,TCP 不是更可靠吗?为什么 VPN 偏要用"不可靠"的 UDP?
答案叫 TCP-over-TCP 问题。如果 VPN 外层走 TCP,内层应用通常也是 TCP(HTTP、SSH 等),那就形成了 TCP 套 TCP。两个 TCP 层各自有独立的重传计时器和拥塞控制算法。外层的 TCP 发现丢包会重传,内层 TCP 也发现丢包也重传,两套机制互相干扰,产生"重传风暴"——性能急剧下降。
用 UDP 做外层就没有这个问题:UDP 不重传,丢了就丢了,重传交给内层 TCP 自己去管。这就是为什么 OpenVPN 和几乎所有现代 VPN 协议的默认外层都是 UDP。
考考你:AH 和 ESP 到底差在哪
下面两种场景,分别应该用 AH 还是 ESP?
场景 1: 公司要求分支间通信"不能被篡改,但内容不是机密"。用 ESP,因为 AH 虽然能做认证,但 ESP 也做认证且还能加密——没有理由退回到只认证不加密。现实中几乎看不到纯 AH 部署。
场景 2: 抓包时如果 VPN 只用了 AH 没有用 ESP,公网监听者能看到什么?能看到完整的载荷明文。AH 只在包头后面附加了校验值,载荷本身没有加密,HTTP 请求、文件内容全部可见。
传输模式 vs 隧道模式:到底要不要换头
IPSec 可以工作在两种模式下,区别在于"保护到什么程度":
传输模式:保护 IP 包的载荷部分,但保留原始 IP 头。适用于两台主机直接通信的场景。好处是额外开销小,缺点是原始 IP 地址暴露在公网上。
隧道模式:把整个原始 IP 包(包括原始 IP 头)都加密,然后加上一个新的 IP 头。适用于网关到网关的场景,也就是站点到站点 VPN。
为什么企业分支互联一定要用隧道模式?因为公网不需要知道内网主机的 IP 地址。北京总部有一个 192.168.1.50 的文件服务器,上海分支有一台 172.16.3.20 的开发机——这些私有 IP 放在公网上是没有意义且危险的。隧道模式把它们整个包进一个外层包里,公网上只看到北京和上海两个 VPN 网关的公网地址在传加密数据,内网拓扑对公网完全不可见。
一张图看清两种模式"包"的区别:
五、 动手建一个微型企业 VPN
讲完了原理,现在我们来动手。目标是用你现在的这台机器,模拟出下面这个拓扑:
lesson14-hq lesson14-branch
172.31.14.1/30 <--- veth ---> 172.31.14.2/30
OpenVPN Server OpenVPN Client
10.88.0.1/24 <--- tun0 ---> 10.88.0.2/24veth-hq 和 veth-branch 是一对虚拟网线,模拟的是"公网链路"。tun0 是 OpenVPN 创建的三层虚拟网卡,模拟的是"隧道内网"。等一下我们会分别在 veth-hq(模拟公网)和 tun0(模拟内网)上抓包,对比能看到什么。
第一步:环境检查
# 查看 OpenVPN 版本——确保已安装且版本足够新(本课需要 2.6+)
openvpn --version | head -1
# 逐一检查每个必需工具是否存在,command -v 返回路径说明已安装
for c in openvpn openssl tcpdump ip python3 curl; do
printf '%-8s ' "$c"
command -v "$c"
done
# 检查 easy-rsa 目录是否存在(Kali 默认预装在 /usr/share/easy-rsa)
test -d /usr/share/easy-rsa && echo 'easy-rsa /usr/share/easy-rsa'
# 检查 /dev/net/tun 设备文件——tun 驱动是否加载,没有它 OpenVPN 无法创建 tun0
ls -l /dev/net/tun真实输出:
OpenVPN 2.7.1 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] [DCO]
openvpn /usr/sbin/openvpn
openssl /usr/bin/openssl
tcpdump /usr/bin/tcpdump
ip /usr/sbin/ip
python3 /usr/bin/python3
curl /usr/bin/curl
easy-rsa /usr/share/easy-rsa
crw-rw-rw- 1 root root 10, 200 5月18日 10:50 /dev/net/tun检查清单里每一项对你的意义:
openvpn:本课的主角,负责建隧道。easy-rsa:帮你生成 CA 和证书。还记得上一课 PKI 实验里你手动签发的那张server.crt吗?easy-rsa 就是把这个过程自动化了。tcpdump:你的"透视镜",让你能看到链路上到底跑了什么数据。ip netns:你的"模拟器",让你在一台机器上造出两个网络隔离的虚拟主机。/dev/net/tun:内核模块,创建tun0虚拟网卡必需的。如果这个文件不存在,后面所有和隧道相关的操作都会失败。
如果缺工具,在 Kali 里补:
# 如果前面环境检查发现缺工具,用这两条命令补装(Kali 下通常不需要)
sudo apt update
sudo apt install -y openvpn easy-rsa tcpdump curl python3 iproute2第二步:创建"公网链路"
# 定义实验目录和 namespace 名称(变量方便后面复用)
LAB=/tmp/lesson14-vpn-lab
HQ=lesson14-hq # 总部命名空间名称
BR=lesson14-branch # 分支命名空间名称
# 清理旧实验残留,确保每次从干净状态开始
rm -rf "$LAB"
mkdir -p "$LAB"
ip netns del "$HQ" 2>/dev/null || true # 删除旧 namespace(不存在也不报错)
ip netns del "$BR" 2>/dev/null || true
# 创建两个独立的网络命名空间——像两台隔离的虚拟主机
ip netns add "$HQ"
ip netns add "$BR"
# 创建一对虚拟网线(veth pair):一头叫 veth-hq,另一头叫 veth-branch
# 数据从 veth-hq 进,必从 veth-branch 出——就像一根真实的网线
ip link add veth-hq type veth peer name veth-branch
# 把虚拟网线的两头分别放进两个命名空间
ip link set veth-hq netns "$HQ"
ip link set veth-branch netns "$BR"
# 给两个接口配公网模拟地址(/30 网段只有 2 个可用 IP,刚好一对)
ip -n "$HQ" addr add 172.31.14.1/30 dev veth-hq
ip -n "$BR" addr add 172.31.14.2/30 dev veth-branch
# 启用回环接口和虚拟网线接口
ip -n "$HQ" link set lo up
ip -n "$BR" link set lo up
ip -n "$HQ" link set veth-hq up
ip -n "$BR" link set veth-branch up
# 验证:分支 ping 总部——通了说明模拟公网链路正常
ip netns exec "$BR" ping -c 1 -W 1 172.31.14.1真实输出:
PING 172.31.14.1 (172.31.14.1) 56(84) bytes of data.
64 bytes from 172.31.14.1: icmp_seq=1 ttl=64 time=0.333 ms
--- 172.31.14.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.333/0.333/0.333/0.000 ms这几条命令完成了什么?你制造了两个网络命名空间 lesson14-hq 和 lesson14-branch,它们各自有独立的路由表、网络接口和 IP 地址,互相隔离得像两台不同的机器。然后用 veth 创建了一根虚拟网线,一头插在 hq 上,一头插在 branch 上。给两头各配了一个 /30 网段的公网模拟地址。
最后的 ping 通了,只证明一件事:这条模拟的公网链路是通的。此刻还没有任何加密、没有任何隧道——如果现在有人在这根链路上抓包,看到的是完全的明文。
第三步:生成证书
上一课你手动生成了 CA 和证书。这一课我们用 easy-rsa 把这个流程自动化,本质上做的是同一件事:先建 CA,再用 CA 签发服务器和客户端证书。VPN 用证书来完成双向身份认证——服务器要向客户端证明"我确实是公司 VPN 网关",客户端要向服务器证明"我确实是被允许接入的员工设备"。
# 进入实验目录,复制 easy-rsa 工具(证书管理脚本集)
cd "$LAB"
cp -r /usr/share/easy-rsa/* .
# 初始化 PKI 目录结构(pki/private、pki/issued 等子目录)
./easyrsa init-pki
# 构建自签名 CA(EASYRSA_BATCH=1 跳过交互,nopass 私钥不加密码仅实验用)
EASYRSA_BATCH=1 EASYRSA_REQ_CN='BGY Lesson14 CA' ./easyrsa build-ca nopass
# 用 CA 签发服务器证书和客户端证书
EASYRSA_BATCH=1 ./easyrsa build-server-full server nopass
EASYRSA_BATCH=1 ./easyrsa build-client-full client1 nopass
# 生成 TLS 认证密钥(给控制信道额外加一层 HMAC 保护)
openvpn --genkey tls-auth ta.key
# 列出已生成的证书和私钥文件及其大小
find pki/issued pki/private -maxdepth 1 -type f -printf '%P %s bytes\n' | sort
# 查看服务器证书详情:持有者、签发者、有效期
openssl x509 -in pki/issued/server.crt -noout -subject -issuer -startdate -enddate真实输出:
ca.key 1704 bytes
client1.crt 4515 bytes
client1.key 1704 bytes
server.crt 4532 bytes
server.key 1704 bytes
subject=CN=server
issuer=CN=BGY Lesson14 CA
notBefore=May 18 08:39:06 2026 GMT
notAfter=Aug 20 08:39:06 2028 GMT输出告诉我们:CA 签发了 server.crt(给 VPN 网关用)和 client1.crt(给接入的客户端用)。两张证书的 issuer 都指向同一个 CA。等一下 VPN 建立连接的时候,双方各出示自己的证书,对端用 CA 公钥验证——只要验证通过,身份就是可信的。
私钥安全
ca.key 是整条信任链的根。真实生产环境中,CA 私钥应该保存在离线设备上,加口令保护,物理隔离。课堂用 nopass 只是为了让实验脚本能自动跑完——不要把这个习惯带到工作中。
第四步:写入 OpenVPN 配置,启动隧道
cat > "$LAB/server.conf" <<EOF
local 172.31.14.1
port 11940
proto udp4
dev tun0
disable-dco
ca $LAB/pki/ca.crt
cert $LAB/pki/issued/server.crt
key $LAB/pki/private/server.key
dh none
tls-auth $LAB/ta.key 0
server 10.88.0.0 255.255.255.0
topology subnet
keepalive 5 30
persist-tun
data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305
auth SHA256
status $LAB/server-status.log
log $LAB/server.log
verb 3
EOF
cat > "$LAB/client.conf" <<EOF
client
dev tun0
proto udp4
remote 172.31.14.1 11940
nobind
persist-tun
ca $LAB/pki/ca.crt
cert $LAB/pki/issued/client1.crt
key $LAB/pki/private/client1.key
tls-auth $LAB/ta.key 1
remote-cert-tls server
data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305
auth SHA256
log $LAB/client.log
verb 3
EOF不要被这些配置项的数量吓到。你只要抓住 5 个关键点,其他都是默认值:
ca/cert/key:三件套,分别指向 CA 证书、自己的证书、自己的私钥。这是双向认证的基础——谁都不能少。tls-auth:给 TLS 控制信道额外加一层 HMAC 保护。在正式 TLS 握手之前,先验一次 HMAC——无效的连接直接丢弃。作用相当于在你家大门外再加一道门禁,连敲门的资格都得先过一遍。data-ciphers:声明数据通道可以使用的加密套件。AES-256-GCM排在第一位,意思就是"优先用它"。GCM 模式同时加密和认证,就是前面说的 AEAD。为什么不能用 CBC?因为 CBC 只加密不认证,攻击者可以通过篡改密文来探测系统行为。proto udp4:外层隧道包走 UDP。为什么不用 TCP?你想想,如果外层走 TCP,内层的应用也是 TCP,那就成了 TCP-over-TCP——两个独立的重传机制会互相打架,性能极差。dh none:OpenVPN 2.7 已经支持 ECDH 密钥协商,不需要静态 DH 参数文件。老版本可能需要加一行dh pki/dh.pem。
现在启动:
# 在总部命名空间启动 OpenVPN 服务端,后台运行(--daemon),PID 写入文件方便后续关闭
ip netns exec "$HQ" openvpn --config "$LAB/server.conf" \
--daemon lesson14-server --writepid "$LAB/server.pid"
# 在分支命名空间启动 OpenVPN 客户端
ip netns exec "$BR" openvpn --config "$LAB/client.conf" \
--daemon lesson14-client --writepid "$LAB/client.pid"
# 等 5 秒让 TLS 握手完成、隧道建立(证书交换 + 密钥协商需要时间)
sleep 5
# 查看四张网卡:公网接口(veth)和内层隧道接口(tun0)各两个
ip -n "$HQ" -br addr show veth-hq # 总部公网:172.31.14.1
ip -n "$HQ" -br addr show tun0 # 总部隧道内:10.88.0.1
ip -n "$BR" -br addr show veth-branch # 分支公网:172.31.14.2
ip -n "$BR" -br addr show tun0 # 分支隧道内:10.88.0.2
# 终极验证:从分支 ping 总部的隧道内网地址——通了说明隧道已正常工作
ip netns exec "$BR" ping -c 2 -W 2 10.88.0.1
# 从日志里提取关键信息:控制信道加密套件、数据信道加密套件
grep -hE 'Data Channel|Control Channel|Initialization Sequence Completed' \
"$LAB/server.log" "$LAB/client.log" | tail -12真实输出:
veth-hq@if23 UP 172.31.14.1/30 fe80::c0dd:a1ff:fea2:f631/64
tun0 UNKNOWN 10.88.0.1/24 fe80::b2ca:2a2a:6045:aaf7/64
veth-branch@if24 UP 172.31.14.2/30 fe80::d464:8eff:fe48:3c3b/64
tun0 UNKNOWN 10.88.0.2/24 fe80::ea92:d4f6:9ea0:78a9/64
PING 10.88.0.1 (10.88.0.1) 56(84) bytes of data.
64 bytes from 10.88.0.1: icmp_seq=1 ttl=64 time=0.724 ms
64 bytes from 10.88.0.1: icmp_seq=2 ttl=64 time=0.300 ms
--- 10.88.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1019ms
rtt min/avg/max/mdev = 0.300/0.512/0.724/0.212 ms
2026-05-18 16:39:23 Initialization Sequence Completed
2026-05-18 16:39:25 udp4:172.31.14.2:43929 Control Channel: TLSv1.3, cipher TLSv1.3 TLS_AES_256_GCM_SHA384, peer certificate: 2048 bits RSA, signature: RSA-SHA256, peer temporary key: 768 bits X25519MLKEM768, peer signing digest/type: rsa_pss_rsae_sha256 RSASSA-PSS, key agreement: X25519MLKEM768
2026-05-18 16:39:26 client1/udp4:172.31.14.2:43929 Data Channel: cipher 'AES-256-GCM', peer-id: 0
2026-05-18 16:39:25 Control Channel: TLSv1.3, cipher TLSv1.3 TLS_AES_256_GCM_SHA384, peer certificate: 2048 bits RSA, signature: RSA-SHA256, peer signing digest/type: rsa_pss_rsae_sha256 RSASSA-PSS, key agreement: X25519MLKEM768
2026-05-18 16:39:25 Initialization Sequence Completed
2026-05-18 16:39:25 Data Channel: cipher 'AES-256-GCM', peer-id: 0我们现在来逐行剖析这个输出。这里包含的信息量,是你理解 VPN 最关键的一步:
ip addr 显示了四张网卡。veth-hq 上有 172.31.14.1——这是模拟的公网地址。tun0 上有 10.88.0.1——这是 OpenVPN 创建出来的内网地址。另一边分支也类似。也就是说,现在你的"总部"和"分支"各有两种身份:在公网上它们是 172.31.14.x,在隧道内网里它们是 10.88.0.x。
ping 10.88.0.1 通了。注意这个 ping 走的是 tun0 隧道接口,不是公网的 veth 接口。分支的 10.88.0.2 发出的 ICMP 包,被 OpenVPN 加密、封装、通过 172.31.14.2 -> 172.31.14.1 的公网链路发过去,对端 OpenVPN 解密、解封装,然后把内层 ICMP 包交给了总部的 tun0。ping 收到了回复——整个流程成功完成。
日志里的 Control Channel: TLSv1.3, cipher TLSv1.3 TLS_AES_256_GCM_SHA384 告诉你控制信道是用 TLS 1.3 建立的,证书做了双向验证。Data Channel: cipher 'AES-256-GCM' 告诉你数据信道协商下来用了 AES-256-GCM——这是我们前面反复强调的同时加密和认证的 AEAD 模式。
六、抓包验证:这条隧道到底"藏"住了什么
理论说了这么多,我们现在用 tcpdump 拿证据。
对照组:不走 VPN,在公网上直接传 HTTP
我们先不经过 VPN,直接用公网地址访问一个 HTTP 服务,然后在公网链路上抓包。
# 准备一个包含"敏感"业务数据的网页文件(模拟企业内网的真实页面)
echo 'intranet payroll=classified' > "$LAB/index.html"
# 在总部启动 HTTP 服务器,绑定公网模拟地址 172.31.14.1(不走 VPN!)
# 后台运行,PID 写入文件方便后续 kill
ip netns exec "$HQ" python3 -m http.server 8888 \
--bind 172.31.14.1 --directory "$LAB" > "$LAB/http-public.log" 2>&1 &
echo $! > "$LAB/http-public.pid"
# 在公网接口 veth-hq 上抓包——假设自己是"公网监听者"
# -A:以 ASCII 显示包内容 -s 0:抓完整包不截断 tcp port 8888:只抓 HTTP 流量
ip netns exec "$HQ" timeout 5 tcpdump -i veth-hq -A -s 0 tcp port 8888 \
> "$LAB/plain-http.txt" 2>/dev/null &
sleep 1
# 从分支用公网地址直接访问 HTTP 服务(模拟"不走 VPN"的情况)
ip netns exec "$BR" curl --noproxy '*' --max-time 5 -s http://172.31.14.1:8888/
sleep 1
# 在抓包输出中搜索敏感字符串——看看公网上能看到什么
grep -aoE 'GET / HTTP/1.1|intranet payroll=classified' "$LAB/plain-http.txt" | sort -u真实输出:
intranet payroll=classified
GET / HTTP/1.1
intranet payroll=classified看到了吗?在 veth-hq(模拟的公网链路)上抓包,你能清楚地看到 HTTP 请求行 GET / HTTP/1.1 和响应里的业务内容 intranet payroll=classified。这就是不走 VPN 的后果——任何一个可以在这条链路上监听的节点,都能看到你的业务数据。
为什么 curl 要加 --noproxy '*'
当前 Kali 环境可能从系统继承了一些代理变量,但 namespace 内并没有代理服务。--noproxy '*' 强制 curl 直连实验地址,避免因为代理连不上而误判成 VPN 不通。
实验组:通过 OpenVPN 隧道访问,同样的位置再次抓包
这次我们把 HTTP 服务绑在隧道内网地址 10.88.0.1 上。然后在两个位置同时抓包:公网接口 veth-hq 和内层隧道接口 tun0。
# 先停掉刚才的明文 HTTP 服务(那个绑定公网地址的)
kill "$(cat "$LAB/http-public.pid")" 2>/dev/null || true
# 重新启动 HTTP 服务,但这次绑定在隧道内网地址 10.88.0.1——必须走 VPN 才能访问
ip netns exec "$HQ" python3 -m http.server 8888 \
--bind 10.88.0.1 --directory "$LAB" > "$LAB/http-vpn.log" 2>&1 &
echo $! > "$LAB/http-vpn.pid"
# 抓包位置 1:公网接口 veth-hq——只抓 OpenVPN 流量(udp port 11940)
# -w 写入 pcap 文件供后续分析,-n 不解析主机名
ip netns exec "$HQ" timeout 5 tcpdump -i veth-hq -n -s 0 \
-w "$LAB/outer-udp11940.pcap" udp port 11940 >/dev/null 2>&1 &
# 抓包位置 2:内层隧道接口 tun0——抓 HTTP 流量(只有解密后才看得到)
ip netns exec "$HQ" timeout 5 tcpdump -i tun0 -A -s 0 tcp port 8888 \
> "$LAB/inner-http.txt" 2>/dev/null &
sleep 1
# 这一次 curl 访问隧道内网地址 10.88.0.1——流量会经过加密隧道
ip netns exec "$BR" curl --noproxy '*' --max-time 5 -s http://10.88.0.1:8888/
sleep 1
# 分析外层抓包:tcpdump 读 pcap 文件,只看到 UDP + 公网地址,看不到 HTTP
tcpdump -nn -r "$LAB/outer-udp11940.pcap" 2>/dev/null | head -5
# 在外层 pcap 中搜索 HTTP 明文——搜不到!因为已被 AES-GCM 加密
strings "$LAB/outer-udp11940.pcap" | grep -E 'GET /|intranet payroll' \
|| echo 'no HTTP plaintext in outer capture'
# 在内层 tun0 抓包中搜索——能搜到!因为隧道端点已解密还原
grep -aoE 'GET / HTTP/1.1|intranet payroll=classified' "$LAB/inner-http.txt" | sort -u真实输出:
intranet payroll=classified
16:39:55.280275 IP 172.31.14.2.43929 > 172.31.14.1.11940: UDP, length 88
16:39:55.280516 IP 172.31.14.1.11940 > 172.31.14.2.43929: UDP, length 88
16:39:55.280774 IP 172.31.14.2.43929 > 172.31.14.1.11940: UDP, length 80
16:39:55.281211 IP 172.31.14.2.43929 > 172.31.14.1.11940: UDP, length 158
16:39:55.281383 IP 172.31.14.1.11940 > 172.31.14.2.43929: UDP, length 80
no HTTP plaintext in outer capture
GET / HTTP/1.1
intranet payroll=classified这个对比实验的价值,比你配十遍 OpenVPN 都大。 仔细看这三组证据:
在公网接口 veth-hq 上:
- tcpdump 只能看到 UDP 包,源和目的地址是
172.31.14.x(两个 VPN 端点),目的端口是11940(OpenVPN 的服务端口)。 - 用
strings在抓包文件里搜索GET /和intranet payroll——搜不到。因为这些字符串已经被 AES-256-GCM 加密了,在公网链路上就是一段不可读的字节流。
在内层接口 tun0 上:
- tcpdump 能清楚地看到
GET / HTTP/1.1和intranet payroll=classified。因为数据在到达tun0之前已经被 VPN 网关解密封装,还原成了原始的 HTTP 流量。
这就是"隧道"的最直观证据:同一次 HTTP 访问,在外层公网链路上完全是加密的 UDP 包,在内层隧道接口上才还原成明文 HTTP。你不再需要"相信"文档上说 VPN 加密了流量——你的 tcpdump 输出就是你的目击证人。
七、学完这一课,你应该怎么跟别人讲 VPN
如果有人问你"VPN 是什么",你不应该再说"翻墙的"。你可以这样说:
"VPN 是在不安全的公共网络上,用加密和认证技术构建的一条安全通道。我搭过一遍真实的 OpenVPN,用两个 network namespace 模拟了总部和分支,抓包验证过——在模拟公网的接口上,tcpdump 只能看到加密的 UDP 包,完全搜不到 HTTP 明文;只有在内层 tun0 接口上才能还原出原始流量。这一套东西的底层,就是上一课学的证书认证、AES 加密和完整性校验的组合。"
能说出这段话,说明你对 VPN 的理解已经超过 90% 的"用过 VPN"的人了。
企业 VPN 的加固清单
真实企业的 VPN 不会像我们实验里这么简单。下面这个清单,是你以后做安全评审时可以逐条对照的:
- 证书管理:有人离职或者设备丢了,证书必须能立即吊销。CRL(证书吊销列表)和 OCSP(在线证书状态协议)就是干这个的。如果你们的 VPN 方案没有吊销机制,等于离职员工手里的证书可以永远用下去。
- 多因素认证:光有证书还不够。证书文件可以被复制。叠加短信验证码、App 推送或者硬件 Token,证书被偷了也得有第二因素才能接入。
- 最小路由:不要让连上 VPN 就等于进了整个内网。只推送员工工作必须访问的网段路由。一个销售部的员工不需要访问财务部的服务器。
- 加密套件不要图省事:AES-256-GCM、ChaCha20-Poly1305 这些 AEAD 套件同时保护机密性和完整性。老旧的 CBC 模式只有加密没有认证,已经不适合新系统。
- 日志保存:谁、什么时间、从哪个 IP 接入、连了多久、访问了什么——这些日志是事后溯源和合规审计的基础。不记日志的 VPN 等于没有黑匣子的飞机。
- 终端安全:接入 VPN 不等于信任终端。检查补丁级别、杀毒软件运行状态、系统防火墙是否开启——这些可以在连接建立之前就检查,不合格的终端直接拒绝接入。
- 资质合规:在国内部署商用密码产品,需要关注是否具备商用密码产品认证资质。这不是技术问题,但项目验收时会被质询。
八、新一代 VPN:WireGuard 与 P2P 组网
OpenVPN 和 IPSec 已经服役了二十多年,成熟稳定,但配置复杂、连接建立慢也是公认的。近几年出现了一些全新的 VPN 方案,思路和传统 VPN 有根本性的不同。
WireGuard:把 VPN 做进内核
WireGuard 是 2018 年发布、2020 年并入 Linux 内核的新一代 VPN 协议。它的代码量只有约 4000 行(OpenVPN 约 10 万行,IPSec 更是天文数字),极简的设计带来了几个实打实的好处:
配置极简。 WireGuard 不需要 CA、不需要证书链、不需要协商加密套件。你只需要给每个节点生成一对密钥,然后把对端的公钥和允许的 IP 段写进配置。下面是一个完整的站点到站点配置:
# 总部 WireGuard 配置 /etc/wireguard/wg0.conf
[Interface]
PrivateKey = <总部私钥>
Address = 10.99.0.1/24 # 隧道内网地址
ListenPort = 51820
[Peer]
PublicKey = <分支公钥>
AllowedIPs = 10.99.0.2/32, 10.88.0.0/24 # 允许该对端使用的 IP就这么多。没有 easy-rsa、没有证书有效期、没有 DH 参数文件。私钥就是一切,丢了私钥就等于丢了身份。
加密套件不协商。 传统 VPN 的 TLS 握手阶段要协商"用哪个加密套件",协商过程本身就是攻击面(TLS 降级攻击)。WireGuard 直接把算法写死:Curve25519 做密钥交换、ChaCha20-Poly1305 做 AEAD 加密、BLAKE2s 做哈希。不协商,就没有降级空间。
漫游无缝切换。 你用笔记本从 WiFi 切到手机热点,IP 地址变了,OpenVPN 通常会断连重拨。WireGuard 是无状态设计——只要有一端的数据包到达对端,对端验签通过就直接处理,不管源 IP 有没有变。所以网络切换几乎无感知。
性能远超用户态 VPN。 WireGuard 运行在内核态,数据包不需要在内核和用户态之间来回拷贝。实测吞吐量通常是 OpenVPN 的 3 到 5 倍。
缺点也很明显:它不帮你管密钥分发,你自己得想办法把公钥安全地发给对端(这个"想办法"就是 Tailscale 等产品补齐的环节);它也没有内置的证书吊销机制,换密钥靠手动改配置。
Tailscale 和 ZeroTier:P2P 网状组网
如果说 WireGuard 解决了"怎么加密"的问题,那 Tailscale 解决的就是"怎么让全世界任意两台设备直接连通"的问题。
Tailscale 的核心思路: 基于 WireGuard,但在上面加了一层控制平面。每台设备装一个 Tailscale 客户端,用你的 Google/Microsoft/GitHub 账号登录,控制平面自动帮你做三件事:
- 密钥交换——自动生成 WireGuard 密钥对,公钥上传到 Tailscale 协调服务器。
- NAT 穿透——用 STUN/ICE 等协议自动打洞,让两台都在 NAT 后面的设备直接建立 P2P 连接。
- ACL 管理——你用 Web 界面写"谁可以访问谁",Tailscale 自动下发路由规则。
打洞成功的情况下,数据流量是端到端加密、P2P 直连的,不经过 Tailscale 的服务器。Tailscale 的服务器只负责"介绍你们认识",认识之后它就从数据路径上消失了。
ZeroTier 的思路类似,但它不用 WireGuard,而是自研了一套协议。它也支持 P2P 直连和 NAT 穿透,而且对虚拟二层网络(Ethernet 级别)的支持比 Tailscale 更强——你可以把分布在全球的多台设备放进同一个虚拟局域网,甚至跑 DHCP。
这两种方案和传统 VPN 最大的区别是:传统 VPN 是 Hub-Spoke(中心辐射)模型——所有流量先到 VPN 网关,再由网关转发;P2P 组网是 Mesh 模型——任意两个节点只要策略允许就可以直接通信,不经过中心节点。
选型对比
| 维度 | OpenVPN/IPSec | WireGuard | Tailscale/ZeroTier |
|---|---|---|---|
| 部署复杂度 | 高(CA、证书、配置) | 低(密钥对 + 几行配置) | 极低(装客户端、登录) |
| 密钥管理 | PKI 体系(证书+吊销) | 手动交换公钥 | 自动(控制平面代管) |
| 性能 | 中(用户态) | 高(内核态) | 高(底层是 WireGuard) |
| NAT 穿透 | 困难 | 需额外工具 | 内置 |
| 拓扑模型 | Hub-Spoke | 点到点 | P2P Mesh |
| 适合场景 | 传统企业站点互联 | 轻量级站点互联、个人使用 | 分布式团队、远程办公、个人网络 |
九、VPN 和"翻墙"不是一回事:技术区别与法律边界
很多人接触 VPN 的第一个场景就是"翻墙",以至于潜意识里把 VPN 和翻墙画了等号。这两个概念在技术上有交集,但在目的、原理和法律性质上完全不同。作为学安全的学生,你必须有清晰的分辨能力。
技术层面:三组关键词看清差异
第一组:加密通道 vs 流量混淆
VPN 的核心目的是在企业网络和公共网络之间建立一条加密通道。它的设计目标不是"让审查系统看不见",而是"让公网上的其他人都看不懂"。所以 VPN 流量的特征非常明显——一个固定的 UDP 端口、固定的 TLS 握手指纹、明文发送的证书信息。任何有 DPI(深度包检测)能力的网络设备都能轻易识别出"这是一条 VPN 连接"。
翻墙工具的核心目的是绕过网络审查,所以它们的首要设计目标是"让审查系统分不出你和正常流量"。代表工具包括:
- Shadowsocks:把流量伪装成普通的 SOCKS5 代理流量,不是 VPN。它只代理单个 TCP 连接,不创建虚拟网卡,不改路由表。一个浏览器通过 Shadowsocks 访问外网,其他应用不受影响。
- V2Ray / Xray:支持多种传输协议(WebSocket、gRPC、HTTP/2、KCP 等),可以伪装成 HTTPS 网站流量。配合 CDN(如 Cloudflare),审查方看到的就像是你在访问一个正常的网站。
- Trojan:把流量伪装成标准 HTTPS,连 TLS 握手的 SNI 字段都填成真实的正常域名(比如
www.microsoft.com)。审查系统在流量层面根本分不出 Trojan 和真实浏览器访问。
第二组:全局代理 vs 分流代理
VPN 客户端的默认行为是接管系统的路由表,所有流量——浏览器、命令行、邮件客户端、系统更新——全部走隧道。这叫"全局代理"。
翻墙工具通常用 PAC 文件或路由规则做分流:国内 IP 直连,国外 IP 走代理。甚至更精细——只代理特定域名的 TCP 连接(比如浏览器访问 GitHub 走代理,但同一个浏览器访问百度直连)。这么设计有两个原因:一是节省海外服务器的带宽,二是降低延迟——访问国内网站何必绕地球一圈。
第三组:你是谁 vs 你看起来像谁
VPN 解决的是"你是谁"——通过证书和认证证明你的合法身份。翻墙工具解决的是"你看起来像谁"——通过协议伪装让你看起来像一个正常的网站访问者。前者的安全模型建立在"双方互信"上,后者的安全模型建立在"混淆+隐匿"上。
法律层面:在中国境内,什么能做、什么不能做
合法用途(企业/机构):
《中华人民共和国密码法》和《网络安全法》并不禁止企业使用 VPN 技术进行合法的远程办公和分支机构互联。前提是:
- VPN 连接的两端都在企业自己的网络边界内(总部和合法分支)。
- 使用的是经过商用密码产品认证的设备或软件。
- VPN 的目的确实是"企业内网的安全延伸",而不是"提供跨境互联网接入服务"。
实际上,每一家在中国有业务的外企、每一家有多地办公室的国内企业,都在使用 IPSec VPN 或 SSL VPN 连接自己的分支机构——这是正常、合法的商业行为。
灰色地带(个人自建跨境 VPN):
个人租用海外 VPS、自建 VPN 或 Shadowsocks 服务、用于访问境外网站——这种行为从技术上讲属于"未经许可自行建立跨境信道"。《计算机信息网络国际联网管理暂行规定》明确规定,计算机信息网络直接进行国际联网必须使用国家公用电信网提供的国际出入口信道,任何单位和个人不得自行建立或使用其他信道进行国际联网。
实际操作中,执法重点通常放在"经营"和"提供"上——即租用境内服务器向他人出售 VPN 接入服务牟利的。个人自用、不盈利、不公开提供的,虽然理论上也不合规,但实际执法优先级很低。但不能因此认为"自用就没事"——如果你的服务器被用来传播违法信息,作为租用者你承担连带责任。
明确违法(未经许可经营 VPN 业务):
2017 年起,工信部要求未经批准不得经营 VPN 业务。简单说:你不能在境内租服务器、搭 VPN、收钱让别人用。这是明确的违法行为,处罚措施包括罚款、停业整顿、吊销许可证,情节严重可追究刑事责任。
课堂红线:
本课所有实验都是在本机模拟的——namespace 之间通信不经过任何真实网络设备,不涉及跨境连接。你在实验里建的 OpenVPN 隧道,只存在于你自己电脑的内存里。不要试图把课堂配置连接到境外 VPS 上测试,这会把一个合法的学习行为变成违规操作。
你应该怎么跟别人讲"VPN 和翻墙的区别"
如果以后有人跟你说"VPN 就是用来翻墙的",你不需要长篇大论反驳。一句话就够了:
"VPN 是一种企业网络安全技术,用来在公网上搭建加密通道——和公司用的 IPSec、WireGuard 是一回事。翻墙是另一套工具,用的是流量伪装和代理,目标不一样,技术路线也不一样。"
如果你想把这句话展开成面试级别的回答,再加上这一段:
"技术上看,VPN 通过隧道接口和路由表做全局加密代理,流量特征非常明显,能被 DPI 轻松识别。翻墙工具如 Shadowsocks、V2Ray 走的是协议伪装路线,目标就是让审查系统分不出来。法律上看,企业用 VPN 连自己的分支机构完全合法,但未经许可在境内经营 VPN 服务或提供跨境接入是违法的。作为安全从业者,我们必须能区分这两种场景。"
本课复盘
学完这一课,你应该能回答这些问题:
- VPN 为什么不是简单"换 IP"?因为它同时解决了认证、加密、完整性和路由四个问题。
- Access VPN、Intranet VPN、Extranet VPN 的区别在哪?信任边界不同,所以访问控制策略也不同。
- AH 和 ESP 的本质区别是什么?AH 只认证不加密,ESP 既认证又加密。所以真实 VPN 实用的是 ESP。
- OpenVPN 配置里
ca、cert、key、data-ciphers分别起什么作用? - 为什么在
veth-hq上抓包看不到 HTTP 明文,而在tun0上能看到?因为veth-hq上的数据经过了 AES-GCM 加密,只有tun0上的数据已经被解密还原。
课后任务
以下任务全部截图提交,每张截图需要能看到你输入的命令和终端输出结果。
- 截图 namespace 创建和 ping 验证:运行创建 namespace 和 veth 的命令,截一张包含
ping通172.31.14.1的图。 - 截图证书生成:运行 easy-rsa 生成 CA、服务器和客户端证书,截一张包含
openssl x509查看证书 subject/issuer 的图。 - 截图隧道建立和 tun0 验证:启动 OpenVPN 服务端和客户端,截一张包含
tun0地址和ping 10.88.0.1成功的图。 - 截图对照组抓包(不走 VPN):用 HTTP 明文访问公网地址,截一张包含抓包输出中能搜到
GET /和业务字符串的图。 - 截图实验组抓包(走 VPN):通过隧道访问
10.88.0.1,截两张图——一张外层veth-hq抓包显示no HTTP plaintext,一张内层tun0抓包显示能搜到 HTTP 明文。
实验环境清理
# 定义实验变量(和创建时保持一致)
LAB=/tmp/lesson14-vpn-lab
HQ=lesson14-hq
BR=lesson14-branch
# 杀掉所有后台进程:OpenVPN 服务端、客户端、HTTP 服务
kill "$(cat "$LAB/server.pid" 2>/dev/null)" 2>/dev/null || true
kill "$(cat "$LAB/client.pid" 2>/dev/null)" 2>/dev/null || true
kill "$(cat "$LAB/http-public.pid" 2>/dev/null)" 2>/dev/null || true
kill "$(cat "$LAB/http-vpn.pid" 2>/dev/null)" 2>/dev/null || true
# 清理命名空间内残余进程,然后删除命名空间
ip netns pids "$HQ" 2>/dev/null | xargs -r kill 2>/dev/null
ip netns pids "$BR" 2>/dev/null | xargs -r kill 2>/dev/null
ip netns del "$HQ" 2>/dev/null || true
ip netns del "$BR" 2>/dev/null || true
# 删除实验目录
rm -rf "$LAB"