Virtual-Machines

如何在使用者模式 QEMU 中轉發埠而不會發生衝突?

  • August 16, 2020

我在 QEMU 下執行 Linux,作為 Python 腳本測試套件的一部分,該腳本執行一些特權操作。執行完整的虛擬機對我來說很重要,因為:

  1. 我不想要求 sudo 訪問來執行測試
  2. 以後在不同的發行版和核心版本上執行測試會更容易
  3. 我不太可能弄壞我的電腦

我的方法是使用轉發到 VM 上的 SSH 的主機埠啟動 QEMU VM,通過 SSH 發送和執行腳本,執行腳本,並對遠端系統進行斷言。這個設置的煩人之處在於 SSH 埠和其他網路應用程序的埠。

我的問題:如何在使用者模式 QEMU 中轉發埠而不會發生衝突?

如上所述,我的目標是能夠在沒有任何事先設置和 sudo 的情況下同時執行多個測試。QEMU 中的使用者模式網路支持埠轉發,當我0在 hostfwd 聲明 ( hostfwd=tcp:127.0.0.1:0-:22) 中傳遞主機埠時,作業系統會動態分配一個。驚人的!問題在於在父程序中可靠地獲取該埠。

我正在做相當於:

#!/bin/sh
set -e

cd "$(mktemp -d)"

# Download cloud image.
image_base_url="https://cloud-images.ubuntu.com/releases/18.04/release/"
image_name="ubuntu-18.04-server-cloudimg-amd64.img"
image_url="$image_base_url$image_name"
curl --location -o linux.img $image_url

# Create SSH key.
ssh_key_path="$PWD/id_rsa"
ssh-keygen -t rsa -b 4096 -N "" -f "$ssh_key_path"

# Create cloud-init payload.
cat <<EOF > user_data
#cloud-config
password: ubuntu
chpasswd: { expire: False }
ssh_pwauth: True
ssh_authorized_keys:
- $(cat "${ssh_key_path}.pub")
EOF
cloud-localds user_data.img user_data

# Socket path for QMP.
qmp_socket="$PWD/qmp.sock"

# Start VM.
qemu-system-x86_64 \
   -drive file=linux.img,format=qcow2 \
   -drive file=user_data.img,format=raw \
   -netdev user,id=net0,hostfwd=tcp:127.0.0.1:0-:22 \
   -device rtl8139,netdev=net0 \
   -enable-kvm \
   -m 2G \
   -serial mon:stdio \
   -nographic \
   -smp 2 \
   -snapshot \
   -qmp unix:$qmp_socket,server,nowait \
   & \
;

# wait a bit, then test stuff here
# ...

來賓作業系統執行良好。

我嘗試過的事情:

  • 玩了一點QMP,但找不到提供資訊的命令

  • 考慮在父程序中綁定一個隨機埠,SO_REUSEPORT然後為子命令指定它,但是QEMU不使用這個標誌,所以它不起作用

  • netstat -nalp | grep $child_pid,但是在轉發多個埠時這將不可用

  • 選擇一個隨機的未綁定埠,嘗試使用它啟動 qemu,如果失敗並顯示消息匹配,則再次嘗試Could not set up host forwarding rule 'tcp:...'。這有效,但是

    1. 更多埠更容易失敗
    2. 可能會掩蓋其他不值得重試的問題這是一個不錯的備份方案,但我對更直接的解決方案抱有希望。

經過一番研究:QEMU 通過info usernet命令公開資訊。這是輸出的樣子:

(qemu) info usernet
VLAN -1 (net0):
 Protocol[State]    FD  Source Address  Port   Dest. Address  Port RecvQ SendQ
 TCP[HOST_FORWARD]  13       127.0.0.1 38117       10.0.2.15    22     0     0
 UDP[236 sec]       24       10.0.2.15 35061   91.189.89.198   123     0     0
 UDP[204 sec]       26       10.0.2.15 60630   91.189.89.198   123     0     0

它不能通過 QMP 獲得,但前段時間送出了一個更新檔以query-usernet 在此處添加等效項。

在我的例子中,使用-monitor unix:$PWD/mon.sock,server,nowait、連接、發送命令、讀取輸出並解析它以獲得所需的值是很簡單的。

所以總而言之,使用 QEMU 測試網路應用程序不需要 sudo 或其他先決條件(QEMU 本身除外),並避免主機上的埠衝突:

  1. -netdev對於每個應用程序,通過命令行 ( ) 或監視器 ( )創建埠映射netdev_add,提供0主機埠
  2. 將 暴露monitor為消費應用程序的 unix 套接字
  3. 通過socket執行info usernet,提取每個映射的Source Port,即OS實際分配的埠

引用自:https://serverfault.com/questions/1030360