ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux端口映射实战:iptables DNAT/SNAT解决白名单联调难题

Linux端口映射实战:iptables DNAT/SNAT解决白名单联调难题 简介当开发环境因第三方白名单限制而无法直连服务时Linux端口映射转发是绕开访问瓶颈、完成联调的实用手段。这份资源以PDF文档形式系统讲解跳板服务、Nginx反向代理转发以及基于iptables的内核端口映射三种方案并对比各自适用场景尤其补充了SFTP、SSH等非HTTP协议的转发配置要点内容兼顾操作步骤与排错思路。文档以场景化方式逐层递进从nginx监听与proxy_pass配置、到开启net.ipv4.ip_forward及DNAT/SNAT链命令均给出可套用的示例并附有保存与重启iptables服务的注意事项便于运维人员直接对照实施。压缩包内仅1个PDF文件大小约50KB轻量易读适合Linux运维、后端开发及网络工程师快速查阅。目前已有2492人学习下载作为一份短小精悍的配置参考能帮助读者在遇到白名单约束时迅速搭建转发环境减少重复摸索。1. Linux端口映射转发被白名单卡住时先用 iptables 兜底做第三方接口联调时最憋屈的就是对方白名单只放行你线上那台机器本地开发环境压根不在名单里。这时候你既不能把代码搬到线上机器去调试也不能缠着对方运维改白名单唯一能做的就是让那台已经“洗白”的机器帮你把流量转过去。不少人第一反应是配 Nginx 反向代理这能解决 HTTP 流量但一旦要连 SSH、SFTP、FTP 这类非 HTTP 协议Nginx 就彻底没辙了得回到内核态用 iptables 做 DNAT SNAT 端口映射。这篇文章会把跳板服务、Nginx 转发、iptables 映射三种做法全部拆开包括参数含义、持久化方式和排错路径适合被联调环境卡住的开发、运维照着直接抄作业。2. 跳板服务与 Nginx 转发HTTP 场景的两种快解法2.1 跳板服务手写代理与 socat 的取舍先说最直觉的方案在已经加入白名单的 2.2.2.2 上跑一个服务把你本地发过去的请求原封不动转给 1.1.1.1:8080。如果目标是 HTTP 接口常见做法是用 Python 标准库写个几十行的转发器不需要引入任何框架。# jump.py —— 基于 http.server 的极简 HTTP 跳板监听 8080 from http.server import HTTPServer, BaseHTTPRequestHandler from urllib.request import Request, urlopen UPSTREAM http://1.1.1.1:8080 # 第三方服务地址可改成 test/pro class JumpHandler(BaseHTTPRequestHandler): def _forward(self): # 把请求路径和 query 原样拼到上游地址后面 url UPSTREAM self.path req Request(url, methodself.command) for key, value in self.headers.items(): # Host 和 Connection 交由 urllib 自己处理不手动透传 if key.lower() not in (host, connection): req.add_header(key, value) with urlopen(req) as resp: self.send_response(resp.status) for key, value in resp.headers.items(): if key.lower() not in (transfer-encoding, connection): self.send_header(key, value) self.end_headers() self.wfile.write(resp.read()) do_GET _forward do_POST _forward if __name__ __main__: HTTPServer((0.0.0.0, 8080), JumpHandler).serve_forever()这段代码核心就一件事把收到的请求原样搬到上游再把上游响应原样搬回来。逻辑上需要注意两点一是self.path同时携带了路径和 query string拼接后urlopen才能正确请求到/test/api/xxx?keyvalue这种完整地址二是请求头里刻意跳过了Host和Connection否则第三方机器按 Host 做路由时会识别成 2.2.2.2而不是预期的 1.1.1.1不少跳板服务翻车就翻在这里。跑起来也简单python3 jump.py本地把接口地址临时改成http://2.2.2.2:8080即可调试。但它只覆盖 GET/POSTPUT/DELETE 没实现cookie、文件上传也会因为 header 处理不完整出现各种幺蛾子。所以真要省事我一般会直接用 socat 做纯 TCP 层跳板它不解析任何应用层协议二进制流量也能透传# 在 2.2.2.2 上执行socat 监听 8080 并转发到 1.1.1.1:8080 socat TCP-LISTEN:8080,fork,reuseaddr TCP:1.1.1.1:8080socat的fork参数让每个连接由独立子进程处理reuseaddr允许端口在 TIME_WAIT 状态下快速复用。跳板方案最大的问题是每次要手动启停、没有日志和超时控制临时调试够用长期用还是要看下一节的 Nginx。2.2 Nginx 转发location 匹配和 proxy_pass 的路径陷阱Nginx 做端口转发比手写跳板省心得多配置写在 2.2.2.2 的/etc/nginx/conf.d/jump.conf里server { listen 8080; # 本机监听端口也就是白名单放行的端口 location /test/api/ { proxy_pass http://1.1.1.1:8080; # 转发到第三方测试环境 } }配完执行nginx -t nginx -s reload就能生效。逻辑上这个配置是把所有以/test/api/开头的请求转给1.1.1.1:8080因为proxy_pass末尾没有路径上游收到的 URI 会保持原样也就是请求http://2.2.2.2:8080/test/api/user后第三方机器实际收到的是http://1.1.1.1:8080/test/api/user路径前缀不会有任何变化。这里有三个参数细节容易被坑。第一proxy_pass必须写http://前缀写proxy_pass 1.1.1.1:8080;会直接报invalid URL prefix启动都起不来。第二如果proxy_pass末尾带了斜杠或具体路径比如http://1.1.1.1:8080/Nginx 会用这个路径替换掉 location 匹配到的部分/test/api/user会变成/user接口直接 404。第三location 用不带结尾斜杠的/test/api也能匹配但会把/test/apixxx也吞进来边界符务必写对。2.3 这两种方案都绕不过的协议墙跳板服务和 Nginx 转发本质都在 HTTP 层工作能处理的只有 80/443 端口的 web 流量。等你需要 sftp 拉文件、ssh 登录服务器排查问题时Nginx 就无能为力了手写跳板服务要解析 SSH 协议也根本不现实。这类场景需要的是透明端口映射你连 2.2.2.2 的 8080实际连上的是 1.1.1.1 的 8080中间不做任何协议解析只有内核在网络层改写地址。这就是 iptables 的 DNAT/SNAT 组合要做的事下一章完整展开。3. 内核级端口映射iptables 的 DNAT 与 SNAT 完整配置3.1 开启内核 IP 转发sysctl 的两条路径iptables 端口映射的本质是让包穿过当前机器而 Linux 默认不会转发不属于自己的包。首先要打开内核转发开关临时生效一条命令即可# 临时开启 IPv4 转发重启后失效 sysctl -w net.ipv4.ip_forward1sysctl -w直接写内核参数参数名net.ipv4.ip_forward对应/proc/sys/net/ipv4/ip_forward这个文件写 1 表示开启。要永久生效CentOS 7 常见做法是在/usr/lib/sysctl.d/50-default.conf里追加配置但我更建议独立建文件避免系统升级时被覆盖# 写入独立配置优先级高于 /usr/lib 下的默认值 echo net.ipv4.ip_forward 1 /etc/sysctl.d/99-ipforward.conf # 立即加载全部 sysctl 配置 sysctl --system/etc/sysctl.d/目录按文件名排序加载99-前缀保证它后加载/etc/sysctl.conf是传统主配置多数发行版也会读取。Debian/Ubuntu 和 CentOS 7 在这条上行为一致唯一区别是默认配置文件位置。验证是否生效直接看内核导出的实际值cat /proc/sys/net/ipv4/ip_forward输出 1 就对了。3.2 DNAT 规则把目的地址改写掉转发开关打开后核心动作就是改写数据包。在 2.2.2.2 上执行# 进入本机的 8080 端口 TCP 包目标地址改写成 1.1.1.1:8080 iptables -t nat -A PREROUTING -p tcp -d 2.2.2.2 --dport 8080 -j DNAT --to-destination 1.1.1.1:8080这条命令拆开看-t nat指定操作 NAT 表-A PREROUTING把规则追加到 PREROUTING 链这条链在包刚进入网卡、还没做路由决策时生效适合改目的地址-p tcp限定 TCP 协议避免把 UDP 流量也卷进来-d 2.2.2.2 --dport 8080是匹配条件只有目的 IP 是本机且端口为 8080 的包才命中。-j DNAT --to-destination 1.1.1.1:8080是动作把包的目的 IP 和端口分别改写为 1.1.1.1 和 8080。这里有个实用经验如果 2.2.2.2 这台机器绑定了多个 IP或者你不想写死入口 IP可以省略-d 2.2.2.2只保留--dport 8080。代价是这台机器所有 IP 的 8080 端口都会被转走可本机如果有服务恰好也监听 8080就会产生端口冲突所以我在生产环境始终写全-d。3.3 SNAT 规则让回包原路返回只配 DNAT 的话连接大概率会卡死。原因是第三方机器 1.1.1.1 收到改写后的包回包时会直接发给发起请求的客户端客户端看到回包来自 1.1.1.1而自己明明连的是 2.2.2.2TCP 状态对不上回包被静默丢弃。解决办法是在转发机上做源地址伪装# 源地址为 1.1.1.1 且源端口为 8080 的回包改写源地址为 2.2.2.2 iptables -t nat -A POSTROUTING -p tcp -s 1.1.1.1 --sport 8080 -j SNAT --to-source 2.2.2.2POSTROUTING链在包即将离开本机时生效适合改源地址。匹配条件是-s 1.1.1.1 --sport 8080也就是上游服务返回的、源端口为 8080 的包SNAT --to-source 2.2.2.2把源 IP 改成 2.2.2.2。客户端看到的就是 2.2.2.2 在和自己通信完全不知道背后有 1.1.1.1 这层。注意原文里写的是--to-source 2.2.2.2:8080实际工程中我建议省略:8080。SNAT 支持指定端口但一旦写死上游任何其他端口的回包都会在改写链路上出问题只改写 IP、保留原始源端口最安全。回包的源端口本来就是 8080不用画蛇添足。3.4 为什么是 PREROUTING 和 POSTROUTINGNAT 链的选择逻辑NAT 表有五条链端口映射只用到其中两条搞清楚它们的分工就不会配错。PREROUTING处理的是刚进网卡、还没确定是本机消费还是要转发的包DNAT 在这里改目的地址正好让路由决策认为这个包该发给 1.1.1.1POSTROUTING处理的是即将离开本机的包SNAT 在这里改源地址让回包和去程在客户端视角对称。至于OUTPUT链只处理本机自身进程发起的包外部流量根本不会经过它INPUT链则相反只管发给本机进程的包。映射外部进来的流量就用 PREROUTING POSTROUTING 这一对。这两条规则还有个顺序问题需要注意PREROUTING的 DNAT 必须先配POSTROUTING的 SNAT 后配不后配不影响匹配但配置时最好保持这个顺序逻辑上先改目的地、再改源地址排查时也容易对号。配完后查看完整规则# 查看 NAT 表全部规则带行号和命中计数 iptables -t nat -L -n -v --line-numbers-L列出规则-n不做 DNS 反解-v显示每条的包计数和字节数--line-numbers给规则编号。命中计数是排错的关键依据后面避坑章节会反复用到。4. 持久化与一键脚本让映射重启后还能用4.1 保存 iptables 规则别把命运交给 service iptables saveiptables 规则默认只活在内存里重启即丢。原文里提到的service iptables save只在装了 iptables-services 的机器上存在CentOS 7 默认用 firewalldDebian/Ubuntu 更是连 iptables 服务都没有。先确认环境属于哪一类再看对应方案环境持久化工具规则文件恢复命令CentOS 6 / 装了 iptables-services 的 CentOS 7service iptables save/etc/sysconfig/iptablesservice iptables restartCentOS 7 默认 firewalld 环境firewall-cmd 或改用 nft取决于防火墙后端firewall-cmd --reloadDebian / Ubuntuiptables-persistent/etc/iptables/rules.v4netfilter-persistent reloadCentOS 7 如果坚持用 iptables常见做法是安装 iptables-services 并接管防火墙然后 save# 安装 iptables 服务并设置开机自启 yum install -y iptables-services systemctl enable iptables # 保存当前内存中的规则到文件 service iptables save cat /etc/sysconfig/iptablesservice iptables save本质是执行iptables-save /etc/sysconfig/iptables把当前所有表的规则序列化到文件systemctl enable iptables保证开机时先读这个文件恢复规则。这套流程的问题在于它接管了整个防火墙如果你原本在用 firewalld 管理其他端口两套体系会互相打架。所以我现在的习惯是生产机器上不用 service 脚本纯粹用iptables-save和iptables-restore手动落地。4.2 sysctl 持久化统一放进 /etc/sysctl.d内核转发参数的持久化相对简单前提是别再往/usr/lib/sysctl.d/50-default.conf里追加内容系统更新会被覆盖。正确姿势是在/etc/sysctl.d/下建独立文件# 99 开头保证加载顺序靠后覆盖默认配置 echo net.ipv4.ip_forward 1 /etc/sysctl.d/99-ipforward.conf # 立即重载所有 sysctl 配置包含开机时的加载路径 sysctl --systemsysctl --system会按/run/sysctl.d、/etc/sysctl.d、/etc/sysctl.conf的顺序依次加载后加载的覆盖先加载的所以自定义值写在/etc/sysctl.d下的独立文件里最稳。验证是否已经在重启后生效可以在执行sysctl --system后立刻cat /proc/sys/net/ipv4/ip_forward看输出。4.3 封装成脚本一套组合拳解决重复劳动端口映射的常规操作包含打开转发、加 DNAT、加 SNAT、放行 FORWARD、持久化五个动作逐个敲命令容易漏。我一般会封装成一个可重复执行的脚本也方便下次换端口直接改变量#!/bin/bash # nat-forward.sh —— 端口映射配置脚本需 root 执行 set -e SELF_IP2.2.2.2 # 当前机器公网 IP白名单放行的机器 TARGET_IP1.1.1.1 # 第三方服务 IP TARGET_PORT8080 # 第三方服务端口 LISTEN_PORT8080 # 本机监听端口 # 1. 开启内核转发参数立即生效 sysctl -w net.ipv4.ip_forward1 # 2. 清理可能残留的旧规则避免反复执行脚本导致规则叠加 iptables -t nat -D PREROUTING -p tcp -d ${SELF_IP} --dport ${LISTEN_PORT} -j DNAT --to-destination ${TARGET_IP}:${TARGET_PORT} 2/dev/null || true iptables -t nat -D POSTROUTING -p tcp -s ${TARGET_IP} --sport ${TARGET_PORT} -j SNAT --to-source ${SELF_IP} 2/dev/null || true # 3. 新增 DNAT 与 SNAT 规则 iptables -t nat -A PREROUTING -p tcp -d ${SELF_IP} --dport ${LISTEN_PORT} -j DNAT --to-destination ${TARGET_IP}:${TARGET_PORT} iptables -t nat -A POSTROUTING -p tcp -s ${TARGET_IP} --sport ${TARGET_PORT} -j SNAT --to-source ${SELF_IP} # 4. 放行 FORWARD 链防止默认策略 DROP 把转发流量拦掉 iptables -C FORWARD -p tcp -d ${TARGET_IP} --dport ${TARGET_PORT} -j ACCEPT 2/dev/null || iptables -A FORWARD -p tcp -d ${TARGET_IP} --dport ${TARGET_PORT} -j ACCEPT echo nat forward configured脚本第 2 步用iptables -D删旧规则2/dev/null || true是为了让规则不存在时继续执行而不中断第 4 步iptables -C先检查规则是否存在存在则跳过不存在才追加避免重复放行。变量集中在头部下次转发别的端口改TARGET_PORT和LISTEN_PORT就行。持久化部分脚本里没写因为不同发行版差异太大。我的习惯是脚本跑完后手动执行一次iptables-save /etc/iptables/rules.v4配合iptables-persistent或 systemd 的ExecStart恢复如果这台机器防火墙策略经常变干脆不持久化把脚本放进/usr/local/bin/每次重启后手动跑一遍反而比看不见的自动规则更好排查。5. 端口映射排查五个翻车现场与对应解法5.1 连接超时只配 DNAT 不配 SNAT回包被客户端丢弃现象客户端执行curl http://2.2.2.2:8080/test/api/user卡在连接阶段直到超时反向 ping 2.2.2.2 正常端口扫描显示 8080 开放。原因只执行了 DNAT 规则缺失 POSTROUTING 链上的 SNAT。第三方机器 1.1.1.1 收到请求后回包直接发给客户端原始 IP客户端看到回包源地址不是自己连接的 2.2.2.2按 TCP 状态机判定为非法包并丢弃。解决补上 SNAT 规则并确认net.ipv4.ip_forward确实是 1。执行完立刻在客户端重试再不行就看转发机上的连接跟踪表确认包有没有走到 POSTROUTING# 确认转发开关必须输出 1 cat /proc/sys/net/ipv4/ip_forward # 查看 TCP 8080 的连接跟踪状态确认回包方向 conntrack -L | grep 8080conntrack输出里能看到原始方向src客户端 dst2.2.2.2 sportxxxx dport8080和应答方向src1.1.1.1 dst客户端 sport8080应答方向出现就说明 DNATSNAT 链路已经打通。5.2 重启后规则消失iptables 规则没落地到文件现象映射配置完一切正常机器重启后第三方接口立刻连不上iptables -t nat -L显示 NAT 表空了。原因iptables 规则只存在内存中没有保存到规则文件也没有对应的开机恢复机制。service iptables save在安装了 iptables-services 的机器上才有效现代 CentOS 7 默认 firewalld、Debian 系默认无服务命令直接报错或静默失败。解决按发行版选持久化工具。CentOS 7 推荐安装 iptables-services 后保存到/etc/sysconfig/iptablesDebian/Ubuntu 安装 iptables-persistent 并启用netfilter-persistent# Debian / Ubuntu 安装持久化工具 apt install -y iptables-persistent # 保存当前规则写入 /etc/iptables/rules.v4 netfilter-persistent savenetfilter-persistent save会把 IPv4 规则写到rules.v4开机时由 systemd 服务自动加载。也可以用最原始的方式手动备份iptables-save /root/nat-rules.bak恢复时iptables-restore /root/nat-rules.bak至少留个后悔药。5.3 本机访问本机映射地址不通PREROUTING 管不到本地发出的包现象外部机器访问 2.2.2.2:8080 完全正常但在 2.2.2.2 本机上执行curl http://2.2.2.2:8080/test/api却超时本地回环访问也一样。原因PREROUTING链只处理从网卡进入本机的包。本机自己 curl 时包由本机进程产生走的是OUTPUT链而不是PREROUTINGDNAT 规则根本不匹配包直接发给了本机 8080 的监听进程而那里往往没有服务。解决不要在转发机上用本机 IP 测试端口映射是否生效。要验证就走客户端或者额外给 OUTPUT 链补一条 DNAT# 让本机访问 2.2.2.2:8080 时也触发 DNAT不推荐用于生产 iptables -t nat -A OUTPUT -p tcp -d 2.2.2.2 --dport 8080 -j DNAT --to-destination 1.1.1.1:8080这条规则会让本机进程的请求也走转发但回包的 SNAT 还要再单独适配两个方向上容易出幺蛾子。我的建议就是老老实实用客户端机器验证别再本机回环上死磕这是最快的解决路径。5.4 Nginx 转发路径多了一段或少了一段proxy_pass 斜杠定生死现象nginx -t报错invalid URL prefix或者配置能加载但请求到第三方接口后变成 404查看第三方日志发现 URI 里的/test/api/前缀不见了。原因invalid URL prefix是因为proxy_pass漏写了http://协议头404 是因为proxy_pass http://1.1.1.1:8080/;末尾带了斜杠Nginx 会用该路径替换掉 location 匹配到的部分/test/api/user变成/user第三方接口按/test/api前缀路由自然找不到。解决不换路径前缀时proxy_pass只写协议和 IP不带任何 URIserver { listen 8080; location /test/api/ { proxy_pass http://1.1.1.1:8080; # 不带斜杠URI 原样转发 } }如果需要把本地路径/local/api/映射到上游/test/api/那才用带路径的写法并在后面追加$request_uri做拼接。这类路径改写问题排查时最好结合第三方访问日志看实际收到的 URI比猜靠谱得多。5.5 FORWARD 链默认 DROP规则明明匹配包就是过不去现象NAT 表规则齐全iptables -t nat -L -n -v看到 PREROUTING 和 POSTROUTING 的计数都在涨但客户端请求一直 resettcpdump 看不到任何回包。原因NAT 规则匹配了但包在路由决策后要经过FORWARD链才能继续往前走而这个链的默认策略是 DROP或者后来追加的防火墙规则把转发流量拦掉了。iptables 对转发的包有三道关卡PREROUTING → FORWARD → POSTROUTING很多人只改了 NAT 表忘了 filter 表的 FORWARD 链。解决显式放行对应方向的转发流量并确认 FORWARD 链默认策略不是 DROP# 显式放行去往第三方服务 8080 端口的转发包 iptables -A FORWARD -p tcp -d 1.1.1.1 --dport 8080 -j ACCEPT # 放行目标端口为高位随机端口的回包允许已建立连接的回包 iptables -A FORWARD -p tcp --sport 8080 -j ACCEPT # 查看默认策略如果第一行是 DROP必须靠上面的 ACCEPT 规则开洞 iptables -L FORWARD -niptables -L FORWARD -n输出最上方的Chain FORWARD (policy DROP)就是默认策略。放行后从客户端重测再用iptables -t nat -L -n -v看计数确认有包穿过了整个链路。这个问题在容器环境、安全加固过的机器上尤其常见我遇到过一次线上映射全通但 FORWARD 默认 DROP 的情况折腾半天才想起是初始安全基线改了默认策略。6. 用 tcpdump 验证映射链路确认 DNAT 和 SNAT 都命中的最后招配置做完最怕的是“看起来通了但不知道是哪条规则在起作用”。我给自己的硬性要求任何端口映射上线前必须在转发机上抓一次包把去程和回程的三段链路看完才算完。先在 2.2.2.2 上开始抓包# 抓取所有进出 8080 端口的 TCP 包不解析域名显示绝对时间戳 tcpdump -i any -nn tcp port 8080 -tt然后从客户端发起一次请求curl http://2.2.2.2:8080/test/api/user。观察 tcpdump 输出重点看每一段的五元组变化# 去程第一段客户端进入转发机目的仍是 2.2.2.2:8080 10.0.0.5.56789 2.2.2.2.8080: Flags [S] # 去程第二段转发机发出目的已被 DNAT 改写 10.0.0.5.56789 1.1.1.1.8080: Flags [S] # 回程第一段上游回包进入转发机源是 1.1.1.1:8080 1.1.1.1.8080 10.0.0.5.56789: Flags [S.] # 回程第二段离开转发机源被 SNAT 改写成 2.2.2.2:8080 2.2.2.2.8080 10.0.0.5.56789: Flags [S.]四行看完思路就彻底清晰了。第二行目的地址变成了1.1.1.1.8080说明 DNAT 生效第四行源地址从1.1.1.1变成了2.2.2.2说明 SNAT 生效。如果第二行看不到问题在 PREROUTING 规则没匹配回去查-d和--dport条件如果第三行看到但第四行没有说明 SNAT 规则漏配或者匹配条件写错。抓包之外配合 NAT 表的命中计数看更直观# 带计数查看 NAT 表连续刷新观察 packets 是否增长 watch -n 1 iptables -t nat -L -n -v --line-numberswatch每一秒刷新一次观察两条规则的pkts列是否同步增长。只涨 DNAT 不涨 SNAT说明回包路径没有走到 POSTROUTING两条都涨但客户端还是不通立刻把注意力转到 FORWARD 链和防火墙默认策略。这套组合拳下来端口映射基本没有排查不出来的问题。我从一次惨痛经历里学到的习惯是配置完从来不信“应该没问题”而是强制自己在转发机上走一遍 tcpdump四行包全看明白才收工。从那以后每次接口联调被白名单卡住我都是同一个流程——先判断协议HTTP 就 Nginx其余全走 iptables配完抓包验证确认三段链路对称再交付。这套配置套路你直接拿去用下次联调时能少踩一大半坑希望帮到你。本文还有配套的精品资源点击获取
返回列表