主题
virsh console 失联后 send-key 盲打恢复 sshd 实战
KVM 虚拟机 SSH 不通、virsh console 串口也无回显的绝境下, 如何通过
virsh send-key向图形控制台盲打命令 + nginx Web 目录读回结果, 完成"无回显诊断",最终修复 SELinux 标签丢失导致的 sshd 启动失败。
背景
- 虚拟机:
rocky-repo(Rocky Linux 8.7,hostnamerepo122156,IP192.168.122.156) - 角色:内网仓库服务器,跑 Harbor(docker-compose,30000 端口)、nginx 文件源(80 端口)、Gitea(3000 端口)
- 故障诉求:需要 SSH 登录该 VM 做变更,但发现 22 端口连接被拒
故障现象
bash
ping 192.168.122.156 # 通,VM 活着
nc -vz 192.168.122.156 22 # refused,sshd 未监听
nc -vz 192.168.122.156 80 # open,nginx 正常
nc -vz 192.168.122.156 30000 # open,Harbor 正常VM 业务一切正常,唯独 SSH 进不去。
第一阶段:virsh console 登录诊断
宿主机上通过串口控制台登录(pexpect 脚本化处理 login 交互):
python
child = pexpect.spawn('virsh console rocky-repo', encoding='utf-8')
# 发送回车唤醒 → 匹配 login: → 输入用户 → 匹配 Password: xxx 输入密码 → 匹配 # 提示符进入系统后发现 sshd 处于启动失败-自动重启死循环:
text
● sshd.service - OpenSSH server daemon
Active: activating (auto-restart) (Result: exit-code)
Main PID: 695141 (code=exited, status=1/FAILURE)但 /usr/sbin/sshd -t 配置检查却返回 0(root 下手动能读配置),线索指向权限之外的原因。
第二阶段:console 失联
排查中途 pexpect 子进程被强杀,遗留 libvirt 控制台残留会话,随后 console 彻底失联:
text
$ virsh console rocky-repo
error: operation failed: Active console session exists for this domain
$ virsh console rocky-repo --force
Connected to domain 'rocky-repo'
(连接成功但串口完全无回显,发送任何字符均无响应)即使 virsh reboot 重启 VM 后,ttyS0 依然沉默。此时面临绝境:
| 通道 | 状态 |
|---|---|
| SSH 22 | 拒绝(sshd 损坏) |
| virsh console (ttyS0) | 无回显 |
| qemu-guest-agent | 未安装,guest-ping 无响应 |
| 离线读磁盘 (virt-cat) | qcow2 属 libvirt-qemu:kvm 且 0600,无 sudo 权限读不了 |
第三阶段:send-key 盲打 + HTTP 读回
突破口:VM 有 SPICE 图形控制台,tty1 上有 getty。virsh send-key 注入的是虚拟键盘事件,直达 tty1。
思路:
- 用
send-key盲打登录 tty1(看不到输出,只能信) - 盲打命令把诊断输出写入 nginx 的 Web 根目录(
/u02/repo/rke2/,80 端口有 autoindex) - 从宿主机
curl http://192.168.122.156/xxx.txt读回结果 —— 用 HTTP 当"显示器"
字符 → Linux 键码映射(Python 实现)
python
base = {'a':'KEY_A', ..., '0':'KEY_0', '-':'KEY_MINUS', '/':'KEY_SLASH',
'.':'KEY_DOT', ';':'KEY_SEMICOLON', ' ':'KEY_SPACE', ...}
shifted = {'_':'KEY_MINUS', '$':'KEY_4', '&':'KEY_7', '*':'KEY_8',
'>':'KEY_DOT', '{':'KEY_LEFTBRACE', '|':'KEY_BACKSLASH', ...}
def press(keys):
subprocess.run(['virsh', 'send-key', 'rocky-repo'] + keys)
time.sleep(0.08)
def type_text(s):
for ch in s:
if ch in base: press([base[ch]])
elif ch in shifted: press(['KEY_LEFTSHIFT', shifted[ch]])
elif ch.isupper(): press(['KEY_LEFTSHIFT', base[ch.lower()]])大写和符号通过 KEY_LEFTSHIFT 组合键实现(send-key 多键码同按时前者为修饰键)。
盲打登录并验证
bash
# 依次注入:回车唤醒 → root 回车 → 密码 回车
# 然后打一条带回显标记的命令验证 shell 可用:
echo ttyok-$(date +%s) > /u02/repo/rke2/ttymark.txt
# 宿主机上读回:
curl http://192.168.122.156/ttymark.txt
# ttyok-1786624097 ← 盲打成功!盲打采集诊断信息
bash
F=/u02/repo/rke2/sshdiag.txt
{ date; /usr/sbin/sshd -t; echo RC=$?; journalctl -u sshd -n 40 --no-pager;
ls -la /etc/ssh/ssh_host_*; grep -vE '^#|^$' /etc/ssh/sshd_config; } > $F 2>&1读回日志,真相大白:
text
sshd[4405]: /etc/ssh/sshd_config: Permission denied根因:SELinux 标签丢失
继续盲打采集 SELinux 信息:
bash
{ getenforce; ls -Z /etc/ssh/sshd_config;
ausearch -m avc -ts recent | tail -5; } > /u02/repo/rke2/sel.txt 2>&1读回结果:
text
Enforcing
system_u:object_r:unlabeled_t:s0 /etc/ssh/sshd_config根因:/etc/ssh/sshd_config 的 SELinux 类型是 unlabeled_t(正常应为 etc_t)。 系统处于 Enforcing 模式,sshd 运行在受限域 sshd_t 中,无权读取 unlabeled_t 文件, 启动即失败,systemd 每 42 秒拉起一次,无限循环。
这也解释了为什么 sshd -t 正常:root 在 unconfined_t 域下手动执行不受限制。
修复(继续盲打)
bash
F=/u02/repo/rke2/fix.txt
restorecon -Rv /etc/ssh > $F 2>&1
systemctl restart sshd
sleep 2
{ systemctl is-active sshd; ss -tlnp | grep -w :22; } >> $F 2>&1读回:
text
Relabeled /etc/ssh/sshd_config from system_u:object_r:unlabeled_t:s0 to system_u:object_r:etc_t:s0
active
LISTEN 0 128 [::]:22 users:(("sshd",pid=5958))宿主机验证:
bash
ssh root@192.168.122.156 'hostname'
# repo122156 ← SSH 完全恢复最后清理 Web 目录下的临时诊断文件(ttymark.txt / sshdiag.txt / sel.txt / fix.txt)。
总结
排障要点回顾:
- sshd 报
Permission denied读配置,但 root 手动sshd -t正常 → 高度怀疑 SELinux, 第一时间看ls -Z和ausearch -m avc,unlabeled_t是典型信号(常见于文件系统异常、 chattr/immutable 操作、跨文件系统移动文件后未 restorecon)。 virsh console报Active console session exists→ 有残留会话,用--force强占; pexpect 自动化时务必sendcontrol(']')正常退出,强杀子进程会留坑。- 串口失联不等于 VM 失联 →
virsh send-key走虚拟键盘到图形 tty1, 配合 VM 内任意一个 HTTP 服务做"输出读回通道",即可实现完全盲打运维。 - 修复 SELinux 标签问题优先用
restorecon(按策略恢复应有标签), 而不是chcon(手写标签,策略变更后可能回退);怀疑大面积丢失时用fixfiles relabel。