端口连接异常排查全流程思路

描述

一、概述

1.1 背景介绍

TCP/IP 网络通信的本质是进程间通信。两台机器之间的数据传输,最终落到"某个 IP 地址的某个端口"这个粒度。端口号是传输层的概念,范围 0-65535,其中 0-1023 为系统保留端口(Well-Known Ports),1024-49151 为注册端口(Registered Ports),49152-65535 为动态端口(Ephemeral Ports)。

当运维人员说"端口不通"时,实际可能指若干种完全不同的故障:

服务未监听:目标端口上没有进程在监听,内核直接返回 RST

防火墙拦截:数据包被 iptables/nftables/firewalld 规则丢弃或拒绝

安全策略阻断:SELinux 阻止进程绑定非标准端口

网络路径不可达:中间路由设备、交换机 ACL、云安全组阻断

应用层协议不匹配:TCP 连接建立成功但应用层握手失败

这五种情况的排查方法完全不同。如果不分层次地乱查,可能花几个小时还找不到原因。

TCP 连接建立过程

理解端口不通的排查逻辑,首先需要理解 TCP 三次握手:

 

客户端                          服务端
  |                               |
  |  SYN (seq=x)                  |
  |------------------------------>|
  |                               |
  |  SYN-ACK (seq=y, ack=x+1)    |
  |<------------------------------|
  |                               |
  |  ACK (seq=x+1, ack=y+1)      |
  |------------------------------>|
  |                               |
  |  连接建立完成                   |

 

三次握手中任何一步出问题都会导致"端口不通"的表象,但根因截然不同:

阶段 现象 常见原因
SYN 发出后无响应 连接超时(通常 30-120 秒) 防火墙 DROP、路由不可达、目标主机宕机
SYN 发出后收到 RST 连接立即被拒绝 端口未监听、防火墙 REJECT
SYN-ACK 已收到但 ACK 丢失 连接超时 客户端出方向防火墙、NAT 问题
三次握手成功但随后断开 连接重置 应用层拒绝、SSL/TLS 握手失败

UDP 的特殊性

UDP 没有连接建立过程,判断"通不通"比 TCP 更复杂:

UDP 端口未监听时,内核返回 ICMP Port Unreachable

如果防火墙丢弃了 ICMP 响应,客户端无法区分"端口不通"和"数据包丢失"

UDP 探测的可靠方法是发送应用层协议数据包并观察是否有响应

1.2 端口不通的七个排查层级

按照从近到远、从简单到复杂的原则,端口不通的排查可以分为七个层级:

 

第一层:服务监听检查(本机)
  ↓
第二层:本地防火墙(iptables/nftables/firewalld)
  ↓
第三层:SELinux 端口策略
  ↓
第四层:本机连通性测试(telnet/nc/curl)
  ↓
第五层:网络路径排查(traceroute/mtr)
  ↓
第六层:云安全组 / 网络 ACL
  ↓
第七层:应用层协议检查

 

每一层只要确认没问题,就往下一层走。这种分层排查法避免了无头苍蝇式的排查。

1.3 适用场景

部署新服务后外部无法访问

服务迁移后端口不通

网络变更后部分端口受影响

容器化部署中的端口映射问题

跨云/跨机房的连通性故障

自动化巡检中的端口健康检查

1.4 环境要求

组件 版本要求 说明
操作系统 Ubuntu 24.04 LTS / Rocky Linux 9.5 内核 6.12+
iproute2 6.x 提供 ss 命令
nftables 1.1.x 新一代防火墙框架
firewalld 2.x 高级防火墙管理
nmap 7.95+ 端口扫描与探测
ncat/nc nmap 附带 连通性测试
mtr 0.95+ 网络路径分析

 

# Ubuntu 24.04 安装必要工具
sudo apt install -y iproute2 nmap netcat-openbsd traceroute mtr-tiny tcpdump

# Rocky Linux 9.5 安装
sudo dnf install -y iproute nmap ncat traceroute mtr tcpdump

 

二、详细步骤

2.1 准备工作

在排查端口不通之前,先明确几个关键信息:

 

# 确认目标 IP 和端口
TARGET_IP="192.168.1.100"
TARGET_PORT="8080"

# 确认本机 IP
ip addr show | grep "inet " | grep -v 127.0.0.1

# 确认本机到目标的基本网络连通性
ping -c 3 $TARGET_IP

 

如果 ping 不通,问题可能在更底层(网络层),需要先解决 IP 可达性问题。但注意:ping 不通不代表端口不通——很多环境禁止 ICMP 但允许 TCP。

2.2 第一层:服务是否监听

这是最常见的原因,也是最先排查的。

使用 ss 命令(推荐)

 

# 查看所有 TCP 监听端口
ss -tlnp

# 查看特定端口是否在监听
ss -tlnp | grep ":8080"

# 输出示例
# LISTEN  0  4096  *:8080  *:*  users:(("java",pid=12345,fd=88))

 

ss 命令参数解释:

-t:只显示 TCP

-l:只显示 LISTEN 状态

-n:数字格式显示端口(不做 DNS 解析)

-p:显示进程信息

监听地址的区别

 

# 查看监听地址
ss -tlnp | grep ":8080"

 

监听地址有三种情况,含义完全不同:

监听地址 含义 外部是否可访问
0.0.0.0:8080 监听所有 IPv4 地址 可以
*:8080  或 :::8080 监听所有地址(含 IPv6) 可以
127.0.0.1:8080 只监听本地回环 不可以
192.168.1.100:8080 只监听特定网卡 仅该网卡可达

最常见的坑:应用配置文件里 bind 地址写了 127.0.0.1 或 localhost,导致只能本机访问。

 

# 典型的配置错误排查
# Nginx
grep -r "listen" /etc/nginx/nginx.conf /etc/nginx/conf.d/

# MySQL
grep "bind-address" /etc/mysql/mysql.conf.d/mysqld.cnf

# Redis
grep "bind" /etc/redis/redis.conf

# Java 应用的 application.yml
grep -r "server.address" /path/to/app/

 

使用 netstat(兼容方案)

 

# 部分老系统还在用 netstat
netstat -tlnp | grep ":8080"

 

netstat 已经被 ss 取代,但在某些极简容器镜像中可能只有 netstat。功能上两者等价。

服务未监听的常见原因

原因 排查命令 解决方法
服务未启动 systemctl status 启动服务
服务启动失败 journalctl -u --no-pager -n 50 查看错误日志
端口被其他进程占用 ss -tlnp | grep ":" 停止冲突进程或更换端口
配置文件端口写错 检查服务配置文件 修正端口配置
bind 地址错误 检查 bind/listen 配置 改为 0.0.0.0

2.3 第二层:本地防火墙

确认服务已经在监听后,下一步检查本机防火墙是否放行了该端口。

firewalld(CentOS/Rocky Linux 默认)

 

# 查看 firewalld 是否运行
systemctl status firewalld

# 查看当前活动区域和规则
firewall-cmd --get-active-zones
firewall-cmd --zone=public --list-all

# 查看是否放行了特定端口
firewall-cmd --zone=public --query-port=8080/tcp

# 放行端口(临时,重启失效)
sudo firewall-cmd --zone=public --add-port=8080/tcp

# 放行端口(永久)
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
sudo firewall-cmd --reload

# 放行服务(推荐方式,比端口号更语义化)
sudo firewall-cmd --zone=public --add-service=http --permanent
sudo firewall-cmd --reload

 

nftables(新一代框架)

Ubuntu 24.04 默认使用 nftables 作为底层框架:

 

# 查看当前规则集
sudo nft list ruleset

# 查看特定链的规则
sudo nft list chain inet filter input

# 添加放行规则
sudo nft add rule inet filter input tcp dport 8080 accept

# 查看规则编号(用于删除)
sudo nft -a list chain inet filter input

# 删除指定规则
sudo nft delete rule inet filter input handle 15

 

iptables(传统方式)

 

# 查看所有规则(带行号)
sudo iptables -L -n -v --line-numbers

# 查看 INPUT 链
sudo iptables -L INPUT -n -v --line-numbers

# 检查是否有 DROP/REJECT 规则拦截了目标端口
sudo iptables -L INPUT -n | grep -E "DROP|REJECT"

# 添加放行规则(插入到 INPUT 链的第一条)
sudo iptables -I INPUT 1 -p tcp --dport 8080 -j ACCEPT

# 持久化规则
sudo netfilter-persistent save    # Ubuntu/Debian
sudo service iptables save        # CentOS/RHEL

 

防火墙排查要点

 

# 快速判断是否是防火墙问题的方法:临时关闭防火墙测试
# 注意:仅在测试环境使用,生产环境禁止关闭防火墙

# 查看 iptables 规则计数器,确认数据包是否命中了 DROP 规则
sudo iptables -L -n -v | grep -i drop

# 实时监控防火墙日志
sudo journalctl -f -t kernel | grep -i "iptables|nft|BLOCKED"

 

一个容易忽略的坑:Docker 会自动在 iptables 中创建 DOCKER 链和 NAT 规则。如果你在 Docker 宿主机上排查端口问题,需要同时检查 DOCKER-USER 链:

 

# Docker 相关的 iptables 规则
sudo iptables -L DOCKER -n -v
sudo iptables -L DOCKER-USER -n -v
sudo iptables -t nat -L -n -v

 

2.4 第三层:SELinux 端口策略

在 Rocky Linux / CentOS 等开启 SELinux 的系统上,即使防火墙放行了端口,SELinux 也可能阻止服务绑定非标准端口。

 

# 查看 SELinux 状态
getenforce
# 输出 Enforcing 表示开启,Permissive 表示仅记录不阻断

# 查看 SELinux 允许的 HTTP 端口
semanage port -l | grep http_port_t
# http_port_t  tcp  80, 81, 443, 488, 8008, 8009, 8443, 9000

# 如果需要 Nginx 监听 8080 端口
sudo semanage port -a -t http_port_t -p tcp 8080

# 查看 SELinux 拒绝日志
sudo ausearch -m avc -ts recent

# 查看 audit.log 中的 SELinux 拒绝记录
sudo grep "denied" /var/log/audit/audit.log | tail -10

# 生成允许策略(排查时使用)
sudo ausearch -m avc -ts recent | audit2allow -w

 

SELinux 导致端口不通的典型特征:

ss -tlnp 看不到服务在目标端口监听

服务日志报 "Permission denied" 或 "Address already in use"(误导性信息)

audit.log 中有 type=AVC msg=audit(...): avc:  denied 记录

2.5 第四层:本机连通性测试

服务监听正常、防火墙已放行,接下来从其他机器测试连通性。

telnet

 

# 最基础的 TCP 端口测试
telnet 192.168.1.100 8080

# 连接成功的输出
# Trying 192.168.1.100...
# Connected to 192.168.1.100.
# Escape character is '^]'.

# 连接失败(被拒绝)的输出
# Trying 192.168.1.100...
# telnet: Unable to connect to remote host: Connection refused

# 连接超时的输出
# Trying 192.168.1.100...
# (长时间无响应)

 

nc (netcat)

 

# TCP 端口测试(推荐,比 telnet 更灵活)
nc -zv 192.168.1.100 8080

# 设置超时时间(避免长时间等待)
nc -zv -w 5 192.168.1.100 8080

# 扫描端口范围
nc -zv 192.168.1.100 8080-8090

# UDP 端口测试
nc -zuv -w 3 192.168.1.100 53

# 参数说明
# -z: 只扫描,不发送数据
# -v: 详细输出
# -w: 超时时间(秒)
# -u: UDP 模式

 

curl(HTTP/HTTPS 服务)

 

# HTTP 服务测试
curl -v http://192.168.1.100:8080/health

# 只测试连通性,不关心响应内容
curl -o /dev/null -s -w "HTTP Code: %{http_code}
Time: %{time_total}s
" http://192.168.1.100:8080/

# HTTPS 服务测试(忽略证书验证)
curl -vk https://192.168.1.100:443/

# 指定超时
curl --connect-timeout 5 --max-time 10 http://192.168.1.100:8080/

 

nmap(全面端口扫描)

 

# 扫描单个端口
nmap -p 8080 192.168.1.100

# 扫描多个端口
nmap -p 80,443,8080 192.168.1.100

# 扫描端口范围
nmap -p 1-1024 192.168.1.100

# 扫描结果解读
# PORT     STATE    SERVICE
# 8080/tcp open     http-proxy   <-- 端口开放
# 8081/tcp closed   tproxy       <-- 端口关闭(RST)
# 8082/tcp filtered unknown      <-- 被防火墙过滤(DROP)

 

nmap 端口状态含义:

状态 含义 对应情况
open 端口开放 有服务监听且防火墙放行
closed 端口关闭 无服务监听但防火墙放行(返回 RST)
filtered 被过滤 防火墙 DROP,无任何响应
unfiltered 未过滤 ACK 扫描可达但无法确定开放/关闭

2.6 第五层:网络路径排查

从客户端能 ping 通目标但端口不通时,问题可能在中间路径。

traceroute

 

# 基于 ICMP 的路由追踪
traceroute 192.168.1.100

# 基于 TCP 的路由追踪(穿透防火墙)
sudo traceroute -T -p 8080 192.168.1.100

# 基于 UDP 的路由追踪
traceroute -U -p 8080 192.168.1.100

 

mtr(推荐)

 

# 实时路由追踪+丢包统计
mtr 192.168.1.100

# 基于 TCP 的 mtr
sudo mtr --tcp -P 8080 192.168.1.100

# 报告模式(非交互)
mtr -r -c 100 192.168.1.100

# 输出示例
#                        Loss%   Snt   Last   Avg  Best  Wrst StDev
# 1. gateway             0.0%   100    0.5   0.6   0.3   2.1   0.3
# 2. 10.0.0.1            0.0%   100    1.2   1.3   0.8   3.5   0.4
# 3. ???                100.0%  100    0.0   0.0   0.0   0.0   0.0
# 4. 192.168.1.1         0.0%   100    2.1   2.3   1.5   5.2   0.6
# 5. 192.168.1.100       0.0%   100    2.3   2.5   1.8   5.8   0.7

 

mtr 结果解读:

某一跳 100% Loss 但后续正常:该设备不响应探测包,不代表故障

某一跳开始 Loss 升高且后续持续高丢包:该跳可能是瓶颈

最后一跳丢包:目标主机或其前面的设备有问题

tcpdump 辅助判断

在目标服务器上抓包,确认 SYN 包是否到达:

 

# 在目标服务器上抓包
sudo tcpdump -i any -nn port 8080 -c 20

# 如果能看到 SYN 包到达但没有 SYN-ACK 响应,说明是本机防火墙或服务问题
# 如果完全看不到 SYN 包,说明数据包在到达目标服务器之前就被拦截了

# 输出示例(正常连接)
# 1701.123456 IP 10.0.0.5.54321 > 192.168.1.100.8080: Flags [S], seq 123456
# 1701.123567 IP 192.168.1.100.8080 > 10.0.0.5.54321: Flags [S.], seq 789012, ack 123457
# 1701.124012 IP 10.0.0.5.54321 > 192.168.1.100.8080: Flags [.], ack 789013

# 输出示例(SYN 到达但被 RST)
# 1701.123456 IP 10.0.0.5.54321 > 192.168.1.100.8080: Flags [S], seq 123456
# 1701.123567 IP 192.168.1.100.8080 > 10.0.0.5.54321: Flags [R.], seq 0, ack 123457

 

2.7 第六层:云安全组 / 网络 ACL

在云环境中(AWS/阿里云/腾讯云),安全组是独立于操作系统防火墙之外的网络层控制。

 

# 阿里云 CLI 查看安全组规则
aliyun ecs DescribeSecurityGroupAttribute --SecurityGroupId sg-xxxxx

# AWS CLI 查看安全组
aws ec2 describe-security-groups --group-ids sg-xxxxx

# 腾讯云 CLI
tccli cvm DescribeSecurityGroupPolicies --cli-unfold-argument 
  --SecurityGroupId sg-xxxxx

 

云安全组排查要点:

检查项 说明
入站规则 是否允许目标端口的入站流量
出站规则 是否允许响应流量的出站
协议类型 TCP/UDP 是否匹配
源地址范围 是否限制了客户端 IP 段
规则优先级 多条规则冲突时的优先级
安全组关联 实例是否绑定了正确的安全组

常见的云安全组陷阱:

安全组默认拒绝所有入站流量

修改安全组规则后不需要重启实例,但生效可能有几秒延迟

VPC 网络 ACL 和安全组是两层控制,两者都要放行

2.8 第七层:应用层协议检查

TCP 连接能建立但业务层面不通,问题在应用层协议。

 

# HTTP 应用层检查
curl -v http://192.168.1.100:8080/api/health 2>&1

# 检查返回状态码
# HTTP/1.1 200 OK -> 正常
# HTTP/1.1 502 Bad Gateway -> 后端服务不可达
# HTTP/1.1 503 Service Unavailable -> 服务过载

# SSL/TLS 握手检查
openssl s_client -connect 192.168.1.100:443 -servername example.com

# 查看证书信息
openssl s_client -connect 192.168.1.100:443 2>/dev/null | openssl x509 -noout -dates -subject

# gRPC 服务检查
grpcurl -plaintext 192.168.1.100:9090 list

# MySQL 连接测试
mysql -h 192.168.1.100 -P 3306 -u root -p -e "SELECT 1"

# Redis 连接测试
redis-cli -h 192.168.1.100 -p 6379 ping

 

2.9 验证与确认

完成排查并修复后,做一次完整的验证:

 

#!/bin/bash
# 端口连通性验证脚本
# port_verify.sh

TARGET=$1
PORT=$2

echo "========== 端口连通性验证 =========="
echo "目标: ${TARGET}:${PORT}"
echo "时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "===================================="

# 1. DNS 解析
echo -e "
[1] DNS 解析"
host $TARGET 2>/dev/null || echo "非域名或解析失败"

# 2. ICMP 连通性
echo -e "
[2] ICMP Ping"
ping -c 3 -W 2 $TARGET 2>/dev/null
if [ $? -ne 0 ]; then
    echo "ICMP 不通(可能被禁止)"
fi

# 3. TCP 端口测试
echo -e "
[3] TCP 端口测试"
nc -zv -w 5 $TARGET $PORT 2>&1

# 4. 路由追踪
echo -e "
[4] TCP 路由追踪"
sudo traceroute -T -p $PORT -m 15 $TARGET 2>/dev/null

echo -e "
========== 验证完成 =========="

 

三、示例代码和配置

3.1 完整的端口排查流程脚本

 

#!/bin/bash
# port_diagnose.sh - 端口不通问题自动诊断脚本
# 用法: ./port_diagnose.sh <目标IP> <端口号>

set -euo pipefail

# 颜色定义
RED='�33[0;31m'
GREEN='�33[0;32m'
YELLOW='�33[1;33m'
NC='�33[0m'

TARGET_IP="${1:?用法: $0 <目标IP> <端口号>}"
TARGET_PORT="${2:?用法: $0 <目标IP> <端口号>}"

log_pass() { echo -e "${GREEN}[PASS]${NC} $1"; }
log_fail() { echo -e "${RED}[FAIL]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_info() { echo -e "[INFO] $1"; }

echo "============================================"
echo " 端口连通性诊断工具"
echo " 目标: ${TARGET_IP}:${TARGET_PORT}"
echo " 时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "============================================"

# 第一层:检查是否为本机
IS_LOCAL=false
if ip addr show | grep -q "${TARGET_IP}"; then
    IS_LOCAL=true
    log_info "目标 IP 是本机地址"
fi

# 第二层:如果是本机,检查服务监听
if $IS_LOCAL; then
    echo -e "
--- 第一层:服务监听检查 ---"
    LISTEN_INFO=$(ss -tlnp | grep ":${TARGET_PORT} " || true)
    if [ -n "$LISTEN_INFO" ]; then
        log_pass "端口 ${TARGET_PORT} 有服务在监听"
        echo "  $LISTEN_INFO"

        # 检查监听地址
        LISTEN_ADDR=$(echo "$LISTEN_INFO" | awk '{print $4}' | cut -d: -f1)
        if [ "$LISTEN_ADDR" = "127.0.0.1" ]; then
            log_warn "服务仅监听 127.0.0.1,外部无法访问"
        fi
    else
        log_fail "端口 ${TARGET_PORT} 没有服务在监听"
        log_info "可能原因:服务未启动、配置端口不对、端口被占用"
    fi

    # 检查防火墙
    echo -e "
--- 第二层:防火墙检查 ---"
    if command -v firewall-cmd &>/dev/null; then
        if systemctl is-active --quiet firewalld; then
            FW_RESULT=$(firewall-cmd --query-port=${TARGET_PORT}/tcp 2>/dev/null || true)
            if [ "$FW_RESULT" = "yes" ]; then
                log_pass "firewalld 已放行端口 ${TARGET_PORT}/tcp"
            else
                log_fail "firewalld 未放行端口 ${TARGET_PORT}/tcp"
            fi
        else
            log_info "firewalld 未运行"
        fi
    fi

    # 检查 iptables
    if command -v iptables &>/dev/null; then
        DROP_RULES=$(sudo iptables -L INPUT -n 2>/dev/null | grep -c "DROP|REJECT" || true)
        if [ "$DROP_RULES" -gt 0 ]; then
            log_warn "iptables INPUT 链有 ${DROP_RULES} 条 DROP/REJECT 规则"
        else
            log_pass "iptables INPUT 链无 DROP/REJECT 规则"
        fi
    fi

    # 检查 SELinux
    echo -e "
--- 第三层:SELinux 检查 ---"
    if command -v getenforce &>/dev/null; then
        SELINUX_STATUS=$(getenforce)
        if [ "$SELINUX_STATUS" = "Enforcing" ]; then
            log_warn "SELinux 处于 Enforcing 模式"
            AVC_DENIED=$(sudo ausearch -m avc -ts recent 2>/dev/null | grep -c "denied" || true)
            if [ "$AVC_DENIED" -gt 0 ]; then
                log_fail "发现 ${AVC_DENIED} 条 SELinux 拒绝记录"
            fi
        else
            log_info "SELinux 状态: $SELINUX_STATUS"
        fi
    else
        log_info "SELinux 未安装"
    fi
fi

# 第四层:连通性测试
echo -e "
--- 第四层:连通性测试 ---"

# TCP 测试
if nc -zv -w 5 "$TARGET_IP" "$TARGET_PORT" 2>&1 | grep -q "succeeded|Connected|open"; then
    log_pass "TCP 连接到 ${TARGET_IP}:${TARGET_PORT} 成功"
else
    log_fail "TCP 连接到 ${TARGET_IP}:${TARGET_PORT} 失败"

    # 进一步诊断
    echo -e "
--- 第五层:网络路径分析 ---"
    log_info "执行路由追踪..."
    if command -v mtr &>/dev/null; then
        mtr -r -c 10 --tcp -P "$TARGET_PORT" "$TARGET_IP" 2>/dev/null || 
        mtr -r -c 10 "$TARGET_IP" 2>/dev/null
    else
        traceroute -m 15 "$TARGET_IP" 2>/dev/null || true
    fi
fi

# nmap 扫描
echo -e "
--- 端口状态扫描 ---"
if command -v nmap &>/dev/null; then
    NMAP_RESULT=$(nmap -p "$TARGET_PORT" "$TARGET_IP" 2>/dev/null | grep "^${TARGET_PORT}" || true)
    if [ -n "$NMAP_RESULT" ]; then
        log_info "nmap 结果: $NMAP_RESULT"
        if echo "$NMAP_RESULT" | grep -q "filtered"; then
            log_warn "端口被防火墙过滤(DROP),数据包未到达目标"
        fi
    fi
fi

echo -e "
============================================"
echo " 诊断完成"
echo "============================================"

 

3.2 案例 1:firewalld 规则遗漏导致端口不通

场景:部署了一个新的 Java 应用,监听 8080 端口,本机 curl 正常,但外部访问超时。

排查过程:

 

# 1. 确认服务在监听
ss -tlnp | grep ":8080"
# LISTEN  0  4096  *:8080  *:*  users:(("java",pid=15234,fd=88))
# 结论:服务正常监听在所有接口

# 2. 本机测试
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8080/health
# 200
# 结论:本机访问正常

# 3. 检查防火墙
systemctl status firewalld
# Active: active (running)

firewall-cmd --zone=public --list-all
# public (active)
#   target: default
#   services: cockpit dhcpv6-client ssh
#   ports:
# 结论:firewalld 运行中,未放行 8080 端口

# 4. 放行端口
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
sudo firewall-cmd --reload

# 5. 验证
firewall-cmd --zone=public --query-port=8080/tcp
# yes

# 从外部再次测试
# curl http://192.168.1.100:8080/health
# 200 OK

 

3.3 案例 2:SELinux 阻止非标准端口

场景:将 Nginx 的监听端口从 80 改为 9090 后,Nginx 启动失败。

 

# 1. 查看 Nginx 状态
sudo systemctl status nginx
# nginx.service - The nginx HTTP and reverse proxy server
#    Active: failed

# 2. 查看错误日志
sudo journalctl -u nginx --no-pager -n 20
# nginx: [emerg] bind() to 0.0.0.0:9090 failed (13: Permission denied)

# 3. 检查 SELinux
getenforce
# Enforcing

# 4. 查看 SELinux 允许的 HTTP 端口
sudo semanage port -l | grep http_port_t
# http_port_t   tcp   80, 81, 443, 488, 8008, 8009, 8443, 9000
# 结论:9090 不在允许列表中

# 5. 添加 9090 到 HTTP 端口类型
sudo semanage port -a -t http_port_t -p tcp 9090

# 6. 重启 Nginx
sudo systemctl start nginx
sudo systemctl status nginx
# Active: active (running)

# 7. 验证
ss -tlnp | grep ":9090"
# LISTEN  0  511  0.0.0.0:9090  0.0.0.0:*  users:(("nginx",...))

 

3.4 案例 3:Docker 容器端口映射异常

场景:运行 Docker 容器时指定了 -p 8080:80,但外部无法访问 8080 端口。

 

# 1. 确认容器运行状态
docker ps | grep myapp
# abc123  myapp:latest  "nginx -g ..."  Up 5 minutes  0.0.0.0:8080->80/tcp  myapp

# 2. 检查端口映射
docker port myapp
# 80/tcp -> 0.0.0.0:8080

# 3. 容器内部测试
docker exec myapp curl -s http://127.0.0.1:80/
# 正常响应

# 4. 宿主机测试
curl -s http://127.0.0.1:8080/
# 正常响应

# 5. 外部测试失败,检查 Docker 的 iptables 规则
sudo iptables -L DOCKER -n -v
# Chain DOCKER (1 references)
# pkts bytes target  prot opt in     out     source    destination
# 125  6500  ACCEPT  tcp  --  !docker0 docker0  0.0.0.0/0  172.17.0.2  tcp dpt:80

# 6. 检查 DOCKER-USER 链(用户自定义规则会在这里)
sudo iptables -L DOCKER-USER -n -v
# Chain DOCKER-USER (1 references)
# pkts bytes target  prot opt in     out     source    destination
# 0    0     DROP    all  --  eth0   *       0.0.0.0/0  0.0.0.0/0
# 结论:DOCKER-USER 链有一条 DROP 规则阻止了所有从 eth0 进入的流量

# 7. 修复:删除错误的 DROP 规则
sudo iptables -D DOCKER-USER -i eth0 -j DROP

# 添加正确的规则(只允许特定端口)
sudo iptables -I DOCKER-USER -i eth0 -p tcp --dport 80 -j ACCEPT
sudo iptables -A DOCKER-USER -j RETURN

# 8. 验证
curl -s http://192.168.1.100:8080/
# 正常响应

 

3.5 端口连通性批量检测脚本

 

#!/bin/bash
# batch_port_check.sh - 批量端口检测脚本
# 用法: ./batch_port_check.sh <配置文件>
# 配置文件格式: IP:端口:服务名(每行一个)

CONFIG_FILE="${1:?用法: $0 <配置文件>}"
TIMEOUT=5
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
TOTAL=0
PASS=0
FAIL=0

RED='�33[0;31m'
GREEN='�33[0;32m'
NC='�33[0m'

echo "=========================================="
echo " 批量端口连通性检测"
echo " 时间: ${TIMESTAMP}"
echo "=========================================="
echo ""
printf "%-20s %-8s %-15s %-10s
" "目标" "端口" "服务" "状态"
printf "%-20s %-8s %-15s %-10s
" "----" "----" "----" "----"

while IFS=: read -r ip port service; do
    # 跳过空行和注释
    [[ -z "$ip" || "$ip" =~ ^# ]] && continue

    TOTAL=$((TOTAL + 1))

    if nc -zv -w $TIMEOUT "$ip" "$port" &>/dev/null; then
        printf "%-20s %-8s %-15s ${GREEN}%-10s${NC}
" "$ip" "$port" "$service" "通"
        PASS=$((PASS + 1))
    else
        printf "%-20s %-8s %-15s ${RED}%-10s${NC}
" "$ip" "$port" "$service" "不通"
        FAIL=$((FAIL + 1))
    fi
done < "$CONFIG_FILE"

echo ""
echo "=========================================="
echo " 检测完成: 总计 ${TOTAL}, 通过 ${PASS}, 失败 ${FAIL}"
echo "=========================================="

# 退出码:有失败则返回 1
[ $FAIL -eq 0 ] && exit 0 || exit 1

 

配置文件示例:

 

# port_check.conf
# 格式: IP:端口:服务名
192.168.1.100Nginx-HTTP
192.168.1.100Nginx-HTTPS
192.168.1.101MySQL
192.168.1.101Redis
192.168.1.102Prometheus
192.168.1.102Grafana
10.0.0.50App-Server-1
10.0.0.51App-Server-2

 

四、最佳实践和注意事项

4.1 最佳实践

防火墙规则管理规范

 

# 1. 使用服务名而非端口号(firewalld)
# 推荐:语义化更强
sudo firewall-cmd --add-service=http --permanent

# 不推荐:端口号不直观
sudo firewall-cmd --add-port=80/tcp --permanent

# 2. 使用 rich rules 实现精细控制
# 只允许特定 IP 段访问 MySQL
sudo firewall-cmd --permanent --add-rich-rule='
  rule family="ipv4"
  source address="10.0.0.0/24"
  port port="3306" protocol="tcp"
  accept'

# 3. 防火墙规则版本化管理
# 导出当前规则
sudo iptables-save > /etc/iptables/rules.v4.$(date +%Y%m%d)
sudo nft list ruleset > /etc/nftables.conf.$(date +%Y%m%d)

# 4. 变更前备份
sudo cp /etc/firewalld/zones/public.xml /etc/firewalld/zones/public.xml.bak

 

端口变更 Checklist

序号 检查项 负责人 完成
1 新端口在应用配置中已修改 开发  
2 操作系统防火墙已放行 运维  
3 SELinux 策略已更新 运维  
4 云安全组/网络 ACL 已更新 运维  
5 负载均衡器后端端口已更新 运维  
6 DNS 记录已更新(如有) 运维  
7 监控探测端口已更新 运维  
8 文档已更新 运维  
9 回滚方案已准备 运维  

安全加固建议

 

# 1. 只开放必要的端口,默认拒绝所有
sudo firewall-cmd --set-default-zone=drop
sudo firewall-cmd --zone=drop --add-service=ssh --permanent

# 2. 限制 SSH 来源 IP
sudo firewall-cmd --permanent --add-rich-rule='
  rule family="ipv4"
  source address="10.0.0.0/8"
  service name="ssh"
  accept'

# 3. 定期审查开放端口
ss -tlnp | awk 'NR>1 {print $4}' | sort -u

# 4. 关闭不需要的服务
sudo systemctl disable --now rpcbind
sudo systemctl disable --now avahi-daemon

 

4.2 注意事项

常见错误

错误现象 原因分析 解决方案
firewall-cmd 规则添加后不生效 未加 --permanent 或未 reload 加上 --permanent 并执行 --reload
iptables 规则重启后丢失 未持久化规则 使用 netfilter-persistent save
Docker 端口映射不通 DOCKER-USER 链有 DROP 规则 检查并修复 DOCKER-USER 链
telnet 超时但 ping 通 防火墙 DROP TCP 但放行 ICMP 检查防火墙 TCP 规则
本机 curl 通但外部不通 服务绑定 127.0.0.1 修改 bind 地址为 0.0.0.0
SELinux 拒绝非标准端口 端口不在 SELinux 策略中 semanage port 添加端口
nmap 显示 filtered 中间设备 DROP 数据包 检查云安全组/网络 ACL
端口状态时通时断 连接数超限或负载均衡问题 检查 conntrack 表和负载均衡

兼容性注意事项

ss 是 iproute2 包的组成部分,在所有现代 Linux 发行版中默认安装

netstat 属于 net-tools 包,在最小安装中可能不存在

nftables 是 iptables 的替代者,但 iptables 命令会自动转换为 nftables 规则

firewalld 2.x 默认使用 nftables 后端

Docker 仍然依赖 iptables 规则,即使系统使用 nftables

容器环境特殊注意

 

# Kubernetes 中的端口排查
# 1. 检查 Service 端口映射
kubectl get svc -n 

# 2. 检查 Endpoints
kubectl get endpoints  -n 

# 3. 检查 Pod 端口
kubectl get pod  -n  -o jsonpath='{.spec.containers[*].ports}'

# 4. 检查 NetworkPolicy
kubectl get networkpolicy -n 

# 5. 在 Pod 内部测试
kubectl exec -it  -n  -- nc -zv  

 

五、故障排查和监控

5.1 故障排查

日志查看

 

# firewalld 日志
sudo journalctl -u firewalld -n 50 --no-pager

# iptables 日志(需要先添加 LOG 规则)
sudo iptables -I INPUT -p tcp --dport 8080 -j LOG --log-prefix "PORT8080: "
sudo journalctl -k | grep "PORT8080"

# SELinux 拒绝日志
sudo ausearch -m avc -ts recent
sudo sealert -a /var/log/audit/audit.log

# 内核网络栈日志
dmesg | grep -i "nf_conntrack|dropped|martian"

 

conntrack 表排查

 

# 查看连接跟踪表大小
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max

# 如果 count 接近 max,新连接会被丢弃
# 临时增大连接跟踪表
sudo sysctl -w net.netfilter.nf_conntrack_max=1048576

# 查看特定端口的连接跟踪记录
sudo conntrack -L -p tcp --dport 8080 2>/dev/null | head -20

# 清除特定连接
sudo conntrack -D -p tcp --dport 8080

 

常见问题排查表

问题 快速诊断命令 预期结果
服务是否监听 ss -tlnp | grep : 看到 LISTEN 行
防火墙是否放行 firewall-cmd --query-port=/tcp yes
SELinux 是否阻止 ausearch -m avc -ts recent 无相关拒绝记录
数据包是否到达 tcpdump -i any port -c 5 看到 SYN 包
路径是否可达 mtr --tcp -P 最后一跳无丢包
conntrack 是否满 cat /proc/sys/net/netfilter/nf_conntrack_count 远小于 max 值

5.2 性能监控

Blackbox Exporter 端口探测

Prometheus 的 blackbox_exporter 是端口连通性监控的标准方案。

 

# blackbox.yml - blackbox_exporter 配置
modules:
  tcp_connect:
    prober: tcp
    timeout: 5s
    tcp:
      preferred_ip_protocol: "ip4"

  tcp_connect_tls:
    prober: tcp
    timeout: 5s
    tcp:
      preferred_ip_protocol: "ip4"
      tls: true
      tls_config:
        insecure_skip_verify: false

  http_2xx:
    prober: http
    timeout: 10s
    http:
      valid_http_versions: ["HTTP/1.1", "HTTP/2.0"]
      valid_status_codes: [200, 204]
      method: GET
      preferred_ip_protocol: "ip4"

  icmp_check:
    prober: icmp
    timeout: 5s
    icmp:
      preferred_ip_protocol: "ip4"
# prometheus.yml - Prometheus 抓取配置
scrape_configs:
  - job_name: 'blackbox-tcp'
    metrics_path: /probe
    params:
      module: [tcp_connect]
    static_configs:
      - targets:
        - '192.168.1.100:8080'
        - '192.168.1.101:3306'
        - '192.168.1.102:6379'
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: 'blackbox-exporter:9115'

  - job_name: 'blackbox-http'
    metrics_path: /probe
    params:
      module: [http_2xx]
    static_configs:
      - targets:
        - 'http://192.168.1.100:80/health'
        - 'https://192.168.1.100:443/health'
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: 'blackbox-exporter:9115'

 

Prometheus 告警规则

 

# port_alerts.yml
groups:
  - name: port_connectivity
    rules:
      - alert: PortUnreachable
        expr: probe_success{job="blackbox-tcp"} == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "端口不可达: {{ $labels.instance }}"
          description: "目标 {{ $labels.instance }} 的端口连通性检测失败,已持续 2 分钟"

      - alert: PortHighLatency
        expr: probe_duration_seconds{job="blackbox-tcp"} > 1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "端口响应延迟高: {{ $labels.instance }}"
          description: "目标 {{ $labels.instance }} 的 TCP 连接耗时超过 1 秒"

      - alert: SSLCertExpiringSoon
        expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 30
        for: 1h
        labels:
          severity: warning
        annotations:
          summary: "SSL 证书即将过期: {{ $labels.instance }}"
          description: "证书将在 {{ $value | humanizeDuration }} 后过期"

      - alert: HTTPStatusError
        expr: probe_http_status_code{job="blackbox-http"} != 200
        for: 3m
        labels:
          severity: critical
        annotations:
          summary: "HTTP 状态异常: {{ $labels.instance }}"
          description: "HTTP 状态码: {{ $value }}"

 

关键监控指标

指标名称 含义 正常范围 告警阈值
probe_success 探测是否成功 1 == 0 持续 2 分钟
probe_duration_seconds 探测耗时 < 0.1s > 1s 持续 5 分钟
probe_dns_lookup_time_seconds DNS 解析耗时 < 0.05s > 0.5s
probe_ssl_earliest_cert_expiry SSL 证书过期时间 > 30 天 < 30 天
node_netfilter_conntrack_entries conntrack 条目数 < max 的 70% > max 的 80%

5.3 备份与恢复

防火墙规则备份

 

#!/bin/bash
# firewall_backup.sh - 防火墙规则备份脚本

BACKUP_DIR="/opt/backup/firewall"
DATE=$(date +%Y%m%d_%H%M%S)

mkdir -p "$BACKUP_DIR"

# 备份 iptables 规则
if command -v iptables-save &>/dev/null; then
    sudo iptables-save > "${BACKUP_DIR}/iptables_${DATE}.rules"
    sudo ip6tables-save > "${BACKUP_DIR}/ip6tables_${DATE}.rules"
    echo "iptables 规则已备份"
fi

# 备份 nftables 规则
if command -v nft &>/dev/null; then
    sudo nft list ruleset > "${BACKUP_DIR}/nftables_${DATE}.conf"
    echo "nftables 规则已备份"
fi

# 备份 firewalld 配置
if [ -d /etc/firewalld ]; then
    tar czf "${BACKUP_DIR}/firewalld_${DATE}.tar.gz" /etc/firewalld/
    echo "firewalld 配置已备份"
fi

# 保留最近 30 天的备份
find "$BACKUP_DIR" -name "*.rules" -mtime +30 -delete
find "$BACKUP_DIR" -name "*.conf" -mtime +30 -delete
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +30 -delete

echo "备份完成: ${BACKUP_DIR}"

 

规则恢复

 

# 恢复 iptables 规则
sudo iptables-restore < /opt/backup/firewall/iptables_20260313.rules

# 恢复 nftables 规则
sudo nft -f /opt/backup/firewall/nftables_20260313.conf

# 恢复 firewalld 配置
sudo tar xzf /opt/backup/firewall/firewalld_20260313.tar.gz -C /
sudo firewall-cmd --reload

 

六、总结

6.1 技术要点回顾

端口不通问题需要分层排查:服务监听 → 防火墙 → SELinux → 网络路径 → 云安全组 → 应用层

ss -tlnp 是确认服务监听的第一选择,重点关注监听地址(0.0.0.0 vs 127.0.0.1)

防火墙排查需要同时检查 firewalld/iptables/nftables,Docker 环境还要检查 DOCKER-USER 链

SELinux 在 Enforcing 模式下会阻止非标准端口绑定,使用 semanage port 添加

nmap 的端口状态(open/closed/filtered)能精确反映问题所在层级

云环境需要额外检查安全组和网络 ACL,这些是独立于操作系统之外的控制

使用 blackbox_exporter 做端口连通性的持续监控

6.2 进阶学习方向

eBPF 网络追踪:使用 bpftrace 追踪内核网络栈中数据包的处理路径,精确定位丢包位置

Kubernetes 网络策略:NetworkPolicy、Calico/Cilium 网络插件的端口控制机制

服务网格:Istio/Linkerd 环境中 sidecar 代理对端口通信的影响

零信任网络:基于身份而非 IP/端口的访问控制架构

6.3 参考资料

Linux man pages: ss(8), iptables(8), nft(8), firewall-cmd(1)

nmap 官方文档: https://nmap.org/book/

Prometheus Blackbox Exporter: https://github.com/prometheus/blackbox_exporter

firewalld 官方文档: https://firewalld.org/documentation/

Red Hat SELinux 管理指南: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/using_selinux

附录

A. 命令速查表

 

# 服务监听检查
ss -tlnp                              # 查看所有 TCP 监听
ss -tlnp | grep ":"             # 查看特定端口
ss -ulnp                              # 查看所有 UDP 监听

# 防火墙操作
firewall-cmd --list-all                # 查看当前规则
firewall-cmd --add-port=8080/tcp --permanent  # 添加端口
firewall-cmd --reload                  # 重载规则
sudo iptables -L -n -v                # iptables 规则
sudo nft list ruleset                  # nftables 规则

# SELinux
getenforce                             # 查看状态
semanage port -l | grep          # 查看端口策略
semanage port -a -t  -p tcp   # 添加端口

# 连通性测试
nc -zv -w 5                 # TCP 测试
nmap -p                      # 端口扫描
curl -v http://:/            # HTTP 测试
telnet                       # 基础测试

# 路径分析
mtr --tcp -P                # TCP 路由追踪
traceroute -T -p            # TCP traceroute
tcpdump -i any port  -nn        # 抓包确认

# conntrack
conntrack -L -p tcp --dport     # 查看连接跟踪
conntrack -D -p tcp --dport     # 清除连接
cat /proc/sys/net/netfilter/nf_conntrack_count  # 当前连接数

 

B. 配置参数详解

firewalld 区域默认行为

区域 默认行为 适用场景
drop 丢弃所有入站,不响应 高安全需求
block 拒绝所有入站,返回 ICMP 拒绝 安全需求
public 只允许指定服务 公网服务器
external NAT 转发 网关服务器
dmz 只允许指定服务 DMZ 区域
work 信任同事网络 办公网络
home 信任家庭网络 家庭环境
internal 信任内部网络 内网服务器
trusted 允许所有流量 完全信任网络

nmap 常用扫描类型

参数 扫描类型 原理 适用场景
-sT TCP Connect 完整三次握手 普通用户
-sS TCP SYN 半开扫描 需要 root
-sU UDP 发送 UDP 包 UDP 服务
-sA TCP ACK 判断防火墙规则 防火墙检测
-sN TCP Null 不设置任何标志 绕过简单过滤

C. 术语表

术语 英文 解释
三次握手 Three-Way Handshake TCP 建立连接的过程:SYN → SYN-ACK → ACK
连接跟踪 Connection Tracking 内核记录网络连接状态的机制(conntrack)
有状态防火墙 Stateful Firewall 能识别连接状态的防火墙,只需放行首包
安全组 Security Group 云平台的虚拟防火墙,控制实例级别的网络访问
ACL Access Control List 网络访问控制列表,控制子网级别的流量
BPF Berkeley Packet Filter 内核数据包过滤框架,用于 tcpdump 等工具
RST Reset TCP 连接重置标志,表示拒绝连接
DNAT Destination NAT 目标地址转换,用于端口映射/转发
Well-Known Ports 知名端口 0-1023 范围内的系统保留端口
Ephemeral Ports 临时端口 49152-65535 范围内的动态分配端口

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分