QEMU hostfwd 端口转发:SLIRP 网络下把主机端口映射进客户机
用 QEMU 的默认用户网络(SLIRP)启动虚拟机后,客户机可以主动访问外网,但主机和局域网里的其他机器却无法主动连进客户机——它躲在 QEMU 进程模拟的 NAT 设备后面,地址是 10.0.2.15,这个网段只存在于 QEMU 进程内部。想在主机上 ssh -p 2222 登录客户机、或者用浏览器访问客户机里跑在 80 端口的 Web 服务,就得靠 QEMU hostfwd 这条端口转发规则。SLIRP 的工作原理与限制在姊妹篇 QEMU SLIRP 用户网络:一条 -netdev user 跑通上网 里展开,本篇只解决一个具体问题:主机怎么把连接送进 NAT 后面的客户机。
如果你对虚拟机、客户机这些概念还陌生,先读 认识 QEMU:从零看懂虚拟机、镜像与快照 建立基本图景;本篇的所有命令都在一台 RISC-V virt 虚拟平台上实测,但 hostfwd 是网络栈的能力,与客户机 CPU 架构无关,x86 虚拟机写法完全一样。
本文要回答的问题
- SLIRP 用户网络下,客户机藏在 NAT 后面,主机为什么不能直接连
10.0.2.15? hostfwd=tcp::2222-:22这串参数里四个位置各自是什么含义,想转发多个端口怎么写?- 规则写下去之后,怎么从 QEMU 内部、主机进程、网络连通性三个层面确认它真的生效了?
- 不带 hostfwd 启动一台一模一样的虚拟机,主机连 2222 会发生什么?
- 端口转发配好了却连不上,通常卡在哪几个环节?
一、为什么需要转发:NAT 后面的客户机没有可达地址
先看没有 hostfwd 时的网络形态。-netdev user 创建的 SLIRP 网络栈运行在 QEMU 用户态进程里,给客户机分配固定地址 10.0.2.15,网关是 10.0.2.2,出口流量经 QEMU 进程做源地址转换后从主机网卡发出。这套机制对客户机是透明的,它以为自己接在一台正常路由器上;但对主机来说,10.0.2.0/24 这个网段并不对应任何真实网卡,主机路由表里没有通往它的路由,直接 ssh 10.0.2.15 只会得到「网络不可达」。
NAT 的本质是「内可出外不可进」:外部主动发起的连接没有对应的转换表项,不知道该送给哪台内部机器。家用路由器解决这个问题靠「端口映射」管理页,QEMU 的等价物就是 hostfwd——告诉 SLIRP:在主机上监听一个指定端口,凡是发到这个端口的 TCP(或 UDP)连接,由 QEMU 代为连接客户机的指定端口,双向搬运字节。整条链路发生在一个 QEMU 进程内部,不需要 root 权限,也不创建虚拟网卡,这是用户网络相对桥接网络最大的便利。
这张流程卡是本篇的主线:前两步是配置,第三步是 QEMU 内部的转发动作,最后一步用三层证据验收。读的时候注意第三步里的监听与重写都发生在同一个 qemu 进程里——主机上看到的监听者不是 sshd,而是 qemu 本身,这也是后面用 lsof 能一眼认出转发口的原因。
二、hostfwd 语法:一条规则的四个位置
本次实测的完整启动参数如下,两条转发规则挂在同一个 user 模式 netdev 上,分别把主机 2222 转到客户机 22(SSH)、主机 8080 转到客户机 80(HTTP):
qemu-system-riscv64 -machine virt -bios default -no-reboot \
-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80 \
-device virtio-net-device,netdev=net0把一条规则拆开看,完整形态是 hostfwd=协议:主机绑定地址:主机端口-客户机地址:客户机端口,连字符 - 分隔主机侧与客户机侧。以 tcp::2222-:22 为例:协议位是 tcp(另一个合法值是 udp);主机绑定地址留空表示绑到通配地址 *,即主机所有网卡的 2222 都收,写成 127.0.0.1:2222 则只允许本机连接;主机端口 2222 是客户端实际访问的端口;客户机地址留空表示默认客户机,也就是 SLIRP 固定分配的 10.0.2.15;客户机端口 22 是服务在客户机里真正监听的端口。两端口号不必相同,2222 转 22 这种错位写法正是为了避免与主机自身的 sshd 抢 22 端口。
需要多个转发时不要试图发明新分隔符,重复写 hostfwd= 即可,规则之间用逗号串在同一个 -netdev 参数里。除了启动时静态声明,运行中的虚拟机也可以通过监视器热加规则,HMP 的 hostfwd_add / hostfwd_remove 可以在不关机的情况下调整转发表,监视器的连接方式与命令形态见 QEMU 监视器入门:QMP/HMP 与 info 命令族。
下面这张链路图对应第一条规则,读图时跟着连接发起方向走:主机客户端只知道 127.0.0.1:2222,完全不需要知道客户机地址,地址转换由 QEMU 进程内的 SLIRP 完成。
图中蓝色是连接发起方,橙色是主机上的监听入口,紫色是完成 NAT 重写的 SLIRP 栈,绿色是连接最终的落点。8080 转 80 的第二条规则与它结构完全相同,只是换了两个端口号,不再另画。要特别区分图中的两个端口:2222 属于主机命名空间,22 属于客户机命名空间,hostfwd 规则就是这两个命名空间之间的桥。
三、第一层证据:QMP 里的 HOST_FORWARD 转发表
参数写对没有,最权威的答案在 QEMU 自己手里。虚拟机启动后连上 QMP,先执行 info network 确认网卡与用户网络栈的挂接关系,再执行 info usernet 查看 SLIRP 的连接与转发表。下面的终端实录是本次实验的原始输出。
info network 给出两块信息:virtio-net-device.0 是客户机看到的网卡,MAC 为 52:54:00:12:34:56;它下面挂的 net0 类型是 user,net=10.0.2.0 标明 SLIRP 网段,restrict=off 表示未限制客户机访问主机服务。网卡和网络栈的挂接关系正确,转发规则才有承载者。
info usernet 的输出是一张标准转发表,两条 TCP[HOST_FORWARD] 记录与命令行参数一一对应:源地址列是 *、端口 2222,目的地址列是 10.0.2.15、端口 22;第二条是 * 8080 到 10.0.2.15 80。表里的 FD 列(13 和 12)是 QEMU 进程内持有监听套接字的文件描述符号,下一节用主机工具看到的监听口正是这两个 FD。RecvQ、SendQ 均为 0,说明当前还没有真实连接经过,规则处于待命状态。
四、第二层与第三层证据:lsof 监听口与 nc 探活
QMP 说规则生效了还不够,主机操作系统也得认。第二个视角换到主机侧:用 lsof 查 qemu 进程持有的网络套接字,再用 nc -z 做零数据探活。同一段实录末尾还安排了阴性对照——启动一台不带任何 hostfwd 的虚拟机,在它存活期间重复探活。
lsof 的两行输出显示 PID 2849 的 qemu 进程分别在 *:2222 与 *:8080 上处于 LISTEN 状态,FD 编号 13u、12u 与 QMP 转发表里的 FD 完全对上——这证实监听者就是 QEMU 本身,而非某个独立的转发守护进程。随后两次 nc -z 127.0.0.1 探活都返回 succeeded,脚本汇总为 nc 127.0.0.1:2222 -> OPEN 和 nc 127.0.0.1:8080 -> OPEN,第三层证据闭环:主机 TCP 栈确实接受了发往这两个端口的连接。
最后三行是整个实验里最有教学价值的阴性对照。对照机与实验机只有一个差别:命令行里没有任何 hostfwd 参数,SLIRP 网络照常工作、虚拟机照常存活,但主机探活 2222 得到的是 connection refused——主机 TCP 栈明确回了 RST,因为根本没有人监听这个端口。这组对照排除了「只要虚拟机开着端口就通」的错觉:转发能力不是 SLIRP 自动提供的,每一条可达路径都必须由一条 hostfwd 规则显式开通。

顺着这张记录从上往下读,证据链一环扣一环:先是 QMP 的 info usernet 给出转发表,2222 -> 10.0.2.15:22 与 8080 -> 10.0.2.15:80 两条规则的 FD 编号分别是 13 和 12;紧接着主机侧 lsof 列出同一 qemu 进程持有的两个 LISTEN 套接字,FD 编号与转发表一一对应,证明监听者就是 QEMU 自己;两次 nc 探活返回 OPEN 后,画面下半段切换到没有 hostfwd 的对照机,同样的探活立刻变成 connection refused。规则存在、端口在听、连接可达、缺失即拒,四件事在同一次运行里全部落到实处。
五、连不上时的排错清单
三层证据中任何一层断掉,现象都不同。把实测机理和常见报错对应起来,排错时按表从下往上逐层排查即可。
| 典型原因 | 主机侧现象 | 判定方法 | 处理 |
|---|---|---|---|
| 主机端口已被占用 | QEMU 启动直接报错退出,提示 Failed to bind socket | lsof -i :2222 看占用者 | 换主机端口,或停掉占用进程 |
| 漏写 hostfwd | connection refused,无监听口 | lsof 查不到 qemu 的 LISTEN;QMP info usernet 无 HOST_FORWARD | 补规则后重启,或用 hostfwd_add 热加 |
| 客户机服务未启动 | 端口 LISTEN 正常、nc 探活 OPEN,但真正用起来连接被重置或秒断 | QMP 转发表存在,客户机内 ss -tlnp 查不到目标端口 | 在客户机内启动 sshd/Web 服务 |
| 协议写错(tcp/udp 弄反) | TCP 连接被拒或 UDP 无响应 | 核对规则首段的 tcp/udp 与客户端协议是否一致 | 改对协议,UDP 服务需写 hostfwd=udp::... |
| 绑定地址限制 | 本机能连、局域网其他机器不能连 | 规则里主机地址写成了 127.0.0.1 | 按需改回通配绑定,注意同时评估暴露面 |
表里第三行是新手最容易误判的一档:hostfwd 的监听口在 QEMU 启动时就开好,与客户机里服务是否就绪无关。所以 nc 探活 OPEN 只证明「主机到 QEMU」这一段通,不保证「QEMU 到客户机服务」那一段通;真正的业务连接进来时,如果客户机 22 端口没有 sshd,SLIRP 会受理后立刻重置连接。完整的判定必须三层都看。
六、安全注意:通配绑定意味着对全网开放
语法一节讲过,主机绑定地址留空时 QEMU 绑的是通配地址 *,实测的 lsof 输出也印证了这一点。这意味着同一局域网内的任何机器都可以访问你主机的 2222、8080 端口,进而触达客户机服务——在公司网络或云主机上,这等同于把客户机的 sshd 直接挂到了网络里。个人本地实验图省事可以用通配绑定;环境不可信时,规则应写成 hostfwd=tcp:127.0.0.1:2222-:22,只允许主机本机连接,需要远程访问时再套一层 SSH 隧道。
另外两个习惯。第一,主机端口尽量选 1024 以上的高端口并避免与主机既有服务冲突,2222、8080 这类约定俗成的映射方便记忆但也容易被扫描器光顾。第二,转发只解决「包能到」,认证仍由客户机服务自己负责:对外暴露的 sshd 要禁用弱口令、优先密钥登录,Web 服务要自行处理鉴权,hostfwd 不提供任何访问控制。
实验环境
- 机器/系统:MacBook Pro(Intel x86_64)/ macOS 26
- 工具版本:QEMU 11.1.2(qemu-system-riscv64)、系统自带 nc(BSD netcat)与 lsof
- 日期:2026-11-07
- 完整命令序列:
./run.sh一条脚本完成全部步骤——带两条 hostfwd 启动虚拟机并经 QMP 执行info network/info usernet,主机侧lsof查监听口、nc -z探活 2222 与 8080,再启动一台无 hostfwd 的对照机重复探活,输出即本篇引用的完整实录。
代码获取:本篇实验的可复现脚本见公开仓库 derekzhuo/geek-doc.cn 的
experiments/qemu-port-forwarding-hostfwd目录。克隆后进入该目录执行./run.sh,终端会依次打印 QMP 转发表、主机监听口与探活结果、无 hostfwd 阴性对照,与本篇实录逐条对应。
这一篇在知识体系里的位置
本篇是 SoC 与 QEMU 入门路线的网络实操篇,与姊妹篇 QEMU SLIRP 用户网络:一条 -netdev user 跑通上网 构成一对:那篇讲客户机怎么「出得去」,本篇讲主机怎么「进得来」。运行时热加规则依赖监视器操作,命令族与握手细节在 QEMU 监视器入门:QMP/HMP 与 info 命令族;当串口成为你登录客户机、配置网络的主要通道时,三种控制台的接线区别见 QEMU 串口与控制台接线:nographic、serial 与 monitor。再往后走,需要局域网其他机器直接访问客户机、或追求接近线速的网络性能时,就该从 SLIRP 换到桥接与 tap 网络,那是后续进阶篇的主题。
总结
QEMU hostfwd 是 SLIRP NAT 之上的端口映射规则,形态为 hostfwd=协议:主机地址:主机端口-客户机地址:客户机端口,地址留空即通配绑定、客户机留空即默认的 10.0.2.15,多条规则重复书写、逗号串联。验证一条规则要看三层证据:QMP 的 info usernet 出现 TCP[HOST_FORWARD] 转发表,主机 lsof 看到 qemu 进程持有对应 LISTEN 套接字,nc 探活返回 OPEN;而不带 hostfwd 的对照机只会得到 connection refused,证明每条可达路径都必须显式开通。排错时记住监听口 OPEN 不等于客户机服务就绪,安全上优先使用 127.0.0.1 绑定并把认证留给客户机服务自身。