我使用以下命令来运行 QEMU VM:
qemu-system-x86_64 -enable-kvm -m 1024 -smp 2 -hda disk.qcow2
默认情况下,来宾操作系统可以访问互联网,但也可以访问主机操作系统上的开放端口。如何防止来宾操作系统访问主机端口(但不限制其互联网访问)?
答案1
如果您没有高级 iptables 操作经验以及 Linux 如何过滤本地进程流量的概述,这看起来有些问题。
在您的模式下,qemu 运行模拟 NAT:来自 guest 虚拟机的所有对 NIC 的调用都将被 qemu 进程本身转换为套接字/连接/发送/接收调用。这意味着连接是由机器本身从 127.0.0.1 建立的。此时,您可以作为另一个用户运行 qemu,并通过添加匹配来过滤该用户owner
:
iptables -I OUTPUT -o lo -m owner --uid-owner username -m multiport --dports ports -j DROP
其中username
是要过滤的用户名,ports
是要为该计算机禁用的端口的逗号分隔列表。要以其他用户身份运行 qemu,您需要通过sudo
或 以用户身份登录su
或 之类的工具来运行它login
。
如果没有这个,您最终会过滤自己,因此如果您添加通用规则来过滤端口,您也将被阻止访问这些端口。
另一种方法是改变 qemu 的网络方式。过滤流量的一个好方法是将 qemu 绑定到虚拟以太网设备:
启用数据包转发。
安装
tunctl
,添加虚拟网络接口,所有者是你:
tunctl -u yourname -t qemu
(记得将此命令添加到诸如rc.local
使其永久化的命令中)
- 配置 qemu 接口(使用
ip
/ifconfig
或其他操作系统提供的工具)为其分配一个空闲的 /24 子网。该子网也需要在您的来宾操作系统中设置。然后使用 运行 qemu-net tap,ifname=qemu,script=off
。再次配置来宾操作系统网络。
qemu
然后您可以轻松过滤由虚拟接口表示的来宾操作系统流量:
iptables -I FORWARD -i qemu -m multiport --dports ports -j DROP
应该管用。
但 NAT 停止工作了。如果您需要使 NAT 再次工作,您应该添加一条规则来修补从您的计算机发出的 IP 地址。如果您有eth0
所有流量都经过的接口,则为其启用 NAT:
iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE
答案2
-netdev user
我想补充一点,如果您使用默认 ( ) 网络,则有一些更简单的防火墙规则。
iptables -I OUTPUT -o lo -m owner --uid-owner qemu-user -d 127.0.0.1 -j DROP
请注意,您必须在用户下运行 qemu qemu-user
,您可以使用sudo -u qemu-user qemu-system-x86_64 -enable-kvm ......
这会阻止主机的所有端口在来宾上可见,而无需指定它们(如第一个答案中所示)。请注意,目标地址是127.0.0.1
而不是10.0.2.2
(访客默认看到主机的地址)的原因是因为 qemu 执行 NAT 的方式。
如果您有兴趣,这里有一些有关 qemu 如何处理默认网络的详细信息:
Qemu 拦截从虚拟化网卡飞出的原始链路层数据包完成的 TCP 和 UDP 连接,并将它们转换为 qemu 进程本身执行的相应连接,通过调用socket()
, connect()
, sendto()
, recvfrom()
... 在这样做的同时,它还执行一些地址转换,因此到10.0.2.2
和 的连接分别10.0.2.3
转换为到127.0.0.1
和 的连接127.0.0.53
。这就是为什么OUTPUT
链式规则会删除 qemu 与 的连接127.0.0.1
,使主机对访客不可见,除了主机中的 DNS 服务器(以及 qemu 内部模拟的 DHCP 服务器,但这都是在 qemu 内部完成的)。
在这里,您可以看到当来宾使用10.0.2.3
( nslookup google.com 10.0.2.3
) 解析 dns 域时,qemu 执行的网络相关系统调用:
$ sudo strace -f -e trace=network -p <qemu_pid>
strace: Process 14529 attached with 10 threads
[pid 14529] socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, IPPROTO_IP) = 99
[pid 14529] sendto(99, "\236\353\1\0\0\1\0\0\0\0\0\0\6google\3com\0\0\1\0\1", 28, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, 16) = 28
[pid 14529] recvfrom(99, "\236\353\201\200\0\1\0\1\0\0\0\0\6google\3com\0\0\1\0\1\300\f\0\1"..., 1500, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, [128->16]) = 44
[pid 14529] socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, IPPROTO_IP) = 100
[pid 14529] sendto(100, "@R\1\0\0\1\0\0\0\0\0\0\6google\3com\0\0\34\0\1", 28, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, 16) = 28
[pid 14529] recvfrom(100, "@R\201\200\0\1\0\1\0\0\0\0\6google\3com\0\0\34\0\1\300\f\0\34"..., 1500, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, [128->16]) = 56
如您所见,10.0.2.3
被翻译为127.0.0.53
.当来宾与此地址通信时,Qemu 仅允许 UDP 流量到达端口 53,因此主机不会进一步暴露,除了其 dns 服务器守护程序之外。