一、Xinetd 超级守护进程
1.1 独立守护进程与瞬时守护进程
Linux 上的服务按启动方式分为两大类。
独立守护进程(standalone daemon)
服务进程在系统启动时就被拉起,持续常驻内存,自行绑定并监听端口,直接处理客户端请求。
典型代表:sshd、httpd、nginx、mysqld、ntpd、chronyd、crond、rsyslogd。
- 优点:
- 响应速度快。进程和相关资源(配置、共享库、缓存、连接池)已经在内存中就绪,请求到达即可立刻处理,无需 fork/exec 与初始化开销。
- 适合高并发、高频访问的服务。
- 可以维护跨请求的状态(连接池、会话、缓存)。
- 自身通常实现了完整的并发模型(prefork、worker、event、线程池)。
- 缺点:
- 常驻内存,即使没有任何请求也占用内存与进程表项。系统上装的服务越多,浪费越大。
- 每个服务各自实现访问控制、日志、并发限制,能力参差不齐(有的干脆没有)。
- 每个服务都要单独配置、单独启停、单独监控。
瞬时守护进程(transient daemon / 由超级守护进程托管)
服务本身平时不运行,端口由超级守护进程 xinetd 统一监听。请求到达时,xinetd 才 fork 并 exec 出真正的服务进程处理这次连接;连接结束后该进程退出。
典型代表:telnet、tftp、rsync(daemon 模式)、vsftpd(可选)、cvs、echo、daytime、time、chargen、discard、swat、amanda、rlogin、rsh、talk、finger、pop3(旧版)。
- 优点:
- 节省资源。不被访问时完全不占内存与进程。系统上可以"挂载"几十个小服务而几乎零开销。
- 统一的管理能力。所有被托管的服务自动获得
xinetd提供的访问控制(IP、时间段)、并发限制、连接速率限制、日志、绑定地址、端口重定向等能力,哪怕该服务自身完全没有这些功能。 - 统一的配置风格与统一的启停(只管
xinetd一个服务)。 - 天然的进程隔离:每个连接一个独立进程,一个崩溃不影响其他。
- 缺点:
- 响应速度慢。每个连接都要 fork + exec + 服务自身初始化(读配置、连数据库等),延迟明显增加(几毫秒到几百毫秒)。
- 高并发下性能极差。大量连接会造成 fork 风暴,进程创建开销压垮系统。
- 无法维护跨请求的状态(无连接池、无缓存复用)。
xinetd本身成为单点,它挂了所有托管服务全部不可用。- 增加一层间接性,排错链路变长。
选择原则
| 服务特征 | 应选择 |
|---|---|
| 访问频繁、并发高(Web、SSH、数据库) | 独立守护进程 |
| 访问稀少、偶尔用一次(tftp、telnet、备份工具) | xinetd 托管 |
| 对延迟敏感 | 独立守护进程 |
| 服务自身缺少访问控制能力,需要外部加固 | xinetd 托管 |
| 需要维护长连接、会话、连接池 | 独立守护进程 |
| 内存紧张、服务数量多但都很少用 | xinetd 托管 |
1.2 inetd 与 xinetd 的演进
inetd(Internet super-server daemon):最早的超级守护进程,源自 4.3BSD(1986 年)。配置集中在单个 /etc/inetd.conf,每行一个服务,字段用空格分隔,可读性差、功能贫弱:
# /etc/inetd.conf 的格式(历史参考)
# service socket_type proto flags user server_path args
ftp stream tcp nowait root /usr/sbin/tcpd in.ftpd -l -a
telnet stream tcp nowait root /usr/sbin/tcpd in.telnetd
inetd 自身没有访问控制能力,需要借助外部的 tcpd(TCP Wrappers)包装才能做 IP 限制,配置繁琐。
xinetd(eXtended InterNET Daemon):inetd 的重写与增强版,1998 年由 Rob Braun 发起,成为现代 Linux 发行版的标准选择。
包描述(rpm -qi xinetd):
yum install xinetd -y
rpm -qi xinetd-2.3.14-34.el6.x86_64
rpm -qi xinetd # 通用写法
Summary : A secure replacement for inetd
Description :
Xinetd is a secure replacement for inetd, the Internet services daemon.
Xinetd provides access control for all services based on the address of
the remote host and/or on time of access and can prevent denial-of-access
attacks. Xinetd provides extensive logging, has no limit on the number of
server arguments, and lets you bind specific services to specific IP
addresses on your host machine. Each service has its own specific
configuration file for xinetd; the files are located in the /etc/xinetd.d
directory.
包描述中提炼出的关键信息:
- Xinetd 是 inetd 的安全替代(a secure replacement for inetd)。
- 可基于远程主机地址和/或访问时间对所有服务进行访问控制。
- 可防止拒绝服务攻击(denial-of-access attacks)。
- 提供丰富的日志功能(extensive logging)。
- 对服务参数的数量没有限制(inetd 限制为 5 个)。
- 可将特定服务绑定到本机特定 IP 地址。
- 每个服务有独立配置文件,位于
/etc/xinetd.d目录。
xinetd 相对 inetd 的增强功能一览:
| 增强功能 | 对应配置项 | 说明 |
|---|---|---|
| 访问控制(基于地址) | only_from、no_access |
内建 IP/网段/主机名白名单与黑名单,不需要 TCP Wrappers |
| 时间段控制 | access_times |
只在指定时间段内提供服务,如 9:00-18:00 |
| 连接数限制 | instances、per_source |
限制服务总并发数与单个源 IP 的并发数,防止资源耗尽 |
| 连接速率限制 | cps |
限制每秒新建连接数,超限则临时禁用服务,抗 DoS 的核心手段 |
| 丰富的日志 | log_type、log_on_success、log_on_failure |
可记录 PID、来源 IP、用户 ID、退出状态、连接时长等,可输出到 syslog 或独立文件 |
| 端口重定向 | redirect |
把请求转发到另一台主机的另一个端口,充当简易 TCP 代理 |
| 绑定指定地址 | bind / interface |
只在指定网卡/IP 上监听,实现内外网服务隔离 |
| 资源限制 | rlimit_cpu、rlimit_as、rlimit_data、rlimit_files |
对被托管进程施加 CPU 时间、内存等 ulimit 限制 |
| 进程优先级 | nice |
调整被托管服务的调度优先级 |
| 环境变量控制 | env、passenv |
为被托管服务设置或过滤环境变量 |
| 横幅信息 | banner、banner_success、banner_fail |
连接时/成功时/被拒时向客户端显示指定文件的内容 |
| IPv6 支持 | flags = IPv6 |
inetd 不支持 |
| 内部服务 | type = INTERNAL |
echo、time、daytime、chargen、discard 由 xinetd 自己实现,无需外部程序 |
| 参数无数量限制 | server_args |
inetd 最多 5 个参数 |
1.3 什么是超级守护进程(形象理解)
Xinetd 也叫 inetd、super daemon(超级守护进程)。它本身不直接提供具体业务服务,而是负责托管其他小服务。
打个比方:银行里有一种综合服务窗口,提供汇款、转账、提款等业务。窗口附近始终坐着一个人(xinetd),她不提供具体服务,提供具体服务的人在里面闲着聊天、喝茶;当有人来汇款时,她会喊一句"小王,有人汇款啦",里面管汇款的小王会立马跑过来办完业务,其他人继续聊天喝茶。这些负责具体业务的人就由超级守护进程来调度。
如果汇款有规则限制(比如不能往北京汇款),管理员会提早告诉 xinetd,有人想汇往北京时直接拒绝,根本不需要喊汇款员,相当于提供了一层管理机制。
对应到技术上:当请求到达时,由 xinetd 监听并按需唤起对应的服务进程;请求结束后,被唤起的进程可以退出。xinetd 还可提前按规则过滤请求(例如"不往北京汇款"),相当于在业务前提供一层访问控制机制。
针对这种窗口还存在多线程和单线程的区别:
- 多线程:将所有用户的要求都提上来,里面的人一起干活。即所有用户请求同时提交,所有服务进程一起工作。对应配置
wait = no(并发)。 - 单线程:大家排队,一个一个来,同一时间只有一个人工作。即请求排队,同一时间只有一个服务进程在工作。对应配置
wait = yes(串行等待)。
1.4 Xinetd 的作用
Xinetd 类似一个托管平台/托管服务,可以托管其他小服务。托管后的好处是可以使用 Xinetd 强大的参数来控制这些服务,并增强安全性(小服务自身可能不具备访问控制等能力)。
例如一个小服务本身没有控制功能,但支持 Xinetd 托管,就可以使用 Xinetd 的参数来控制它。
1.5 Xinetd 的工作流程
1. 系统启动 → xinetd 启动
2. xinetd 读取 /etc/xinetd.conf 与 /etc/xinetd.d/ 下的所有配置文件
3. 对每个 disable = no 的服务:
a. 从 /etc/services 查出该服务名对应的端口号(除非配置中显式指定了 port)
b. 创建 socket 并 bind 到该端口(若配置了 bind/interface 则只绑定指定地址)
c. 对 TCP(socket_type = stream)执行 listen()
4. xinetd 用 select()/poll() 同时监视所有已绑定的 socket,进入等待
5. 客户端连接到达某个端口:
a. xinetd 从 accept() 取得连接(TCP)或收到数据报(UDP)
b. 【访问控制检查】依次判定:
- 服务是否被 cps 限速临时禁用?
- 源地址是否满足 only_from / no_access?
- 当前时刻是否在 access_times 范围内?
- 当前总并发是否超过 instances?
- 该源 IP 的并发是否超过 per_source?
- (若程序链接了 libwrap)TCP Wrappers 的 hosts.allow/hosts.deny 是否允许?
c. 任一检查不通过 → 关闭连接,按 log_on_failure 记录日志(可先发送 banner_fail)
d. 全部通过 → 按 log_on_success 记录日志(可先发送 banner / banner_success)
6. 启动服务:
- type = INTERNAL:xinetd 自己处理(echo/time/daytime/chargen/discard)
- 否则:fork() 出子进程
→ 把已连接的 socket 复制为子进程的 stdin(0)/stdout(1)/stderr(2)
→ 按 user/group 切换身份(setuid/setgid)
→ 应用 nice、rlimit_* 等限制
→ 设置 env/passenv 指定的环境变量
→ exec() 执行 server 指定的程序,并传入 server_args
7. 被托管的服务进程直接通过标准输入输出与客户端通信(它"以为"自己在和终端对话)
8. 服务处理完毕,进程退出
9. xinetd 回收子进程(wait),按 log_on_success 记录 EXIT 状态与 DURATION 时长
10. xinetd 继续回到第 4 步等待下一个连接
关键理解点:
- 被托管的服务不需要自己写网络代码。它只需从 stdin 读、向 stdout 写,
xinetd负责把 socket 接到它的标准输入输出上。这就是为什么sshd -i中的-i表示"inetd 模式"。 wait的真正含义:wait = no(TCP 典型):xinetd fork 出子进程后立即返回继续 accept 下一个连接,因此可以并发处理。wait = yes(UDP 典型):xinetd 把 socket 交给子进程后等待子进程退出才继续处理,期间不再接受新请求,因此是串行的。UDP 无连接,socket 只有一个,必须由子进程独占直到处理完。
1.6 安装与服务管理
安装
yum install xinetd -y
rpm -qa | grep xinetd
rpm -ql xinetd # 查看包内文件
关键文件:
| 路径 | 说明 |
|---|---|
/usr/sbin/xinetd |
主程序 |
/etc/xinetd.conf |
主配置文件 |
/etc/xinetd.d/ |
子配置文件目录,每个被托管服务一个文件 |
/etc/sysconfig/xinetd |
服务启动参数 |
/etc/init.d/xinetd |
CentOS 6 的 init 脚本 |
/usr/lib/systemd/system/xinetd.service |
CentOS 7 的 systemd 单元 |
/usr/share/doc/xinetd-2.3.14/sample.conf |
官方示例配置(重要参考) |
/etc/services |
服务名与端口的对应表 |
CentOS 6 的启停与开机自启
/etc/init.d/xinetd start
/etc/init.d/xinetd stop
/etc/init.d/xinetd restart
/etc/init.d/xinetd status
/etc/init.d/xinetd reload # 重载配置(发送 SIGHUP,不中断已有连接)
service xinetd start
service xinetd restart
service xinetd status
# 查看开机启动状态
chkconfig --list | grep xinetd
xinetd 0:off 1:off 2:off 3:off 4:off 5:off 6:off
设置开机自启:
chkconfig xinetd on
chkconfig xinetd off
chkconfig --list xinetd
chkconfig --level 35 xinetd on
查看 Xinetd 托管的服务(这是 CentOS 6 特有的展示方式):
chkconfig --list # 查看 xinetd based services 段落
chkconfig --list | grep -A 20 'xinetd based'
默认托管的服务示例:
xinetd based services:
chargen-dgram: off
chargen-stream: off
cvs: off
daytime-dgram: off
daytime-stream: off
discard-dgram: off
discard-stream: off
echo-dgram: off
echo-stream: off
rsync: off
tcpmux-server: off
tftp: on
time-dgram: off
time-stream: off
用 chkconfig 直接开关被托管的服务(等价于修改对应配置文件里的 disable):
chkconfig rsync on # 等价于把 /etc/xinetd.d/rsync 里的 disable 改为 no
chkconfig rsync off # 等价于 disable = yes
chkconfig tftp on
chkconfig --list rsync
注意:这里的
chkconfig <服务> on对 xinetd 托管服务而言不是"开机自启"的意思,而是"启用该托管服务"。改完仍需/etc/init.d/xinetd reload或restart才生效(新版 chkconfig 会自动触发 reload)。
CentOS 7 的启停与开机自启
systemctl start xinetd
systemctl stop xinetd
systemctl restart xinetd
systemctl reload xinetd
systemctl status xinetd
systemctl enable xinetd
systemctl disable xinetd
systemctl is-enabled xinetd
systemctl enable --now xinetd
journalctl -u xinetd -f # 查看日志
CentOS 6 与 7 的差异总结:
| 操作 | CentOS 6 | CentOS 7 |
|---|---|---|
| 启动 | /etc/init.d/xinetd start 或 service xinetd start |
systemctl start xinetd |
| 重启 | /etc/init.d/xinetd restart |
systemctl restart xinetd |
| 重载 | /etc/init.d/xinetd reload |
systemctl reload xinetd |
| 状态 | /etc/init.d/xinetd status |
systemctl status xinetd |
| 开机自启 | chkconfig xinetd on |
systemctl enable xinetd |
| 查看托管服务列表 | chkconfig --list(有 xinetd based services 段) |
无此展示,需 ls /etc/xinetd.d/ + grep disable 自行查看 |
| 开关单个托管服务 | chkconfig <服务> on/off |
不支持,必须手工改配置文件的 disable |
| xinetd 是否默认安装 | 通常已安装 | 默认不安装,需 yum install xinetd |
| 地位 | 标准做法 | 已弱化/不推荐,被 systemd socket 激活取代 |
CentOS 7 中查看托管服务状态的替代方法:
ls /etc/xinetd.d/
grep -H 'disable' /etc/xinetd.d/* # 查看每个服务的 disable 状态
grep -l 'disable.*=.*no' /etc/xinetd.d/* # 列出已启用的服务
CentOS 7 中 xinetd 已被 systemd socket 激活取代
这是 CentOS 6 到 7 最重要的架构变化之一。
CentOS 7 引入 systemd 后,xinetd 的核心功能——按需启动服务(on-demand activation)——已经由 systemd 原生的 socket 激活(socket activation) 机制实现,且实现得更好:
| 对比项 | xinetd | systemd socket 激活 |
|---|---|---|
| 按需启动 | 支持 | 支持 |
| 并行启动加速 | 不支持 | 支持(socket 先创建好,依赖服务可并行启动) |
| 资源限制 | rlimit_* |
完整的 cgroup 限制(CPU、内存、IO、进程数) |
| 访问控制 | only_from/no_access |
需配合 firewalld 或服务自身,.socket 无内建 IP 白名单 |
| 日志 | 自己实现 | 统一进 journald |
| 安全加固 | 有限 | PrivateTmp、ProtectSystem、NoNewPrivileges、SELinuxContext 等丰富选项 |
| 服务重启不丢连接 | 不支持 | 支持(socket 由 systemd 持有,服务重启期间连接被排队) |
| 是否默认安装 | CentOS 7 需手工装 | 系统内建 |
CentOS 7 中已改用 socket 激活的典型服务:
systemctl list-unit-files --type=socket
systemctl list-sockets
UNIT ACTIVATES
cups.socket cups.service
dbus.socket dbus.service
sshd.socket sshd@.service
rsyncd.socket rsyncd@.service
systemd-journald.socket systemd-journald.service
.socket 与 .service 单元的对照写法详见 4.10 节。
因此在 CentOS 7 及以上:
- 新服务应优先使用 systemd socket 激活,不要用 xinetd。
xinetd仍然可用,主要用于兼容遗留服务(如某些老旧的 tftp、telnet、商业软件的许可证服务)。- 部分服务(如 rsync、tftp)在 CentOS 7 中同时提供了 xinetd 配置文件和 systemd socket 单元,二者不能同时启用(会争抢端口)。
详见 08-01-系统启动与服务管理.md
1.7 配置文件
| 路径 | 说明 |
|---|---|
/etc/xinetd.conf |
主配置文件,一般无需修改/一般不用配置 |
/etc/xinetd.d/ |
子配置文件目录,每个被托管服务一个文件,主要配置在这里 |
xinetd 类似于一个托管平台,可托管其他小服务。托管后的好处是可利用 xinetd 强大的参数控制这些服务,并增强安全性(小服务自身可能不具备访问控制等能力)。
/etc/xinetd.conf 主配置与 defaults 段逐项详解
cat /etc/xinetd.conf
vim /etc/xinetd.conf
CentOS 默认内容:
#
# This is the master xinetd configuration file. Settings in the
# default section will be inherited by all service configurations
# unless explicitly overridden in the respective service
# configuration. See xinetd.conf in the man pages for a more
# detailed explanation of these attributes.
defaults
{
# The next two items are intended to be a quick access place to
# temporarily enable or disable services.
#
# enabled =
# disabled =
# Define general logging characteristics.
log_type = SYSLOG daemon info
log_on_failure = HOST
log_on_success = PID HOST DURATION EXIT
# Define access restriction defaults
#
# no_access =
# only_from =
# max_load = 0
cps = 50 10
instances = 50
per_source = 10
# Address and networking defaults
#
# bind =
# mdns = yes
v6only = no
# setup environmental attributes
#
# passenv =
groups = yes
umask = 002
# Generally, banners are not used. This sets up their global defaults
#
# banner =
# banner_fail =
# banner_success =
}
includedir /etc/xinetd.d
defaults 段的作用:其中的设置会被所有服务配置继承,除非在各服务自己的配置文件中显式覆盖。
逐项详解:
log_type —— 日志输出方式
两种形式:
log_type = SYSLOG <facility> [<level>]
log_type = FILE <文件路径> [<软限制字节> [<硬限制字节>]]
| 形式 | 说明 |
|---|---|
SYSLOG daemon info |
通过 syslog 输出,facility 为 daemon,级别为 info。最终由 rsyslog 按 /etc/rsyslog.conf 的规则写入 /var/log/messages。这是默认方式 |
SYSLOG authpriv |
写入认证相关日志,通常落到 /var/log/secure |
SYSLOG local0 info |
使用自定义 facility,可在 rsyslog 中单独定向到独立文件 |
FILE /var/log/xinetd.log |
直接写入指定文件,绕过 syslog |
FILE /var/log/xinetd.log 8388608 15728640 |
带大小限制:软限制 8MB(超过时记录一条警告到 syslog),硬限制 15MB(达到后 xinetd 停止记录该服务的日志) |
可用的 syslog facility:daemon、auth、authpriv、user、mail、news、uucp、local0 ~ local7。
可用的 level:emerg、alert、crit、err、warning、notice、info、debug。
配合 rsyslog 把 xinetd 日志单独定向:
vim /etc/rsyslog.conf
# 把 local0 facility 的日志单独写到一个文件
local0.* /var/log/xinetd_custom.log
systemctl restart rsyslog # CentOS 7
/etc/init.d/rsyslog restart # CentOS 6
日志管理详见 08-02-日志管理.md
log_on_success —— 连接成功时记录哪些信息
log_on_success = PID HOST DURATION EXIT USERID
| 取值 | 含义 |
|---|---|
PID |
记录被启动的服务进程 ID。对 type = INTERNAL 的服务记为 0 |
HOST |
记录客户端 IP 地址(最有用的一项) |
USERID |
通过 RFC 1413 identd 协议向客户端反查发起连接的远端用户名。需要客户端运行 identd 服务,现代系统基本都不跑,因此通常记为 unknown,且会引入额外延迟(要等 identd 超时)。生产环境建议不要在 log_on_success 中使用 |
EXIT |
记录服务进程的退出状态/退出码。若进程被信号杀死则记录信号编号 |
DURATION |
记录该次会话的持续时长(秒) |
TRAFFIC |
记录 redirect 转发的流量字节数(仅对使用 redirect 的服务有意义) |
ATTEMPT |
记录一次失败的连接尝试(此项通常用于 log_on_failure,写在 success 中无意义) |
log_on_failure —— 连接失败/被拒时记录哪些信息
log_on_failure = HOST ATTEMPT USERID
| 取值 | 含义 |
|---|---|
HOST |
记录被拒绝的客户端 IP |
ATTEMPT |
记录这是一次失败的访问尝试。日志中会出现 FAIL: <service> address from=<IP> 之类的记录 |
USERID |
同上,通过 identd 反查远端用户名 |
RECORD |
记录尽可能多的关于该客户端的信息(本地地址、远端地址、端口、identd 结果等),用于安全审计与入侵取证 |
+= 与 -= 运算符(xinetd 配置的重要语法):
log_on_failure = HOST # 直接赋值,覆盖 defaults 中的设置
log_on_failure += USERID # 在继承 defaults 的基础上【追加】 USERID
log_on_success -= EXIT # 在继承 defaults 的基础上【移除】 EXIT
这三个运算符适用于所有"取值为列表"的属性(log_on_success、log_on_failure、only_from、no_access、server_args、env、passenv、flags)。
instances —— 服务最大并发连接数(全局默认)
instances = 50
instances = UNLIMITED # 不限制
同一时刻该服务最多可以有多少个进程在运行。达到上限后,新连接会被直接拒绝(TCP 连接被关闭),并按 log_on_failure 记录。这是防止单个服务耗尽系统进程/内存的第一道防线。
per_source —— 每个源 IP 的最大连接数(全局默认)
per_source = 10
per_source = UNLIMITED
限制单个客户端 IP 最多能建立多少个并发连接。防止某一个 IP 通过大量并发把 instances 名额全部占满(这本身就是一种 DoS)。
instances = 50+per_source = 10:总共最多 50 个连接,任何单个 IP 最多占 10 个,即至少需要 5 个不同 IP 才能打满。
cps —— 连接速率限制(抗 DoS 的核心)
cps = 50 10
cps = <每秒最大新建连接数> <超限后暂停服务的秒数>
两个参数的含义:
- 第 1 个参数
50:每秒允许的最大新建连接数(connections per second)。 - 第 2 个参数
10:一旦超过上述速率,xinetd 会临时禁用该服务 10 秒。在这 10 秒内所有新连接都被立即拒绝,10 秒后自动恢复。
这是对抗连接洪泛(connection flood)攻击最有效的手段。日志中会出现:
xinetd[1234]: Deactivating service telnet due to excessive incoming connections. Restarting in 10 seconds.
xinetd[1234]: Activating service telnet
其他取值示例:
cps = 25 30 # 每秒最多 25 个连接,超限暂停 30 秒
cps = 10 60 # 更严格
cps = 0 0 # 关闭 cps 限制(不推荐)
includedir /etc/xinetd.d
指示 xinetd 读取指定目录下的所有文件作为额外配置。这是"每个服务一个文件"设计的实现基础。
- 目录中的文件按文件名排序依次读取。
- 会跳过以
.开头的隐藏文件、以~结尾的备份文件、以及包含.的文件名(如ssh.bak、rsync.rpmsave都不会被读取)。这是一个很实用的特性:备份配置时用cp ssh ssh.bak是安全的。 - 相关指令
includefile <文件>用于包含单个文件。
ls -a /etc/xinetd.d/
# 备份配置的安全做法(带点号的文件不会被加载)
cp /etc/xinetd.d/telnet /etc/xinetd.d/telnet.bak
# 危险做法(会被加载,造成服务重复定义报错)
cp /etc/xinetd.d/telnet /etc/xinetd.d/telnet_bak # 没有点号,会被加载!
defaults 段的其他常用属性
| 属性 | 说明 |
|---|---|
enabled = <服务列表> |
白名单:只启用列出的服务,其他全部禁用。快速临时开关的手段 |
disabled = <服务列表> |
黑名单:禁用列出的服务。优先级高于各服务自己的 disable |
no_access = <地址> |
全局黑名单,对所有服务生效 |
only_from = <地址> |
全局白名单,对所有服务生效。慎用,会限制所有托管服务 |
max_load = <数值> |
当系统 1 分钟平均负载超过此值时,停止接受新连接。如 max_load = 2.5。0 表示不限制。编译时需启用 --with-loadavg |
bind = <IP> |
全局绑定地址 |
mdns = yes/no |
是否通过 mDNS(多播 DNS/Bonjour)注册服务 |
v6only = yes/no |
是否只使用 IPv6(no 表示 IPv4/IPv6 双栈) |
passenv = <变量列表> |
传递给被托管服务的环境变量列表。空值表示不传递任何环境变量 |
groups = yes/no |
yes 表示被托管进程会获得 user 所属的所有附加组权限;no 只有主组 |
umask = 002 |
被托管进程的 umask 值(八进制) |
banner = <文件> |
全局连接横幅 |
banner_fail = <文件> |
全局拒绝横幅 |
banner_success = <文件> |
全局成功横幅 |
/etc/sysconfig/xinetd 启动参数:
cat /etc/sysconfig/xinetd
# xinetd command line options.
EXTRAOPTIONS=""
xinetd 命令行常用选项:
| 选项 | 说明 |
|---|---|
-d |
调试模式,前台运行并输出详细信息 |
-f <文件> |
指定配置文件(默认 /etc/xinetd.conf) |
-filelog <文件> |
把日志输出到指定文件 |
-syslog <facility> |
把日志输出到 syslog |
-stayalive |
即使没有配置任何服务也不退出 |
-pidfile <文件> |
PID 文件路径 |
-dontfork |
不后台化,保持前台运行(systemd 下常用) |
-limit <n> |
全局进程数上限 |
-logprocs <n> |
用于 identd 反查的最大并发进程数 |
-reuse |
设置 socket 的 SO_REUSEADDR(新版本已默认) |
-cc <秒> |
每隔指定秒数做一次内部状态一致性检查 |
-inetd_compat |
同时读取 /etc/inetd.conf(兼容模式) |
配置文件语法检查与重载:
xinetd -d -f /etc/xinetd.conf # 前台调试运行,可看到配置解析错误,Ctrl+C 退出
kill -HUP $(cat /var/run/xinetd.pid) # 发送 SIGHUP 平滑重载配置(不中断已有连接)
kill -USR1 $(pidof xinetd) # 软重配置:重读配置文件
kill -USR2 $(pidof xinetd) # 硬重配置:重读配置并重启所有服务
kill -QUIT $(pidof xinetd) # 终止 xinetd
kill -TERM $(pidof xinetd) # 转储内部状态到 /var/run/xinetd.dump
1.8 /etc/xinetd.d/ 下服务配置文件属性详解
默认配置示例(rsync)
以 /etc/xinetd.d/rsync 为例:
vim /etc/xinetd.d/rsync
cat /etc/xinetd.d/rsync
# default: off
# description: The rsync server is a good addition to an ftp server, as it \
# allows crc checksumming etc.
service rsync
{
disable = yes # yes 表示关闭,no 表示开启
flags = IPv6
socket_type = stream # 使用 tcp/udp
wait = no # 并发连接
user = root # 使用超级守护进程的用户
server = /usr/bin/rsync # 启动程序路径
server_args = --daemon # 启动参数
log_on_failure += USERID # 和日志格式有关
}
配置文件的基本语法:
service <服务名>
{
<属性> <运算符> <值>
...
}
- 运算符可以是
=(赋值/覆盖)、+=(在继承值上追加)、-=(从继承值中移除)。 - 属性名与值之间的空格数量无关,但
=两侧建议加空格。 - 以
#开头的行为注释。 - 大括号
{建议单独一行或紧跟服务名之后。
服务启动三要素
一个服务启动需要考虑(启动任意服务都需明确的三点):
- 用什么用户来启动(
user/group)。 - 启动命令是什么(
server,程序的绝对路径)。 - 是否有启动参数(
server_args)。
全部常用属性逐项详解
service <名称> —— 服务名称与 /etc/services 的关系
service telnet
service ssh
service rsync
服务名的作用:xinetd 用这个名字去 /etc/services 中查找对应的端口号和协议。
grep -w telnet /etc/services
grep -w ssh /etc/services
grep -w rsync /etc/services
telnet 23/tcp
telnet 23/udp
ssh 22/tcp # The Secure Shell (SSH) Protocol
ssh 22/udp # The Secure Shell (SSH) Protocol
rsync 873/tcp # rsync
rsync 873/udp # rsync
因此:
- 若
service名称在/etc/services中不存在,xinetd 启动时会报错:Service <名称> not found in /etc/services或unknown service。 - 解决办法有两个:(a) 在配置中显式加
port = <端口>;(b) 在/etc/services中添加一行定义该服务名与端口的对应关系。 - 若指定了
port且与/etc/services中的定义不一致,xinetd 会报警告(port and /etc/services disagreement),但仍以配置中的port为准。为避免混乱,建议二者保持一致。 type = UNLISTED表示"该服务不在 /etc/services 中",此时必须同时指定port。
disable —— 是否禁用该服务
disable = yes # yes 表示关闭(不加载该服务)
disable = no # no 表示开启(加载并监听)
yes 表示关闭,no 表示开启。这是最容易忘记改的一项,所有系统自带的配置文件默认都是 disable = yes。
也可以用主配置的 defaults 段中的 enabled/disabled 全局覆盖:
# 在 /etc/xinetd.conf 的 defaults 段中
disabled = telnet rsh rlogin # 无论各服务的 disable 是什么,这些一律禁用
enabled = tftp rsync # 只启用这些
CentOS 6 中也可用 chkconfig 快捷开关:
chkconfig rsync on # 把 disable 改为 no
chkconfig rsync off # 把 disable 改为 yes
socket_type —— 套接字类型
socket_type = stream # 面向流,对应 TCP
socket_type = dgram # 数据报,对应 UDP
socket_type = raw # 原始套接字,直接访问 IP 层
socket_type = seqpacket # 可靠的顺序数据报(很少用)
套接字类型,stream 对应 TCP,dgram 对应 UDP。必须与 protocol 匹配:stream ↔ tcp,dgram ↔ udp。
protocol —— 协议
protocol = tcp
protocol = udp
协议,通常为 tcp 或 udp。取值必须在 /etc/protocols 中有定义。
- 若省略此项,xinetd 会使用
/etc/services中该服务名对应的默认协议。 - 建议总是显式写出,避免歧义。
cat /etc/protocols | head -20
wait —— 单线程还是多线程(最容易理解错的一项)
wait = no # 多线程/并发(TCP 服务的典型值)
wait = yes # 单线程/串行等待(UDP 服务的典型值)
| 取值 | 含义 |
|---|---|
wait = no |
并发(多线程)模式。xinetd 每收到一个连接就 fork 一个子进程处理,自己立即返回继续 accept 新连接。多个客户端可同时被服务。TCP(stream)服务通常用 no |
wait = yes |
单线程模式。xinetd 把 socket 完整交给被启动的服务进程,然后等待(wait)该进程结束才继续处理新请求。期间新请求会被排队或丢弃。UDP(dgram)服务通常用 yes,因为 UDP 无连接、只有一个 socket,必须由子进程独占 |
no 表示并发(多线程),yes 表示单线程等待。
记忆方法:wait 问的是"xinetd 需要等待服务进程结束吗?"
no= 不等,马上回去接下一个 → 并发。yes= 要等,等完才接下一个 → 串行。
常见搭配:
| socket_type | protocol | wait | 典型服务 |
|---|---|---|---|
stream |
tcp |
no |
telnet、ssh、rsync、time-stream |
dgram |
udp |
yes |
tftp、time-dgram |
例外:某些 UDP 服务(如 xinetd 内建的
echo-dgram、daytime-dgram)因为每个数据报独立处理、处理极快,也可以用wait = no。
user 与 group —— 以什么身份运行
user = root # 使用超级守护进程的用户
user = nobody # 降权运行(安全推荐)
group = nobody
user = tftp
group = tftp
指定被托管服务以哪个用户/组身份运行。
- 只有 xinetd 以 root 运行时才能切换到其他用户(
setuid)。 - 安全原则:能不用 root 就不用。
tftp用nobody,rsync只读同步可用普通用户。需要绑定 1024 以下特权端口的服务由 xinetd 完成绑定,因此被托管进程本身不需要 root。 - 用户名必须存在于
/etc/passwd(组名在/etc/group),否则启动报Unknown user。 - 若省略
group,则使用该用户在/etc/passwd中的默认主组。 - 配合
groups = yes可让进程获得该用户的所有附加组权限。
server —— 被托管服务的启动程序路径
server = /usr/sbin/sshd
server = /usr/bin/rsync
server = /usr/sbin/in.telnetd
server = /usr/sbin/in.tftpd
被托管服务的启动程序路径。必须是绝对路径,且文件必须存在并可执行。
which rsync # 确认路径
ls -l /usr/sbin/in.telnetd
test -x /usr/sbin/in.tftpd && echo OK
若路径不对,xinetd 启动时会报:
xinetd[1234]: Executable /usr/sbin/in.telnetd does not exist
xinetd[1234]: Error parsing attribute server - DISABLING SERVICE
type = INTERNAL 的服务不需要此项(由 xinetd 自己实现)。
server_args —— 启动参数
server_args = -i # sshd 的 inetd 模式
server_args = --daemon # rsync 的 daemon 模式
server_args = -s /var/lib/tftpboot # tftpd 的根目录
server_args = -s /var/lib/tftpboot -c -v # 多个参数用空格分隔
server_args += --config=/etc/rsyncd.conf # 追加参数
启动参数。传递给 server 程序的命令行参数。
- 不包含程序名本身(即不要重复写 argv[0])。
- 参数数量无限制(这是 xinetd 优于 inetd 的一点,inetd 最多 5 个)。
- 若参数中包含空格,需要用引号包裹(xinetd 支持有限,复杂情况建议写包装脚本)。
instances —— 最大并发连接数
instances = 3 # 该服务最多 3 个并发连接
instances = UNLIMITED
服务最大并发连接数 / 最大并发连接数。达到上限后新连接被拒绝。
per_source —— 每个源 IP 的最大连接数
per_source = 1 # 每个源 IP 只能有 1 个连接
per_source = 5
per_source = UNLIMITED
每个源 IP 的最大连接数。
组合使用限制并发:
instances = 10 # 总共最多 10 个
per_source = 2 # 单 IP 最多 2 个,即至少要 5 个不同 IP 才能打满
超限时的日志:
xinetd[1234]: FAIL: telnet per_source_limit from=10.0.0.26
xinetd[1234]: FAIL: telnet service_limit from=10.0.0.26
cps —— 连接速率限制(两个参数)
cps = <每秒最大新建连接数> <超限后暂停服务的秒数>
cps = 25 30 # 每秒最多 25 个新连接,超限则暂停该服务 30 秒
cps = 5 10 # 更严格:每秒 5 个,超限暂停 10 秒
cps = 50 10 # 默认值
两个参数的含义:
- 第 1 个参数:每秒允许的最大新建连接数(rate)。
- 第 2 个参数:超过速率后,该服务被临时禁用(deactivate)的秒数,之后自动恢复。
与 instances 的区别:
| 属性 | 限制的是 |
|---|---|
instances |
同时存在的连接数(瞬时并发量) |
per_source |
单个 IP 同时存在的连接数 |
cps |
单位时间内新建的连接数(速率),并附带惩罚性冷却期 |
cps 是防御连接洪泛(快速建连再断开)的关键,instances 防御的是长连接堆积。两者应同时配置。
日志表现:
xinetd[1234]: Deactivating service telnet due to excessive incoming connections. Restarting in 30 seconds.
xinetd[1234]: Activating service telnet
only_from 与 no_access —— 基于地址的访问控制(核心功能)
only_from = <允许的地址列表> # 白名单:仅允许的 IP 或网段
no_access = <禁止的地址列表> # 黑名单:禁止的 IP 或网段
地址的多种写法:
| 写法 | 示例 | 说明 |
|---|---|---|
| 单个 IP | only_from = 10.0.0.26 |
精确匹配一个地址 |
| 多个 IP | only_from = 10.0.0.26 10.0.0.200 |
用空格分隔(不是逗号!) |
| CIDR 网段 | only_from = 10.0.0.0/24 |
最常用、最清晰的网段写法 |
| 掩码写法 | only_from = 10.0.0.0/255.255.255.0 |
等价于上面 |
| 不完整地址 | only_from = 10.0.0. |
末尾带点,表示 10.0.0.0/24。写 10.0. 表示 10.0.0.0/16,写 10. 表示 10.0.0.0/8 |
| 主机名 | only_from = client1.example.com |
需要能正确 DNS 反解,会引入延迟且不可靠,生产不推荐 |
| 域名后缀 | only_from = .example.com |
开头带点,匹配该域下所有主机(依赖反解) |
| 网络名 | only_from = mynetwork |
从 /etc/networks 中查找 |
| 本地 | only_from = localhost |
等价于 127.0.0.1 |
| 全部 | only_from = 0.0.0.0/0 |
允许所有(等于不限制) |
| 清空 | only_from = |
赋空值表示"谁都不允许"(等价于关闭服务)。注意与"不写这一项"完全不同 |
| IPv6 | only_from = 2001:db8::/32 |
IPv6 网段 |
| 追加 | only_from += 10.0.1.0/24 |
在继承的值上追加 |
| 移除 | only_from -= 10.0.0.200 |
从继承的值中移除 |
判定与优先级规则(关键,容易搞错):
- 若只配置了
only_from:只有列表内的地址能访问,其余全部被拒(白名单模式)。 - 若只配置了
no_access:列表内的地址被拒,其余全部允许(黑名单模式)。 - 若两者都配置:xinetd 分别在
only_from和no_access中查找与客户端地址匹配最精确(最长前缀)的那一条,然后比较:- 若
only_from中的匹配更精确 → 允许。 - 若
no_access中的匹配更精确 → 拒绝。 - 若两边精确度相同 → 拒绝(xinetd 报
address_confirm错误并拒绝)。 - 若只在一边匹配上 → 按那一边的结果。
- 若两边都没匹配上 → 拒绝(因为配了
only_from就意味着白名单模式)。
- 若
- 与书写顺序完全无关。
access_times —— 时间段控制
access_times = 9:00-18:00 # 只允许 9:00 到 18:00 访问
access_times = 08:00-12:00 13:00-17:30 # 多个时间段用空格分隔
access_times = 00:00-23:59 # 全天(等于不限制)
access_times = 22:00-23:59 00:00-06:00 # 跨零点必须拆成两段!
允许访问的时间范围。格式为 hh:mm-hh:mm。
重要注意点:
- 判定的是
xinetd所在服务器的本地时间(因此服务器时区和时间必须正确——这正是本文前半部分的内容与 xinetd 的关联点)。 - 不能直接跨零点。写
access_times = 22:00-06:00是无效的,必须拆成22:00-23:59 00:00-06:00。 - 只在建立连接的那一刻判定。已经建立的连接不会在超出时间段后被断开。
- 不支持按星期几限制(无法表达"仅工作日")。需要这种能力得用 firewalld 的 rich rule 或 iptables 的 time 模块。
- 超出时间段时的日志:
xinetd[1234]: FAIL: telnet time from=10.0.0.26
bind 或 interface —— 绑定指定地址
bind = 10.0.0.2 # 只在这个 IP 上监听
interface = 10.0.0.2 # interface 是 bind 的同义词(旧名称)
bind = 127.0.0.1 # 只允许本机访问
bind = ::1 # IPv6 本地
只在指定的本机 IP 地址上提供该服务。用途:
- 服务器有内外网双网卡时,让服务只在内网 IP 上监听,外网完全无法访问(比
only_from更彻底,因为端口在外网上根本不存在)。 - 同一个服务名在不同 IP 上提供不同配置(配合
id属性区分):
service telnet
{
id = telnet-internal
bind = 10.0.0.2
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/in.telnetd
only_from = 10.0.0.0/24
}
service telnet
{
id = telnet-loopback
bind = 127.0.0.1
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/in.telnetd
access_times = 00:00-23:59
}
验证效果:
ss -tnlp | grep :23
LISTEN 0 64 10.0.0.2:23 0.0.0.0:* users:(("xinetd",pid=5472,fd=5))
LISTEN 0 64 127.0.0.1:23 0.0.0.0:* users:(("xinetd",pid=5472,fd=6))
注意监听地址是具体 IP 而不是 0.0.0.0,这就是 bind 生效的标志。
redirect —— 端口转发/TCP 代理
redirect = <目标IP或主机名> <目标端口>
redirect = 10.0.0.100 22 # 把本机该端口的连接转发到 10.0.0.100:22
redirect = 192.168.1.50 3306 # 转发 MySQL
redirect = backend.example.com 8080
把到达本服务端口的连接转发到另一台主机的另一个端口,xinetd 充当简易的 TCP 代理。
- 使用
redirect时不需要(也不应该)配置server与server_args。 - 只支持 TCP(
socket_type = stream)。 - xinetd 会 fork 一个子进程在两个 socket 之间双向搬运数据。
- 可配合
log_on_success = TRAFFIC记录转发的字节数。 - 性能有限,不适合高流量场景(每连接一个进程)。生产环境更应使用 HAProxy、Nginx stream 模块或 iptables DNAT。
完整示例:把本机 2222 端口转发到内网某机器的 22 端口(跳板机场景):
service ssh-jump
{
disable = no
type = UNLISTED
socket_type = stream
protocol = tcp
wait = no
user = root
port = 2222
redirect = 10.0.0.100 22
only_from = 10.0.0.0/24
log_on_success = HOST PID DURATION TRAFFIC
log_on_failure = HOST ATTEMPT
}
log_on_success 与 log_on_failure —— 覆盖全局日志设置
log_on_success = PID HOST DURATION EXIT # 完全覆盖 defaults 中的设置
log_on_success += TRAFFIC # 在 defaults 基础上追加
log_on_success -= EXIT # 从 defaults 中移除
log_on_failure = HOST ATTEMPT RECORD
log_on_failure += USERID # 和日志格式有关
取值含义见 4.7 节。这里可针对单个服务覆盖或调整全局设置。
log_type —— 单个服务的独立日志
log_type = FILE /var/log/xinetd_ssh.log
log_type = FILE /var/log/xinetd_telnet.log 8388608 15728640
log_type = SYSLOG local0 info
日志输出方式,如 file <路径>。为单个服务指定独立的日志文件,便于分析。
注意:属性值
FILE大小写不敏感(写file也可以),但建议统一大写以符合官方文档风格。
nice —— 调整进程优先级
nice = 10 # 降低优先级(数值越大优先级越低)
nice = -5 # 提高优先级(需要 root)
nice = 0 # 默认
设置被托管服务进程的 nice 值,范围 -20(最高优先级)到 19(最低优先级)。
用途:让不重要的小服务(如 tftp、备份同步)让出 CPU,避免影响主业务:
nice = 15 # 备份类服务让出 CPU
进程优先级与 nice 详见 阶段06(进程管理)
env 与 passenv —— 环境变量控制
env = LANG=en_US.UTF-8 # 为被托管服务【添加】环境变量
env = MYVAR=value ANOTHER=value2 # 多个用空格分隔
env += EXTRA=1 # 追加
passenv = PATH HOME LANG # 【只传递】列出的环境变量给被托管服务
passenv = # 赋空值 = 不传递任何环境变量(最安全)
| 属性 | 含义 |
|---|---|
env |
添加环境变量到被托管服务的环境中。格式 名称=值 |
passenv |
过滤从 xinetd 继承的环境变量,只传递列出的那些。空值表示一个都不传 |
安全用途:清空环境变量可防止通过 LD_PRELOAD、LD_LIBRARY_PATH、IFS 等环境变量进行的注入攻击:
passenv = # 不继承任何环境变量
env = PATH=/usr/bin:/bin # 只显式给出一个干净的 PATH
flags —— 标志位
flags = IPv6
flags = REUSE
flags = IPv4
flags = NAMEINARGS
flags = REUSE IPv6 # 多个用空格分隔
| 取值 | 说明 |
|---|---|
IPv6 |
该服务使用 IPv6(同时也能接受 IPv4 映射地址,除非 v6only = yes) |
IPv4 |
该服务只使用 IPv4 |
REUSE |
设置 socket 的 SO_REUSEADDR,允许重启时立即重新绑定端口(新版 xinetd 已默认启用,此标志已废弃) |
INTERCEPT |
拦截数据包,检查来源地址是否合法后再转交。不能与 wait = yes 的服务或 INTERNAL 服务一起用 |
NORETRY |
fork 失败时不重试 |
IDONLY |
只接受能通过 identd 确认远端用户身份的连接(需要 USERID 在 log_on_success 中) |
NAMEINARGS |
server_args 的第一个参数被当作 argv[0](程序名)。用于兼容 inetd 风格的配置,尤其是配合 tcpd(TCP Wrappers)使用时必须加 |
NODELAY |
设置 TCP 的 TCP_NODELAY(禁用 Nagle 算法),降低小包延迟 |
KEEPALIVE |
设置 SO_KEEPALIVE,启用 TCP 保活探测 |
NOLIBWRAP |
禁用该服务的 TCP Wrappers(libwrap)检查,只使用 xinetd 自己的 only_from/no_access |
SENSOR |
把该服务变成一个蜜罐/探测器:任何连接到该端口的 IP 会被自动加入全局黑名单一段时间(配合 deny_time)。用于捕获端口扫描 |
LABELED |
用于 SELinux 标签化网络(MLS 环境) |
NAMEINARGS 的经典用法(通过 tcpd 包装):
service telnet
{
disable = no
flags = REUSE NAMEINARGS
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/tcpd # 实际执行的是 tcpd
server_args = in.telnetd # 第一个参数是被包装的真正程序
}
SENSOR 蜜罐用法(捕获扫描者):
service sensor-trap
{
disable = no
type = UNLISTED
socket_type = stream
protocol = tcp
wait = no
user = root
port = 1234
flags = SENSOR
deny_time = 60 # 把扫描者拉黑 60 分钟
log_on_failure = HOST ATTEMPT RECORD
banner_fail = /etc/xinetd.banner.fail
}
deny_time 的取值:FOREVER(永久)、NEVER(不拉黑)、或分钟数。
rlimit_cpu、rlimit_as 等 —— 资源限制
rlimit_cpu = 10 # 该服务进程最多消耗 10 秒 CPU 时间
rlimit_cpu = UNLIMITED
rlimit_as = 8M # 地址空间(虚拟内存)上限 8MB
rlimit_as = 16777216 # 也可用字节数
rlimit_data = 4M # 数据段大小上限
rlimit_stack = 2M # 栈大小上限
rlimit_rss = 8M # 常驻内存集大小上限
rlimit_files = 64 # 最大打开文件描述符数
| 属性 | 对应的 ulimit | 说明 |
|---|---|---|
rlimit_as |
RLIMIT_AS / ulimit -v |
地址空间(虚拟内存)上限。单位可用 K/M,或直接写字节数。超限时进程的 malloc 失败 |
rlimit_cpu |
RLIMIT_CPU / ulimit -t |
CPU 时间上限(秒)。超限时进程收到 SIGKILL。可防止死循环的服务耗尽 CPU |
rlimit_data |
RLIMIT_DATA / ulimit -d |
数据段大小上限 |
rlimit_stack |
RLIMIT_STACK / ulimit -s |
栈大小上限 |
rlimit_rss |
RLIMIT_RSS / ulimit -m |
常驻内存集上限 |
rlimit_files |
RLIMIT_NOFILE / ulimit -n |
最大打开文件数 |
这是在 cgroup 出现之前限制服务资源的手段。CentOS 7+ 若改用 systemd socket 激活,可用更强大的 MemoryLimit=、CPUQuota=、TasksMax= 等 cgroup 指令。
安全加固示例(严格限制一个不可信的小服务):
service risky-service
{
disable = no
type = UNLISTED
port = 9999
socket_type = stream
protocol = tcp
wait = no
user = nobody
group = nobody
server = /usr/local/bin/risky
nice = 15
instances = 5
per_source = 1
cps = 5 30
rlimit_cpu = 10
rlimit_as = 32M
rlimit_files = 32
passenv =
env = PATH=/usr/bin:/bin
only_from = 10.0.0.0/24
log_on_success = HOST PID DURATION EXIT
log_on_failure = HOST ATTEMPT RECORD
}
type —— 服务类型
type = INTERNAL
type = UNLISTED
type = RPC
type = TCPMUX
type = TCPMUXPLUS
type = INTERNAL UNLISTED # 可组合
| 取值 | 说明 |
|---|---|
INTERNAL |
该服务由 xinetd 自身实现,不需要外部程序,因此不需要 server 属性。xinetd 内建实现的服务有:echo(回显)、time(RFC 868 时间)、daytime(可读时间)、chargen(字符生成,测试用)、discard(丢弃,即 /dev/null 服务) |
UNLISTED |
该服务未在 /etc/services(或 /etc/rpc)中列出,因此必须显式指定 port(RPC 服务则需 rpc_number)。这是自定义端口服务的标准做法,比修改 /etc/services 更干净 |
RPC |
该服务是 RPC 服务(如 NFS 相关),需要配合 rpc_version 与 rpc_number |
TCPMUX / TCPMUXPLUS |
通过 RFC 1078 的 TCPMUX 协议(端口 1)访问的服务。需要 tcpmux-server 服务已启用 |
INTERNAL 服务示例(/etc/xinetd.d/echo-stream):
service echo
{
disable = yes
type = INTERNAL
id = echo-stream
socket_type = stream
protocol = tcp
user = root
wait = no
}
UNLISTED 服务示例(自定义端口,推荐做法):
service my-custom-service
{
disable = no
type = UNLISTED
port = 9000
socket_type = stream
protocol = tcp
wait = no
user = nobody
server = /usr/local/bin/myserver
server_args = --verbose
only_from = 10.0.0.0/24
}
type = UNLISTED+port的组合是给服务指定非标准端口的正确姿势,无需污染/etc/services。
id —— 服务的唯一标识
id = telnet-internal
id = time-stream
id = time-dgram
当同一个服务名(service 名称相同)需要定义多个不同配置时,用 id 加以区分。
典型场景:
- 同一服务的 TCP 与 UDP 版本:
time-stream与time-dgram都叫service time,靠id区分。 - 同一服务绑定到不同 IP:如前面
bind一节的例子。 - 同一端口的不同访问控制策略。
- 若不指定,
id默认等于service名称。 id也是日志中显示的服务标识,因此起个有意义的名字便于排查。- 若有两个配置块的
service名和id都相同,xinetd 会报错:service <名> already exists。
port —— 显式指定监听端口
port = 2222
port = 9000
指定监听端口(若与 /etc/services 不一致,需同步修改 /etc/services 或改用 type = UNLISTED)。
banner、banner_success、banner_fail —— 横幅信息
banner = /etc/xinetd.banner # 【任何】连接建立时,先把该文件内容发给客户端
banner_success = /etc/xinetd.banner.ok # 访问控制【通过】后,把该文件内容发给客户端
banner_fail = /etc/xinetd.banner.deny # 访问控制【被拒】时,把该文件内容发给客户端后关闭连接
| 属性 | 触发时机 |
|---|---|
banner |
在做任何访问控制判定之前,连接刚建立时就发送。无论后续是否被拒都会看到 |
banner_success |
访问控制通过之后、启动真正的服务之前发送 |
banner_fail |
访问控制被拒时发送,然后立即关闭连接 |
只对 TCP(socket_type = stream) 服务有效。
创建横幅文件:
cat > /etc/xinetd.banner << 'EOF'
********************************************************************
* WARNING: Authorized access only. *
* All connections are monitored and recorded. *
* Disconnect IMMEDIATELY if you are not an authorized user. *
********************************************************************
EOF
cat > /etc/xinetd.banner.deny << 'EOF'
********************************************************************
* ACCESS DENIED *
* Your IP address is not permitted to access this service. *
* This attempt has been logged and reported. *
* Contact ops@example.com if you believe this is an error. *
********************************************************************
EOF
cat > /etc/xinetd.banner.ok << 'EOF'
Welcome. Connection established at server time.
EOF
chmod 644 /etc/xinetd.banner /etc/xinetd.banner.deny /etc/xinetd.banner.ok
在服务中使用:
service telnet
{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/in.telnetd
only_from = 10.0.0.0/24
banner = /etc/xinetd.banner
banner_fail = /etc/xinetd.banner.deny
banner_success = /etc/xinetd.banner.ok
log_on_failure = HOST ATTEMPT RECORD
}
法律意义:
banner中的授权警告文字在很多司法辖区是追究非授权访问责任的必要前提,等级保护测评也会检查。
属性汇总速查表
| 字段 | 说明 |
|---|---|
disable |
yes 表示关闭,no 表示开启 |
socket_type |
套接字类型,stream 对应 TCP,dgram 对应 UDP |
wait |
no 表示并发(多线程),yes 表示单线程等待 |
user |
以哪个用户身份运行被托管服务 |
group |
以哪个组身份运行 |
server |
被托管服务的启动程序路径(绝对路径) |
server_args |
启动参数 |
log_on_failure |
登录失败时的日志记录选项 |
log_on_success |
连接成功时的日志记录选项 |
log_type |
日志输出方式,如 file <路径> 或 SYSLOG <facility> |
protocol |
协议,通常为 tcp 或 udp |
only_from |
仅允许的 IP 或网段 |
no_access |
禁止的 IP 或网段 |
instances |
最大并发连接数 |
per_source |
每个源 IP 的最大连接数 |
cps |
每秒最大连接数 + 超限暂停秒数 |
access_times |
允许访问的时间范围 |
port |
指定监听端口(需同步修改 /etc/services,或配合 type = UNLISTED) |
bind / interface |
绑定到指定本机 IP |
redirect |
转发到其他主机的端口 |
nice |
进程优先级(-20 ~ 19) |
env / passenv |
环境变量的添加 / 过滤 |
flags |
标志位(IPv6、NAMEINARGS、SENSOR、NOLIBWRAP 等) |
rlimit_cpu / rlimit_as / rlimit_files 等 |
资源限制 |
type |
服务类型(INTERNAL / UNLISTED / RPC / TCPMUX) |
id |
服务唯一标识(同名服务多配置时区分) |
banner / banner_success / banner_fail |
横幅信息文件 |
deny_time |
配合 flags = SENSOR,把扫描者拉黑的分钟数 |
umask |
被托管进程的 umask |
groups |
是否继承用户的附加组 |
max_load |
系统负载超过此值时停止接受连接 |
mdns |
是否通过 mDNS 注册 |
v6only |
是否只用 IPv6 |
rpc_version / rpc_number |
RPC 服务的版本与编号 |
更多配置模板可参考(官方示例,非常有价值):
cat /usr/share/doc/xinetd-2.3.14/sample.conf
ls /usr/share/doc/xinetd-*/
man xinetd.conf # 最权威的属性参考
man xinetd
man 5 xinetd.conf
1.9 与独立守护进程的区别(小结)
- 独立守护进程(standalone):服务进程持续运行并自行监听端口(如
sshd、ntpd),响应快,但常驻内存。 - xinetd 托管守护进程:由
xinetd统一监听端口,请求到达时才唤起被托管服务,节省资源,并可借助xinetd提供访问控制、日志、并发限制等增强功能;适合访问频率低、自身功能简单的小服务。
1.10 xinetd 配置实验
实验 1(例一):将 sshd 托管到 xinetd
sshd 自身配置文件为 /etc/ssh/sshd_config,但功能有限,可选择托管到 xinetd 以实现额外功能 / 获取额外控制能力。
警告:本实验会中断 SSH 服务。请务必通过物理终端、VNC 或虚拟机控制台操作,不要在生产环境的唯一 SSH 通道上做,否则一旦配置出错将彻底失去远程访问能力。建议在虚拟机快照后进行。
先停止独立的 sshd 服务:
/etc/init.d/sshd stop # CentOS 6
systemctl stop sshd # CentOS 7
手动建立配置文件 /etc/xinetd.d/ssh(默认不存在):
vim /etc/xinetd.d/ssh
service ssh
{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/sshd
server_args = -i
}
关键点解释:
server_args = -i:sshd的-i选项表示"inetd 模式",让 sshd 从标准输入输出读写数据,而不是自己去 bind/listen。这个参数不能省,否则 sshd 会尝试自己绑定端口从而失败。service ssh:ssh在/etc/services中有定义(22/tcp),所以不需要port。user = root:sshd 需要 root 权限来做用户认证与 setuid。
重启 xinetd:
/etc/init.d/xinetd restart # CentOS 6
systemctl restart xinetd # CentOS 7
验证 22 端口:
lsof -i:22
netstat -ntlp | grep :22
ss -tnlp | grep :22 # CentOS 7
输出:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
xinetd 5472 root 5u IPv6 58673 0t0 TCP *:ssh (LISTEN)
查看 22 端口的监听进程由 sshd 变为 xinetd,即表示托管成功。
从客户端测试连接:
ssh root@10.0.0.200
ssh -v root@10.0.0.200 # -v 查看详细过程,便于排查
在服务端观察进程变化(连接期间):
ps -ef | grep sshd # 应能看到一个由 xinetd 派生的临时 sshd 进程
pstree -p | grep -A 2 xinetd
xinetd(5472)───sshd(6103)───bash(6108)
连接断开后再看:
ps -ef | grep sshd # 临时 sshd 进程已消失
恢复为独立守护进程:
vim /etc/xinetd.d/ssh # 把 disable 改为 yes
# 或直接删掉/改名
mv /etc/xinetd.d/ssh /etc/xinetd.d/ssh.bak # 带点号,xinetd 不会加载
systemctl restart xinetd
systemctl start sshd
ss -tnlp | grep :22 # 应重新显示 sshd
生产环境不要把 sshd 托管到 xinetd:SSH 是高频访问、需要高性能与稳定性的服务,托管后每次连接都要 fork+exec sshd(初始化密钥、加载 PAM),延迟明显增加;且 xinetd 挂掉就完全无法登录,风险极高。此实验仅用于理解 xinetd 的托管机制与访问控制能力。真实的 SSH 访问控制应使用
sshd_config的AllowUsers/DenyUsers/Match、firewalld 或 TCP Wrappers(CentOS 6)。
实验 2(例二):基于 IP 或网段的访问控制
下面在例一基础上加上对 IP 或网段的访问控制功能,这是 sshd 本身不具备的(除非写 iptables)。托管后可直接使用 only_from / no_access。
仅允许指定 IP(白名单):
service ssh
{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/sshd
server_args = -i
only_from = 10.0.0.26 10.0.0.200 # 只有这两个 IP 能连
}
拒绝指定 IP(黑名单):
service ssh
{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/sshd
server_args = -i
no_access = 10.0.0.26 10.0.0.200 # 只有这两个 IP 不能连,其它都可以
}
允许网段但排除个别 IP(最常用的组合):
允许 10.0.0.0/24 网段,但排除其中两台:
service ssh
{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/sshd
server_args = -i
only_from = 10.0.0.0/24
no_access = 10.0.0.200 10.0.0.26 # 只能 10 网段访问,但 200 和 26 不能访问
}
这个组合能生效的原因:no_access 中的 10.0.0.200(/32 精确匹配)比 only_from 中的 10.0.0.0/24 更精确,因此 no_access 胜出,该 IP 被拒绝。而 10.0.0.50 只匹配 only_from 的 /24,因此被允许。
规则优先级说明(关于 only_from 与 no_access 的叠加规则):
若写为下面这样:
no_access = 10.0.0.0/24
only_from = 10.0.0.10 10.0.0.26
结果为 10.0.0.10 与 10.0.0.26 也不能连接,调换顺序结果一样(与书写顺序无关)。
原因:按 xinetd 的匹配精确度规则,对于 10.0.0.10 这个客户端:
only_from中匹配到10.0.0.10(/32,精确度 32 位)。no_access中匹配到10.0.0.0/24(精确度 24 位)。- 按"最精确者胜",应该是
only_from胜出而被允许……
但实际结果是被拒绝。这是因为 xinetd 的实现中,当 no_access 中存在覆盖该地址的规则时会整体拒绝——禁止范围优先级使明确禁止的网段整体被拒绝。
实践建议:不要依赖复杂的优先级推理。写法要清晰:
# 推荐写法 1:只用 only_from,把允许的地址一一列出(最清晰、最可靠)
only_from = 10.0.0.10 10.0.0.26 10.0.1.0/24
# 推荐写法 2:宽范围 only_from + 精确 no_access 排除
only_from = 10.0.0.0/24
no_access = 10.0.0.200 10.0.0.26
# 不推荐:宽范围 no_access + 精确 only_from(行为反直觉)
no_access = 10.0.0.0/24
only_from = 10.0.0.10
验证访问被拒:
# 在被禁止的 IP(如 10.0.0.26)上尝试连接
ssh root@10.0.0.200
ssh_exchange_identification: Connection closed by remote host
或
ssh_exchange_identification: read: Connection reset by peer
注意:xinetd 拒绝时是先接受 TCP 连接再立即关闭,所以客户端看到的不是"Connection refused"(那是端口没监听),而是"Connection closed by remote host"。这是判断"是被 xinetd 的访问控制拒绝"还是"端口没开/被防火墙 DROP"的重要线索。
服务端日志中的记录:
tail -f /var/log/messages | grep xinetd
grep xinetd /var/log/messages | tail -20
journalctl -u xinetd -f # CentOS 7
Aug 22 11:05:31 web01 xinetd[5472]: FAIL: ssh address from=10.0.0.26
Aug 22 11:06:12 web01 xinetd[5472]: START: ssh pid=6103 from=10.0.0.50
Aug 22 11:08:44 web01 xinetd[5472]: EXIT: ssh status=0 pid=6103 duration=152(sec)
日志格式说明:
| 关键字 | 含义 |
|---|---|
START: <服务> pid=<PID> from=<IP> |
服务成功启动,连接被接受 |
EXIT: <服务> status=<退出码> pid=<PID> duration=<秒>(sec) |
服务进程退出(log_on_success 含 EXIT 和 DURATION 时出现) |
FAIL: <服务> address from=<IP> |
因地址访问控制(only_from/no_access)被拒绝 |
FAIL: <服务> time from=<IP> |
因 access_times 时间段限制被拒绝 |
FAIL: <服务> service_limit from=<IP> |
因超过 instances 上限被拒绝 |
FAIL: <服务> per_source_limit from=<IP> |
因超过 per_source 上限被拒绝 |
FAIL: <服务> libwrap from=<IP> |
因 TCP Wrappers(hosts.deny)被拒绝 |
Deactivating service <服务> due to excessive incoming connections |
因超过 cps 速率被临时禁用 |
实验 3(例三):连接数、时间和日志控制(man xinetd.conf)
在例二基础上增加功能。需求:
- 控制该服务最多 3 个并发连接,每个源 IP 只能 1 个连接。
- 控制只能 9:00 到 18:00 进行 ssh 连接。
- 指定日志记录到
/var/log/xinetd_ssh.log。
service ssh
{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/sshd
server_args = -i
instances = 3
per_source = 1
access_times = 9:00-18:00
log_type = file /var/log/xinetd_ssh.log
}
常用参数说明:
| 参数 | 说明 |
|---|---|
instances |
服务最大并发连接数 |
per_source |
每个源 IP 的最大连接数 |
access_times |
允许访问的时间段 |
log_type |
日志记录方式,file 指定文件 |
重启并验证:
/etc/init.d/xinetd restart # CentOS 6
systemctl restart xinetd # CentOS 7
验证 per_source:
# 在同一台客户端上开两个终端,各自 ssh 到服务器
# 第 1 个连接成功,第 2 个立即被断开
ssh root@10.0.0.200 # 终端 1:成功
ssh root@10.0.0.200 # 终端 2:被拒
tail -f /var/log/xinetd_ssh.log
FAIL: ssh per_source_limit from=10.0.0.26
验证 access_times:
# 把服务器时间临时改到时间段之外(测试环境!)
date -s "20:00:00"
systemctl restart xinetd
# 从客户端连接,应被拒绝
FAIL: ssh time from=10.0.0.26
# 恢复正确时间(这里体现了时间同步与 xinetd 的关联:
# access_times 依赖服务器时间准确,时间错了访问控制就会误判!)
ntpdate -u ntp.aliyun.com # 或 chronyc makestep
hwclock -w
date
重要关联:
access_times判定使用的是服务器本地时间。如果服务器时间或时区不对,会出现"明明在工作时间却被拒绝"或"半夜也能访问"的诡异现象。这正是本文前三章时间同步内容与 xinetd 的直接关联点。
验证独立日志文件:
ls -l /var/log/xinetd_ssh.log
cat /var/log/xinetd_ssh.log
tail -f /var/log/xinetd_ssh.log
11/8/22@11:05:31: START: ssh pid=6103 from=10.0.0.50
11/8/22@11:08:44: EXIT: ssh status=0 pid=6103 duration=193(sec)
11/8/22@11:09:02: FAIL: ssh address from=10.0.0.26
注意
log_type = FILE的日志不会同时进入/var/log/messages,二者是互斥的输出目标。
实验 4(例四):修改 SSH 连接端口
在实验 3 配置基础上增加 port = 2222。编辑 /etc/xinetd.d/ssh:
service ssh
{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/sshd
server_args = -i
instances = 3
per_source = 1
access_times = 9:00-18:00
log_type = file /var/log/xinetd_ssh.log
port = 2222
}
同时需要修改 /etc/services 中对应的端口 / 对应条目:
cp /etc/services /etc/services.bak # 务必备份!这个文件很重要
vim /etc/services
将其中 ssh 相关行改为:
ssh 2222/tcp
ssh 2222/udp
(源文档中另一处的写法为 ssh 2222 两行,含义相同,标准格式应带 /tcp 与 /udp。)
man xinetd.conf中有说明,若在 Xinetd 中指定非标准端口,需同步修改/etc/services。否则 xinetd 会报警告port and /etc/services disagreement。
重启 xinetd:
/etc/init.d/xinetd restart # CentOS 6
systemctl restart xinetd # CentOS 7
验证新端口:
netstat -ntlp | grep :2222
ss -tnlp | grep :2222
lsof -i:2222
客户端访问时需指定端口 / 客户端连接需指定端口:
ssh 10.0.0.200 -p 2222
ssh -p 2222 root@10.0.0.200
注意:在服务器本机上连接自己可不加
-p 2222,因为本机/etc/services中默认端口也已改为 2222。自己连自己不用加-p 2222,因为默认就是用 2222 连接。也就是说/etc/services中的端口既决定服务端监听,也影响本机作为客户端访问其他主机时的默认端口。
这个副作用是修改 /etc/services 的最大问题:它是全局的,会影响本机上所有读取该文件的程序。例如改了 ssh 的条目后,本机 ssh 其他主机 会默认连对方的 2222 端口而失败。
更好的做法:用 type = UNLISTED 而不修改 /etc/services:
service ssh-alt
{
disable = no
type = UNLISTED # 声明该服务不在 /etc/services 中
port = 2222 # 直接指定端口,无需改 /etc/services
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/sshd
server_args = -i
instances = 3
per_source = 1
access_times = 9:00-18:00
log_type = FILE /var/log/xinetd_ssh.log
only_from = 10.0.0.0/24
}
systemctl restart xinetd
ss -tnlp | grep :2222
ssh -p 2222 root@10.0.0.200
这样既实现了自定义端口,又不污染 /etc/services,是推荐做法。
别忘了防火墙也要放行新端口:
# CentOS 6
iptables -I INPUT -p tcp --dport 2222 -j ACCEPT
service iptables save
# CentOS 7
firewall-cmd --permanent --add-port=2222/tcp
firewall-cmd --reload
若启用了 SELinux,非标准端口还需要打标签:
semanage port -l | grep ssh
semanage port -a -t ssh_port_t -p tcp 2222 # 添加端口标签
# 若已存在则用 -m 修改
semanage port -m -t ssh_port_t -p tcp 2222
详见 07-03(防火墙与 SELinux)
更多配置模板可参考:
cat /usr/share/doc/xinetd-2.3.14/sample.conf
1.11 完整实验:用 xinetd 管理 telnet 服务
这是最经典的 xinetd 实验,因为 telnet 是天生的瞬时守护进程(本身没有 daemon 模式),必须靠 xinetd 托管。
安全警告:telnet 以明文传输密码,生产环境绝对禁止使用。本实验仅在隔离的测试环境中进行,做完请立即关闭。
环境: 服务端 10.0.0.200,客户端 10.0.0.26 与 10.0.0.50。
第 1 步:服务端安装
yum install xinetd telnet-server telnet -y
rpm -qa | grep telnet
| 包 | 说明 |
|---|---|
telnet-server |
服务端,提供 /usr/sbin/in.telnetd |
telnet |
客户端命令 telnet |
xinetd |
超级守护进程 |
安装 telnet-server 后会自动生成配置文件:
ls -l /etc/xinetd.d/telnet
cat /etc/xinetd.d/telnet
# default: on
# description: The telnet server serves telnet sessions; it uses \
# unencrypted username/password pairs for authentication.
service telnet
{
flags = REUSE
socket_type = stream
wait = no
user = root
server = /usr/sbin/in.telnetd
log_on_failure += USERID
disable = yes
}
第 2 步:编写配置(启用并加上完整控制)
cp /etc/xinetd.d/telnet /etc/xinetd.d/telnet.bak # 带点号,不会被 xinetd 加载
vim /etc/xinetd.d/telnet
service telnet
{
# ---------- 基本 ----------
disable = no
flags = REUSE
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/in.telnetd
server_args = -a none
# ---------- 并发与速率限制 ----------
instances = 5
per_source = 2
cps = 10 30
# ---------- 访问控制 ----------
only_from = 10.0.0.0/24
access_times = 00:00-23:59
# ---------- 日志 ----------
log_type = FILE /var/log/xinetd_telnet.log
log_on_success = PID HOST DURATION EXIT
log_on_failure = HOST ATTEMPT RECORD
# ---------- 横幅 ----------
banner = /etc/xinetd.banner
banner_fail = /etc/xinetd.banner.deny
# ---------- 资源限制 ----------
nice = 10
rlimit_cpu = 3600
}
创建横幅文件:
cat > /etc/xinetd.banner << 'EOF'
====================================================================
WARNING: Authorized users only. All activity is logged.
====================================================================
EOF
cat > /etc/xinetd.banner.deny << 'EOF'
====================================================================
ACCESS DENIED - Your IP is not authorized. This has been logged.
====================================================================
EOF
chmod 644 /etc/xinetd.banner /etc/xinetd.banner.deny
第 3 步:启动服务
# CentOS 6
/etc/init.d/xinetd restart
chkconfig xinetd on
chkconfig --list | grep -A 20 'xinetd based'
# CentOS 7
systemctl restart xinetd
systemctl enable xinetd
systemctl status xinetd
若配置有语法错误,status 会显示,也可用调试模式查看:
xinetd -d -f /etc/xinetd.conf # Ctrl+C 退出
grep xinetd /var/log/messages | tail -20
journalctl -u xinetd -n 30
第 4 步:用 netstat / ss 验证监听
netstat -ntlup | grep :23 # CentOS 6 常用
ss -tnlp | grep :23 # CentOS 7 推荐
lsof -i:23
ps -ef | grep xinetd
tcp 0 0 0.0.0.0:23 0.0.0.0:* LISTEN 5472/xinetd
或(ss 输出格式):
LISTEN 0 64 *:23 *:* users:(("xinetd",pid=5472,fd=5))
关键判断点:监听 23 端口的进程是 xinetd,而不是 in.telnetd。 因为 telnetd 平时并不运行。
netstat 与 ss 常用选项对照:
| 选项 | 说明 |
|---|---|
-n |
不解析服务名/主机名,直接显示数字(更快) |
-t |
只显示 TCP |
-u |
只显示 UDP |
-l |
只显示监听状态(LISTEN)的 socket |
-p |
显示占用该 socket 的进程 PID 与名称(需 root) |
-a |
显示所有 socket(含已建立的连接) |
第 5 步:防火墙放行
# CentOS 6
iptables -I INPUT -s 10.0.0.0/24 -p tcp --dport 23 -j ACCEPT
service iptables save
service iptables restart
# CentOS 7
firewall-cmd --permanent --add-service=telnet
# 或
firewall-cmd --permanent --add-port=23/tcp
firewall-cmd --reload
firewall-cmd --list-all
第 6 步:客户端连接测试
# 在客户端 10.0.0.50 上
telnet 10.0.0.200
telnet 10.0.0.200 23
成功的输出(可看到横幅):
Trying 10.0.0.200...
Connected to 10.0.0.200.
Escape character is '^]'.
====================================================================
WARNING: Authorized users only. All activity is logged.
====================================================================
Kernel 3.10.0-1160.el7.x86_64 on an x86_64
login: testuser
Password:
[testuser@web01 ~]$
注意:CentOS 默认禁止 root 通过 telnet 登录(由
/etc/securetty控制)。用普通用户登录,或(极不推荐)把pts/0等加入/etc/securetty。
useradd testuser
echo "testuser123" | passwd --stdin testuser # 创建测试用户
cat /etc/securetty # 查看允许 root 登录的终端
在服务端观察进程(连接期间):
ps -ef | grep telnetd
pstree -p | grep -A 3 xinetd
ss -tnp | grep :23 # 查看已建立的连接
xinetd(5472)───in.telnetd(6203)───login(6204)───bash(6210)
断开后(在 telnet 会话中输入 exit 或按 Ctrl+] 然后 quit):
ps -ef | grep telnetd # 进程已消失,验证了"瞬时"特性
第 7 步:修改 only_from 后验证访问被拒
vim /etc/xinetd.d/telnet
把 only_from 改为只允许一个 IP,排除测试机:
only_from = 10.0.0.50
no_access = 10.0.0.26
或者用网段 + 排除的写法:
only_from = 10.0.0.0/24
no_access = 10.0.0.26
systemctl restart xinetd # 或 /etc/init.d/xinetd restart
从被禁止的 IP(10.0.0.26)连接:
telnet 10.0.0.200
Trying 10.0.0.200...
Connected to 10.0.0.200.
Escape character is '^]'.
====================================================================
ACCESS DENIED - Your IP is not authorized. This has been logged.
====================================================================
Connection closed by foreign host.
关键观察:TCP 连接建立成功了(显示 Connected to),然后立刻被关闭并显示 banner_fail 的内容。这与"端口未开放"(Connection refused)和"被防火墙 DROP"(长时间无响应后 Connection timed out)有明显区别,是排错时的重要线索:
| 客户端表现 | 原因 |
|---|---|
Connected to 后立即 Connection closed by foreign host |
被 xinetd 的访问控制拒绝(only_from/no_access/access_times/instances) |
Connection refused |
端口没有任何程序在监听,或防火墙用了 REJECT |
Connection timed out(长时间挂起) |
防火墙 DROP,或网络不通 |
No route to host |
路由问题或防火墙 REJECT with icmp-host-prohibited |
从允许的 IP(10.0.0.50)连接,应正常成功。
第 8 步:修改 access_times 后验证访问被拒
date # 先确认当前时间,假设是 14:30
vim /etc/xinetd.d/telnet
把时间段改成一个不包含当前时刻的范围:
access_times = 02:00-04:00
systemctl restart xinetd
从任意允许的 IP 连接:
telnet 10.0.0.200
Trying 10.0.0.200...
Connected to 10.0.0.200.
Escape character is '^]'.
====================================================================
ACCESS DENIED - Your IP is not authorized. This has been logged.
====================================================================
Connection closed by foreign host.
再改回包含当前时刻的范围验证恢复:
access_times = 09:00-18:00
systemctl restart xinetd
telnet 10.0.0.200 # 应恢复正常(若当前在 9-18 点之间)
测试跨零点的正确写法:
# 错误写法(无效)
# access_times = 22:00-06:00
# 正确写法:拆成两段
access_times = 22:00-23:59 00:00-06:00
第 9 步:验证 instances 与 per_source 限制
instances = 2
per_source = 1
systemctl restart xinetd
在同一台客户端开 2 个终端各自 telnet,第 2 个会被拒(per_source = 1)。
从 2 台不同客户端各连 1 个成功,第 3 台的连接会被拒(instances = 2)。
第 10 步:查看日志中的记录
cat /var/log/xinetd_telnet.log
tail -f /var/log/xinetd_telnet.log
11/8/22@14:05:31: START: telnet pid=6203 from=10.0.0.50
11/8/22@14:08:44: EXIT: telnet status=0 pid=6203 duration=193(sec)
11/8/22@14:09:02: FAIL: telnet address from=10.0.0.26
11/8/22@14:12:15: FAIL: telnet time from=10.0.0.50
11/8/22@14:15:33: FAIL: telnet per_source_limit from=10.0.0.50
11/8/22@14:16:02: FAIL: telnet service_limit from=10.0.0.99
若使用 log_type = SYSLOG(默认),日志在 /var/log/messages:
grep xinetd /var/log/messages | tail -30
grep 'FAIL' /var/log/messages | grep telnet
journalctl -u xinetd --since "1 hour ago" | grep telnet # CentOS 7
统计被拒次数最多的 IP(安全分析):
grep 'FAIL' /var/log/xinetd_telnet.log | awk -F'from=' '{print $2}' | sort | uniq -c | sort -rn
第 11 步:实验收尾(关闭 telnet)
vim /etc/xinetd.d/telnet # disable = yes
systemctl restart xinetd
ss -tnlp | grep :23 # 应无输出
yum remove telnet-server -y # 彻底移除
firewall-cmd --permanent --remove-port=23/tcp
firewall-cmd --reload
1.12 完整实验:用 xinetd 管理 tftp 服务
tftp 是最典型的 xinetd 托管服务(UDP、访问稀少、常用于网络设备配置备份和 PXE 网络安装)。
服务端配置
yum install xinetd tftp-server tftp -y
cat /etc/xinetd.d/tftp
默认配置:
# default: off
# description: The tftp server serves files using the trivial file transfer \
# protocol. The tftp protocol is often used to boot diskless \
# workstations, download configuration files to network-aware printers, \
# and to start the installation process for some operating systems.
service tftp
{
socket_type = dgram
protocol = udp
wait = yes
user = root
server = /usr/sbin/in.tftpd
server_args = -s /var/lib/tftpboot
disable = yes
per_source = 11
cps = 100 2
flags = IPv4
}
注意 UDP 服务的典型特征:socket_type = dgram、protocol = udp、wait = yes(单线程)。
修改为启用并加上访问控制:
cp /etc/xinetd.d/tftp /etc/xinetd.d/tftp.bak
vim /etc/xinetd.d/tftp
service tftp
{
disable = no
socket_type = dgram
protocol = udp
wait = yes
user = root
server = /usr/sbin/in.tftpd
server_args = -s /var/lib/tftpboot -c -v -v
per_source = 11
cps = 100 2
flags = IPv4
only_from = 10.0.0.0/24
log_type = FILE /var/log/xinetd_tftp.log
log_on_success = PID HOST DURATION
log_on_failure = HOST ATTEMPT
}
in.tftpd 的 server_args 常用参数:
| 参数 | 说明 |
|---|---|
-s <目录> |
指定根目录(chroot),客户端只能访问该目录内的文件 |
-c |
允许客户端创建新文件(上传)。不加则只能下载已存在的文件 |
-v |
增加日志详细程度,可重复(-v -v 更详细) |
-u <用户> |
以指定用户身份运行 |
-p |
使用标准权限检查(配合 -c 时需要) |
-l |
独立监听模式(standalone,不通过 xinetd) |
-a <地址:端口> |
指定监听地址与端口 |
-r <选项> |
禁用某个 TFTP 选项协商 |
-T <超时> |
设置超时时间 |
准备目录与文件:
ls -ld /var/lib/tftpboot
mkdir -p /var/lib/tftpboot
chmod 777 /var/lib/tftpboot # 允许上传时需要写权限(测试环境)
chown -R nobody:nobody /var/lib/tftpboot # 生产建议用专用用户
echo "hello tftp from server" > /var/lib/tftpboot/testfile.txt
chmod 644 /var/lib/tftpboot/testfile.txt
# 若允许上传,目标文件必须【预先存在】且可写(TFTP 协议限制)
touch /var/lib/tftpboot/upload.txt
chmod 666 /var/lib/tftpboot/upload.txt
启动并验证:
systemctl restart xinetd # CentOS 7
/etc/init.d/xinetd restart # CentOS 6
# 验证 UDP 69 端口(注意必须用 -u!)
netstat -nulp | grep :69
ss -unlp | grep :69
udp 0 0 0.0.0.0:69 0.0.0.0:* 5472/xinetd
常见错误:用
netstat -ntlp(只看 TCP LISTEN)查不到 tftp,因为 tftp 是 UDP,UDP 没有 LISTEN 状态。必须用-u。
防火墙放行 UDP 69:
# CentOS 7
firewall-cmd --permanent --add-service=tftp
firewall-cmd --permanent --add-port=69/udp
firewall-cmd --reload
# TFTP 使用随机高位端口回传数据,需要加载连接跟踪模块
modprobe nf_conntrack_tftp
echo "nf_conntrack_tftp" > /etc/modules-load.d/tftp.conf
# CentOS 6
iptables -I INPUT -p udp --dport 69 -j ACCEPT
modprobe nf_conntrack_tftp
service iptables save
SELinux 相关:
getsebool -a | grep tftp
setsebool -P tftp_home_dir on # 允许访问家目录
setsebool -P tftp_anon_write on # 允许匿名写入(上传)
ls -Z /var/lib/tftpboot
restorecon -Rv /var/lib/tftpboot
semanage fcontext -l | grep tftp
详见 07-03(防火墙与 SELinux)
客户端测试
yum install tftp -y
# 下载文件
tftp 10.0.0.200
tftp> get testfile.txt
tftp> quit
cat testfile.txt
# 一行命令下载
tftp -c get 10.0.0.200 testfile.txt
tftp 10.0.0.200 -c get testfile.txt
# 上传文件(服务端需 -c 参数且目标文件预先存在可写)
echo "client data" > upload.txt
tftp -c put 10.0.0.200 upload.txt
tftp 10.0.0.200 -c put upload.txt
# 交互模式的常用命令
tftp 10.0.0.200
tftp> status # 查看当前状态
tftp> binary # 二进制传输模式(默认,推荐)
tftp> ascii # ASCII 模式
tftp> verbose # 详细输出
tftp> trace # 显示协议交互
tftp> timeout 10 # 设置超时
tftp> get testfile.txt
tftp> put upload.txt
tftp> quit
验证 only_from 生效:
# 从不被允许的 IP 执行,会超时(UDP 无连接,被拒时客户端只能等超时)
tftp -c get 10.0.0.200 testfile.txt
Transfer timed out.
UDP 服务的排错难点:被 xinetd 拒绝时,客户端表现为超时而不是明确的拒绝消息(因为 UDP 无连接,xinetd 直接丢弃数据报)。必须查服务端日志才能确认是被访问控制拒绝还是网络不通。
查看日志:
cat /var/log/xinetd_tftp.log
grep tftp /var/log/messages
11/8/22@15:02:11: START: tftp pid=6410 from=10.0.0.50
11/8/22@15:02:11: EXIT: tftp status=0 pid=6410 duration=0(sec)
11/8/22@15:03:45: FAIL: tftp address from=10.0.0.99
1.13 完整实验:用 xinetd 管理 rsync 服务
rsync 既可以作为独立守护进程(rsync --daemon),也可以由 xinetd 托管。
rsync 的完整用法与 rsyncd.conf 详解,详见 阶段09(备份与同步)
服务端配置
yum install xinetd rsync -y
cat /etc/xinetd.d/rsync
service rsync
{
disable = yes
flags = IPv6
socket_type = stream
wait = no
user = root
server = /usr/bin/rsync
server_args = --daemon
log_on_failure += USERID
}
修改为启用并加上完整控制:
cp /etc/xinetd.d/rsync /etc/xinetd.d/rsync.bak
vim /etc/xinetd.d/rsync
service rsync
{
disable = no
flags = IPv6
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/bin/rsync
server_args = --daemon --config=/etc/rsyncd.conf
instances = 20
per_source = 5
cps = 30 15
only_from = 10.0.0.0/24
no_access = 10.0.0.99
access_times = 01:00-06:00 22:00-23:59
nice = 15
log_type = FILE /var/log/xinetd_rsync.log
log_on_success = PID HOST DURATION EXIT
log_on_failure += USERID
}
这里
access_times = 01:00-06:00 22:00-23:59限制只在业务低峰期允许同步,避免占用带宽——这是 xinetd 时间段控制的一个非常实用的真实场景。
编写 rsync 自己的配置文件:
vim /etc/rsyncd.conf
uid = rsync
gid = rsync
use chroot = no
max connections = 200
timeout = 300
pid file = /var/run/rsyncd.pid
lock file = /var/run/rsyncd.lock
log file = /var/log/rsyncd.log
[backup]
path = /data/backup/
comment = backup directory
read only = false
list = false
auth users = rsync_backup
secrets file = /etc/rsync.password
hosts allow = 10.0.0.0/24
hosts deny = 0.0.0.0/32
useradd -M -s /sbin/nologin rsync
mkdir -p /data/backup
chown -R rsync:rsync /data/backup
echo "rsync_backup:oldboy123" > /etc/rsync.password
chmod 600 /etc/rsync.password
启动并验证:
systemctl restart xinetd
ss -tnlp | grep :873
netstat -ntlp | grep :873
LISTEN 0 64 *:873 *:* users:(("xinetd",pid=5472,fd=5))
关键点:监听 873 端口的是 xinetd,而不是 rsync。这与直接 rsync --daemon 的方式不同。
防火墙:
firewall-cmd --permanent --add-service=rsyncd
firewall-cmd --permanent --add-port=873/tcp
firewall-cmd --reload
客户端测试
# 查看服务端提供的模块列表
rsync --list-only rsync://10.0.0.200/
rsync rsync://10.0.0.200/
# 拉取
rsync -avz rsync_backup@10.0.0.200::backup /tmp/pull/
rsync -avz rsync://rsync_backup@10.0.0.200/backup /tmp/pull/
# 推送
echo "test data" > /tmp/test.txt
rsync -avz /tmp/test.txt rsync_backup@10.0.0.200::backup/
# 免密(客户端保存密码文件)
echo "oldboy123" > /etc/rsync.password
chmod 600 /etc/rsync.password
rsync -avz /tmp/test.txt rsync_backup@10.0.0.200::backup/ --password-file=/etc/rsync.password
验证 access_times 生效(在时间段之外执行):
rsync -avz rsync_backup@10.0.0.200::backup /tmp/pull/
rsync: failed to connect to 10.0.0.200 (10.0.0.200): Connection reset by peer (104)
rsync error: error in socket IO (code 10) at clientserver.c(128)
查看服务端日志:
cat /var/log/xinetd_rsync.log
11/8/22@15:30:11: FAIL: rsync time from=10.0.0.50
xinetd 托管 rsync 与独立 rsync --daemon 的对比:
| 对比项 | xinetd 托管 | 独立 daemon |
|---|---|---|
| 启动方式 | systemctl restart xinetd |
rsync --daemon 或 systemctl start rsyncd |
| 监听进程 | xinetd |
rsync |
| 访问控制 | xinetd 的 only_from + rsyncd.conf 的 hosts allow 双层 |
只有 rsyncd.conf 的 hosts allow |
| 时间段控制 | 支持(access_times) |
不支持 |
| 并发限制 | xinetd 的 instances + rsyncd.conf 的 max connections |
只有 max connections |
| 性能 | 每连接 fork,稍慢 | 更快 |
| 常驻内存 | 不常驻 | 常驻 |
| CentOS 7 推荐 | 用 rsyncd.socket(systemd socket 激活)代替 |
systemctl enable --now rsyncd |
CentOS 7 中 rsync 的 systemd 单元(推荐方式):
ls /usr/lib/systemd/system/rsyncd*
systemctl enable --now rsyncd.service # 独立守护进程方式
# 或用 socket 激活方式(等价于 xinetd 托管)
systemctl enable --now rsyncd.socket
systemctl status rsyncd.socket
注意:
rsyncd.service、rsyncd.socket与 xinetd 的 rsync 配置三者不能同时启用(都要绑定 873 端口)。
1.14 TCP Wrappers 与 /etc/hosts.allow、/etc/hosts.deny
TCP Wrappers(软件包 tcp_wrappers,库文件 libwrap.so)是一套独立于 xinetd 的主机访问控制机制,作用在应用层(第 7 层),比 iptables/firewalld(网络层)更靠上。
它的历史地位:在 inetd 时代,inetd 自身没有访问控制能力,因此需要 tcpd 包装器;后来许多独立守护进程(sshd、vsftpd、xinetd 自身)也链接了 libwrap 库,从而能直接读取 /etc/hosts.allow 与 /etc/hosts.deny。
rpm -qa | grep tcp_wrappers
rpm -ql tcp_wrappers
rpm -ql tcp_wrappers-libs
ls -l /lib64/libwrap.so*
配置文件与语法格式
两个文件:
| 文件 | 作用 |
|---|---|
/etc/hosts.allow |
允许列表(白名单) |
/etc/hosts.deny |
拒绝列表(黑名单) |
语法格式:
<daemon 列表> : <client 列表> [: <shell 命令>]
即 daemon:client 的写法。各字段说明:
- daemon:程序的进程名(即
server指向的可执行文件的 basename),不是服务名,不是端口号。例如sshd、vsftpd、in.telnetd、in.tftpd、rsync、xinetd。多个用逗号或空格分隔。 - client:客户端标识。可以是 IP、网段、主机名、域名等。多个用逗号或空格分隔。
- shell 命令(可选):匹配时执行的命令,用于告警或记录。
client 的各种写法:
| 写法 | 示例 | 说明 |
|---|---|---|
| 单个 IP | 10.0.0.26 |
精确匹配 |
| 多个 IP | 10.0.0.26, 10.0.0.50 |
逗号或空格分隔 |
| 不完整 IP | 10.0.0. |
末尾带点,表示 10.0.0.0/24 |
10.0. |
表示 10.0.0.0/16 |
|
10. |
表示 10.0.0.0/8 |
|
| 网段/掩码 | 10.0.0.0/255.255.255.0 |
传统写法 |
| CIDR | 10.0.0.0/24 |
较新版本才支持,老版本 tcp_wrappers 不认,建议用上面两种 |
| 主机名 | client1.example.com |
需要正/反向 DNS 都能解析 |
| 域名后缀 | .example.com |
开头带点,匹配该域下所有主机 |
| 网络名 | @mynetwork |
NIS netgroup |
| IPv6 | [2001:db8::]/32 |
IPv6 需用方括号 |
| 特殊 | LOCAL |
主机名中不含点的客户端(即同域内的本地主机) |
| 特殊 | UNKNOWN |
无法确定主机名或用户名的客户端 |
| 特殊 | KNOWN |
可确定主机名和用户名的客户端 |
| 特殊 | PARANOID |
正反向 DNS 解析不一致的客户端(DNS 欺骗嫌疑) |
ALL 与 EXCEPT 通配符:
| 关键字 | 说明 |
|---|---|
ALL |
匹配所有。可用于 daemon 字段(所有服务)或 client 字段(所有客户端) |
EXCEPT |
排除。语法:A EXCEPT B,表示"A 中除了 B"。可以嵌套:A EXCEPT B EXCEPT C 等价于 A EXCEPT (B EXCEPT C) |
判定顺序(关键规则)
TCP Wrappers 的判定流程严格按以下顺序,一旦匹配立即停止:
1. 读取 /etc/hosts.allow,自上而下逐行匹配
→ 若匹配成功,【立即允许】访问,不再看 hosts.deny
2. 读取 /etc/hosts.deny,自上而下逐行匹配
→ 若匹配成功,【立即拒绝】访问
3. 两个文件都不匹配(或文件不存在/为空)
→ 【默认允许】访问
三个必须记住的要点:
hosts.allow优先于hosts.deny。若同一个客户端在两个文件中都匹配,allow 胜出。- 两个文件都不匹配则默认允许(这是"默认开放"策略)。因此若要实现"默认拒绝"的安全基线,必须在
hosts.deny中明确写ALL: ALL。 - 每个文件内部按行顺序匹配,先匹配到的生效,后面的行不再判定。
常用示例
示例 1:只允许特定网段 SSH,其余全部拒绝(最常用的加固配置)
vim /etc/hosts.allow
sshd: 10.0.0.0/255.255.255.0
sshd: 192.168.1.
sshd: 127.0.0.1
vim /etc/hosts.deny
sshd: ALL
示例 2:默认拒绝一切,只放行明确允许的(最严格的安全基线)
# /etc/hosts.allow
sshd: 10.0.0.
vsftpd: 10.0.0.
in.telnetd: 10.0.0.26
ALL: 127.0.0.1 localhost
# /etc/hosts.deny
ALL: ALL
危险警告:写
ALL: ALL到hosts.deny之前,必须先在hosts.allow中放行自己的管理 IP,否则会立刻把自己锁在外面!建议先在另一个已登录的会话中保持连接,验证新会话能连上后再关闭旧会话。
示例 3:使用 EXCEPT 排除
# /etc/hosts.allow
# 允许 10 网段所有主机访问 sshd,但排除 10.0.0.26 和 10.0.0.99
sshd: 10.0.0. EXCEPT 10.0.0.26 10.0.0.99
# 允许所有服务给内网,但 telnet 除外
ALL EXCEPT in.telnetd: 10.0.0.
# /etc/hosts.deny
# 拒绝所有主机的所有服务,除了本机
ALL: ALL EXCEPT 127.0.0.1
# 拒绝所有服务,但 sshd 除外(sshd 交给 hosts.allow 单独控制)
ALL EXCEPT sshd: ALL
示例 4:多个服务与多个客户端
# /etc/hosts.allow
sshd, vsftpd, in.telnetd: 10.0.0.0/255.255.255.0, 192.168.1.
in.tftpd: 10.0.0.100 10.0.0.101
xinetd: 10.0.0.
示例 5:带 shell 命令的告警(安全审计)
# /etc/hosts.deny
# 被拒绝时记录详细信息并发邮件告警
sshd: ALL : spawn /bin/echo "$(/bin/date): SSH attempt from %h (%a) to %d" >> /var/log/ssh_denied.log
sshd: ALL : spawn (/bin/echo "Denied SSH from %h at $(date)" | /bin/mail -s "SSH ALERT on %H" ops@example.com) &
in.telnetd: ALL : twist /bin/echo "Access denied. This attempt has been logged."
可用的扩展变量:
| 变量 | 含义 |
|---|---|
%a |
客户端 IP 地址 |
%A |
服务端 IP 地址 |
%c |
客户端信息(用户@主机) |
%d |
守护进程名(daemon) |
%h |
客户端主机名(无法解析时为 IP) |
%H |
服务端主机名 |
%n |
客户端主机名(无法解析时为 unknown) |
%N |
服务端主机名 |
%p |
守护进程的 PID |
%s |
服务端信息 |
%u |
客户端用户名(通过 identd) |
%% |
一个百分号 |
shell 命令关键字:
| 关键字 | 说明 |
|---|---|
spawn <命令> |
在子进程中执行命令,不影响原本的连接处理 |
twist <命令> |
用该命令替换原本要启动的服务,命令的输出发给客户端(用于返回自定义拒绝信息) |
banners <目录> |
从指定目录读取与 daemon 同名的文件内容发给客户端 |
nice <n> |
调整优先级 |
keepalive |
启用 TCP 保活 |
linger <秒> |
设置 SO_LINGER |
rfc931 [超时] |
使用 identd 查询远端用户名 |
setenv <名> <值> |
设置环境变量 |
umask <值> |
设置 umask |
user <用户>[.<组>] |
以指定用户运行 |
allow |
显式允许(可写在 hosts.deny 中作为例外行末) |
deny |
显式拒绝(可写在 hosts.allow 中作为例外行末) |
用行末 allow/deny 可以把两个文件合并成一个(新版本推荐写法):
# 只用 /etc/hosts.allow 一个文件即可完成全部控制
sshd: 10.0.0. : allow
sshd: ALL : deny
in.telnetd: 10.0.0.26 : allow
in.telnetd: ALL : deny
ALL: 127.0.0.1 : allow
ALL: ALL : deny
用 ldd 判断程序是否支持 libwrap
这是使用 TCP Wrappers 前必须做的检查:程序必须在编译时链接了 libwrap 库才会读取 hosts.allow/hosts.deny。
ldd $(which sshd) | grep libwrap
ldd /usr/sbin/sshd | grep libwrap
ldd /usr/sbin/xinetd | grep libwrap
ldd /usr/sbin/vsftpd | grep libwrap
ldd /usr/sbin/in.telnetd | grep libwrap
ldd /usr/bin/rsync | grep libwrap
ldd /usr/sbin/httpd | grep libwrap
支持的输出(能看到 libwrap):
libwrap.so.0 => /lib64/libwrap.so.0 (0x00007f8c3a2b1000)
不支持的输出(无任何输出,grep 返回码为 1):
(空)
批量检查系统上哪些服务支持 libwrap:
for f in /usr/sbin/* /usr/bin/*; do
if ldd "$f" 2>/dev/null | grep -q libwrap; then
echo "支持 libwrap: $f"
fi
done
也可用 strings 辅助判断(静态链接的情况):
strings $(which sshd) | grep -i 'hosts.allow\|hosts.deny\|libwrap'
典型的支持情况:
| 程序 | CentOS 6 | CentOS 7 |
|---|---|---|
sshd |
支持 | 不支持(见下) |
xinetd |
支持 | 支持 |
vsftpd |
支持(需 tcp_wrappers=YES) |
支持 |
in.telnetd |
支持 | 支持 |
in.tftpd |
支持 | 支持 |
rsync(daemon 模式) |
支持 | 支持 |
slapd、nfs、portmap、rpcbind |
支持 | 部分支持 |
httpd、nginx、mysqld |
不支持 | 不支持 |
CentOS 7 中 sshd 已不再链接 libwrap
这是一个非常重要的版本差异,也是最常见的踩坑点。
背景:OpenSSH 从 6.7 版本(2014 年)起,官方移除了对 TCP Wrappers 的支持,理由是 libwrap 已经过时、维护不足、且与现代防火墙功能重复。RHEL/CentOS 7 早期版本(7.0 ~ 7.3)的 OpenSSH 仍带 libwrap 补丁,但 CentOS 7.4 及以后(OpenSSH 7.4+)已彻底移除。
验证方法:
# CentOS 6 上
ldd /usr/sbin/sshd | grep libwrap
libwrap.so.0 => /lib64/libwrap.so.0 (0x00007f...) ← 支持
# CentOS 7.4+ 上
ldd /usr/sbin/sshd | grep libwrap
(无输出) ← 不支持!
rpm -q openssh-server
ssh -V
后果:在 CentOS 7.4+ 上,把 sshd: ALL 写进 /etc/hosts.deny 完全不起作用,SSH 依然可以从任何地址连入。这会造成"以为已经加固了,其实没有"的严重安全误判。
CentOS 7+ 上限制 SSH 来源的正确做法(替代方案):
方案 1:firewalld(推荐)
# 移除默认的 ssh 服务放行
firewall-cmd --permanent --remove-service=ssh
# 只允许特定网段
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/24" service name="ssh" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.100/32" service name="ssh" accept'
# 拒绝其他所有(放在最后,优先级最低)
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" service name="ssh" reject'
firewall-cmd --reload
firewall-cmd --list-all
方案 2:sshd_config 的 Match 与 AllowUsers
vim /etc/ssh/sshd_config
# 只允许特定用户从特定地址登录
AllowUsers admin@10.0.0.* deploy@10.0.0.26
DenyUsers root guest test
# 按组限制
AllowGroups sshusers wheel
DenyGroups nologin
# 按来源做差异化配置
Match Address 10.0.0.0/24
PermitRootLogin yes
PasswordAuthentication yes
Match Address *,!10.0.0.0/24
PermitRootLogin no
PasswordAuthentication no
AllowUsers deploy
sshd -t # 检查配置语法
systemctl reload sshd
方案 3:iptables 直接限制
iptables -A INPUT -p tcp --dport 22 -s 10.0.0.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j DROP
方案 4:把 sshd 托管到 xinetd 并用 only_from(即本文实验 1、2 的做法。可行但不推荐生产使用,性能与可靠性都有代价)
详见 07-03(防火墙与 SELinux)
TCP Wrappers 与 xinetd 的 only_from 的关系与区别
这两套机制可以同时生效,理解它们的关系很重要。
| 对比项 | xinetd 的 only_from/no_access |
TCP Wrappers 的 hosts.allow/hosts.deny |
|---|---|---|
| 生效范围 | 只对该 xinetd 服务生效 | 对所有链接了 libwrap 的程序生效(包括独立守护进程) |
| 配置位置 | /etc/xinetd.d/<服务> 或 /etc/xinetd.conf 的 defaults |
/etc/hosts.allow、/etc/hosts.deny |
| 配置粒度 | 每服务独立配置 | 按 daemon 名全局配置 |
| 默认策略 | 不写 only_from = 允许所有;写空值 = 拒绝所有 |
两文件都不匹配 = 默认允许 |
| 判定优先级 | 最精确匹配优先 | hosts.allow 优先于 hosts.deny,文件内按行顺序 |
| 判定时机 | xinetd accept 连接后,在 fork 服务进程前 | 由程序自己在启动/接受连接时调用 hosts_access() |
| 是否需要程序支持 | 不需要(xinetd 代为判定) | 需要程序链接 libwrap |
| 能否记录日志 | 能(log_on_failure) |
能(spawn 命令自己写) |
| 时间段控制 | 支持(access_times) |
不支持 |
| 并发/速率限制 | 支持(instances/per_source/cps) |
不支持 |
| 能否执行自定义命令 | 不能 | 能(spawn/twist) |
| CentOS 7 中的地位 | 弱化(socket 激活取代) | 进一步弱化(sshd 已移除支持,RHEL 8 起 tcp_wrappers 被完全移除) |
对 xinetd 托管服务的判定顺序(两套机制叠加时):
客户端连接到达 xinetd
↓
1. xinetd 检查自己的 cps 限速(服务是否处于冷却期)
↓
2. xinetd 检查 only_from / no_access ← xinetd 的访问控制
↓ 通过
3. xinetd 检查 access_times
↓ 通过
4. xinetd 检查 instances / per_source
↓ 通过
5. xinetd 本身链接了 libwrap,会用【服务的 id 或 service 名】
查询 hosts.allow / hosts.deny ← TCP Wrappers 第一层
↓ 通过
6. fork + exec 被托管的服务程序
↓
7. 若该程序自己也链接了 libwrap,它会用【自己的进程名】
再查一次 hosts.allow / hosts.deny ← TCP Wrappers 第二层
↓ 通过
8. 服务正式开始处理请求
关键推论:任何一层拒绝,连接就被拒绝。 因此排查"连不上"时必须逐层检查。
用 flags = NOLIBWRAP 可让 xinetd 跳过 TCP Wrappers 检查,只用自己的 only_from:
service telnet
{
disable = no
flags = NOLIBWRAP # 禁用 libwrap 检查,避免双重控制的混乱
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/in.telnetd
only_from = 10.0.0.0/24
}
实践建议:
- 选一套,不要混用。同时配置两套极易造成"改了 A 不生效因为 B 在拦"的困惑。
- 对 xinetd 托管的服务:优先用 xinetd 的
only_from(功能更强,还能做时间段和并发限制),必要时加flags = NOLIBWRAP排除干扰。 - 对独立守护进程:CentOS 6 可用 TCP Wrappers,CentOS 7+ 应改用 firewalld。
- 无论用哪套,都应该有防火墙作为第一道防线(网络层拦截比应用层更高效、更彻底)。
测试 TCP Wrappers 规则的工具:
yum install tcp_wrappers -y
# tcpdmatch:模拟判定某个客户端访问某个服务的结果
tcpdmatch sshd 10.0.0.26
tcpdmatch sshd client1.example.com
tcpdmatch in.telnetd 10.0.0.99
# tcpdchk:检查配置文件语法与常见错误
tcpdchk
tcpdchk -v # 详细列出所有规则的解析结果
tcpdchk -a # 报告没有显式 ACCESS 控制的规则
tcpdmatch 输出示例:
client: address 10.0.0.26
server: process sshd
matched: /etc/hosts.allow line 3
access: granted
client: address 10.0.0.99
server: process sshd
matched: /etc/hosts.deny line 1
access: denied
RHEL 8 / CentOS 8 起,
tcp_wrappers包已被完全移除,TCP Wrappers 机制不再可用。所有主机访问控制应迁移到 firewalld 或服务自身的配置。
1.15 xinetd 与 systemd socket 激活的对照
CentOS 7+ 中,systemd 的 socket 激活是 xinetd 的原生替代。下面给出一一对照的写法。
基本概念
systemd socket 激活需要两个单元文件:
| 单元 | 作用 |
|---|---|
<名称>.socket |
定义监听的 socket(端口、地址、协议)。由 systemd 创建并持有 |
<名称>.service 或 <名称>@.service |
定义实际的服务。当 socket 上有连接到达时被 systemd 拉起 |
Accept= 的两种模式(核心区别)
| 取值 | 行为 | 对应 xinetd | 需要的 service 单元 |
|---|---|---|---|
Accept=yes |
systemd 自己 accept 每个连接,然后为每个连接启动一个服务实例,并把连接的 socket 作为该实例的 stdin/stdout。这就是经典的 inetd/xinetd 行为 | wait = no |
模板单元 <名称>@.service(带 @) |
Accept=no(默认) |
systemd 只创建并监听 socket,不 accept,把监听 socket 本身传给服务,由服务自己 accept 所有连接。只启动一个服务实例 | 类似 standalone,但支持按需启动 | 普通单元 <名称>.service |
选择原则:
- 传统的、"每连接一进程"的小服务(telnet、tftp、旧式 CGI)→
Accept=yes。 - 现代的、自己实现并发的服务(sshd、rsyncd、nginx)→
Accept=no(性能好得多)。
对照示例 1:telnet(Accept=yes,等价于 xinetd 的 wait = no)
xinetd 写法:
# /etc/xinetd.d/telnet
service telnet
{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/in.telnetd
instances = 5
per_source = 2
cps = 10 30
only_from = 10.0.0.0/24
nice = 10
rlimit_as = 32M
log_on_success = PID HOST DURATION EXIT
}
systemd socket 激活写法:
vim /usr/lib/systemd/system/telnet.socket
[Unit]
Description=Telnet Server Activation Socket
Documentation=man:telnetd(8)
[Socket]
# 监听 TCP 23 端口(对应 socket_type=stream + protocol=tcp + port)
ListenStream=23
# 也可只在指定 IP 上监听(对应 xinetd 的 bind)
# ListenStream=10.0.0.2:23
# 为每个连接启动一个服务实例(对应 wait = no)
Accept=yes
# 最大并发实例数(对应 instances)
MaxConnections=5
# 每个源 IP 的最大连接数(对应 per_source,systemd 232+)
MaxConnectionsPerSource=2
# 连接速率限制(对应 cps 的第一个参数,粗略等价)
TriggerLimitIntervalSec=30s
TriggerLimitBurst=10
[Install]
WantedBy=sockets.target
vim /usr/lib/systemd/system/telnet@.service
[Unit]
Description=Telnet Server Per-Connection Instance
Documentation=man:telnetd(8)
[Service]
# 对应 server 与 server_args
ExecStart=-/usr/sbin/in.telnetd
# 对应 wait = no 的 per-connection 语义
StandardInput=socket
StandardError=journal
# 对应 user / group
User=root
# 对应 nice
Nice=10
# 对应 rlimit_as
MemoryLimit=32M
LimitAS=32M
# 对应 rlimit_cpu
LimitCPU=3600
# 对应 rlimit_files
LimitNOFILE=64
# xinetd 没有的加固能力(cgroup + namespace)
PrivateTmp=yes
NoNewPrivileges=yes
ProtectSystem=full
ProtectHome=read-only
systemctl daemon-reload
systemctl enable --now telnet.socket
systemctl status telnet.socket
systemctl list-sockets | grep telnet
ss -tnlp | grep :23 # 监听进程是 systemd(PID 1)
LISTEN 0 5 *:23 *:* users:(("systemd",pid=1,fd=42))
连接期间查看被拉起的实例:
systemctl status 'telnet@*'
systemctl list-units 'telnet@*'
journalctl -u 'telnet@*' -f
telnet@1-10.0.0.200:23-10.0.0.50:51234.service loaded active running
实例名中自动包含了连接的五元组信息,这比 xinetd 的日志更直观。
对照示例 2:rsyncd(Accept=no,服务自己 accept)
CentOS 7 自带的 rsync socket 单元就是这个模式:
cat /usr/lib/systemd/system/rsyncd.socket
cat /usr/lib/systemd/system/rsyncd@.service
# /usr/lib/systemd/system/rsyncd.socket
[Unit]
Description=Rsync Server Socket
Conflicts=rsyncd.service
[Socket]
ListenStream=873
Accept=yes
[Install]
WantedBy=sockets.target
# /usr/lib/systemd/system/rsyncd@.service
[Unit]
Description=Rsync Server
After=local-fs.target
[Service]
ExecStart=/usr/bin/rsync --daemon
StandardInput=socket
StandardError=journal
注意 Conflicts=rsyncd.service:明确声明与独立守护进程模式互斥,systemd 会自动确保二者不同时运行。这比 xinetd 需要人工避免冲突要可靠。
systemctl enable --now rsyncd.socket
systemctl status rsyncd.socket
ss -tnlp | grep :873
对照示例 3:UDP 服务 tftp(对应 xinetd 的 wait = yes)
xinetd 写法:
service tftp
{
disable = no
socket_type = dgram
protocol = udp
wait = yes
user = root
server = /usr/sbin/in.tftpd
server_args = -s /var/lib/tftpboot
}
systemd 写法(CentOS 7 自带):
cat /usr/lib/systemd/system/tftp.socket
cat /usr/lib/systemd/system/tftp.service
# /usr/lib/systemd/system/tftp.socket
[Unit]
Description=Tftp Server Activation Socket
[Socket]
# UDP 用 ListenDatagram(对应 socket_type = dgram + protocol = udp)
ListenDatagram=69
# Accept=no(默认),把监听 socket 直接给服务,服务自己处理
# 对应 xinetd 的 wait = yes 语义
[Install]
WantedBy=sockets.target
# /usr/lib/systemd/system/tftp.service
[Unit]
Description=Tftp Server
Requires=tftp.socket
Documentation=man:in.tftpd
[Service]
ExecStart=/usr/sbin/in.tftpd -s /var/lib/tftpboot
StandardInput=socket
[Install]
Also=tftp.socket
systemctl enable --now tftp.socket
ss -unlp | grep :69
配置项完整对照表
| xinetd 属性 | systemd 对应 | 单元/段 | 备注 |
|---|---|---|---|
service <名> |
单元文件名 <名>.socket |
— | |
disable = no |
systemctl enable --now <名>.socket |
— | |
socket_type = stream |
ListenStream=<端口> |
[Socket] |
TCP |
socket_type = dgram |
ListenDatagram=<端口> |
[Socket] |
UDP |
protocol = tcp/udp |
由 ListenStream/ListenDatagram 隐含 | [Socket] |
|
port = 2222 |
ListenStream=2222 |
[Socket] |
无需改 /etc/services |
bind = 10.0.0.2 |
ListenStream=10.0.0.2:23 |
[Socket] |
|
wait = no |
Accept=yes + 模板单元 @.service |
[Socket] |
每连接一实例 |
wait = yes |
Accept=no(默认) |
[Socket] |
服务自己 accept |
user = root |
User=root |
[Service] |
|
group = nobody |
Group=nobody |
[Service] |
|
server = /path/prog |
ExecStart=/path/prog |
[Service] |
|
server_args = -i |
ExecStart=/path/prog -i |
[Service] |
合并到 ExecStart |
instances = 5 |
MaxConnections=5 |
[Socket] |
|
per_source = 2 |
MaxConnectionsPerSource=2 |
[Socket] |
systemd 232+ |
cps = 10 30 |
TriggerLimitBurst=10 + TriggerLimitIntervalSec=30s |
[Socket] |
语义略有差异 |
only_from = 10.0.0.0/24 |
无直接对应 | — | 用 firewalld / IPAddressAllow=(systemd 235+,需 cgroup v2 BPF) |
no_access |
无直接对应 | — | 用 firewalld / IPAddressDeny= |
access_times = 9:00-18:00 |
无直接对应 | — | 用 systemd timer 定时 start/stop socket,或 firewalld rich rule |
nice = 10 |
Nice=10 |
[Service] |
|
rlimit_as = 32M |
LimitAS=32M 或 MemoryLimit=32M |
[Service] |
cgroup 更强 |
rlimit_cpu = 3600 |
LimitCPU=3600 或 CPUQuota=50% |
[Service] |
|
rlimit_files = 64 |
LimitNOFILE=64 |
[Service] |
|
env = VAR=val |
Environment=VAR=val |
[Service] |
|
passenv = |
默认就是干净环境 | [Service] |
systemd 默认不继承 |
umask = 002 |
UMask=002 |
[Service] |
|
log_type = SYSLOG |
默认进 journald | [Service] |
StandardOutput=journal |
log_type = FILE /path |
StandardOutput=append:/path(systemd 240+) |
[Service] |
或用 journald + rsyslog 转发 |
log_on_success/failure |
journald 自动记录(含 PID、时间、退出码) | — | 信息更丰富 |
banner = /path |
无直接对应 | — | 需服务自己实现 |
redirect = IP PORT |
无直接对应 | — | 用 systemd-socket-proxyd |
type = INTERNAL |
无对应(systemd 不内建 echo/time 等服务) | — | |
type = UNLISTED |
不需要(systemd 从不读 /etc/services) | — | |
flags = IPv6 |
BindIPv6Only=both/ipv6-only |
[Socket] |
|
max_load = 2.5 |
无直接对应 | — | |
flags = SENSOR |
无对应 | — | 用 fail2ban / firewalld |
redirect 的 systemd 替代(systemd-socket-proxyd):
# proxy-to-backend.socket
[Socket]
ListenStream=2222
[Install]
WantedBy=sockets.target
# proxy-to-backend.service
[Unit]
Requires=proxy-to-backend.socket
After=proxy-to-backend.socket
[Service]
ExecStart=/usr/lib/systemd/systemd-socket-proxyd 10.0.0.100:22
总结建议:
| 场景 | 推荐 |
|---|---|
| CentOS 6 | xinetd(systemd 不存在) |
| CentOS 7+ 新部署的服务 | systemd socket 激活 |
| CentOS 7+ 需要 IP 白名单 | systemd socket + firewalld,或 xinetd 的 only_from |
| CentOS 7+ 需要时间段控制 | xinetd 的 access_times(systemd 无对应),或 systemd timer 控制 socket 启停 |
| CentOS 7+ 迁移遗留 xinetd 服务 | 保留 xinetd 直到有时间改造 |
详见 08-01-系统启动与服务管理.md
排错清单
5.6 xinetd 服务连不上的排查流程
按网络分层从下往上逐层排查:
# ===== 第 1 层:xinetd 本身是否在运行? =====
systemctl status xinetd # CentOS 7
/etc/init.d/xinetd status # CentOS 6
ps -ef | grep xinetd | grep -v grep
# ===== 第 2 层:配置是否被加载?disable 是否为 no? =====
cat /etc/xinetd.d/<服务>
grep -H 'disable' /etc/xinetd.d/*
# 检查是否有语法错误
xinetd -d -f /etc/xinetd.conf # 前台调试,Ctrl+C 退出
grep xinetd /var/log/messages | tail -30
journalctl -u xinetd -n 50
# ===== 第 3 层:端口是否在监听?监听进程是谁? =====
netstat -ntlup | grep :<端口>
ss -tunlp | grep :<端口>
lsof -i:<端口>
# 关键:监听进程应该是 xinetd
# ===== 第 4 层:网络是否可达? =====
# 客户端执行
ping <服务器IP>
telnet <服务器IP> <端口> # TCP 端口连通性测试
nc -zv <服务器IP> <端口> # 更通用
nc -zvu <服务器IP> <端口> # UDP
traceroute <服务器IP>
# ===== 第 5 层:防火墙是否放行? =====
# 服务端执行
iptables -L -n --line-numbers | grep <端口> # CentOS 6
firewall-cmd --list-all # CentOS 7
# 抓包确认包是否到达
tcpdump -i any -nn 'port <端口>' -c 20
# ===== 第 6 层:xinetd 的访问控制是否拒绝? =====
grep -E 'only_from|no_access|access_times|instances|per_source|cps' /etc/xinetd.d/<服务>
date # 确认当前时间在 access_times 范围内!
# 查日志中的 FAIL 记录
grep 'FAIL' /var/log/messages | tail -20
cat /var/log/xinetd_<服务>.log
# ===== 第 7 层:TCP Wrappers 是否拒绝? =====
cat /etc/hosts.allow
cat /etc/hosts.deny
tcpdmatch xinetd <客户端IP>
tcpdmatch <daemon名> <客户端IP>
ldd /usr/sbin/xinetd | grep libwrap
# ===== 第 8 层:SELinux 是否阻止? =====
getenforce
ausearch -m avc -ts recent | tail -30
grep denied /var/log/audit/audit.log | tail -20
setenforce 0 # 临时关闭测试(确认是否为 SELinux 问题)
# 若确认是 SELinux,用 audit2allow 生成策略而不是永久关闭
ausearch -m avc -ts recent | audit2allow -M myxinetd
semodule -i myxinetd.pp
# ===== 第 9 层:被托管的程序本身是否有问题? =====
ls -l <server 指定的路径>
test -x <server 路径> && echo "可执行"
# 手工运行看是否报错
<server 路径> <server_args>
# 检查程序自身的配置(如 rsyncd.conf、sshd_config)
根据客户端的错误现象快速定位:
| 客户端现象 | 最可能的原因 | 检查点 |
|---|---|---|
Connection refused |
端口没有任何程序监听 | xinetd 是否运行?disable 是否为 no?端口是否正确?ss -tnlp |
防火墙用了 REJECT |
iptables -L -n、firewall-cmd --list-all |
|
Connected to 后立即 Connection closed by foreign host |
被 xinetd 访问控制拒绝 | only_from/no_access/access_times/instances/per_source;查日志 FAIL 记录 |
| 被 TCP Wrappers 拒绝 | hosts.deny |
|
| 被托管程序启动失败后立即退出 | server 路径是否正确?手工执行试试 |
|
Connection timed out(长时间挂起) |
防火墙 DROP | 服务端 tcpdump 看包是否到达 |
| 网络不通、路由问题 | traceroute、检查云安全组 |
|
No route to host |
路由问题;防火墙 REJECT with icmp-host-prohibited | 检查路由表与防火墙 |
UDP 服务 Transfer timed out |
UDP 被拒时无明确反馈,可能是访问控制、防火墙或程序问题 | 必须查服务端日志 |
| 连上但立即断开且日志无记录 | xinetd 没加载该配置 | 检查文件名是否含 .(含点号的文件不被加载);xinetd -d 看解析结果 |
5.7 only_from 不生效
| 原因 | 排查/解决 |
|---|---|
| 改完配置忘记重启/重载 xinetd | systemctl restart xinetd 或 kill -HUP $(pidof xinetd)。这是最常见的原因 |
| 地址列表用逗号分隔了 | xinetd 的 only_from 必须用空格分隔,不能用逗号!写 only_from = 10.0.0.1,10.0.0.2 是错的 |
| 不完整地址的点号写错 | 10.0.0. 表示 /24(末尾必须有点);写 10.0.0 无点号会被当成主机名解析而失败 |
| CIDR 写法在老版本不被支持 | 改用 10.0.0.0/255.255.255.0 或 10.0.0. |
only_from 与 no_access 优先级理解错误 |
见 4.10 实验 2 的规则说明。避免"宽 no_access + 精确 only_from"的写法 |
defaults 段里的全局 only_from 在起作用 |
检查 /etc/xinetd.conf;服务级用 only_from = 赋值会覆盖,用 += 会追加 |
写了 only_from =(空值) |
空值表示谁都不允许,不是"不限制" |
| 客户端 IP 不是你以为的那个 | 经过 NAT、代理、负载均衡时,xinetd 看到的是中间设备的 IP。用 tcpdump 或日志中的 from= 确认真实来源 IP |
| 同名服务有多个配置块,改错了那个 | 用 id 区分;检查 bind 是否让请求命中了另一个配置块 |
| 配置文件名含点号,根本没被加载 | includedir 会跳过含 . 的文件。ls /etc/xinetd.d/ 检查 |
| 改的是备份文件 | 确认编辑的路径正确 |
| TCP Wrappers 在更早的一层放行/拒绝了 | 检查 hosts.allow(allow 优先!);加 flags = NOLIBWRAP 排除干扰 |
redirect 服务的访问控制 |
redirect 也受 only_from 约束,确认配置在同一个 service 块内 |
| 主机名/DNS 写法解析失败 | 改用 IP。DNS 反解不可靠且引入延迟 |
验证 only_from 是否生效的方法:
# 1. 确认配置已被加载(前台调试模式会打印解析后的配置)
xinetd -d -f /etc/xinetd.conf 2>&1 | grep -A 20 '<服务名>'
# 2. 从被禁止的 IP 连接,观察日志
tail -f /var/log/messages | grep xinetd
# 应出现:FAIL: <服务> address from=<IP>
# 3. 确认 xinetd 看到的客户端 IP
tcpdump -i any -nn 'port <端口>' -c 10
5.8 xinetd 启动报错找不到 server
| 错误信息 | 原因 | 解决 |
|---|---|---|
Executable /usr/sbin/in.telnetd does not exist |
server 指定的程序不存在 |
yum install telnet-server;which/ls -l 确认真实路径 |
Error parsing attribute server - DISABLING SERVICE |
server 路径错误或不可执行 |
ls -l <路径>;chmod +x;确认是绝对路径 |
service <名> not found in /etc/services 或 unknown service |
服务名不在 /etc/services 中且未指定 port |
加 type = UNLISTED + port = <端口>(推荐),或在 /etc/services 中添加条目 |
port and /etc/services disagreement |
port 与 /etc/services 中该服务名的端口不一致 |
二者改成一致,或用 type = UNLISTED |
Unknown user: xxx / Unknown group: xxx |
user/group 指定的用户/组不存在 |
useradd/groupadd;id <用户> 确认 |
bind failed (Address already in use) |
端口已被其他程序占用 | ss -tunlp | grep :<端口> 找出占用者。常见是同名的独立守护进程(sshd/rsyncd)或 systemd socket 单元也在监听 |
bind failed (Permission denied) |
绑定 1024 以下端口但 xinetd 非 root 运行 | 确认 xinetd 以 root 启动 |
service <名> already exists |
有两个配置块的 service 名 + id 都相同 |
用 id 区分,或删除重复的配置文件(常见于 cp telnet telnet_bak 这种不含点号的备份) |
Server /usr/sbin/xxx is not executable |
权限问题 | chmod +x <路径> |
no server attribute specified for service <名> |
缺少 server,且 type 不是 INTERNAL |
补上 server,或若是内建服务则加 type = INTERNAL |
Service <名> missing attribute socket_type |
缺少必需属性 | 必需属性:socket_type、user(非 INTERNAL 时)、server(非 INTERNAL 时)、wait |
unrecognized attribute: xxx |
属性名拼错 | 对照 man xinetd.conf |
bad access_times |
时间段格式错误 | 用 hh:mm-hh:mm;不能跨零点,需拆两段 |
Deactivating service xxx due to excessive incoming connections |
cps 限速触发(不是错误) |
正常保护机制。若误触发则调大 cps 第一个参数 |
| xinetd 启动后立即退出 | 没有任何启用的服务 | 至少要有一个 disable = no 的服务,或加 -stayalive 选项 |
排查命令:
# 最有效的手段:前台调试运行,能看到所有解析错误
xinetd -d -f /etc/xinetd.conf
# 查看启动日志
journalctl -u xinetd -n 50 --no-pager # CentOS 7
grep xinetd /var/log/messages | tail -50 # CentOS 6
# 确认所有配置文件的语法
for f in /etc/xinetd.d/*; do
echo "=== $f ==="
grep -vE '^\s*#|^\s*$' "$f"
done
# 检查端口占用
ss -tunlp | grep :<端口>
lsof -i:<端口>
fuser -n tcp <端口>
# 检查是否有冲突的 systemd socket 单元
systemctl list-sockets
systemctl list-unit-files --type=socket | grep -E 'rsync|tftp|telnet'
5.9 防火墙与 SELinux 的影响
防火墙(最常见的"莫名连不上"原因):
# ===== CentOS 6:iptables =====
iptables -L -n --line-numbers # 查看规则
iptables -L INPUT -n -v # 带计数器,看是否有包被匹配
service iptables status
# 临时关闭测试(确认是否为防火墙问题)
service iptables stop
# 放行
iptables -I INPUT -p tcp --dport 23 -j ACCEPT
iptables -I INPUT -p udp --dport 123 -j ACCEPT
service iptables save
service iptables restart
chkconfig iptables on
# ===== CentOS 7:firewalld =====
firewall-cmd --state
firewall-cmd --list-all
firewall-cmd --list-ports
firewall-cmd --list-services
# 临时关闭测试
systemctl stop firewalld
# 放行
firewall-cmd --permanent --add-port=23/tcp
firewall-cmd --permanent --add-port=123/udp
firewall-cmd --permanent --add-service=ntp
firewall-cmd --reload
# 查看被拒绝的包(需先开启日志)
firewall-cmd --set-log-denied=all
tail -f /var/log/messages | grep -i reject
# ===== 通用:抓包确认 =====
tcpdump -i any -nn 'port 23' -c 20 # 服务端执行,看包有没有到
tcpdump -i any -nn 'udp port 123' -c 20
云平台安全组(极易被忽略):阿里云、腾讯云、AWS 的安全组是独立于系统防火墙的一层。系统内 firewall-cmd --list-all 显示已放行,但安全组没放行同样连不上。必须在云控制台检查入方向规则。
SELinux:
getenforce # Enforcing / Permissive / Disabled
sestatus
# 临时切换到 Permissive 测试(不重启,立即生效)
setenforce 0
# 若问题消失 → 确认是 SELinux 导致
setenforce 1 # 测完改回
# 查看被拒绝的记录
ausearch -m avc -ts recent
ausearch -m avc -ts today | audit2why # 解释原因
grep denied /var/log/audit/audit.log | tail -20
sealert -a /var/log/audit/audit.log # 需 setroubleshoot 包,给出修复建议
# 正确的修复方式:生成并加载策略模块(而不是永久关闭 SELinux)
ausearch -m avc -ts recent | audit2allow -M myservice
semodule -i myservice.pp
# 常见的布尔值调整
getsebool -a | grep -E 'tftp|rsync|ftp|nis'
setsebool -P tftp_home_dir on
setsebool -P tftp_anon_write on
setsebool -P rsync_full_access on
setsebool -P nis_enabled on # 某些 xinetd 服务需要
# 非标准端口需要打端口标签
semanage port -l | grep -E 'ssh|telnet|tftp'
semanage port -a -t ssh_port_t -p tcp 2222
semanage port -m -t ssh_port_t -p tcp 2222 # 若已存在用 -m 修改
semanage port -d -t ssh_port_t -p tcp 2222 # 删除
# 文件上下文
ls -Z /var/lib/tftpboot
restorecon -Rv /var/lib/tftpboot
semanage fcontext -a -t tftpdir_rw_t "/data/tftp(/.*)?"
restorecon -Rv /data/tftp
SELinux 对时间同步的影响:
getsebool -a | grep ntp
ls -Z /etc/ntp.conf /var/lib/ntp/drift
restorecon -v /etc/ntp.conf
restorecon -Rv /var/lib/ntp/
ls -Z /etc/chrony.conf /var/lib/chrony/
restorecon -Rv /var/lib/chrony/
常见的时间同步 SELinux 问题:手工创建或从其他机器复制 /etc/ntp.conf、/etc/chrony.conf、driftfile 后,文件的 SELinux 上下文不正确,导致服务无法读写。用 restorecon 修复。
防火墙与 SELinux 的完整配置详解,详见 07-03(防火墙与 SELinux)
5.10 快速自检脚本
#!/bin/bash
# 时间同步与 xinetd 健康自检脚本
echo "===== 1. 时间与时区 ====="
date +'%F %T %Z %z'
if command -v timedatectl &>/dev/null; then timedatectl; fi
ls -l /etc/localtime
echo "TZ 环境变量: ${TZ:-(未设置)}"
echo -e "\n===== 2. 硬件时钟 ====="
hwclock -r 2>/dev/null || echo "无法读取 RTC(可能是容器环境)"
cat /etc/adjtime 2>/dev/null | tail -1
echo -e "\n===== 3. 时间同步服务 ====="
for svc in chronyd ntpd systemd-timesyncd; do
if systemctl is-active "$svc" &>/dev/null; then
echo "运行中: $svc"
fi
done
ss -unlp 2>/dev/null | grep -E ':123|:323' || echo "无 NTP 端口监听"
echo -e "\n===== 4. 同步状态 ====="
if command -v chronyc &>/dev/null && systemctl is-active chronyd &>/dev/null; then
chronyc tracking | grep -E 'Reference ID|Stratum|System time|Leap status'
chronyc sources -v | tail -n +4
elif command -v ntpq &>/dev/null && systemctl is-active ntpd &>/dev/null; then
ntpq -pn
ntpstat
else
echo "警告:没有时间同步服务在运行!"
fi
echo -e "\n===== 5. xinetd 状态 ====="
if systemctl is-active xinetd &>/dev/null || [ -f /var/run/xinetd.pid ]; then
echo "xinetd 运行中,已启用的服务:"
grep -l 'disable.*=.*no' /etc/xinetd.d/* 2>/dev/null
echo "监听的端口:"
ss -tunlp 2>/dev/null | grep xinetd
else
echo "xinetd 未运行(或未安装)"
fi
echo -e "\n===== 6. 防火墙与 SELinux ====="
if command -v firewall-cmd &>/dev/null; then
firewall-cmd --state 2>/dev/null
firewall-cmd --list-ports 2>/dev/null
fi
getenforce 2>/dev/null
echo -e "\n===== 7. 最近的相关日志 ====="
grep -Ei 'ntpd|chronyd|xinetd' /var/log/messages 2>/dev/null | tail -10
chmod +x /root/check_time_xinetd.sh
/root/check_time_xinetd.sh
填空与简答
4. 通过 xinetd 托管的 time 服务使用哪个端口?客户端用什么命令同步?
使用 TCP/UDP 37 端口(time-stream 对应 TCP,time-dgram 对应 UDP),客户端用 rdate -s <服务器IP> 同步。这是 RFC 868 的老式时间协议,精度只有秒级,仅作教学与兼容老设备使用,生产环境请用 NTP。
5. 简述什么是超级守护进程 xinetd,用生活中的例子说明。
xinetd(也叫 inetd、super daemon)本身不提供具体业务服务,只负责托管其他小服务:由它统一监听端口,请求到达时才唤起对应的服务进程,请求结束后被唤起的进程即可退出。
类比银行的综合服务窗口:窗口边始终坐着一个人(xinetd),她不办具体业务,办业务的人在里面喝茶聊天;有人来汇款时她喊一句"小王,有人汇款啦",管汇款的小王跑过来办完就回去休息。若规定"不能往北京汇款",管理员提前告诉她,遇到汇往北京的请求她直接拒绝,根本不用喊汇款员——这就是在业务之前多加的一层访问控制机制。
6. xinetd 的多线程与单线程有什么区别?在配置文件里由哪个参数决定?
- 多线程:所有用户请求同时提上来,多个服务进程一起工作 —— 对应
wait = no(并发)。 - 单线程:请求排队,同一时间只有一个服务进程在工作 —— 对应
wait = yes。
一般 socket_type = stream(TCP)配 wait = no,socket_type = dgram(UDP)配 wait = yes。
7. /etc/xinetd.conf 与 /etc/xinetd.d/ 的分工是什么?
| 路径 | 说明 |
|---|---|
/etc/xinetd.conf |
主配置文件,放全局默认值(defaults 段),一般无需修改 |
/etc/xinetd.d/ |
子配置文件目录,每个被托管的服务一个文件,日常配置都在这里 |
8. 子配置文件里 disable、socket_type、wait、user、server、server_args 各表示什么?
以 /etc/xinetd.d/rsync 为例:
service rsync
{
disable = yes # yes 关闭,no 开启
flags = IPv6
socket_type = stream # stream=TCP,dgram=UDP
wait = no # no=并发(多线程),yes=单线程等待
user = root # 以哪个用户身份运行被托管服务
server = /usr/bin/rsync # 被托管服务的启动程序路径
server_args = --daemon # 启动参数
log_on_failure += USERID # 失败时额外记录的日志字段
}
9. 独立守护进程(standalone)与 xinetd 托管的服务有什么区别?各适合什么场景?
- 独立守护进程:进程常驻内存、自行监听端口(如
sshd、ntpd、httpd),响应快,但一直占用资源。适合访问频繁、并发高的服务。 - xinetd 托管:由 xinetd 统一监听,来请求才唤起服务,节省内存,并可借 xinetd 获得访问控制、时段限制、并发限制、独立日志等增强能力。适合访问频率低、自身功能简单(往往连 IP 访问控制都没有)的小服务,如
rsync、tftp、telnet、time。
10. 启动任意一个服务前必须明确的三点是什么?
- 用什么用户身份启动(
user); - 启动命令 / 程序路径是什么(
server); - 是否带启动参数(
server_args)。
15. only_from 与 no_access 同时出现时的生效规则是什么?
先按"最精确匹配"判断:地址同时落入两者时,no_access 优先,连接被拒绝,且与两行的书写顺序无关。所以下面这种写法的结果是 10.0.0.10 与 10.0.0.26 也连不上(它们被 10.0.0.0/24 的 no_access 整体拒掉):
no_access = 10.0.0.0/24
only_from = 10.0.0.10 10.0.0.26
正确的"放行网段、排除个别 IP"写法是把网段写在 only_from、把个别 IP 写在 no_access。
综合实验
三、综合实验
实验一(源题 + 补充):搭建内网 NTP 服务器,让另一台机器同步
题目:修改 /etc/ntp.conf 允许本网段客户端同步,重启 ntpd,在另一台机器上用 ntpdate 完成同步;要求写出 restrict 与防火墙放行。
server(10.0.0.2):
yum install ntp -y
systemctl stop chronyd && systemctl disable chronyd # CentOS 7 必做,二者抢 123 端口
# 先手工对准一次,避免初始偏差过大被上游拒绝
ntpdate ntp.aliyun.com
hwclock -w
vim /etc/ntp.conf
# 允许本网段主机同步,但不允许修改本服务器时间、不接受 trap 服务
restrict 10.0.0.0 mask 255.255.255.0 nomodify notrap
# 上游时间源(iburst 加快首次同步)
server ntp.aliyun.com iburst
server ntp1.aliyun.com iburst
# 外网不可达时退化为本地时钟,保证内网仍能同步(stratum 10 优先级最低)
server 127.127.1.0
fudge 127.127.1.0 stratum 10
driftfile /var/lib/ntp/drift
systemctl restart ntpd && systemctl enable ntpd # CentOS 6: /etc/init.d/ntpd restart
netstat -ntlup | grep :123 # 确认 123 端口监听
firewall-cmd --permanent --add-service=ntp # 等价于 --add-port=123/udp
firewall-cmd --reload
# CentOS 6: iptables -I INPUT -p udp --dport 123 -j ACCEPT; service iptables save
# 等约 5 分钟,直到自己同步完成
ntpq -p # 上游前出现 * 号
ntpstat # synchronised to NTP server
client(10.0.0.3):
ntpdate -q 10.0.0.2 # 先只查询不改时间
ntpdate 10.0.0.2
hwclock -w
# 定时同步(企业常用方案)
crontab -e
*/10 * * * * /usr/sbin/ntpdate 10.0.0.2 >/dev/null 2>&1 && /sbin/hwclock -w
踩坑与验证:
| 现象 | 原因 | 处理 |
|---|---|---|
no server suitable for synchronization found |
server 自身还没同步好(stratum=16),或只放行了 TCP 没放行 UDP 123 | 等 5 分钟;ntpstat 确认 server 已 synchronised;检查 firewall-cmd --list-ports |
the NTP socket is in use |
client 上自己也开着 ntpd/chronyd | 停掉本机服务,或改用 ntpdate -u |
server 上 ntpq -p 全是空白/无 * |
上游不可达或被防火墙拦住出向 UDP 123 | ntpdate -q 直接测上游;检查出口策略 |
| 客户端时间不对但同步"成功" | server 时区错了 | timedatectl 校正时区,NTP 传的是 UTC,显示才用时区 |
企业方案:让一台能出外网的服务器同步公网 NTP,再把它配成内网 NTP 服务器;内网其他机器用 crond + ntpdate(或 chrony 指向它)向它同步,全公司时间就与北京时间一致。
实验二(补充):chrony 的 makestep 与 iburst 效果对比
题目:在同一台机器上分别验证 iburst(加快首次同步)与 makestep(允许跳变校正)的作用,并解释两者的区别。
A. iburst 对比:
vim /etc/chrony.conf
# 第一轮:server ntp.aliyun.com (不加 iburst)
systemctl restart chronyd
watch -n 1 'chronyc tracking | head -4; chronyc sources'
# 观察:Reference ID 长时间为 00000000 (),Stratum 0,通常要 1~2 分钟甚至更久才选定源
# 第二轮:server ntp.aliyun.com iburst (加 iburst)
systemctl restart chronyd
chronyc sources -v
# 观察:几秒内就出现 ^* ntp.aliyun.com,Stratum 变为 3
结论:正常轮询间隔是 64 秒起步,iburst 让 chronyd 启动后立刻连发 4~8 个报文(间隔 2 秒),把首次同步从"分钟级"压到"秒级"。ntpd 的 server ... iburst 作用相同。生产配置一律加 iburst。
B. makestep 对比:
# 制造一个 30 秒的人为偏差
systemctl stop chronyd
date -s "$(date -d '-30 seconds' +'%F %T')"
date
# 第一轮:注释掉 makestep
vim /etc/chrony.conf # #makestep 1.0 3
systemctl start chronyd
watch -n 2 'date; chronyc tracking | grep -E "System time|Frequency"'
# 观察:System time 缓慢减小,靠 slew(调整时钟频率)慢慢磨平,30 秒偏差要走很久
# 第二轮:启用 makestep
systemctl stop chronyd
date -s "$(date -d '-30 seconds' +'%F %T')"
vim /etc/chrony.conf # makestep 1.0 3
systemctl start chronyd
sleep 5; date; chronyc tracking | grep 'System time'
# 观察:时间被一次性"跳"回正确值
chronyc makestep # 手工立即跳变一次,等价于 ntpdate 的效果
chronyc -a makestep # 带认证(CentOS 7.4+ 部分版本需要)
结论对比表:
| 指令 | 作用 | 触发时机 | 副作用 |
|---|---|---|---|
iburst |
启动时快速连发报文,加快首次选源 | 服务启动瞬间 | 无,推荐常开 |
makestep 1.0 3 |
偏差超过 1 秒时直接跳变校正,仅对前 3 次时钟更新生效 | 服务启动初期 | 时间跳变,可能打断数据库/日志时序,故限定次数 |
| 默认(slew) | 微调时钟频率平滑校正 | 稳定运行期 | 偏差大时收敛极慢 |
记法:iburst 管"多快选到源",makestep 管"选到源后允不允许猛拉时间";makestep 1.0 -1 表示始终允许跳变(虚拟机、笔记本长期休眠场景可用,生产慎用)。
实验三(源题 / 源文档例一):将 sshd 托管到 xinetd
题目:sshd 自身配置文件 /etc/ssh/sshd_config 功能有限,把它托管到 xinetd 以获取额外控制能力,并验证 22 端口由 xinetd 监听。
/etc/init.d/sshd stop # 先停独立的 sshd(CentOS 7: systemctl stop sshd)
vim /etc/xinetd.d/ssh # 该文件默认不存在,需手动建立
service ssh
{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/sshd
server_args = -i
}
/etc/init.d/xinetd restart
lsof -i:22
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
xinetd 5472 root 5u IPv6 58673 0t0 TCP *:ssh (LISTEN)
答案要点:22 端口的监听进程由 sshd 变成 xinetd,即托管成功。server_args = -i 的 -i 表示 sshd 以 inetd 模式运行(不自行 fork、不自行监听,直接用 xinetd 递过来的 socket)。做这个实验务必留一个已连上的 SSH 会话或控制台,配置写错会把自己关在门外。
实验四(源题 / 源文档例二、例三):访问控制 + 并发 + 时段 + 独立日志
题目:在托管 sshd 的基础上完成——仅允许指定 IP、仅禁止指定 IP、放行网段但排除个别 IP;再限制最多 3 个并发连接、每个源 IP 只 1 个连接、只允许 9:00–18:00 连接,并把日志单独记到 /var/log/xinetd_ssh.log。
仅允许两个 IP 访问:
service ssh
{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/sshd
server_args = -i
only_from = 10.0.0.26 10.0.0.200 # 只有这两个 IP 能连
}
仅禁止两个 IP 访问:
no_access = 10.0.0.26 10.0.0.200 # 只有这两个 IP 不能连,其他都可以
允许网段但排除个别 IP:
only_from = 10.0.0.0/24
no_access = 10.0.0.200 10.0.0.26 # 只能 10 网段访问,但 200 和 26 除外
并发、时段、日志(man xinetd.conf 查参数):
service ssh
{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/sshd
server_args = -i
instances = 3 # 该服务最多 3 个并发连接
per_source = 1 # 每个源 IP 只允许 1 个连接
access_times = 9:00-18:00 # 只允许这个时段连接
log_type = file /var/log/xinetd_ssh.log
}
验证:
/etc/init.d/xinetd restart
tail -f /var/log/xinetd_ssh.log # 另开窗口盯日志
# 被 only_from 拒绝时日志形如
# ssh: FAIL: ssh address from=10.0.0.7
# 超出 per_source 时形如
# ssh: FAIL: ssh per_source_limit from=10.0.0.26
从第 4 台机器发起连接应被拒;同一 IP 开第 2 个会话应被拒;把系统时间调到 20:00 后连接也应被拒(这条最容易被误判成"服务坏了")。
实验五(源题 / 源文档例四):把 SSH 监听端口改成 2222
题目:在实验四配置基础上增加 port = 2222,同步修改 /etc/services,从客户端用 ssh -p 2222 连接并验证。
service ssh
{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/sbin/sshd
server_args = -i
instances = 3
per_source = 1
access_times = 9:00-18:00
log_type = file /var/log/xinetd_ssh.log
port = 2222
}
vim /etc/services
ssh 2222/tcp
ssh 2222/udp
/etc/init.d/xinetd restart
netstat -ntlup | grep :2222
firewall-cmd --permanent --add-port=2222/tcp && firewall-cmd --reload
semanage port -a -t ssh_port_t -p tcp 2222 # SELinux 为 enforcing 时必做
ssh 10.0.0.200 -p 2222 # 客户端需指定端口
答案要点:man xinetd.conf 明确要求——在 xinetd 中指定非标准端口时,必须同步修改 /etc/services。改完后本机连自己不用加 -p 2222,因为 /etc/services 里 ssh 的默认端口已经是 2222;也就是说 /etc/services 既决定服务端监听的端口名映射,也决定本机作为客户端访问别人时的默认端口。更多模板见 /usr/share/doc/xinetd-2.3.14/sample.conf。
实验六(补充):把 rsync(或 telnet)托管到 xinetd,用 only_from 限制来源网段并验证
题目:将 rsync --daemon 交给 xinetd 托管,只允许 10.0.0.0/24 访问 873 端口,其他网段一律拒绝;然后从两个不同网段的客户端验证效果。
yum install -y rsync xinetd
cat > /etc/rsyncd.conf <<'EOF'
uid = root
gid = root
use chroot = no
max connections = 10
log file = /var/log/rsyncd.log
[backup]
path = /data/backup
comment = xinetd lab
read only = no
auth users = rsyncuser
secrets file = /etc/rsync.password
EOF
mkdir -p /data/backup
echo "rsyncuser:123456" > /etc/rsync.password && chmod 600 /etc/rsync.password
vim /etc/xinetd.d/rsync
service rsync
{
disable = no # 默认是 yes,改为 no
flags = IPv6
socket_type = stream
wait = no
user = root
server = /usr/bin/rsync
server_args = --daemon
only_from = 10.0.0.0/24 # 只允许本网段
log_on_failure += USERID
log_type = file /var/log/xinetd_rsync.log
}
systemctl restart xinetd # 改完子配置文件必须 reload/restart 才生效
ss -tnlp | grep :873 # 监听进程应为 xinetd,而不是 rsync
firewall-cmd --permanent --add-port=873/tcp && firewall-cmd --reload
验证:
# 在 10.0.0.3(允许网段)
rsync --list-only rsyncuser@10.0.0.2::backup # 能列出模块
# 在 192.168.1.50(非允许网段)
rsync --list-only rsyncuser@10.0.0.2::backup
# 报 Connection reset by peer / connection unexpectedly closed
# 服务端看拒绝记录
tail -5 /var/log/xinetd_rsync.log
# rsync: FAIL: rsync address from=192.168.1.50
telnet 版本(同理):yum install -y telnet-server,编辑 /etc/xinetd.d/telnet,disable = no 并加 only_from = 10.0.0.0/24,重启 xinetd 后 telnet 10.0.0.2 23 验证。telnet 明文传输口令,仅用于实验,验证完立刻 disable = yes。
要点:only_from 是 xinetd 层的控制,被拒绝的连接根本不会唤起 rsync/telnet 进程,因此在 rsync 自己的日志里看不到任何记录——只能在 xinetd 的日志里看到 FAIL: ... address from=。
实验七(补充):用 TCP Wrappers 做二次限制,并说明与 only_from 的生效顺序
题目:在实验六基础上,再用 /etc/hosts.allow 与 /etc/hosts.deny 把 rsync 收紧到只允许 10.0.0.3 一台,并说明 TCP Wrappers 与 only_from 谁先生效。
ldd /usr/sbin/sshd | grep libwrap # 有 libwrap.so 说明支持 TCP Wrappers
ldd /usr/sbin/xinetd | grep libwrap # xinetd 本身编译了 libwrap 支持
vim /etc/hosts.allow
# 格式:服务名(daemon列表) : 客户端列表
rsync: 10.0.0.3
sshd: 10.0.0.0/255.255.255.0
vim /etc/hosts.deny
rsync: ALL
# hosts.allow / hosts.deny 修改后立即生效,无需重启服务
# 验证
# 10.0.0.3 → 允许
# 10.0.0.26 → 被拒(虽然在 only_from 的网段内)
tail -f /var/log/messages | grep -i rsync
生效顺序(重点):
客户端连接
↓
① 防火墙 firewalld / iptables —— 包级过滤,最先执行,拒绝后对方看到超时或 reset
↓
② xinetd 的 only_from / no_access / access_times / instances / per_source
↓
③ TCP Wrappers:先查 /etc/hosts.allow,命中即允许并停止判断
再查 /etc/hosts.deny,命中即拒绝
两个文件都没命中 → 默认允许
↓
④ 被托管服务自身的认证与授权(rsync 的 auth users / hosts allow、sshd 的 AllowUsers)
↓
⑤ 文件系统权限 + SELinux
结论:
only_from先于 hosts.allow 生效。xinetd 在决定要不要唤起子进程时就先做自己的only_from/no_access判断,被它拒掉的连接根本走不到 TCP Wrappers。- 因此两层是"与"的关系——必须同时通过。
only_from = 10.0.0.0/24加上hosts.allow: rsync: 10.0.0.3的最终效果就是只有10.0.0.3能连。 - TCP Wrappers 内部则是
hosts.allow优先于hosts.deny,且"都不匹配就放行",所以想做白名单必须在hosts.deny里写ALL兜底。 - 排查这类问题的顺序:
ss -tnlp看监听 → xinetd 日志看FAIL: address→/var/log/messages看 wrappers 拒绝 → 服务自身日志 →ausearch -m avc看 SELinux。
本章小结
三、xinetd 要点清单
1. 独立守护进程与 xinetd 托管的区别
| 维度 | 独立守护进程(standalone) | xinetd 托管 |
|---|---|---|
| 端口监听者 | 服务自己(如 sshd、ntpd) |
统一由 xinetd 监听 |
| 进程常驻 | 常驻内存 | 有请求才唤起,处理完退出 |
| 响应速度 | 快 | 每次连接多一次 fork,稍慢 |
| 资源占用 | 高 | 低 |
| 访问控制 | 依赖服务自身或 iptables | 由 xinetd 统一提供,能力更强 |
| 适用场景 | 高并发、频繁访问 | 访问频率低、自身功能简单的小服务(rsync、tftp、telnet、time) |
2. 常用配置项
| 配置项 | 作用 | 示例 |
|---|---|---|
disable |
服务开关,yes 是关、no 是开(最容易看反) |
disable = no |
socket_type |
stream = TCP,dgram = UDP |
socket_type = stream |
wait |
no = 并发(多线程),yes = 单线程排队 |
wait = no |
user / server / server_args |
服务启动三要素:身份、程序路径、参数 | server = /usr/bin/rsync |
only_from |
白名单:仅允许的 IP 或网段 | only_from = 10.0.0.0/24 |
no_access |
黑名单:禁止的 IP 或网段,与 only_from 冲突时它优先,与书写顺序无关 |
no_access = 10.0.0.26 |
access_times |
允许访问的时间段(服务器本地时间) | access_times = 9:00-18:00 |
instances |
该服务的最大并发连接总数 | instances = 3 |
per_source |
每个源 IP 的最大连接数,防单机刷连接 | per_source = 1 |
log_on_success / log_on_failure |
成功/失败时记录的字段(PID、HOST、USERID、EXIT、DURATION),用 += 追加 |
log_on_success += PID HOST DURATION |
log_type |
日志去向:SYSLOG <facility> <level> 或 file <路径> |
log_type = file /var/log/xinetd_ssh.log |
port |
指定非标准端口,必须同步改 /etc/services |
port = 2222 |
flags |
附加标记,如 IPv6、REUSE |
flags = IPv6 |
bind / interface |
只绑定某个本机 IP | bind = 10.0.0.2 |
cps |
连接速率限制:每秒最多 n 个,超限暂停 m 秒 | cps = 25 30 |
3. 改完配置一定要 reload
systemctl reload xinetd # 或 restart;CentOS 6: /etc/init.d/xinetd reload
kill -HUP $(cat /var/run/xinetd.pid) # 等价的信号方式
/etc/xinetd.d/ 下的文件改完不重载就完全不生效,这是最高频的"配置明明写对了却没效果"的原因。托管服务的开关也要顺带确认:grep -l 'disable.*=.*no' /etc/xinetd.d/* 能一眼列出所有已启用的服务。
四、排错口诀
-
先看服务在不在跑,再看能不能通 123 端口,再看 stratum 与 offset。
systemctl status chronyd|ntpd→ss -unlp | grep :123→chronyc sources -v/ntpq -p。三步走完,八成的问题已经定位。 -
123 端口只有一个主人:ntpd、chronyd、ntpdate 三者不能同台共舞。 看到
the NTP socket is in use就先停服务;看到服务起不来就查是不是另一个时间服务已经占了端口。 -
UDP 不是 TCP:
telnet测不出 123 通不通,防火墙也别只放 TCP。 用ntpdate -q、chronyc sources、nmap -sU -p123验证,放行写--add-service=ntp或--add-port=123/udp。 -
stratum 16 等于没同步:客户端报
no server suitable先怀疑服务端还没准备好。 新建的 NTP 服务器要等约 5 分钟自身同步完成才能对外服务;fudge 127.127.1.0 stratum 10可作外网断开时的兜底。 -
时间对不上先分清"时刻错"还是"时区错"。
date -u与date对比:UTC 正确、本地时间偏整小时 → 时区问题(timedatectl set-timezone);UTC 本身就错 → 同步问题。改完时间别忘hwclock -w,否则重启前功尽弃。 -
xinetd 排错四问:disable 改了没、xinetd reload 了没、only_from/access_times 有没有把自己挡在外面、端口改了
/etc/services跟了没。 被 xinetd 拒绝的连接不会唤起子进程,所以要去 xinetd 的日志(log_type指定的文件或/var/log/messages)找FAIL: address/per_source_limit/time,而不是去翻服务自身的日志。 -
一层层剥:防火墙 → xinetd 的 only_from → TCP Wrappers(hosts.allow 先于 hosts.deny)→ 服务自身认证 → 权限与 SELinux。 多层控制是"与"的关系,任何一层拒绝都连不上;被 SELinux 拦住的特征是服务本身正常但操作被拒,用
ausearch -m avc -ts recent确认。
此处评论已关闭