在前面讲 IPv4 的时候,我们反复强调过一件事:IPv4 地址只有 32 位,理论最多 43 亿个,去掉保留段、广播段、组播段,真正能分给全球设备的公网地址远比这个数字少。而如今地球上联网的设备早就几十亿上百亿,光靠"每台设备一个公网 IP"这条路,早已走不通了。
可现实是,你的电脑、手机、家里的空调、门口的监控,全都在上网,它们用的是公网地址吗?通常不是。它们躲在路由器后面,用着一串"10.x.x.x""192.168.x.x"的私网地址,只有路由器自己才有一个公网地址。那这些私网设备是怎么和外网通信的?靠的正是今天这篇的主角——NAT。
这篇文章我们按"为什么需要 → 怎么工作 → 有什么坑 → 怎么破解"的顺序,一步步把 NAT、代理服务、内网穿透这三兄弟讲透。为了帮你建立全局印象,我先给一张地图,看看这一整篇我们会路过哪些站:
┌──────────────────────────────────────────────┐
│ 我们今天要解决的问题链 │
│ │
IP 地址不够用 ──► │ ① NAT:私网 ⇄ 公网的地址转换 │
│ ├─ SNAT(出站方向:改源地址) │
│ └─ NAPT(IP+端口,解决多对一) │
│ ② 代理:应用层的"中间人" │
│ ├─ 正向代理(替客户端出门) │
│ └─ 反向代理(替服务器迎客) │
│ ③ 内网穿透:NAT 之外的连接 │
│ └─ 打洞 / 端口映射回连 │
└──────────────────────────────────────────────┘
你应该有的知识准备
这篇文章是 Linux 网络编程系列的进阶篇。往下读之前,希望你至少对下面几个概念有印象,看不懂的可以先回翻前面几篇:
- IPv4 与公网/私网地址:32 位地址空间,公网地址全球唯一,私网地址(
10.0.0.0/8、172.16.0.0/12、192.168.0.0/16)只在局域网内有效。 - TCP/UDP 与端口号:传输层用"IP+端口"唯一定位一个进程。NAPT 一节会大量用到端口。
- IP 地址与 MAC 地址的区别:IP 是逻辑寻址(跨网络用),MAC 是物理寻址(同一链路内用)。
- 路由:一个数据包如何根据目的 IP 一步步跳向目标网络。
如果你有这些底子,读起来会非常顺;哪一条不熟,恰好说明今天的内容正好能帮你补上网络的最后一块拼图。
NAT 技术背景
先明确一个概念:NAT(Network Address Translation,网络地址转换),指的是"把数据包里的 IP 地址在私网地址和公网地址之间搬来搬去"的技术。它现在是解决 IPv4 地址不够用的最主要手段,也是路由器的看家本领之一。
为什么说它"当前是主要手段"?因为彻底解决 IP 不够用的正路其实是 IPv6——128 位地址空间,理论上多到可以给地球上的每一粒沙子编地址。但 IPv6 这些年推进缓慢,存量设备、网络运营商、老系统的兼容压力都太大。所以在 IPv6 真正普及之前,NAT 就是那个"让几十亿私网设备挤在少数公网 IP 后面活着"的过渡方案,也是务实方案。
NAT 的核心思想一句话:让每个终端用私网 IP 在局域网里自由通信,等数据要出局域网时,由路由器把私网地址翻译成公网地址。这样:
- 公网 IP 要求全球唯一,必须花钱买、稀少;
- 私网 IP 不要求唯一,你家里可以有一台
192.168.1.10,邻居家也可以有一台192.168.1.10,互不影响,因为它们是两个完全隔离的局域网; - 私网地址几乎可以随便用,省下了宝贵的公网地址。
于是典型组网长这样:
我的笔记本 我的手机 我的电视
192.168.1.10 192.168.1.11 192.168.1.12
│ │ │
└──────┌─────────┴──────┐─────────────┘
│ 家庭路由器 │ WAN 口:202.244.174.37 (公网)
│ (NAT + 网关) │
└────────┬────────┘
│
互联网(公网)
内网里每台设备一个私网 IP,只有路由器出口那个 WAN 口持有公网 IP。私网设备对外说话时,工会不会露馅?不会,因为路由器会替它们"伪装"。这个"伪装"的过程,就是后来要讲的 NAT 转换。
这里点出一个常常被忽略的事实,也值得先记下:NAT 不只是一张表,它还是私网和公网之间的"单向玻璃"——私网可以主动向外发起连接,外面却很难主动连进来。这个特性既是 NAT 的功劳(安全),也是 NAT 的缺陷(限制),后面会反复用到。
NAT IP 转换过程
前面说了 NAT 的核心是"翻译地址"。现在我们把"翻译"这件事的具体过程拆开。先看一段静态的转化——SNAT。
这里出现第一个要解释的术语:SNAT(Source NAT,源地址转换),指在数据包离开内网、经过路由器时,把源 IP 地址从私网地址替换成公网地址。与之对应的是 DNAT(Destination NAT,目的地址转换),指把目的 IP 地址改掉,后面讲"从外面连进来、端口映射"时会用到。
以一个具体例子说明 SNAT 全过程。我的笔记本 10.0.0.10 想访问一个服务器 163.221.120.9,出口路由器持公网 IP 202.244.174.37:
出发时——请求包(源地址是私网):
src: 10.0.0.10 dst: 163.221.120.9
│
▼
进入路由器 WAN 口,SNAT 把源地址替换成公网 IP:
src: 202.244.174.37 dst: 163.221.120.9
│
▼
数据包到达目标服务器,服务器看到的"对方"是 202.244.174.37,
完全不知道内网里有个 10.0.0.10
返回时——响应包(目的地是公网 IP):
src: 163.221.120.9 dst: 202.244.174.37
│
▼
到达路由器,路由器的转换表告诉我们"这个目的 IP 其实对应内网的 10.0.0.10",
于是把目的 IP 从 202.244.174.37 再替换回 10.0.0.10:
src: 163.221.120.9 dst: 10.0.0.10
│
▼
数据包最终回到我的笔记本
这里的关键是那句"路由器的转换表"。它是一张自动生成、用来记录地址映射关系的表。什么时候生成?当 10.0.0.10 第一次向 163.221.120.9 发送数据时,路由器发现"内网有个新地址要出门",就顺手记下这条映射:"出站的源地址 10.0.0.10 翻译成公网地址 202.244.174.37"。之后响应回来时,路由器照着这张表就能把目的地址"翻译回去"。
用 Linux 手动配置 SNAT,最常用的工具是 iptables(配合 netfilter 内核模块,它们就是 Linux 里实现 NAT 的那层机制)。比如在内网出口的这台 Linux 网关机器上执行:
# 让内核把来自 eth0 内网网卡的、去往外网 eth1 的包,都把源地址改成 eth1 的公网 IP
iptables -t nat -A POSTROUTING -o eth1 -s 10.0.0.0/24 -j SNAT --to-source 202.244.174.37这行的意思是:在 nat 表的 POSTROUTING(出站后置)链上追加一条规则,凡是"从 eth1 出去、源地址属于 10.0.0.0/24 网段"的数据包,都把源地址改写为 202.244.174.37。这样整个 10.0.0.0/24 网段的私网设备,出站时都会被伪装成同一个公网 IP。
但到这里,你会发现一个严重的问题——下面这一节就来解决它。
NAPT
试想一个场景:局域网里有三台主机,10.0.0.10、10.0.0.11、10.0.0.12,它们同时访问同一个外网服务器 163.221.120.9 的同一个服务端口,比如都去请求网页(80 端口)。
如果只用前面那种 SNAT(只改 IP 不改端口),三台主机出门后源地址都变成了 202.244.174.37,那么服务器的响应包目的 IP 一律是 202.244.174.37。路由器收到这些响应时,就蒙了:这些包该转给 10.0.0.10、10.0.0.11 还是 10.0.0.12?只凭 IP 已经分不清了。
靠谁来解决?NAPT。
进入第二个术语解释:NAPT(Network Address Port Translation,网络地址端口转换),有时也叫 PAT(Port Address Translation,端口地址转换)或"多对一 NAT"。它的招数是:不光记录源 IP 到公网 IP 的映射,还把"私网 IP + 私网端口"整体映射成"公网 IP + 一个空闲公网端口",用 IP + 端口 一起来建立关联关系。
原理图(三台主机同时访问同一服务器):
10.0.0.10:40000 ─┐
10.0.0.11:40000 ─┼──► 路由器(NAPT)──► 服务器 163.221.120.9:80
10.0.0.12:40000 ─┘
路由器内部的 NAPT 转换表(示意):
内网侧 → 公网侧
10.0.0.10:40000 → 202.244.174.37:52000
10.0.0.11:40000 → 202.244.174.37:52001
10.0.0.12:40000 → 202.244.174.37:52002
注意:三个主机虽然源端口都恰好是 40000,但只要出站时源 IP 都伪装成 202.244.174.37,路由器就必须给它们分配不同的公网端口(52000/52001/52002)来区分。于是服务器的三条响应:
- 目的端口 52000 → 表中查出是
10.0.0.10:40000,转给 10.0.0.10; - 目的端口 52001 → 表中查出是
10.0.0.11:40000,转给 10.0.0.11; - 目的端口 52002 → 表中查出是
10.0.0.12:40000,转给 10.0.0.12。
有了端口这一维,NAT 路由器就能精准把响应"还"给正确的内网主机了。绝大多数实际环境里,我们说的"NAT"其实都是 NAPT——因为全公司、全家庭设备都挤在一个公网 IP 后面,不用端口区分根本玩不转。后面凡是提到"NAT 转换表",你默认就当它是"IP+端口"级别的 NAPT 表即可。
在实际 Linux 上,NAPT 的配置比纯 SNAT 更常用,也叫 masquerade(伪装):
# 出站:对去往 eth1 的包做源地址伪装
iptables -t nat -A POSTROUTING -o eth1 -s 10.0.0.0/24 -j MASQUERADE-j MASQUERADE 跟 -j SNAT --to-source 固定IP 的区别在于:MASQUERADE 会自动读取出口网卡当前的 IP(哪怕这个 IP 是 DHCP 动态分配的,变了它也能自动跟上),适合家里、公司这种出口公网 IP 会变的情况;SNAT 则要求你写死一个明确的源地址,适合地址固定不变的场景。
关于这张 NAPT 表,还有两个"存活期"的细节要记住:
第一,表项由 NAT 路由器自动维护,不劳内网设备操心。对 TCP 来说,建连时通常就会生成表项;断开连接后,根据 TCP 四次挥手结束,超时后表项会被删除。对 UDP 这种没有"连接"概念的协议,NAT 会在一个超时时间(通常是几十秒到几分钟)内保留临时映射,超时即删。
第二,表项有"刷新"机制,否则会被主动掐掉。很多 NAT 设备会在超时后静默删除长时间空闲的表项,所以像 SSH、即时通讯这类"长时间挂机"的连接,客户端需要定期发个"心跳"包来保持映射存活(这就是为什么 SSH 有 ServerAliveInterval、很多长连接有 keepalive)。这也是后面"内网穿透"一节里反复要踩的坑。
我们再来完整写一遍 NAPT 的转换流程,把 SNAT 和对应的"反向翻译"拼起来:
① 内网主机 10.0.0.10:40000 向 163.221.120.9:80 发请求
② 路由器查转换表没有记录,于是:
- 给这条流分配一个空闲公网端口 52000
- 写入表项:10.0.0.10:40000 ⇄ 202.244.174.37:52000
- 把包改成 src: 202.244.174.37:52000 后发出去
③ 服务器响应,目的地址是 202.244.174.37:52000
④ 路由器查表,发现 52000 对应 10.0.0.10:40000
- 把目的地址改回 10.0.0.10:40000
- 发给内网主机
⑤ 连接断开/超时后,路由器删除这条表项
这一步讲完,你已经了解了 SNAT 和 NAPT 在"方向一(私网主动出站)"上的完整逻辑。那么反过来——外网主动向内网建连,行不行?这就触及 NAT 的软肋了,下一节我们专门盘它的缺陷。
NAT 技术的缺陷
NAT 解决了 IP 不够用,但它不是免费的午餐。它依赖那张转换表,这带来了一系列限制,我们必须一条条讲清楚,因为很多网络排障的"灵异现象"都源于此。
缺陷一:无法从 NAT 外部主动向内网服务器建立连接。
这是 NAT 最根本的限制,也是内网穿透要解决的问题。为什么连不进来?因为 NAT 转换表里那一条映射,是内网主机先主动出站才创建的。如果内网没有人先开口,那映射表里就是空的——外面来的包,路由器不知道该把它翻译成内网哪个 IP,只能丢掉。打个比方:你住的小区大门(NAT)只认"业主出门时登记的访客名单",但陌生人想直接进去却没有登记记录,门卫直接把陌生人拦下。
所以如果你在家里架了个网站,跑在 192.168.1.10:8080 上,这个 IP 是私网 IP,公网世界里根本没有这台机器;外面的用户连你的公网 IP(门卫)都会被拦下来,更别提访问你那台私网网站了。要让外面能访问,得靠后面讲的 DNAT 端口映射,或者内网穿透。
缺陷二:转换表的生成和销毁需要额外开销。
建连时 NAT 要分配端口、写表项;断开或超时时要回收端口、删表项。在海量并发、频繁建连断开的高压力场景下,NAT 设备的 CPU 和内存都会被这张表侵蚀。它不像单纯转发那样"无脑直通",每一条流都是一次哈希查找、一次状态维护。这就是为什么一些高性能网络设备会尝试"旁路"或者用硬件加速——纯软件 NAT 在千万级并发下是会成为瓶颈的。
缺陷三:通信过程中 NAT 设备一旦异常,即使有热备,所有 TCP 连接也会断开。
这句话里"热备"指的是"路由器挂了之后,备用设备能马上顶上"的高可用方案。但问题在于:热备设备顶上时,它自己是"一身干净"的——没有原来的转换表。那条"内网主机 ⇄ 公网端口"的映射关系没了,NAT 设备就认不出正在通信的这条流该翻译成什么,于是一大批正在进行的 TCP 连接全部断掉。要恢复,所有内网设备只能重新发起连接。这提醒我们:NAT 是有状态设备,它的"状态"(转换表)就是它的命脉;表丢了,连接就全完了。 所以真要上高可用,光有热备不够,还得做"状态同步",把主设备的转换表实时复制到备设备上,否则热备形同虚设。
归纳一下,NAT 的三大缺陷其实都是同一个根子——它是一张"由内向外精灵创建、单向透明的有状态翻译表"。这张表既是它省地址的功臣,也是它"进不来、有开销、怕断表"的元凶。
到这里顺手抛第一个思考题,等你读完"内网穿透"一节再回来验证答案。
思考题 1:NAT 设备会把内网发往不同公网目标的包"混在一起"吗?假如内网同一台主机、同一个端口,同时访问两个不同的公网服务器,NAT 转换表会怎么处理?
参考答案(先自己想想,再点开)
不会混。NAT(NAPT)区分一条"流"靠的是五元组里的(源 IP、源端口、目的 IP、目的端口、协议)。同一台主机、同一源端口,访问两个不同的目的服务器(目的 IP 或目的端口不同),在路由器看来就是两条完全独立的流,可以各记一条表项。
更妙的是,这两个表项里那条"公网侧端口"(映射端口)通常可以复用同一个,也可以不同——因为返回时,路由器是靠"目的 IP + 目的端口"去反查表的:服务器 A 的响应目的 IP 是 A,匹配到流 A 的表项;服务器 B 的响应目的 IP 是 B,匹配到流 B 的表项。只要目的 IP/端口能在表里唯一定位到一条记录,就不会混淆。
(当然,严谨地说不同 NAT 实现有差异,尤其是"对称 NAT(Symmetric NAT)"会为每个"目的地址"单独分配一个不同映射端口,而"锥形 NAT(Full Cone NAT)"则允许一个内网端口映射到一个公网端口不限目的。这里你能抓住"以五元组区分流、以目的信息反查表"这条主线就够了。)
代理服务器
解决了"内网怎么出门",我们来聊一个和 NAT 经常被混为一谈、但本质在应用层的东西——代理服务器。
先给术语下定义:代理服务器(Proxy Server),是一台"中间人"服务器,它位于客户端和目标服务器之间,帮两边"传话"。客户端不再直接访问目标服务器,而是把请求交给代理,代理再去请求真正的目标,最后把目标的响应带回来转交给客户端。
注意这句话里最关键的区别:NAT 工作在"网络层",直接改的是 IP 包头;代理往往工作在"应用层",处理的是应用层数据(如 HTTP 请求/响应)。 NAT 是一条透明管道(内网设备根本没意识到自己被改了地址),而代理是两边都看得见的"中转站"(客户端和服务器都知道有个中间人在传话)。NAT 改地址,代理不改 IP、但会"代表"一方重新发起一次连接。
代理又按"替谁说话"分成两大类:正向代理和反向代理。我们一个一个来。
正向代理
正向代理(Forward Proxy):位于客户端和目标服务器之间,代表客户端去请求服务器。你(客户端)不想直接访问目标网站,于是把请求丢给正向代理,正向代理拿着你的请求去访问目标,把结果带回来给你。站在目标服务器的视角,正在访问它的是"代理",而不是真正的你——目标的 IP 出现在代理身上。
“正向”的“正”指的是这个代理是替你(访问方)办事的,方向和你请求的方向一致(向前替你去够目标)。
工作原理走一遍:
你的请求 代理再发请求
客户端 ──────────► 正向代理 ──────────► 目标服务器
服务器响应 代理带回响应
客户端 ◄────────── 正向代理 ◄────────── 目标服务器
- 客户端把请求发送给正向代理服务器(客户端要显式配置使用哪个代理,比如在浏览器里填代理地址);
- 正向代理接收到请求,按配置处理:可能是查缓存,可能是做内容过滤,也可能是别的规则;
- 正向代理把(处理后的)请求转发给目标服务器;
- 目标服务器处理完,把响应返回给正向代理;
- 正向代理把响应再返回给客户端。
正向代理能干什么?功能取决于它"顺手做的处理",常见的包括:
- 缓存:把经常访问的资源缓存在代理本地。客户端再次请求同一个资源时,代理直接命中缓存返回,不用再去远端拉,访问变快、省流量、减轻源站压力。这是典型的"代理缓存"。
- 内容过滤:按预设规则拦截请求或响应,比如屏蔽广告域名、拦截恶意网站。
- 访问控制:限制能访问哪些网站。比如公司限制员工工作时间访问娱乐网站,就是让员工流量全走公司代理,由代理来管哪些放行、哪些拦截。
- 隐藏客户端身份:目标服务器只看到代理的 IP,看不到你真正的 IP,你的真实地址被遮蔽了,隐私得到保护。
- 负载均衡/访问加速:把请求分发给多个上游,或在多链路上做分流,提升整体可用性和速度。
应用场景典型的有这几类:
- 企业网络管理:员工全部经过代理上网,公司就能对员工访问进行管理控制,确保工作时间专注、避免访问不良站点或泄露机密。
- 公共网络环境:图书馆、学校提供的上网,通过代理实现对网络资源的合理分配,保证公平和安全。
- 内容过滤与保护:家长设置代理过滤不良内容,保护孩子。
- 提高访问速度:对常访问的站点走缓存,减少网络延迟。
- 跨境/海外访问:跨境电商或要访问海外资源的,通过位于国外的正向代理"借道",突破地域限制。
跑一个真实的正向代理并不复杂。经典做法是 Squid(老牌代理缓存软件)或者更轻量的方案,比如用 tinyproxy:
# 以 Ubuntu/Debian 为例,安装并启动一个轻量正向代理 tinyproxy
sudo apt install tinyproxy
sudo nano /etc/tinyproxy.conf # 找到端口号,默认 8888;可限制允许的 IP 段
sudo systemctl start tinyproxy
sudo systemctl enable tinyproxy之后在客户端把代理指向这台机器的 IP:8888 即可。这算一个"零成本上手"的正向代理示例,重点是体会"客户端显式指过去"这个动作——正向代理靠客户端主动配合才成立。
反向代理
反向代理(Reverse Proxy):作为 Web 服务器(后端) 的前置服务器,代表后端来迎接客户端的请求。客户端发起请求时,先到达的是反向代理,反向代理按规则把请求转发给合适的后端服务器,再把后端的响应返回给客户端。
“反向”的“反”在于:这回代理是替**被访问方(服务器)**办事的。对客户端来说,它根本不知道真正干活的是哪台服务器——它只知道自己和"那个反向代理"在通信。整个过程中,客户端并不需要做任何配置(不用像正向代理那样手动指过去),它访问的地址就是代理的地址。
“反向”的“反”字,正体现在这个"方向反转"上:正向代理是"跟在客户端后面的跑腿",反向代理是"挡在服务器前面的门卫"。后者挡在服务器前,把访问者都拦在自己面前,于是"所有访问者看到的就是它一个"。
工作原理:
客户端 ──────► 反向代理 ────► 后端服务器 A
└──(并不知道后端是谁)──► 后端服务器 B
后端服务器 C
客户端 ◄────── 反向代理 ◄──── 后端响应
- 客户端发起请求,DNS 解析后请求首先到达反向代理服务器;
- 反向代理根据配置的规则(路径、负载均衡策略、会话要求等)选择一台后端 Web 服务器;
- 它把请求转发给选定的后端,后端的响应再原路返回;
- 反向代理把响应交给客户端。
客户端"只知道跟代理通信",这是反向代理的招牌特性——配合它,能解锁一大堆能力:
- 负载均衡:按策略把客户端请求分发到多台后端,把单台机器的压力摊平,提升整体性能与吞吐,尤其在并发很高的场景下。
- 安全保护:把后端真实 IP 藏起来,降低其被直接攻击的风险;还可以在反向代理上配防火墙、访问控制列表(ACL)等安全策略,先过滤再转后,等于给后端加了一道护城河。
- 缓存加速:缓存后端返回的响应,遇到重复请求直接命中缓存返回,无需每次都打后端,大幅减轻后端负载。
- 内容过滤与重写:按规则改请求头、改路径,甚至做 URL 重写、加用户认证等业务逻辑。
- 动静分离:大型网站把静态资源(图片、CSS、JS)和动态资源(真正要查库的)分开。把静态资源直接放在反向代理(或它背后的静态存储)上,访问静态资源时挨都不挨后端一下,直接由反向代理返回,极大提速——因为静态资源不占数据库、不需要计算,正向还是反问,这样设计能极大缓解后端压力。
- CDN:CDN(Content Delivery Network,内容分发网络),就是把内容复制到离用户更近的边缘节点、用户访问时命中最近的节点。它的底层原理正是"反向代理的缓存+就近分发的思路",所以说"CDN 就是采用了反向代理的原理",一点都不夸张。
在企业里最主流的反向代理就是 Nginx。下面给一个极简的、把客户端请求反向转发到 127.0.0.1:8080 后端 Java 服务的配置:
# /etc/nginx/conf.d/myapp.conf
server {
listen 80; # 对外监听 80 端口
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8080; # 转发给后端
proxy_set_header Host $host; # 把原始 Host 头透传给后端
proxy_set_header X-Real-IP $remote_addr; # 把客户端真实 IP 透传给后端
}
}这里顺手点出一个反向代理的经典"坑",也是必须理解的边界:后端要拿到客户端真实 IP 是很麻烦的。因为请求到反向代理后,反向代理再转发给后端时,TCP 连接本身是"反向代理 ⇄ 后端"两方在通信,后端着 TMP 看到连接来自反向代理的 IP,不是客户端 IP。所以上例里要用 X-Real-IP、X-Forwarded-For 这样的 HTTP 头手动把客户端 IP "带"给后端。如果你后端的访问日志里全是同一个代理 IP,多半就是忘了配这套头。
关于反向代理的缓存还有一个边界要提醒:代理缓存的体验是有取舍的。缓存能提速,但也可能把"本应变化的动态内容"错误地当静态内容缓存了。所以"动态接口"通常要标 no-cache 或者按 Cache-Control 的语义来,别一刀切全缓存——否则用户看到的就是陈旧数据。这是"代理缓存"这个功能最大的坑。
NAT 和代理服务器的区别
讲完两类代理,我们把 NAT 和代理放在一起横向对比,它们的区别可以总结成四句话:
| 对比维度 | NAT | 代理服务器 |
|---|---|---|
| 应用定位 | 网络基础设备,核心是解决 IP 不足 | 更贴近具体应用,比如翻墙、游戏加速器 |
| 工作层次 | 网络层,直接替换 IP 地址 | 往往工作在应用层 |
| 部署范围 | 一般在局域网出口部署 | 局域网、广域网、跨网都可以 |
| 部署形态 | 集成在防火墙、路由器等硬件里 | 一个软件程序,跑在服务器上 |
再补一个课件的经典比喻,帮我们把正向/反向代理刻进脑子——"代购尿不湿":
- 我想买日本的花王尿不湿,自己跑日本太麻烦。我拜托在日本工作的表姐去超市买好再快递给我。此刻超市(卖方)看到的买家是表姐,表姐就是我的"正向代理"——她代表我这个买方去采购。
- 后来找表姐代购的人太多,表姐干脆直接去超市囤一大批货放家里,谁要就直接从库存发货,不用再跑去超市。这时表姐就是"反向代理"——她手里攥着货(缓存),替"超市"这个货源方迎客,别人根本不用知道超市的存在。
对照一下就能记住:正向代理替你(买方)出门采购,反向代理替你(卖方)挡客发货。代理后面挂的是服务器"库存"。
也正因为如此,课件里那句总结很有指导意义:正向代理常用于请求转发(比如借助代理绕过反爬虫,因为源 IP 变了);反向代理常作为一个缓存/负载均衡前置层。
读完这一段,你可以自测一下有没有把两类代理分清。
思考题 2:一个爬虫程序在访问某网站时频繁被网站封 IP。分析下面两个方案各属于正向还是反向代理,并说明为什么能(或不能)缓解封禁:方案 A,用一台位于别处的代理服务器代发请求;方案 B,在公司网关出口把目标网站的响应缓存到本地后返回。(提示:一个改的是"访问者",一个改的是"被访问资源的提供方式"。)
参考答案(先自己想想,再点开)
方案 A 属于正向代理。爬虫把自己伪装成"从代理服务器访问",网站的访问记录里看到的就是代理的 IP,而不是爬虫所在机器的真实 IP。网站如果只封看到的源 IP,那爬虫换一个正向代理(换一个出口 IP)就能继续访问——这确实能缓解基于 IP 的封禁。但网站也可以加深手段(比如限流、校验行为特征、识别代理 IP 段),所以"换 IP"只能治标不治本,这就是"借助代理绕过反爬虫"的典型场景。
方案 B 属于反向代理思路,但它解决的不是"封禁"问题——它只是在公司局域网出口把网站内容缓存一份。对爬虫来说,它其实是"命中缓存、减少了对源站的请求次数"。但是:一方面,如果爬虫要的是新鲜数据,缓存反而会把过时的页面给它;另一方面,缓存只能在"语义允许缓存"的场景下工作,动态页面(如验证码、登录态)根本无法缓存。所以用方案 B 来对抗封禁基本是南辕北辙——它属于"反向代理/缓存加速"的范畴,不解决"你是谁、从哪来"的问题。
一句话收尾:正/反向代理的关键区别不在于"是不是中间人",而在于"替谁办事、站在哪边"。替客户端出门 → 正向;挡在服务器前迎客 → 反向。
内网穿透
现在回到 NAT 那个最头疼的缺陷:"外面连不进内网"。那如果我确实需要"外面访问进来"呢?比如我在家里电脑上跑了个网站、跑了个远程桌面,可它在私网后面,公网人访问不到。怎么办?
这就是内网穿透要解决的问题。内网穿透(NAT Traversal / Intranet Penetration),指的是一整套"让位于 NAT/私网后面的设备,能从公网被访问到"的技术手段。核心难点就是那句"NAT 表是内网主机先出站才创建的"——那我们能不能反过来利用"出站"这个免费的通道?
思路是:让内网主机主动"往外捅一个洞",再让外面的访问流量从那个洞里钻进来。这就是常说的"内网打洞"。
看一张打洞的经典图。想象有台中心服务器(打洞服务器/信令服务器,公网可达),内网主机 P 和另一台需要互通的设备 Q:
公网打洞/信令服务器(有公网 IP,双方都能连上)
▲ ▲
│ 建立关系 │
内网主机 P ─────┘ └───── 内网主机 Q
(在 NAT1 后面) (在 NAT2 后面)
自身私网地址:192.168.0.10 自身私网地址:192.168.0.20
打洞的一般流程(这里以最典型的 UDP 打洞、P2P 直连为例,TCP 的变体后面补):
- 两端都先联系打洞服务器:P 和 Q 分别向公网服务器发出 UDP 包。这一刻,两个 NAT 设备(NAT1、NAT2)都各自建立了一条"出站映射"——在这条映射的存活期内,从公网发往"NAT 出口的公网 IP + 映射端口"的数据,会被 NAT 转交给内网的 P(或 Q)。打洞服务器因此知道了 P 和 Q 各自的"公网可见地址"(NAT 出口地址 + 映射端口)。
- 换地址:打洞服务器把 P 的公网地址告诉 Q,把 Q 的公网地址告诉 P。注意,它告诉给双方的,是"对方在各自 NAT 出口后的可见地址",而不是对方的私网地址(私网地址在对方网络里根本不通)。
- 两边同时往里捅洞:P 主动向"Q 的公网可见地址"发包,Q 也主动向"P 的公网可见地址"发包。这时两边的 NAT 设备都看到一条"自己内网主机发往那个公网地址"的流,于是又各自多记了/复用了一条映射。
- 洞打通了:经过第 3 步的双向"预热",两边的 NAT 都认识了"对端地址",于是 P 和 Q 之间就能直接互发数据了,不再绕心经信服务器。真正的通信走的是 P ⇄ Q 的直接路径。
这里必须补一个至关重要的前提,也是打洞能成功的坑与条件:
-
打洞要求 NAT 的种类配合。像前面提到的"锥形 NAT(Cone NAT)"(尤其是 Full Cone)配合度最高,打洞容易成功;而"对称 NAT(Symmetric NAT)"会"每换一个目的地址就分配一个新的映射端口",导致"对方拿到的地址"和"自己实际映射出去用的地址"对不上,打洞失败。所以早年很多内网直连在"对称 NAT 后面"就直接没戏了。
-
戳洞有时效,要保活。NAT 映射会在超时后删除,所以打通后的连接两端都要定期发心跳来续命,否则打到一半洞又关了。这就是为什么很多穿透工具要求"两端都保持在线并周期性握手"。
用一个很直观的比喻:NAT 就像一个门卫,他记"谁出门时的访客名单"。P 想找 Q,得先各自去小区门卫(打洞服务器)报个到,拿一张"出站时发的出入证",然后把出入证信息交换给对方,再两边同时往对方门口走一遍、让门卫都认识"这个对端我见过",以后就能自由出入了——前提是这个小区门卫(NAT)比较温和,记性好(锥形 NAT),不搞"见新客就重新发新证"(那就成对称 NAT 了)。
不过要注意,真实的打洞并非总是成功——尤其当两端都在对称 NAT 后面,或者中间还有企业级的严格防火墙时,Direct 打洞会失效。这时候就要退一步,用"中继(Relay)"方案:两端都不直连,而是都连到同一台公网中继服务器,由中继替它们转发数据。中继能解决一切 NAT 组合的连通问题,代价是流量要经过中继这台机器,有带宽瓶颈和额外的转发延迟。这其实就是很多商业远程工具"失败时自动降级为中继模式"的原因。
除了打洞这种"P2P 直连"思路,还有更"传统/人工"的两条实现路线,它们也算内网穿透,但实现思路完全不同:
路线一:端口映射(DNAT / 端口转发)。 这条适合"你有公网 IP 可支配"的场景(比如自己的云主机、管得了的网关)。做法是在 NAT 设备上手动配置一条 DNAT 规则,把"公网某端口进来的请求"转给"内网的某台主机某端口"。前面提过的 DNAT(Destination NAT,目的地址转换)在这里登场——进站时改的不再是源地址,而是把目的地址从"公网 IP"改写成"内网目标服务器的私网 IP"。
用 iptables 配置一个 DNAT 端口映射(把公网 8080 端口映射进内网的 10.0.0.10:80):
# 进站:把目的地址为 eth1 公网 IP、目的端口 8080 的包,改写目的地址为内网 10.0.0.10:80
iptables -t nat -A PREROUTING -i eth1 -p tcp --dport 8080 \
-j DNAT --to-destination 10.0.0.10:80配合这样一条 PREROUTING(进站前置)的 DNAT 规则,公网用户访问你家路由器的公网 IP:8080 时,路由器就会把请求的目的地址改成 10.0.0.10:80 并发进内网。多数家用路由器的后台也有图形化的"端口映射 / DMZ / 虚拟服务器"设置,本质都是做这件事。但注意,端口映射要求你手里有可路由的公网 IP——如果你家是运营商给的家庭宽带,往往处于运营商内网(CGN,Carrier-Grade NAT,运营商级 NAT)后面,你自己根本没有公网 IP,那端口映射也白搭,只能走打洞或中继。
路线二:主动回连(反向连接 / 隧道)。 这条适合"完全没有公网 IP"的场景。思路是:内网主机主动去连一个公网上可达的"中转服务器",保持一条长连接(相当于内网主机自己在 NAT 后面"捅了个面向中继的洞");公网用户要访问内网时,通过中继把流量经由这条长连接隧道"灌"进去。内网一侧装个客户端、公网一侧装个服务端,中间就是一条隧道。这就是 frp、ngrok、nps、Tailscale/ZeroTier 这类穿透工具的工作原理。这种方案对 NAT 种类不挑剔(因为洞是内网自己主动出站捅的),因此最通用。
以 frp 为例,一个非常小的配置让你看懂"主动回连"长什么样。公网服务器(frps):
# frps.toml(跑在有公网 IP 的服务器上)
bindPort = 7000内网机器(frpc)把自己"反向暴露":
# frpc.toml(跑在你要被访问的内网机器上)
serverAddr = "1.2.3.4" # 公网 frps 的地址
serverPort = 7000
[[proxies]]
name = "home-ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6000 # 公网 frps 上对外开放的端口启动 frps / frpc 之后,公网用户 SSH 到 1.2.3.4:6000,frps 就会把流量通过 frpc 那条长连接隧道,forward 给内网机器的 22 端口——内网 SSH 服务就这样被"穿透"出来公开访问了。全程不需要内网有公网 IP,因为发起连接的是内网机器自己。
到这里,我们可以把"内网穿透"的三种手段串起来对比:
| 手段 | 是否需要公网 IP | 对 NAT 种类要求 | 适合场景 |
|---|---|---|---|
| 端口映射(DNAT) | 需要 | 无所谓(自己网关) | 有公网 IP、管得住网关 |
| 打洞(P2P) | 不需要 | 锥形 NAT 容易成功 | 两端私网、想直连省带宽 |
| 主动回连(隧道/frp) | 不需要 | 无所谓 | 最通用,任何 NAT |
总结
走完这一篇,我们其实补完了整个 TCP/IP 网络的最后几块拼图。回顾一下 NAT、代理、内网穿透这三层,以及它们对应的"问题—方案—代价":
- NAT(网络层):解决"IP 地址不够用"。用 SNAT/NAPT 把私网地址翻译成公网地址,用 IP+端口区分多路连接。代价是"外面进不来""表有开销""断表断连"。这是网络基础设备级的解决方案。
- 代理(应用层):解决"代表一方中转",分正向(代表客户端)与反向(代表服务器)。正向代理替你出门(缓存、过滤、隐藏身份、访问控制);反向代理替你迎客(负载均衡、安全、缓存、动静分离、CDN)。它是软件形态、可跨网部署,贴近具体业务。
- 内网穿透:破解 NAT"进不来"的限制。端口映射(DNAT)适合有公网 IP 的网关;打洞(P2P)靠双方出站后互通,省带宽但依赖 NAT 的"锥形"配合;主动回连(frp/ngrok 隧道)最通用,靠内网主动出站维持长隧道。
如果把这篇文章放进整个《Linux 网络编程》的体系里,你会发现它的位置很特殊:NAT、代理、内网穿透都不是某个单一"协议",而是搭建在网络各层之上的"架构手段"。它们串联起了你前面学过的每一层——数据链路层(MAC、ARP、MTU)是数据落到链路上的基础;网络层(IP、路由、ICMP)是"跨网寻址"的骨架,NAT 就在这一层动刀;传输层(TCP/UDP、端口、粘包、流量控制)决定了连接怎么建、怎么维护,NAPT 依赖端口、打洞依赖连接的存活;应用层(HTTP、DNS、自研协议)里,代理和穿透的隧道正是活跃在主战场。
所以一句话收尾:NAT 是"省地址、单向透明的翻译",代理是"应用层的代表",穿透是"对 NAT 那扇单向门的逆向利用"。它们不是孤立的冷知识,而是当你真正部署生产系统、排查"为什么外网访问不了内网服务"这类问题时,迟早会正面撞上的真家伙。
至于那些频繁出现在配置里的 iptables 规则、nginx 反代片段、frp 隧道参数,现在你读懂它们背后的原理了吗?读懂了,就不再是"照着抄",而是"知道为什么这么写"。下一篇文章,我们可以进入更实战的领域——走一遍"一个数据包从你的浏览器出发,穿过 NAT、经过层层路由、抵达内网服务器,再原路返回"的完整旅程,把今天这张地图真正走到头。
思考题 3(综合):你在公网云主机上部署了一个网站(Nginx + 后端),公司办公室的同事却说访问时走公网很慢。你有两个优化候选:(a) 在公司网关用 Nginx 做反向代理并开缓存;(b) 建议同事把浏览器的代理指向一个随手可建的正向代理。请判断哪个方案符合"用代理解决访问慢"的本质,并说明理由。
参考答案(先自己想想,再点开)
更符合本质的是方案 (a)。
理由:让"多人访问同一站点变快"的合理手段是"就近缓存 + 避免重复回源"。在公司网关做反向代理并开缓存后,第一位同事访问时命不中、回源拉一份,之后所有同事再访问同一个静态资源,都能直接命中公司本地缓存转发返回——既缩短了物理链路,又避免了对公网云主机的重复请求。这是"反向代理=缓存加速"的核心用法。
方案 (b) 起的是反作用:正向代理只是"多绕一跳"。把浏览器代理指到另一个正向代理,请求反而要多经过那台代理,链路更长、还可能多一层瓶颈,通常只会更慢而非更快(除非正向代理自身也做了缓存,但那就又变成"正向代理+缓存"的加速玩法了,本质已经不是单纯的正向代理转发)。
所以一句话:要"替服务器缓存分发"→ 反向代理;只想"改道/隐藏访问者"→ 正向代理。用代理解决"访问慢",靠的是缓存命中与就近分发,这正是反向代理的主场。
到这里,我们已经把 NAT、代理服务与内网穿透这三兄弟掰开揉碎讲了一遍。从"IP 地址为什么不够用"起,到 SNAT/NAPT 的每一步地址翻译,再到 NAT 那张单向透明表带来的三大缺陷;接着认识了替客户端出门的正向代理和替服务器迎客的反向代理,厘清了它们与 NAT 在层级与定位上的差别;最后用端口映射、P2P 打洞、主动回连三条路线解开了"外面进不来"的死结。
文中出现的几段真实配置——iptables 的 SNAT/MASQUERADE、Nginx 反代片段、frp 隧道——建议你在自己机器上照着敲一遍、跑起来,理解会比只看文字深得多。也别忘了回到前面那三道思考题,用你自己的话把答案讲出来,讲得清的才是真懂了。
还没有评论 — 第一条由你来留。