开了tun模式,npm run dev后会进不了页面

一、正常状态(不开 TUN)—— 走“内部直达地铁”

假设你在浏览器输入 http://127.0.0.1:3000npm run dev 正在监听。

Step 1: 应用层(浏览器发起请求)
浏览器调用操作系统 Socket API,告诉系统:“我要建立一个 TCP 连接到 IP 127.0.0.1,端口 3000”。此时数据还是字节流(HTTP 报文)。

Step 2: 传输层(TCP 封装)
操作系统内核将数据切分成 TCP 段(Segment),加上源端口(随机高位端口,如 52341)和目标端口(3000)。计算出校验和。

Step 3: 网络层(IP 封装)
这是最关键的一步。 内核查看路由表(Routing Table):

  • 目标 IP:127.0.0.1

  • 路由表匹配结果:这是一个 Loopback 地址

  • 内核决定:不经过任何物理网卡(如 eth0/wifi),直接将数据包交给 Loopback 虚拟网卡(lo0)

Step 4: 数据链路层与物理层(省略)
在 Loopback 接口上,数据包不离开计算机,直接从协议栈的“发送队列”复制到“接收队列”。没有电信号,没有网线传输,纯内存拷贝,速度极快(微秒级)。

Step 5: 接收端(npm dev 服务)
操作系统内核发现 Loopback 接口收到发往端口 3000 的数据,查找正在监听的进程(Node.js),将数据从内核缓冲区复制到 Node.js 的用户态内存。

Step 6: 响应
Node.js 处理完返回 HTML,数据包原路逆序返回(也是 Loopback),浏览器渲染页面。

本质:数据包从应用层下去,到网络层立刻掉头,从网络层直接上来。全程停留在内核态和用户态之间,没出过“屋子”。


二、开启 TUN 故障状态 —— 被扔上“过山车”死循环

现在,你在代理软件里开启了 TUN 模式(全局虚拟网卡),并设置了“全局代理(所有流量走 Proxy)”。

Step 1: 应用层(浏览器发起请求)
浏览器:“我要访问 127.0.0.1:3000。” (同正常状态)

Step 2: 传输层(TCP 封装)
同正常状态。

Step 3: 网络层(IP 封装)—— 命运的分岔路
内核再次查看路由表。但是 TUN 模式修改了路由表!

  • 原本路由表:127.0.0.0/8lo0(优先级 100)。

  • TUN 修改后0.0.0.0/0(所有流量)走 utun 虚拟网卡(优先级 50,比 lo0 高)。

因为优先级更高,内核无视了 Loopback 地址的特殊性,强制把数据包从“内部地铁”拽出来,扔给了 TUN 虚拟网卡(如 utun0)。

Step 4: TUN 驱动拦截(用户态代理介入)
数据包到达 utun0 网卡后,不是直接发送,而是被代理软件(如 ***)的用户态进程从内核偷走(读取)

代理软件拿到原始 IP 包后,一看目的地:127.0.0.1:3000。开始匹配规则:

  • 规则列表顶头有没有 127.0.0.0/8 -> DIRECT(没有!)

  • 兜底规则:MATCH, Proxy(所有流量走代理节点)。

Step 5: 封装成新请求(代理协议隧道)
代理软件决定把这包数据发给远在美国的代理服务器(IP 假设为 1.2.3.4:443)。

于是,软件把原始 IP 包当作 Payload(负载),外面包裹一层新 IP 头:

  • 新源 IP:本机物理网卡 IP(如 192.168.1.5

  • 新目标 IP:1.2.3.4(代理服务器)

  • 新目标端口:443

Step 6: 内核再次发送(陷入死循环的起点)
代理软件调用系统 write() 接口,要求将这个新的大包发往 1.2.3.4

数据包再次进入操作系统网络协议栈,到达网络层,再次查询路由表
路由表说:“所有流量(0.0.0.0/0)走 utun0”

于是,这个要去往 1.2.3.4 的数据包,又一次被扔进了 utun0 网卡

Step 7: TUN 驱动再次拦截(二次劫持)
代理软件又从这个 TUN 网卡读到了一个新包。它定睛一看:“咦?这包的目标 IP 是 1.2.3.4,这不就是我的代理服务器吗?我再把它发给代理服务器......”

结果:
数据包 A(原始请求) -> TUN -> 代理软件试图发往 1.2.3.4 -> 生成数据包 B -> TUN -> 代理软件又拿到 B -> 试图发往 1.2.3.4 -> 生成数据包 C -> TUN......


三、最终结局(超时与拒绝)

结局 A:TTL 耗尽(超时)
每经过一次 TUN,IP 头的 TTL(生存时间)减 1。当 TTL 减到 0 时,操作系统丢弃该包,并返回 ICMP Time Exceeded。浏览器等待几秒后,报错:ERR_TIMED_OUT

结局 B:端口耗尽(拒绝)
代理软件在封装新请求时,需要随机分配源端口。死循环以每秒数万次的速度消耗系统端口资源,直到端口耗尽。操作系统拒绝新的连接请求,浏览器报错:ERR_CONNECTION_REFUSED

结局 C:代理软件防护(直接丢弃)
一些高级代理软件(如 Surge/*** Meta)检测到 127.0.0.1 试图发往代理服务器,发现这是一个“本地回环 + 外部代理”的异常组合,出于防死循环保护,直接静默丢弃(Drop)该数据包,不返回任何响应。浏览器一直转圈,直到超时。