1. 为什么把DNS、ICMP、NAPT放在一起讲
1.1 一台Linux服务器去上网,要过哪三道关卡
做Linux运维这些年,我排查最多的网络问题不是业务代码本身,而是网络层里的“老三样”——DNS解析、ICMP探测、NAT/NAPT转换。很多人把它们当成三个孤立的概念来背,其实在日常工作中,它们经常是一条链路里的三个环节。
你在浏览器输入一个域名,系统首先要通过DNS把这个域名“翻译”成IP地址,这是第一关“找地址”;数据从内网机器发往公网时,如果这台机器用的是私网地址,就要靠NAPT把源地址和源端口转换成公网地址和对应端口,这是第二关“转流量”;而中间出了故障,你怎么判断网络通不通、走到哪一跳断了、数据包被谁丢了?这时候就离不开ICMP,这是第三关“探通路”。我经常跟团队里新来的同事说,把这三件事串成一条线去理解,Linux网络排障的基本功就算打了一半底子。
这篇文章想做的就是把DNS、ICMP、NAPT三块内容完整拆开,从配置、原理、抓包到故障排查,结合我在实际服务器上折腾过的经验来讲。无论你是刚开始接触Linux的运维新人,还是准备面试的开发者,或是要独立把一台Linux配成网关、自建DNS服务器的老手,都能在这篇里找到可以直接照抄的命令和配置。
1.2 一个典型的“内网访问公网”场景长什么样
先画一个我工作中最常见的拓扑:一台Linux服务器,一块网卡接内网(比如192.168.1.1),一块网卡接公网(比如202.100.x.x),内网的一堆PC和服务器都通过这台Linux上网。PC要访问一个公网域名时,它的请求大概会经历这些步骤:
- PC先向Linux上跑的DNS服务或者上游公共DNS发起解析请求,查到目标公网服务器的真实IP。
- PC把数据包发给默认网关,也就是Linux服务器的内网网卡IP。
- Linux服务器收到这台PC发来的包,发现源地址是192.168.1.x这种私网地址,不能直接路由到公网,于是通过NAPT把源IP变成Linux服务器的公网IP,把源端口映射成一个空闲的随机端口,然后从公网网卡发出去。
- 目标服务器回包时,目的地是Linux服务器的公网IP加那个随机端口,Linux再根据连接跟踪表反向查一下,把包送回原来的那台PC。
整个过程里,DNS负责“把域名翻译成目标IP”,NAPT负责“把私网源地址翻译成公网源地址”,而一旦中途出问题,你ping一下、traceroute一下,用到的是ICMP。三者缺一环,用户就会反馈“上不了网”“域名解析不出来”“时通时断”。
所以我一直觉得这三个技术根本不应该分开学。下面我按顺序把每一块掰开揉碎地讲。
2. DNS在Linux下的“四层皮”:配置、缓存、排障、加固
2.1 从输入域名到拿到IP,Linux其实走了一条链
很多人以为解析域名就是“查一下DNS服务器”,但Linux系统内部其实有一条完整的解析链,顺序错了,结果就完全不一样。以最常见的glibc系统为例,一次域名解析大致会经过:/etc/hosts->/etc/nsswitch.conf里定义的解析顺序 ->/etc/resolv.conf里指定的DNS服务器 -> 系统缓存 -> 上游DNS递归。
先说/etc/hosts。这个文件里手工指定的“域名->IP”映射是优先度极高的,几乎可以覆盖所有域名解析。有些人会在这里做内网域名映射,也有些人会用它临时屏蔽某个域名(把域名指向127.0.0.1)。但它的问题是条目多了以后很难管理,而且普通用户没法修改,所以定位要清楚:它只适合做少量、固定的主机映射。
接下来是/etc/nsswitch.conf,很多人不知道它的存在。它决定了系统按什么顺序去获取主机信息,典型配置里有这么一行:
hosts: files dnsfiles表示先查/etc/hosts,dns表示再走DNS。如果哪一天你改了/etc/hosts却不生效,先去看是不是这行的顺序变成了dns files,有时候系统某个服务会自动改掉这个配置。
然后才是/etc/resolv.conf。传统情况下它里面写着nameserver,比如:
nameserver 223.5.5.5 nameserver 114.114.114.114系统会按顺序选择DNS服务器发起查询。现代Ubuntu系统里这个文件往往是一个软链接,指向systemd-resolved生成的stub解析器,这也是很多人改完它重启后又“变回去”的根本原因。
最后是缓存。systemd-resolved、dnsmasq、nscd都可以做DNS缓存。缓存在提升速度的同时也会带来“修改了DNS不生效”“域名换了IP还是解析到旧地址”的假故障。我处理过太多类似的工单,第一反应基本都是先清缓存再谈其他。
2.2 Ubuntu 22.04上改DNS最容易踩的几个坑
Ubuntu 22.04默认使用netplan管理网络,DNS又被systemd-resolved接管,于是改DNS成了一件“看起来简单,做起来容易翻车”的事。我见过最多的坑有三个:改完/etc/resolv.conf重启失效、只改netplan但没应用、误删了resolv.conf软链接导致解析直接挂掉。
先说最稳妥的做法:如果你的网卡配置由netplan管理,修改/etc/netplan/下的yaml文件,在对应网卡里加上:
network: version: 2 ethernets: ens33: dhcp4: true nameservers: addresses: - 223.5.5.5 - 114.114.114.114然后执行sudo netplan apply。注意yaml文件的缩进非常严格,一个空格错了都可能报错,建议修改前先备份。
如果系统没装netplan,或者你是用NetworkManager管理的,还可以直接改/etc/systemd/resolved.conf:
[Resolve] DNS=223.5.5.5 114.114.114.114 FallbackDNS=119.29.29.29保存后执行sudo systemctl restart systemd-resolved。验证是否生效用resolvectl status,能看到当前每个网卡绑定的DNS服务器。
还有一种情况是你临时想试一下某个DNS,不想改任何文件,那直接加一条命令:
sudo resolvectl dns ens33 223.5.5.5这是最轻量的方式,重启后失效,适合做对比测试。不论哪种方式,改完务必用dig @新DNS 域名去验证一下,不要只看配置文件里写了什么就以为生效了。
顺带提一句,很多人在Docker环境里遇到过容器内域名解析慢的问题,这跟宿主机DNS其实是一套逻辑:Docker守护进程默认从宿主的resolv.conf继承DNS,如果宿主机配置了奇怪的DNS或者用了systemd-resolved的localhost stub,容器内部解析就可能异常。可以在/etc/docker/daemon.json里显式指定:
{ "dns": ["223.5.5.5", "114.114.114.114"] }然后重启Docker。这个做法就是“把上游DNS固定住”,原理和Linux改DNS完全一样,只是换了个配置文件的位置。
2.3 dig、nslookup、getent:排查DNS问题的一线命令
说到排查DNS问题,我最常用的命令不是ping域名,而是dig、nslookup和getent,三个命令干的活其实不一样。
dig展示的是原始的DNS报文内容,最直观。你看一个最简单查询:
dig www.example.com输出里重点看ANSWER SECTION,里面有解析出来的IP;看Query time判断解析耗时;看SERVER确认是从哪台DNS服务器返回的。怀疑上游DNS有问题时,可以直接指定服务器:
dig @223.5.5.5 www.example.com dig @114.114.114.114 www.example.com对比两组结果,很快就能判断是“所有DNS都解析不了”还是“某一家的DNS有问题”。想跟踪完整递归过程,用dig +trace,可以看到从根域名服务器到权威服务器的完整路径,这个命令对理解DNS层级非常有帮助,面试时讲解析流程也比背书生动得多。
nslookup更老派,但脚本里很好用,加-debug参数可以看到详细的查询过程,包括重试了几次。getent hosts 域名则跟前面提到的NSS机制相关,它测试的是系统程序实际解析时会走的完整路径,因为ping、curl这类程序是走glibc的NSS流程的,而dig是直接发DNS请求的,两者结果可能不一致。所以我的习惯是:怀疑系统级解析问题时先getent hosts,怀疑上游DNS内容问题时才dig @服务器。
再补充两个运维场景。第一个是查看本地DNS缓存:systemd-resolved可以用resolvectl statistics看缓存命中统计,想清空缓存就sudo resolvectl flush-caches。第二个是抓包看DNS报文,tcpdump -i eth0 port 53 -nn -vv,能看到请求发给谁、响应返回了什么,这是判断“是不是有设备在劫持DNS”的最硬核手段。
2.4 自建DNS服务器:dnsmasq与bind9怎么选
讲到“自己搭建DNS服务器”,很多人第一反应是装bind9,其实大多数场景根本用不上。我自己的经验是:小型局域网、嵌入式网关、临时缓存加速,用dnsmasq就够了;正式的权威解析、多域管理、复杂ACL,才需要bind9。
dnsmasq的配置文件是/etc/dnsmasq.conf,核心就几项:
listen-address=192.168.1.1 resolv-file=/etc/resolv.dnsmasq.conf cache-size=1000 address=/internal.lan/10.0.0.5它既能做缓存DNS,又能配置内网域名解析,还能把某个域名直接解析到指定IP(也就是address=这一行),非常适合作网关设备。很多家用路由器、嵌入式Linux里的DNS功能,底层跑的就是dnsmasq。好处是轻量、内存占用小、配置简单,缺点是权威区域管理和权限控制比较弱。
bind9更适合正经的域名服务。生产环境里我一般会在/etc/bind/named.conf.options里设置递归白名单,避免被外部滥用:
options { directory "/var/cache/bind"; recursion yes; allow-query { 10.0.0.0/8; 192.168.0.0/16; }; allow-recursion { 10.0.0.0/8; 192.168.0.0/16; }; forwarders { 223.5.5.5; 114.114.114.114; }; dnssec-validation auto; };这里面最关键的一点是限制递归查询来源。如果你不做限制,任何人都能用你的DNS服务器做递归解析,轻则被当成肉鸡DDoS放大器,重则被运营商拉黑IP,这是我踩过的最深的坑。自建DNS还有一个常见用途:给内网服务提供稳定的域名入口,比如内网GitLab、NAS、监控平台,配好之后不再依赖外部DNS,长期用下来明显比在/etc/hosts里堆条目稳定得多。
2.5 用DNS做安全防线:恶意域名过滤的正确姿势
DNS不只能解析域名,也是一道非常有价值的安全防线。现实中很多攻击流量会跟恶意域名做通信,比如C2回连、挖矿脚本下载、勒索软件请求等。把恶意域名在DNS层面“拆掉”,往往比在防火墙上封IP更彻底,因为域名会变IP,但域名本身的特征很难变。
我之前遇到过一个典型的黑产反复攻击场景:服务器被植入了一个恶意脚本,会定期向外网某个可疑域名发起请求。处理步骤大致是:先在网络层把可疑目标的IP通过iptables drop掉,阻断最直接的通信;然后在DNS层把该域名解析到黑洞地址,比如返回0.0.0.0或者NXDOMAIN,让依赖域名解析的攻击代码找不到目标;同时翻系统日志、抓包,定位脚本是怎么进来的、改了哪些文件;最后做主机加固,清掉计划任务、SSH公钥和异常用户,再在WAF或者IDS里加上对应的规则,防止下次再进来。
这里要强调的是,DNS过滤只能防“依赖域名解析”的那一类攻击,如果攻击代码直接写死IP通信,光靠DNS就拦不住了,必须配合网络层封禁。所以安全处理一定要分步骤联动,而不是只做一个动作。另外,生产环境的DNS服务器上还可以启用DNSSEC验证,防止DNS缓存投毒,这个基础安全项在自建DNS时建议直接开启。
3. ICMP协议:ping通了不等于网络没问题
3.1 ICMP报文里到底装了什么
ICMP全称是Internet Control Message Protocol,很多人把它看作是“网络自带的报错与诊断工具”。它并不像TCP/UDP那样承载业务数据,而是封装在IP报文里,专门用来传递控制信息和差错报告。在IP头里,ICMP的协议号是1,你抓包时如果看到IP层里有一个“Protocol: ICMP (1)”的字段,那就是它。
ICMP报文结构比TCP简单得多,头部就三块:类型(Type)、代码(Code)、校验和(Checksum),后面跟着的是具体数据内容。光一个类型字段就能区分很多种用途,我列几个最常碰到的:
| 类型 | 代码 | 含义 | 常见场景 |
|---|---|---|---|
| 0 | 0 | Echo Reply | ping的回复 |
| 8 | 0 | Echo Request | ping的请求 |
| 3 | 0 | Destination Network Unreachable | 网络不可达 |
| 3 | 1 | Destination Host Unreachable | 主机不可达 |
| 3 | 3 | Port Unreachable | 端口不可达 |
| 11 | 0 | Time Exceeded | 数据包TTL超时 |
| 13 | 0 | Timestamp Request | 时间戳请求,常见于安全扫描 |
搞懂类型和代码的对应关系,看抓包分析就能少很多迷惑。比如traceroute能逐跳显示路由器,靠的就是“Time Exceeded”类型;你访问某台服务器的某个端口不通,返回“Port Unreachable”,说明路由是通的,问题出在目标机器的端口上。这比直接用“网络不通”四个字去排查要精确得多。
3.2 ping与traceroute背后的机制,很多人理解是错的
ping命令是最常见的ICMP应用。它发送一个Type=8的Echo Request报文,目标是主机收到后返回一个Type=0的Echo Reply报文。Linux下默认的ping会持续发送,所以脚本里记得加-c指定次数,比如ping -c 4 223.5.5.5。常用参数还有-i控制发送间隔、-s指定payload大小、-I指定源IP。判断网络是否通畅,重点看有没有回复,以及时间(RTT)稳不稳、有没有丢包。
但很多人把“ping通了”等同于“业务正常”,这是大忌。ICMP只证明了IP层可达,不代表TCP端口能连上,更不代表应用层正常。我自己排障时遇到过太多这样的场景:用户说“外网不通”,我ping域名能通,但curl一直卡住,最后发现是防火墙把TCP 443端口给DROP了,ICMP倒是放行的。所以结论还是那句话:ping不通,网络大概率有问题;ping通了,只能说明三层通,不能说明二到七层没问题。
traceroute的原理比ping更巧妙,值得展开讲。它利用IP报文里的TTL(生存时间)字段,每经过一个路由器TTL减1,减到0时路由器会回一个“Time Exceeded”的ICMP报文。traceroute先发一个TTL=1的探测包,第一个路由器收到后TTL变0,被迫回复“超时”,于是traceroute记录下第一个跳的IP;再发TTL=2的包,第二个路由器回复,这样逐跳增加TTL,就能把整条路径上的每一跳都给“逼问”出来。Linux版本默认用UDP探测包发往高端口(33434开始),Windows的tracert默认用ICMP的Echo Request,所以跨系统对比路径时,看到结果有差异不要觉得奇怪。
3.3 Wireshark抓ICMP包:手把手解析一个echo请求
抓包永远是理解协议最直观的方式。我常用的组合是:PC上开Wireshark,过滤框输入icmp,然后在终端里执行ping baidu.com,再回来看捕获的报文。这里提一句,很多人都知道过滤icmp,但不知道怎么进一步过滤某个类型,比如只想看echo请求就输入icmp.type == 8,只想看echo回复就输入icmp.type == 0。
实际点开一个Echo Request报文,你会看到三层信息。第一层以太网头,能看到源MAC和目的MAC。第二层IP头,注意里面有个很重要的字段叫TTL,Linux默认发出的ping包TTL通常是64,Windows通常是128,这也是为什么网上经常有人靠TTL初值猜测对方系统类型。第三层才是ICMP头,Type=8表示请求,Identifier和Sequence Number用于匹配请求与回复,我实际操作时经常会去看这两个值,因为多台机器同时ping的时候,就是靠它们区分报文到底是哪台机器发出的。
如果是wireshark没有,纯命令行环境用tcpdump也一样能看:
sudo tcpdump -i eth0 icmp -nn输出里可以看到类似这样的内容:
IP 192.168.1.10 > 223.5.5.5: ICMP echo request, id 1, seq 1, length 64 IP 223.5.5.5 > 192.168.1.10: ICMP echo reply, id 1, seq 1, length 64一条是请求一条是回复,id和seq能对上,说明ping是通的。抓包最大的好处是能把“我以为的”变成“实际的”,比如之前有人报告ping不通某台服务器,我抓包一看,请求发出去了,回复也回来了,但ICMP报文在网络中间被人为丢掉了。这种情况单看命令行永远只能看到丢包,问题定位就会跑偏。
3.4 ICMP的安全风险:时间戳探测、ping flood与隧道
ICMP是诊断工具,也是攻击面。至少有三个风险点是网络加固时必须考虑的。
第一个是ICMP时间戳请求(Type=13)。它本来是让主机回复当前时间的,但攻击者可以利用它探测主机存活状态、校正时间、甚至在特定场景下泄露系统运行时间信息。我做Windows Server 2012安全整改的时候,就经常接到要求“关闭ICMP时间戳响应”。Windows上可以通过防火墙的高级入站规则阻止“Core Networking - ICMPv4-In”里的Timestamp Request,Linux上则可以通过iptables直接丢弃这类报文,或者在内核参数里忽略异常ICMP报文。这类整改看起来很小,等保测评和红队评估里却很常见。
第二个是ping flood攻击,也就是大量Echo Request涌入目标机器,消耗带宽和CPU,属于DDoS的一种简单形态。防护思路是不让外部直接ping自己,生产环境我一般会在防火墙把入站的echo request丢弃,但保留一种例外:允许“内网主动发起的ping返回”。也就是说,别人ping不进你,但你可以ping出去。这个用iptables的-m state --state ESTABLISHED逻辑就能实现,只放行已有连接关联的回复。
第三个是ICMP隧道。攻击者可以把数据封装在ICMP payload里,穿透只放行ICMP的防火墙,让恶意流量看起来像普通的ping请求。这类行为靠单纯的“禁ping”防不住,需要配合流量审计,看ICMP报文的大小是否异常、频率是否规律。遇到内网上线的新设备疯狂发ICMP包,不要只当它是“误报”,先抓包看几眼再说。
4. NAPT技术:一台Linux如何让整个内网上网
4.1 公网IPv4不够用之后:NAPT解决了什么与代价
IPv4公网地址只有四十多亿个,全球设备数量早就远超这个数。于是就有了NAT(Network Address Translation,网络地址转换)这类技术,把私网地址映射成公网地址后再访问外部网络。NAT有几种形态:静态NAT是一对一映射,适合需要被外部访问的服务器;动态NAT是把一组公网IP映射给多个私网设备,但同一时刻每个私网设备独占一个公网IP;而NAPT(Network Address Port Translation,网络地址端口转换)更进一步,允许“多对一”映射。
形象的类比是这样:一个公司只对外公布一个总机号码,员工外呼时,总机分配一个内部专线分机,外部回电话时就拨总机号码加分机号,总机再找到对应的员工。NAPT就是这个总机,公网IP是总机号码,端口号就是分机号。多台私网机器同时上网时,NAPT把不同的源端口动态分配给不同的内网设备,于是只需要一个公网IP就能支撑成百上千台设备的出站访问。
NAPT解决了地址短缺问题,但代价也要讲清楚。第一,外网主动访问内网变得困难,因为对外只暴露一个IP加一堆动态端口,外部设备根本不知道请求应该发给谁,所以需要端口映射(DNAT)才能让外网主动访问内网服务。第二,NAPT会改写数据包里的IP和端口,对一些要求端到端透明的协议不友好,例如IPsec、部分P2P应用、FTP的主动模式等,处理不好就会变成“连不上”。第三,NAPT依赖连接跟踪表,表的容量和老化时间直接决定了网络的并发能力。
4.2 conntrack是理解NAPT的钥匙
NAPT在Linux里的实现离不开一个关键模块:连接跟踪(conntrack,connection tracking)。简单说,Linux内核会为每一条经过NAT的会话建立一张表项,记录这条连接的原始五元组(源IP、源端口、目标IP、目标端口、协议)和转换后的五元组,以及当前连接的状态。回程的数据包到了之后,内核就靠查这张表,确定应该转给哪台内网机器。
实际查看连接跟踪表,可以读/proc/net/nf_conntrack这个虚拟文件,也可以用conntrack命令,比如:
sudo conntrack -L每行记录大概长这样:
tcp 6 431999 ESTABLISHED src=192.168.1.100 dst=180.101.50.242 sport=45678 dport=443 src=202.100.x.x dst=192.168.1.100 sport=443 dport=45678 [ASSURED] mark=0 use=1前面的src和sport是内网机器的原始地址和端口,中间的是转换成公网地址后的数据,最后是回程对应的逆向映射。看到这里你就明白NAPT为什么能同时撑那么多连接:只要映射出去的“公网IP+端口”组合不冲突,两条连接到同一个目标IP的同一端口也能被区分开。
连接跟踪表不是无限大的,它由内核参数控制。常见的调优项有这么几个:
net.netfilter.nf_conntrack_max = 65536 net.netfilter.nf_conntrack_tcp_timeout_established = 432000 net.netfilter.nf_conntrack_udp_timeout = 30 net.netfilter.nf_conntrack_icmp_timeout = 30当表满了之后,新连接会直接失败,现象就是“网络突然断了,重启后又恢复”。排查这类问题,先看dmesg | tail里有没有nf_conntrack: table full, dropping packet这样的报错,然后调整nf_conntrack_max并确认系统内存是否足够。这是我排过最多的NAT故障之一。
调试NAPT还有一个很实用的工具链:ss -tnp查看本机端口占用,iptables -t nat -L -n -v查看NAT规则命中次数,再加上conntrack表,基本能覆盖绝大多数排查场景。
4.3 iptables/nftables实现NAPT:最常用的几条命令
Linux做NAPT,绕不开的就是netfilter框架。传统写法用的是iptables,新系统里越来越多的是nftables,但命令逻辑是相通的。我给一个最少必要配置,大家可以直接抄:
第一步,开启内核转发。没有这一步,Linux收到内网网卡的数据包后根本不会把它发往公网:
sudo sysctl -w net.ipv4.ip_forward=1想永久生效,写到/etc/sysctl.conf或者/etc/sysctl.d/99-forward.conf里,内容一行:
net.ipv4.ip_forward = 1第二步,设置NAT规则。最常用的两种写法,一种是固定公网IP用SNAT:
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 202.100.x.x另一种是公网IP是动态获取的,比如PPPoE拨号用户,公网IP可能变,用MASQUERADE更省心:
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADEMASQUERADE会自动从出口网卡上取当前IP做转换,不需要手写具体地址,所以IP变了也不用改规则,代价是会稍微多一些开销。
第三步,放行FORWARD链。很多人在NAT配置好了之后内网还是上不了网,十有八九是FORWARD链默认策略是DROP。要增加两条:
iptables -A FORWARD -i eth1 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT iptables -A FORWARD -i eth0 -o eth1 -j ACCEPT第一条允许“从外面回来、属于已有连接”的报文进入内网,第二条允许内网发出的报文出去。这里要强调,-m state --state RELATED,ESTABLISHED非常重要,不能省略,否则内网机器发出去的包可以出去,但外部回包进不来,实际效果就是“单向通”。
如果用的是更现代的Debian系新版系统,nftables写法类似:
nft add table nat nft add chain nat postrouting { type nat hook postrouting priority 100; } nft add rule nat postrouting ip saddr 192.168.1.0/24 masquerade核心逻辑跟iptables完全一致,只是语法换了风格。不管用哪种工具,做完之后都建议用iptables -t nat -L -n -v看规则命中计数,有没有流量匹配到规则一眼就能看出来。
4.4 NAPT常见故障:表满、NAT回流、存量连接断开
用了这么多年NAPT,我把遇到过的典型故障归纳成四类,每一种都有固定的排查套路。
第一类:能出去、回不来。现象是内网机器ping公网IP有去无回。排查时先看FORWARD链的ESTABLISHED规则是不是写反了,再看conntrack表里有没有对应条目。如果表里有条目但回包还是进不来,就要抓包看回包到达Linux服务器时的目标IP和端口,是不是跟NAPT映射的完全对应。这里有一个很容易错的地方:在PREROUTING链做了DNAT的,回包会自动走反向转换,如果你在POSTROUTING链又重复做SNAT,就可能造成回包源地址和连接跟踪不匹配,被内核丢包。
第二类:conntrack表满。现象前面说了,新连接全失败,老连接还能坚持一阵。临时缓解是先改大nf_conntrack_max并清空一次表:
sudo sysctl -w net.netfilter.nf_conntrack_max=262144 sudo conntrack -F根治思路是找为什么连接数会这么大:是正常业务高峰?是攻击扫描?还是NAT老化时间太长导致表里堆满了僵尸连接?如果是僵尸连接太多,可以把TCP和UDP的超时时间调短,同时让内网应用主动复用连接,减少新建连接的频率。
第三类:NAT回流问题。内网用户用公网域名访问内网自己发布的Web服务,结果访问不通。原因在于,内网机器发往公网域名的包,经过NAPT转换后从公网口出去了,可目标服务器的回应又因为目标IP指向公网地址,绕回NAT设备时,conntrack无法正确把流量导回原来的内网机器。解决办法有三种:一是做hairpin NAT,让NAT设备在内部直接转发这个流量;二是用DNS视图,让内网用户解析到内网IP,直接走内网访问;三是在PREROUTING链对目标IP做DNAT到内网服务地址。我最推荐的是第二个方案,简单可靠,性能也好。
第四类:存量连接在NAT规则变更后全部断开。比如重新加载了iptables规则、重启了iptables服务或者清空了conntrack表,这个比较常见,因为Linux的NAT规则和conntrack表是联动的,规则一变,已有连接要么被丢弃要么被重置。所以生产环境里修改NAT规则要尽量在维护窗口执行,先备份规则,改完规则后用conntrack -L确认现有业务连接是否还在,不要为了改一条规则把线上连接全打断。
4.5 Linux共享上网实操:从网关配置到验证
讲完理论,给一套可以直接照抄的共享上网完整方案。假设Linux服务器内网网卡是eth1(IP 192.168.1.1),公网网卡是eth0(拨号或者静态IP都可以),下面按顺序来:
检查内核是否支持转发功能,执行sysctl net.ipv4.ip_forward,如果不是1就设置。有的内核默认开启IPv6转发,但IPv4没有,所以一定要单独确认。
配置内网网卡地址,如果网卡还没有静态IP,用netplan或者ip命令配置都行,关键是网关机器的内网IP要固定在最前面几位,方便内网设备填默认网关。然后是NAT规则,公网IP固定就选SNAT,IP会变就选MASQUERADE。我这种拨号环境的机器几乎都用MASQUERADE。
再设好FORWARD链放行规则,上面已经给了命令。最后再做一个很重要的小细节:给内网设备分配DNS。如果局域网里没有自建DNS,可以在dnsmasq里加个配置统一指向上游;简单一点,直接在客户端把DNS指到223.5.5.5这种公共DNS,但这样要求内网设备得有外网权限。想省心的话,在Linux网关上跑一个dnsmasq,监听内网口,把上游指向公共DNS,这样内网设备的DNS就填192.168.1.1,既能解析外网域名,以后要加内网域名解析的时候也方便。
配置完成后的验证步骤,我喜欢按这个顺序走:先在Linux本机上ping 223.5.5.5确认公网口有网;再让内网某台PC设置网关和DNS,先ping 192.168.1.1确认内网能到网关;然后ping 223.5.5.5确认NAPT转发生效;最后dig www.baidu.com确认DNS解析正常。如果中间某一步挂了,基本就能定位到大致的故障范围,不用东查西查。
5. 一次真实的故障排查实录:DNS、ICMP、NAPT一起上
5.1 故障现象与初步判断:全网“上不了网”其实是假象
聊了这么多理论,不如拿一个真实案例收一收。有一回接手一个办公网的“网络报障”:内网PC普遍反映网页打不开,但企业微信这种即时通信软件却偶尔能用。我第一反应就是这么奇怪的现象说明“所有网络都断了”是假象,可能是域名解析挂了,部分软件因为内置了IP直连或者有本地缓存的IP,所以还能跑。这正是DNS问题的典型特征:IP通信还有,但域名解析不行。
为了快速核实,我在一台内网PC上做了四个动作:第一,ping 网关,通;第二,ping 223.5.5.5这个公网IP,通,说明NAPT和路由没问题;第三,nslookup www.baidu.com,提示超时,这就把矛头指向了DNS;第四,用nslookup www.baidu.com 223.5.5.5直接指定公共DNS查询,居然也是超时。当时我就判断,问题多半不在局域网内的DNS服务器,而是整条出口到外部的UDP 53端口流量都被拦截了。后来在网关上抓包,tcpdump -i eth0 udp port 53 -nn,发现查询包发出去了,但没有任何回应包进来。最后查到是出口防火墙策略误伤,把目的端口为53的UDP报文当成了风险流量直接丢弃。把策略改回放行后,域名解析立刻恢复。
这类“全网断网”的报障,排查顺序记住一句口诀:先通不通,再能不能解析。先ping网关、再ping公网IP、最后解析域名,每一步都能把故障范围缩小一半。DNS超时和网络完全不通的区别,单靠浏览器是判断不出来的,必须自己动手分层测。
5.2 分层排查的完整流程:三层法
我把上面这个案例抽象成一套可以复用的三层排查流程。
第一层:物理和数据链路层。看网卡状态ip link,看是否up,有没有RX/TX错误计数,网线或Wi-Fi是不是稳定。交换机端口如果有大量CRC错误,优先怀疑网线或光纤模块问题。这一层很多人跳过了,但恰恰是“时通时断”问题的高发区,因为三层以上一切都是正常的,却因为二层丢包导致应用层表现异常。
第二层:网络层和NAT。在出问题的PC上ping 网关,通了说明二层和三层的本地路由没问题;然后ping 公网IP,通说明NAPT转发、防火墙FORWARD、出口路由基本正常。这一步如果失败,就要去Linux网关上iptables -t nat -L -n -v查NAT规则命中次数,cat /proc/sys/net/ipv4/ip_forward查转发开关,conntrack -L | wc -l看连接表是否已满。
第三层:DNS和应用层。nslookup解析域名,dig @公共DNS 域名指定公共DNS测试,对比是不是某个DNS服务器的问题;再curl -I http://域名看HTTP响应。这一层真正对应了用户感知的“能用还是不能用”。三层法最大的价值不是技术高深,而是让排查有次序、有边界,不瞎猜。
5.3 问题根因与修复:定位到防火墙策略
回到那个案例,根因是出口防火墙策略误伤UDP 53。修复动作说起来简单:在防火墙策略里放行内网到公共DNS服务器的UDP 53流量,并把日志加上,方便以后审计。但真正有价值的是复盘:为什么策略会误伤?因为最初写策略的人把“所有出UDP流量”一刀切地限制了,只放行了一些特定端口,没考虑DNS用的就是UDP 53。这类问题在Linux系统里同样常见,比如你写iptables规则时只放行了TCP端口,忘了UDP,那内网设备虽然能正常上网,但一解析域名就卡死,现象和这个案例一模一样。
修复后我还在网关和公共DNS之间做了一次连通性验证:nc -uvz 223.5.5.5 53,虽然UDP的“连通”判断不太可靠,但只要出口有回包,就说明UDP 53链路是通的;再跑一次ping 域名,确认解析恢复。这种“先网络后服务、先系统后应用”的思维,其实每个运维都会用,只是遇到压力时容易乱,建议大家把三层法写进自己的排障手册。
5.4 这个案例教会我的排查习惯
每次复盘我都会跟同事强调两个习惯。第一个习惯是,不要只听现象描述,要自己动手分步骤复现。用户能告诉你的只是“网页打不开”,但网页打不开的原因可能有几十种,必须靠命令一层层往下逼。第二个习惯是,排查网络问题一定要把“缓存”这个变量考虑进去。当时如果我先在内网PC上清了浏览器缓存、清了系统DNS缓存,可能就能更快地发现“不是解析不了,而是解析结果有误”这一层,能少走很多弯路。
有些问题在使用dig指定的公共DNS时表现正常,但系统默认DNS却解析异常,这种就高度怀疑是DNS服务器配置或者本地缓存问题。遇到这种情况,直接把DNS临时改成公共DNS测试一次,是效率最高的动作。而我个人更倾向于在Linux网关上给内网设备统一下发稳定的DNS,并且把systemd-resolved的缓存清空命令教给值班同事,毕竟故障排查中“想当然地认为某个环节没问题”才是最大的问题。
6. 相关面试题、常用命令与我的个人建议
6.1 Linux网络排查命令速查表
这篇文章里提到的命令,我整理成一个速查表,干活时贴在手边很方便:
| 排查目标 | 常用命令 | 关键输出 |
|---|---|---|
| 本机IP/网卡状态 | ip addr、ip link | link state、inet地址 |
| 路由表 | ip route、route -n | 默认网关、出口网卡 |
| 端口监听 | ss -tlnp、netstat -tunlp | 监听IP和进程 |
| 连通性测试 | ping -c 4 目标IP | 通断、丢包、RTT |
| 路径测试 | traceroute -n 目标IP | 每一跳的延迟与IP |
| DNS解析 | dig +short 域名、nslookup 域名 | 解析结果、查询耗时 |
| NSS测试 | getent hosts 域名 | 系统实际解析路径 |
| 抓包 | tcpdump -i eth0 port 53、tcpdump -i eth0 icmp | 报文内容 |
| NAT规则 | iptables -t nat -L -n -v | 规则命中计数 |
| 连接跟踪 | cat /proc/net/nf_conntrack、sudo conntrack -L | 会话条目 |
| 内核转发 | sysctl net.ipv4.ip_forward | 是否等于1 |
| 防火墙规则 | iptables -L -n -v | 策略和命中计数 |
命令不用全背,只要知道“想看什么用哪条”就行。真正干活的效率来自熟练度,建议大家在测试环境多抓几次包、多清几次conntrack,用多了自然就记住了。
6.2 面试经常会问到的三个“魔鬼细节”
第一个问题:讲一下一次完整的DNS解析过程。很多人的回答停留在“客户端问DNS服务器,DNS返回IP”,但真正的完整流程要区分递归和迭代。客户端向本地DNS服务器发起递归查询,本地DNS服务器若没有缓存,就替客户端去问根域名服务器,根服务器告诉它“去问com域的权威服务器”,然后再去问com域的权威服务器,它再指向目标域名的权威服务器,最后拿到A记录。这个过程面试官真正想听的,不是背出这个链条,而是你能把dig +trace的输出对应到每一层。我建议准备面试的朋友拿一个真实域名跑一遍dig +trace,你会看到从根到权威的完整路径,印象会非常深。
第二个问题:ping通了,TCP连接却失败,可能的原因是什么?这个问题考察的是“网络分层”思维。ICMP只代表IP层可达,TCP连接还需要经过三次握手,所以可能的根因包括:对端防火墙丢弃了TCP SYN报文、目标服务本身没监听对应端口、中间设备做了TCP粒度限制、或者MTU导致大包被丢弃。如果面试者能说出“ping只看三层,TCP要看四层”这个点,基本就算合格了。
第三个问题:一台服务器只有一个公网IP,为什么能让几百个内网用户同时访问外网?这个问题考察的就是NAPT。关键在于端口复用:不同内网用户访问同一个外部IP时,NAPT会把它们的源端口映射成不同端口,所以外部服务器返回数据包时,根据目标端口就能区分是哪个用户的请求,再由连接跟踪表还原给对应的内网机器。面试时再加上“如果端口不够用、连接跟踪表满会发生什么”的延伸,就能很自然地体现出实操深度。
6.3 我给刚入行的人提几个实在建议
从我做Linux运维的体感来说,网络这块不是靠看书学会的,而是靠一个个故障“喂”出来的。所以我建议大家刻意给自己安排一些实验:找两台虚拟机,一台当网关,一台当内网PC,自己把NAT配一遍,然后用tcpdump抓包看连接建立过程;自己改坏一次resolv.conf,体验一下“什么都上不了网”的感觉,再动手修好;自己开一台dnsmasq,给局域网内一个自定义域名做解析,看看返回结果跟外部DNS有什么不同。这些实验做完之后,DNS、ICMP、NAPT就不再是书本上的抽象名词,而是脑子里实实在在的网络模型。
还要提醒一点,网络排障一定要懂得“留证据”。不要等出问题的时候才想起来抓包,平时就要养成习惯:修改配置前备份原文件,操作命令前记录当前状态,出现故障第一时间保留dmesg、conntrack -L、iptables -L -n -v的输出。很多问题事后复盘时,缺少当时的现场状态,就只能靠猜。我吃过的亏已经不少了,希望大家不要再重复踩这个坑。
最后再分享一个我个人的小技巧:排查DNS问题时,不要只测一个域名。很多设备的DNS策略对常见大域名和冷门域名结果不一样,多测几个不同域名的解析,比单测一个更接近真实用户感受。与之类似,排查NAPT问题时,不要只看当前一条连接,要看整体连接数、表容量和老化超时,很多间歇性网络故障的根因都藏在“量”上。这套“DNS、ICMP、NAPT”的组合拳,我不敢说能解决所有网络问题,但至少能覆盖掉我日常遇到的八成以上,希望对你有用。