CentOS 7/8/9 已经全面弃用 tcp_wrappers 访问控制机制,取而代之的是 nftables 防火墙框架和 systemd 内置的 socket 激活控制。如果你还在用 /etc/hosts.allow 和 /etc/hosts.deny 来做服务访问限制,现在必须迁移到新方案。具体怎么做?核心就是用 firewalld 或 nftables 规则替代原来的 hosts.allow/deny 逻辑,同时利用 systemd 的 socket 单元实现更细粒度的访问控制。下面我把整个迁移过程、原理和实操全部讲清楚。

tcp_wrappers 为什么被弃用

tcp_wrappers 是一个基于 libwrap 库的老旧访问控制方案,工作原理是在服务程序启动时通过 LD_PRELOAD 劫持 libc 的网络连接函数,然后去查 /etc/hosts.allow 和 /etc/hosts.deny 文件决定是否放行。这套机制有几个致命缺陷:第一,它只对链接了 libwrap 库的程序有效,现在很多程序根本不链接这个库;第二,它工作在应用层,绕过它的方法太多,比如直接用 iptables 规则或者绕过 libwrap 劫持;第三,RHEL/CentOS 从版本 7 开始就逐步弱化,到 CentOS 9 已经彻底移除了 tcp_wrappers 相关的软件包和默认配置。继续依赖它,等于你的安全策略形同虚设。

CentOS 各版本对 tcp_wrappers 的处理状态

CentOS 7:tcp_wrappers 软件包(tcp_wrappers、tcp_wrappers-libs)仍然存在,但默认很多服务已经不再链接 libwrap。你可以手动安装使用,但官方已经不推荐。CentOS 8:tcp_wrappers 被标记为废弃(deprecated),软件包仍然可以安装,但系统默认不启用。CentOS 9 / RHEL 9:tcp_wrappers 相关包已从官方仓库移除,彻底不可用。如果你在 CentOS 9 上还想用,只能自己编译安装,但这完全没有意义,因为替代方案已经非常成熟。

替代方案一:firewalld 富规则(推荐大多数用户)

firewalld 是 CentOS 7/8/9 默认的动态防火墙管理工具,它底层调用 nftables(CentOS 8+)或 iptables(CentOS 7),提供了比 tcp_wrappers 更强大、更底层的访问控制能力。你原来在 hosts.allow 里写的规则,现在用 firewalld 的富规则(rich rule)就能实现,而且控制的是网络层,比应用层劫持更可靠。

举个例子,原来你在 /etc/hosts.allow 里写:

sshd: 192.168.1.0/24, 10.0.0.5

现在用 firewalld 实现同样效果:

firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" port protocol="tcp" port="22" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.5" port protocol="tcp" port="22" accept'
firewall-cmd --reload

如果你原来还有 hosts.deny 的逻辑,比如拒绝所有其他来源:

firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="0.0.0.0/0" port protocol="tcp" port="22" drop'
firewall-cmd --reload

注意 rich rule 的匹配顺序很重要,firewalld 按规则添加顺序从上到下匹配,先匹配到的生效。所以你要把允许的规则放在拒绝规则前面。

替代方案二:nftables 直接编写规则(适合高级用户)

如果你需要更精细的控制,或者想摆脱 firewalld 这层抽象,可以直接写 nftables 规则。nftables 是 Linux 内核 3.13+ 引入的新一代包过滤框架,语法比 iptables 简洁,性能更好,支持集合(set)和映射(map),非常适合做批量 IP 访问控制。

创建一个 nftables 配置文件 /etc/nftables.conf:

#!/usr/sbin/nft -f
flush ruleset

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;

        # 允许本地回环
        iif "lo" accept

        # 允许已建立的连接
        ct state established,related accept

        # SSH 允许特定 IP
        ip saddr { 192.168.1.0/24, 10.0.0.5 } tcp dport 22 accept

        # HTTP 允许特定 IP
        ip saddr { 192.168.1.100, 192.168.1.101 } tcp dport 80 accept

        # 其他全部丢弃(policy drop 已经处理)
    }
}

加载规则:

nft -f /etc/nftables.conf

设置开机自动加载:

systemctl enable nftables
systemctl start nftables

nftables 的优势在于你可以用 set 来管理 IP 列表,动态增删而不用重写整条规则:

nft add set inet filter allowed_ips { type ipv4_addr; }
nft add element inet filter allowed_ips { 192.168.1.50 }
nft add element inet filter allowed_ips { 192.168.1.51 }

然后规则里引用这个 set:

ip saddr @allowed_ips tcp dport 22 accept

替代方案三:systemd socket 单元控制(最硬核的方案)

这是很多人不知道但非常有效的方案。systemd 可以在 socket 层面控制谁能连接某个服务,根本不需要服务程序本身做任何访问控制。原理是:你把原来的服务 socket 替换成 systemd 管理的 .socket 单元,systemd 在 accept 连接时就可以根据来源 IP 做过滤,过滤不通过的连接直接在 socket 层拒绝,服务程序根本看不到这个连接请求。

以 sshd 为例,禁用原来的 sshd.service,创建 sshd.socket:

[Unit]
Description=OpenSSH Server Socket

[Socket]
ListenStream=22
Accept=yes

# 关键:限制来源 IP
# 注意:systemd 原生不支持按 IP 过滤,需要配合 iptables/nftables 的 socket mark
# 或者用更简单的方式:直接在 socket 单元里指定 BindToDevice 等
# 实际生产中推荐结合 nftables 的 cgroup 或 socket mark 实现

[Install]
WantedBy=sockets.target

更实用的做法是用 nftables 给特定连接打 mark,然后 systemd socket 配合:

# nftables 规则:给来自特定 IP 的 22 端口连接打 mark
nft add rule inet filter input ip saddr 192.168.1.0/24 tcp dport 22 meta mark set 0x1 accept

# 然后 systemd socket 可以配合,但更直接的是用 nftables socket 过滤
# 实际上,systemd socket 方案在纯 IP 过滤场景下不如直接用 nftables

说实话,systemd socket 方案在实际 IP 访问控制场景下,不如直接用 nftables 或 firewalld 来得直接。它更适合做端口转发、按需启动服务等场景。但如果你想从根本上让服务"看不到"非法连接,这个思路值得了解。

迁移过程中的注意事项和常见坑

第一,迁移前一定要先备份原来的 hosts.allow 和 hosts.deny 文件,万一新规则出问题可以快速回退。第二,firewalld 和 nftables 不能同时管理同一套规则,CentOS 8/9 默认用 firewalld 调用 nftables,如果你直接写 nftables 规则,可能和 firewalld 冲突。建议二选一:要么完全用 firewalld,要么停掉 firewalld 纯用 nftables。第三,测试的时候不要直接在生产环境操作,先在测试机上验证规则顺序和逻辑,避免把自己锁在外面。第四,如果你的服务是通过 xinetd 管理的(比如 tftp、rsync 等),xinetd 本身也支持 tcp_wrappers,迁移时需要把访问控制逻辑挪到 xinetd 的配置或者防火墙层面。

xinetd 服务的特殊处理

CentOS 上一些老服务是通过 xinetd 超级守护进程管理的,xinetd 配置文件里有 only_from、no_access 等参数,本质上也是 tcp_wrappers 的变体。迁移方法:要么在 xinetd 配置里继续用 only_from(因为 xinetd 链接了 libwrap,在 CentOS 8 上还能用),要么直接在防火墙层面限制对应端口的访问来源,然后把 xinetd 服务的监听地址改为 127.0.0.1,只允许本地访问。

# /etc/xinetd.d/rsync 示例
service rsync
{
    disable = yes
    socket_type = stream
    wait = no
    user = root
    server = /usr/bin/rsync
    server_args = --daemon
    # 改为只监听本地
    bind = 127.0.0.1
    only_from = 192.168.1.0/24
}

为什么新方案比 tcp_wrappers 更安全

tcp_wrappers 工作在应用层,攻击者只要找到一个不链接 libwrap 的同类程序,或者直接用 raw socket 绕过,就能突破。而 nftables/firewalld 工作在内核的 netfilter 钩子上,是网络栈的第一道关卡,所有流量都必须经过。systemd socket 方案则是在连接被 accept 之前就做判断。这三种新方案的安全层级都远高于 tcp_wrappers。而且新方案支持状态检测、连接跟踪、速率限制等高级功能,tcp_wrappers 完全不具备这些能力。

总结:你现在该做什么

如果你还在 CentOS 7 上,赶紧评估迁移计划,因为 CentOS 7 已经 EOL,安全更新都停了。如果在 CentOS 8/9 上,立即检查系统中还有哪些服务依赖 tcp_wrappers,用 firewalld rich rule 或 nftables 规则逐一替换。不要再花时间维护 /etc/hosts.allow 和 /etc/hosts.deny 了,这套东西已经是历史。把精力放在构建基于 nftables 的分层防火墙策略上,配合 fail2ban 做动态封禁,这才是现代 Linux 服务器访问控制的正确姿势。