☰
👁️ 预览
取消
💾 保存
C
Claude
▾
C
Claude
claude@note.center
🔔
消息通知
🔄
账号切换
⚙️
设置
🚪
退出登录
Markdown
富文本
# 防火墙与 TCP/IP 网络基础学习笔记 > 学习日期:2026-07-08 > 背景:个人运维平台(zhoujiayi.xyz)无法访问,排查发现是 firewalld 启动后拦截了 80/443 端口 > 完整记录这次学习过程中的每个问题和讲解 --- ## 一、故障排查:网站无法访问 ### 现象 用户反馈「个人运维平台无法访问此网站」,域名是 zhoujiayi.xyz。 ### 排查命令与解说 #### 1. 查看端口监听:`ss -tlnp` **命令:** ```bash ss -tlnp ``` **命令拆解:** | 参数 | 含义 | |------|------| | `ss` | Socket Statistics,查看套接字状态,替代旧的 `netstat` | | `-t` | TCP 协议 | | `-l` | 只显示 LISTEN(监听)状态的端口 | | `-n` | 不解析服务名,直接显示端口号 | | `-p` | 显示使用该端口的进程信息 | **执行结果:** ``` State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 128 127.0.0.1:5001 0.0.0.0:* users:(("python3",pid=2594376,fd=4)) LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=877734,fd=7)) LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1031,fd=3)) LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=877734,fd=10)) LISTEN 0 4096 0.0.0.0:3306 0.0.0.0:* users:(("docker-proxy",pid=2410519,fd=8)) ``` **逐行解读:** | 端口 | 进程 | 说明 | |------|------|------| | 127.0.0.1:5001 | python3 (Flask) | 只监听本地,Nginx 代理访问 | | 0.0.0.0:80 | nginx | HTTP,对外公开 | | 0.0.0.0:22 | sshd | SSH 远程登录 | | 0.0.0.0:443 | nginx | HTTPS,对外公开 | | 0.0.0.0:3306 | docker-proxy | MySQL 容器端口映射 | > `0.0.0.0` 表示绑定所有网络接口(公网可访问),`127.0.0.1` 只绑定本地环回(只有本机可访问)。 **结论:** Flask、Nginx、SSH、MySQL 都在正常监听 ✅ #### 2. 本地测试服务是否正常 ```bash # 测试 Flask 本身 curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:5001/ # 返回 200 # 测试 Nginx HTTP(应该重定向到 HTTPS) curl -I http://localhost/ # 返回 301 # 测试 Nginx HTTPS curl -sk -o /dev/null -w "%{http_code}" https://localhost/ # 返回 200 ``` **命令拆解:** | 参数 | 含义 | |------|------| | `-s` | Silent,静默模式,不显示进度 | | `-o /dev/null` | 丢弃响应体,只看状态码 | | `-w "%{http_code}"` | 输出 HTTP 状态码 | | `-I` | HEAD 请求,只返回响应头 | | `-k` | 允许自签名证书(insecure) | **结论:** 本地访问一切正常 ✅ #### 3. 检查防火墙状态:`systemctl status firewalld` ```bash systemctl status firewalld ``` **命令拆解:** | 参数 | 含义 | |------|------| | `systemctl` | Systemd 服务管理器,管理 Linux 系统服务 | | `status` | 查看服务状态 | | `firewalld` | 防火墙服务名称 | **执行结果:** ``` ● firewalld.service - firewalld - dynamic firewall daemon Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; preset: disabled) Active: active (running) since Tue 2026-07-07 18:44:55 CST; 19h ago Docs: man:firewalld(1) Main PID: 2966394 (firewalld) Tasks: 2 (limit: 4341) Memory: 20.7M (peak: 39.3M) CPU: 928ms CGroup: /system.slice/firewalld.service └─2966394 /usr/bin/python3 /usr/sbin/firewalld --nofork --nopid ``` **逐行解读:** | 行 | 含义 | |----|------| | `● firewalld.service` | 服务名称,`●` 表示正在运行(白色=未运行) | | `Loaded: loaded` | 服务配置文件已加载 | | `(disabled; preset: disabled)` | ⚠️ 服务器重启后**不会自启动** | | `Active: active (running)` | ✅ 当前正在运行 | | `since ... 18:44:55` | 🔑 **关键线索:昨天18:44才启动的!** | | `Main PID: 2966394` | 进程ID | | `Memory: 20.7M` | 占用内存 | | `CGroup` | Control Group,systemd 的资源隔离 | | `└─2966394 /usr/bin/python3 /usr/sbin/firewalld` | 实际执行的命令 | **这里有个重要问题:** 为什么网站原来可以访问?因为这个 firewalld 是**昨天下午18:44**才启动的!之前防火墙没运行,所以 80/443 畅通。启动之后它的默认规则只放行了 ssh,80/443 就被拦截了。 #### 4. 查看防火墙规则:`firewall-cmd --list-all` ```bash firewall-cmd --list-all ``` **命令拆解:** | 参数 | 含义 | |------|------| | `firewall-cmd` | firewalld 的命令行管理工具 | | `--list-all` | 列出当前 zone 的所有配置 | **完整输出和逐行解读:** ``` public (active) target: default icmp-block-inversion: no interfaces: eth0 sources: services: dhcpv6-client mdns ssh ports: protocols: forward: yes masquerade: no forward-ports: source-ports: icmp-blocks: rich rules: ``` **逐行讲解:** | 行 | 含义 | |----|------| | `public (active)` | 当前 zone 是 public,且正在使用中 | | `target: default` | 没有规则匹配时走默认行为(通常 reject) | | `icmp-block-inversion: no` | 不反转 ICMP 拦截 | | `interfaces: eth0` | 此 zone 应用在 eth0 网卡上 | | `sources:(空)` | 没有指定来源 IP 限制 | | `services: dhcpv6-client mdns ssh` | 🔑 **放行的服务列表:IPv6自动配置 / 多播DNS / SSH** | | `ports:(空)` | 没有额外开放端口 | | `protocols:(空)` | 没有放行特定协议 | | `forward: yes` | 允许转发 | | `masquerade: no` | 不开启 NAT 伪装 | | `forward-ports:(空)` | 没有端口转发 | | `source-ports:(空)` | 没有源端口限制 | | `icmp-blocks:(空)` | 没有拦截 ICMP | | `rich rules:(空)` | 没有富规则 | > **关键发现:** `services` 里只有 `dhcpv6-client mdns ssh`,没有 `http` 和 `https`,`ports` 也是空的 → **80 和 443 端口没有被放行** ❌ #### 5. 查看 iptables 兼容层(踩坑演示) ```bash iptables -L -n ``` **命令拆解:** | 参数 | 含义 | |------|------| | `iptables` | 传统的 Linux 防火墙工具 | | `-L` | List,列出规则 | | `-n` | Numeric,不解析名称,直接显示数字 | **输出(节选):** ``` Chain INPUT (policy ACCEPT) target prot opt source destination YJ-FIREWALL-INPUT all -- 0.0.0.0/0 0.0.0.0/0 ``` **陷阱:** 看到 `policy ACCEPT` 很容易误以为防火墙没拦截。但新版系统(CentOS 8+ / OpenCloudOS)的 firewalld 默认用 **nftables** 后端,`iptables` 命令只是一个兼容层,**看不到真实规则**。 #### 6. 查看 nftables 真实规则:`nft list ruleset` ```bash nft list ruleset ``` **命令拆解:** | 参数 | 含义 | |------|------| | `nft` | nftables 的命令行工具 | | `list ruleset` | 列出所有表和链的全部规则 | **输出节选+讲解:** ``` table inet firewalld { ``` **`table inet firewalld`** → 定义一个名为 firewalld 的表,`inet` 表示同时处理 IPv4 和 IPv6。 ``` chain filter_IN_public_allow { tcp dport 22 ct state { new, untracked } accept ip daddr 224.0.0.251 udp dport 5353 ct state { new, untracked } accept ip6 daddr ff02::fb udp dport 5353 ct state { new, untracked } accept ip6 daddr fe80::/64 udp dport 546 ct state { new, untracked } accept tcp dport 80 ct state { new, untracked } accept tcp dport 443 ct state { new, untracked } accept } ``` **说明:** 这是修复后的规则。修复前只有前 4 行,没有 `http` 和 `https`。 **规则解释:** | 规则 | 含义 | |------|------| | `tcp dport 22` | TCP 目标端口 22(SSH) | | `ct state { new, untracked }` | 新连接或不需要追踪的连接 | | `accept` | 允许通过 | | `ip daddr 224.0.0.251` | 目标 IP 是组播地址(mDNS) | | `udp dport 5353` | UDP 目标端口 5353 | | `tcp dport 80` | TCP 目标端口 80(HTTP)— 修复后新增 | | `tcp dport 443` | TCP 目标端口 443(HTTPS)— 修复后新增 | ``` chain filter_IN_public { jump filter_INPUT_POLICIES_pre jump filter_IN_public_pre jump filter_IN_public_log jump filter_IN_public_deny jump filter_IN_public_allow jump filter_IN_public_post jump filter_INPUT_POLICIES_post meta l4proto { icmp, ipv6-icmp } accept reject with icmpx admin-prohibited } ``` **规则匹配流程(从上到下,匹配第一条就停止):** ``` 请求进来(比如浏览器访问 443 端口) → jump filter_IN_public_pre(预处理) → jump filter_IN_public_log(日志) → jump filter_IN_public_deny(黑名单检查) → jump filter_IN_public_allow(白名单检查) → dport 22? → 不是,下一条 → dport 5353? → 不是,下一条 → dport 80? → 不是,下一条 → dport 443? → ✅ 匹配!accept 放行 ← 修复后 → jump filter_IN_public_post(后处理) → ICMP 规则(ping) → ⭐ reject with icmpx admin-prohibited(兜底:全部拒绝) ``` > **修复之前:** 没有 80/443 规则 → 经过 allow 链没有匹配 → 回到主链 → 跳过前面的 → 落到最后的 `reject` → ❌ 被拒 **总结:iptables vs nftables 的关系** | 工具 | 本质 | 适用场景 | |------|------|---------| | `iptables` | 旧版防火墙工具 | CentOS 7 及之前 | | `nftables` | 新版防火墙引擎 | CentOS 8+ / OpenCloudOS | | `firewall-cmd` | firewalld 管理工具 | 统一管理,不管后端是啥 | | `nft` | nftables 直接操作工具 | 查看/调试底层规则 | > 🔑 **关键教训:** 新系统上 `iptables -L -n` 看到的是**兼容层**,并非真实规则。查防火墙一定先用 `systemctl status firewalld`,再用 `firewall-cmd --list-all`。 #### 7. 测试公网 IP 连通性 ```bash timeout 5 bash -c 'echo > /dev/tcp/43.134.84.11/80' 2>&1 && echo "可达" || echo "不可达" ``` **命令拆解:** - `timeout 5` — 5 秒超时,防止命令卡住 - `bash -c '...'` — 用 Bash 执行命令 - `echo > /dev/tcp/IP/端口` — Bash 内置的 TCP 连接测试(不是普通文件!) - `&&` — 前面命令执行成功(连接建立)才执行后面的 - `||` — 前面命令执行失败才执行后面的 - `2>&1` — 将错误输出重定向到标准输出 **执行结果:** ``` bash: connect: No route to host 端口80不可达 ``` 从服务器内部连自己的公网 IP 的 80 端口都提示 "No route to host" — 说明流量在到达网卡之前就被拦截了,这是防火墙(不是服务本身)的问题。 作为对比,ping(ICMP)是通的: ```bash ping -c 2 43.134.84.11 # 64 bytes from 43.134.84.11: icmp_seq=1 ttl=63 time=1.11ms ✅ ``` #### 8. 确认云平台 ```bash curl -s http://metadata.tencentyun.com/latest/meta-data/ ``` 返回了 `app-id`、`instance-id`、`public-ipv4` 等信息,确认是腾讯云服务器。 > 注意:当时老师一开始错误地认为是腾讯云安全组的问题,实际上是因为没先查 `systemctl status firewalld` 看到 firewalld 刚刚启动。正确顺序应该是先查本地防火墙,最后再查云平台安全组。 --- ### 修复 ```bash # 放行 HTTP 和 HTTPS(永久生效) firewall-cmd --add-service=http --add-service=https --permanent # 重载配置使永久规则生效 firewall-cmd --reload ``` **命令拆解:** 第一行: | 参数 | 含义 | |------|------| | `--add-service=http` | 放行 HTTP 服务(等价于 `--add-port=80/tcp`) | | `--add-service=https` | 放行 HTTPS 服务(等价于 `--add-port=443/tcp`) | | `--permanent` | 写入磁盘配置文件,reload/重启后永久保留 | 第二行: | 参数 | 含义 | |------|------| | `--reload` | 重新加载磁盘配置到运行时,使 `--permanent` 规则生效 | **关于 `--permanent` 和 `--reload` 的完整说明:** firewalld 的规则分两层: | 层 | 位置 | 特点 | |----|------|------| | **运行时(Runtime)** | 内存 | 立即生效,但 reload/重启后丢失 | | **永久(Permanent)** | 磁盘文件 | 重启保留,但需要 reload 才生效 | 四种操作对比: | 操作 | 立即生效? | 重启保留? | |------|-----------|-----------| | `--add-service=http` | ✅ | ❌ | | `--add-service=http --permanent` | ❌ | ✅ | | `--permanent` + 单独 `--reload` | ✅ | ✅ | | 不加 `--permanent` 然后 `--reload` | 规则会丢失! | — | **验证修复:** ```bash firewall-cmd --list-services # 输出: dhcpv6-client http https mdns ssh ✅ timeout 5 bash -c 'echo > /dev/tcp/43.134.84.11/80' 2>&1 && echo "可达" # 端口80 ✅ 可达 timeout 5 bash -c 'echo > /dev/tcp/43.134.84.11/443' 2>&1 && echo "可达" # 端口443 ✅ 可达 ``` --- ## 二、防火墙基础知识 ### 问题 1:防火墙和端口是什么关系? 防火墙就像大楼的保安,服务器 IP 是大楼地址,端口是门牌号。 常用端口: | 端口 | 服务 | 说明 | |------|------|------| | 22 | SSH | 管理员远程登录 | | 80 | HTTP | 网页访问(无加密) | | 443 | HTTPS | 网页访问(加密) | | 21 | FTP | 文件传输 | | 25 | SMTP | 发邮件 | | 3306 | MySQL | 数据库 | | 6379 | Redis | 缓存 | | 5001 | Flask | 自建应用 | 防火墙的职责: 1. 哪些端口对外开放 2. 什么 IP 可以访问 3. 什么协议可以通过 4. 连接状态追踪 --- ### 问题 2:firewalld 的 Zone(区域)是什么? **Zone 是一套预定义的规则模板**,不同场景用不同 Zone。 **查看系统所有 Zone:** ```bash firewall-cmd --get-zones ``` ``` block dmz drop external home internal nm-shared public trusted work ``` **常用 Zone 对比:** | Zone | 场景 | 通常开放的服务 | |------|------|--------------| | **public**(默认) | 公网服务器 | ssh, http, https, dhcpv6-client, mdns | | trusted | 内网/家庭网络 | 全部放行 | | drop | 高危环境 | 全部丢弃(无任何回复) | | block | 受限环境 | 全部拒绝(回复"不可达") | | internal | 公司内网 | ssh, samba-client, mdns, dhcpv6-client | | external | 外网网关 | ssh(其余受限) | | dmz | 隔离区 | ssh(只允许管理) | | home | 家庭网络 | ssh, samba-client, mdns, dhcpv6-client | | work | 工作网络 | ssh, dhcpv6-client | **指定 Zone 查看规则:** ```bash firewall-cmd --zone=public --list-all firewall-cmd --zone=trusted --list-all ``` **Zone 的用途:** 服务器有多个网口时,可以给不同网口分配不同 Zone: ```bash firewall-cmd --zone=public --change-interface=eth0 # 外网口:严格限制 firewall-cmd --zone=internal --change-interface=eth1 # 内网口:开放更多 ``` --- ### 问题 3:`--add-port=80/tcp` 为什么必须加 `/tcp`? 测试结果: ```bash firewall-cmd --add-port=80 # Error: INVALID_PORT: bad port (most likely missing protocol), # correct syntax is portid[-portid]/protocol ``` 因为**同一个端口号 + 不同协议可以是完全不同的服务**: | 端口 | TCP 用途 | UDP 用途 | |------|---------|---------| | 53 | DNS 区域传送(服务器间同步) | DNS 普通查询(日常上网) | | 80 | HTTP 网页 | 视频流(极少用) | | 443 | HTTPS | QUIC/HTTP3 | 格式规则: ```bash firewall-cmd --add-port=80/tcp # ✅ 单个端口+协议 firewall-cmd --add-port=80 # ❌ 不写协议 → 报错 firewall-cmd --add-port=3000-3010/tcp # ✅ 端口范围 firewall-cmd --add-port=53/tcp --add-port=53/udp # ✅ TCP+UDP 同时放行 ``` --- ### 问题 4:`--add-service=http` 和 `--add-port=80/tcp` 有什么区别? 本质一样。`--add-service=http` 是 firewalld 帮你封装好的快捷方式: ```bash cat /usr/lib/firewalld/services/http.xml ``` ```xml <?xml version="1.0" encoding="utf-8"?> <service> <short>WWW (HTTP)</short> <description>HTTP is the protocol used to serve Web pages...</description> <port protocol="tcp" port="80"/> </service> ``` 推荐原则: - 标准服务用 `--add-service=http`(更语义化) - 自建服务用 `--add-port=5001/tcp`(灵活) --- ## 三、TCP 与 UDP 的区别 ### 核心对比 **Q:TCP 和 UDP 本质上有什么区别?** 用生活类比: | | TCP | UDP | |--|-----|-----| | **类比** | 📞 **打电话** | 📡 **对讲机** | | **连接** | 拨号→对方接起→"喂?"→"能听到吗?" | 按下去直接说,不管对方在不在 | | **传输过程** | 发一个包→等确认→收到→发下一个 | 直接发,不确认 | | **丢包处理** | 重发,保证完整 | 丢了就丢了 | | **顺序保证** | 按发送顺序到达 | 可能先发的后到 | | **速度** | 慢(每次要确认) | 快(没有确认过程) | | **头部大小** | 20-60 字节 | 8 字节 | **实际应用:** **用 TCP 的场景(必须完整准确):** - HTTP/HTTPS 网页浏览:页面必须完整加载 - SSH:按键不能丢 - 邮件(SMTP):内容不能缺 - 文件下载(FTP):文件损坏一点就没法用 **用 UDP 的场景(追求速度,能容忍少量丢包):** - 视频直播:卡一帧无所谓,但等重发就卡死了 - 在线游戏:开枪要即时显示 - DNS 查询:一问一答,没必要建连接 - 语音通话:丢几个字能猜出来,延迟更致命 --- ## 四、TCP 三次握手 **Q:TCP 建立连接时具体怎么握手的?** 先用 tcpdump 抓包看真实过程: ```bash # 在终端1运行:抓取三次握手 timeout 3 tcpdump -i lo -c 3 -nn -tt 'tcp port 443 and (tcp[tcpflags] & (tcp-syn|tcp-ack) != 0)' # 在终端2运行:发起 HTTPS 请求 curl -sk https://127.0.0.1/ ``` **tcpdump 命令拆解:** | 参数 | 含义 | |------|------| | `tcpdump` | 网络抓包工具 | | `-i lo` | 监听 lo(本地环回)网卡 | | `-c 3` | 只抓 3 个包 | | `-nn` | 不解析主机名和端口名 | | `-tt` | 显示完整时间戳 | | `'tcp port 443 ...'` | 过滤条件:只抓 443 端口且包含 SYN/ACK 标志的 TCP 包 | **抓到的三个包:** **① 第一次握手(SYN):** ``` 127.0.0.1.58046 > 127.0.0.1.443: Flags [S], seq 2669367652 ``` | 字段 | 含义 | |------|------| | `127.0.0.1.58046` | 源 IP.源端口(客户端随机选的临时端口) | | `>` | 流向标记:从源到目标 | | `127.0.0.1.443` | 目标 IP.目标端口 | | `Flags [S]` | SYN 标志位(Synchronize),请求建立连接 | | `seq 2669367652` | 序列号,用于后续数据排序和确认 | > **客户端说:**"你好,我想连接你的 443 端口,这是我的序列号 2669367652" **② 第二次握手(SYN-ACK):** ``` 127.0.0.1.443 > 127.0.0.1.58046: Flags [S.], seq 2641890000, ack 2669367653 ``` | 字段 | 含义 | |------|------| | `Flags [S.]` | SYN + ACK 标志 | | `seq 2641890000` | 服务器自己的序列号 | | `ack 2669367653` | 确认号 = 客户端的 seq + 1 | > **服务器说:**"收到你的连接请求!这是我的序列号 2641890000,我收到了你的第 2669367652 号包(所以我期望你下一个发 2669367653)" **③ 第三次握手(ACK):** ``` 127.0.0.1.58044 > 127.0.0.1.443: Flags [.], ack 1, win 11, length 0 ``` | 字段 | 含义 | |------|------| | `Flags [.]` | 纯 ACK 标志,不带数据 | | `ack 1` | 确认号(简化显示,实际是服务器的 seq+1) | | `length 0` | 这条包不携带数据,纯粹是确认 | > **客户端说:**"好的,我确认收到了你的序列号,连接建立完毕!" **完整流程图:** ``` 客户端 服务器 │ │ │ ── ① SYN, seq=2669367652 ───────→ │ 客户端:我要连443! │ │ │ ← ② SYN-ACK, seq=2641890000 ───── │ 服务器:收到,这是我的seq │ ack=2669367653 │ 确认收到你的seq │ │ │ ── ③ ACK, ack=2641890001 ───────→ │ 客户端:好,确认收到! │ │ │ ←──────── 开始传数据 ────────────→ │ ✅ 连接建立成功 ``` **Q:为什么非要三次?两次不行吗?** 假设只有两次握手: ``` 场景:客户端发 SYN 建立连接,这个 SYN 在网络中延迟了 时间线: ① 客户端发 SYN ──→(网络延迟中) ② 客户端等太久,以为丢了 → 重发 SYN → 服务器回 SYN-ACK → 连接建立 ③ 传输数据 → 完成 → 关闭连接 ④ ❗ 延迟的 SYN 终于到达服务器 ⑤ 服务器以为新连接 → 回 SYN-ACK → 资源分配 → 傻等客户端发数据 ⑥ 客户端根本不知道这个"连接"存在 → 浪费服务器资源 ``` 三次握手解决了这个问题: ``` 第三次 ACK 是客户端对服务器 SYN-ACK 的确认 如果客户端根本没想建立连接,就不会回 ACK 服务器收不到 ACK → 知道这是无效连接 → 不浪费资源 ``` **一句话:三次握手保证了双方都有收发能力,避免了"空连接"浪费资源。** --- ## 五、TCP 滑动窗口 **Q:TCP 怎么保证传输效率的?什么是滑动窗口?** **没有窗口的传输(停等协议):** ``` 客户端 → 发 包1 → 服务器 客户端 ← ACK1 ← 服务器 客户端 → 发 包2 → 服务器 客户端 ← ACK2 ← 服务器 ... 每发一个包就要等确认,效率极低 ``` **滑动窗口的传输:** ``` 窗口大小=4(一次可以连续发4个包,不用等) 客户端 → 发包1 ─┐ 客户端 → 发包2 ├─ 一口气发出去 客户端 → 发包3 │ 客户端 → 发包4 ─┘ ← ACK 包1 ✅ 客户端 → 发包5 ← 收到ACK,窗口往前滑 ← ACK 包2 ✅ 客户端 → 发包6 ← 继续发 像一扇窗在数据流上滑动: ┌───────────┐ │1│2│3│4│5│6│7│8│... └───────────┘ ↑收到ACK1→窗口右移 ┌───────────┐ │2│3│4│5│6│7│... └───────────┘ ``` **窗口大小是动态协商的:** 从抓包可以看到,建立连接时双方就协商了窗口大小: ``` 第一次握手: win=43690 ← 客户端说"我一次能收 43690 字节" 第二次握手: win=65483 ← 服务器说"我一次能收 65483 字节" ``` 系统配置: ```bash sysctl net.ipv4.tcp_wmem # 4096 65536 134217728 # 最小 默认 最大(128MB) sysctl net.ipv4.tcp_rmem # 4096 87380 134217728 ``` | 值 | 含义 | |----|------| | 4096(最小) | 每个 TCP 连接至少分配 4KB 缓冲区 | | 65536/87380(默认) | 新建连接时的初始窗口大小 | | 134217728(最大) | 网络通畅时最多能涨到 128MB | **窗口大小动态调整:** - 网络通畅 + 不丢包 → **窗口增大**(一次发更多包,更快) - 出现丢包 → **窗口缩小**(减少并发,降低丢包率) 这就是 TCP 能同时适应光纤(高带宽)和卫星链路(高延迟)的关键机制。 --- ## 六、面试模拟题 以下是在学习过程中老师模拟面试官出的题目: ### 第一题(基础排查) **面试官:** 服务器上 `ss -tlnp` 显示 Nginx 在监听 80 和 443 端口,但从外网访问 `http://你的IP` 就是连不上,你会按什么顺序排查?用哪些命令? **答题思路(标准流程):** ``` 第一步:ss -tlnp → 确认端口在监听 ✅ 第二步:curl localhost → 确认服务本地正常 ✅ 第三步:systemctl status firewalld → 确认防火墙是否在运行 🔑 第四步:firewall-cmd --list-all → 查看放行了哪些服务 第五步:firewall-cmd --add-service=http --add-service=https → 修复 第六步:如果还不行,检查云平台安全组 ``` **追问:** 看到 `iptables -L -n` 显示 `policy ACCEPT`,能确认防火墙没拦截吗? **答:** 不能。新版系统(CentOS 8+ / OpenCloudOS)用 nftables 后端,`iptables` 只是兼容层,看不到完整规则。要用 `firewall-cmd --list-all` 或 `nft list ruleset` 查看真实规则。 --- ### 第二题(概念理解) **面试官:** `iptables -L -n` 显示 `policy ACCEPT`,但你的网站还是被拦了。这是为什么? **答:** 可能有三个原因: 1. **系统用 nftables 后端** — `iptables` 显示的是兼容层,不是真实规则 2. **firewalld 在运行** — 规则通过 nftables 管理,需要用 `firewall-cmd` 查看 3. **云平台安全组** — 腾讯云/阿里云的安全组在虚拟机外部,服务器内部看不到 排查方法: ```bash systemctl status firewalld # 先看防火墙是否运行 firewall-cmd --list-all # 查看真实的放行规则 nft list ruleset # 查看 nftables 底层规则 ``` --- ### 第三题(实操命令) **面试官:** 要用 firewalld 放行一个自定义端口 `8080/tcp`,命令怎么写?要求重启后依然生效。 **答:** ```bash # 方法一:分开写 firewall-cmd --add-port=8080/tcp --permanent firewall-cmd --reload # 方法二:一条命令多个参数 firewall-cmd --add-port=8080/tcp --permanent && firewall-cmd --reload ``` **追问:** 如果还要放行 8080/udp 呢? **答:** ```bash firewall-cmd --add-port=8080/tcp --add-port=8080/udp --permanent && firewall-cmd --reload ``` 可以一条命令加多个 `--add-port` 参数。 --- ### 第四题(理解深度) **面试官:** 为什么 firewalld 要求端口必须带协议写成 `80/tcp`?TCP 和 UDP 在同一个端口号上有什么区别? **答:** 因为端口号只是传输层的一个编号,TCP 和 UDP 是两种完全不同的协议,同一个端口号可以承载不同的服务。例如: - 53/tcp → DNS 区域传送(服务器间同步大量数据) - 53/udp → DNS 普通查询(日常域名解析) 如果用 `--add-port=53` 不写协议,防火墙不知道你要放行 TCP 还是 UDP。 --- ### 第五题(故障排查) **面试官:** 用户说"昨天网站还能访问,今天就不行了"。你登录服务器发现 `systemctl status firewalld` 显示服务是今天凌晨才启动的。可能是什么原因?怎么确认? **答:** 基本可以确定是 firewalld 启动后默认规则拦截了 Web 端口。 确认方法: ```bash firewall-cmd --list-all # 如果 services 里没有 http/https → 就是这个问题 # 验证:临时放行测试 firewall-cmd --add-service=http --add-service=https # 修复:永久放行 firewall-cmd --add-service=http --add-service=https --permanent firewall-cmd --reload ``` **根本原因分析:** firewalld 安装后默认配置只放行 SSH。如果系统重启或服务启动,防火墙规则立即生效。对应到这次故障,就是 firewalld 服务启动后,以前能进来的 80/443 流量被拦截了。如果希望避免类似问题,可以设置 firewalld 不自启动: ```bash systemctl disable firewalld ``` --- ## 七、排查总结 ### 网站无法访问的正确排查顺序 ``` ① ss -tlnp → 进程是否在监听 ② curl localhost / curl IP → 本地服务是否正常 ③ systemctl status firewalld → 🔑 防火墙是否在运行 ④ firewall-cmd --list-all → 查看放行了哪些服务 ⑤ nft list ruleset → 查看 nftables 真实规则 ⑥ 以上都正常 → 最后查云平台安全组 ``` ### 关键教训 1. **不要信任 `iptables -L -n`** — 新系统上它显示的是兼容层,不是真实规则 2. **调试前先看 `systemctl status firewalld`** — 知道防火墙是否在运行 3. **firewalld 默认只放行 ssh** — 装完系统或启动 firewalld 后,记得开放需要的端口 4. **`--permanent` 单独用不会立即生效** — 需要配合 `--reload` 5. **不加 `--permanent` 直接生效** — 但 reload/重启后会丢失 ### 常用命令速查 | 命令 | 用途 | |------|------| | `ss -tlnp` | 查看 TCP 监听端口和进程 | | `systemctl status firewalld` | 查看防火墙运行状态 | | `firewall-cmd --list-all` | 查看当前 zone 全部配置 | | `firewall-cmd --add-service=http --permanent` | 永久放行 HTTP | | `firewall-cmd --add-port=8080/tcp --permanent` | 永久放行自定义端口 | | `firewall-cmd --reload` | 重载配置 | | `firewall-cmd --get-zones` | 列出所有 zone | | `nft list ruleset` | 查看 nftables 真实规则 | | `curl -s -o /dev/null -w "%{http_code}" http://...` | 只检查 HTTP 状态码 | | `timeout 3 bash -c 'echo > /dev/tcp/IP/PORT'` | 测试 TCP 端口连通性 | | `tcpdump -i lo -c 3 -nn 'tcp port 443'` | 抓 TCP 包 | | `sysctl net.ipv4.tcp_wmem` | 查看 TCP 窗口大小配置 | ### 排查时的错误判断 **老师当时的错误:** 看到 `iptables -L -n` 显示 `policy ACCEPT`,同时发现服务器是腾讯云实例,从本机连公网 IP 的 80/443 又连不上,就错误地推断是腾讯云安全组的问题。实际上是因为没先跑 `systemctl status firewalld`,没发现 firewalld 昨天刚刚启动。正确做法是先查本地防火墙状态,这是排查的第一步。
预览
编辑内容后实时预览...