Como posso encaminhar portas no modo de usuário QEMU sem conflitos?

Como posso encaminhar portas no modo de usuário QEMU sem conflitos?

Estou executando o Linux no QEMU como parte de um conjunto de testes para um script Python que realiza algumas operações privilegiadas. Executar uma máquina virtual completa é importante para mim porque:

  1. Não quero exigir acesso sudo para executar testes
  2. mais tarde será mais fácil executar os testes em diferentes distribuições e versões de kernel
  3. Tenho menos probabilidade de quebrar meu computador

Minha abordagem é iniciar a VM QEMU com uma porta host encaminhada para SSH na VM, enviar e executar o script por SSH, executar o script e afirmar coisas sobre o sistema remoto. O irritante dessa configuração é a porta SSH e portas para outros aplicativos de rede.

Minha pergunta:Como posso encaminhar portas no modo de usuário QEMU sem conflitos?

Meu objetivo aqui é poder executar vários testes simultaneamente sem qualquer configuração prévia e sem sudo, conforme mencionado acima. A rede de modo de usuário no QEMU oferece suporte ao encaminhamento de porta e, quando passo 0pela porta do host na declaração hostfwd ( hostfwd=tcp:127.0.0.1:0-:22), o sistema operacional aloca uma dinamicamente. Incrível! O problema é obter essa porta de forma confiável no processo pai.

Estou fazendo o equivalente a:

#!/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
# ...

O sistema operacional convidado funciona bem.

Coisas que tentei:

  • brinquei um pouco comQMP, mas não foi possível encontrar um comando que fornecesse as informações

  • considerou vincular uma porta aleatória no processo pai SO_REUSEPORTe especificá-la para o comando filho, mas o QEMU não usa esse sinalizador, portanto não funciona

  • netstat -nalp | grep $child_pid, mas isso não poderá ser usado ao encaminhar várias portas

  • escolhendo uma porta não vinculada aleatória, tentando iniciar o qemu com ela e tentando novamente se falhar com uma mensagem correspondente Could not set up host forwarding rule 'tcp:...'. Isso funciona, mas

    1. é mais provável que falhe com mais portas
    2. pode obscurecer outros problemas pelos quais não vale a pena tentar novamente

    É uma boa alternativa, mas tenho esperança de uma solução mais direta.

Responder1

Depois de mais um pouco de pesquisa: o QEMU expõe as informações via info usernetcomando. Esta é a aparência da saída:

(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

Não está disponível via QMP, mas foi enviado um patch para adicionar um equivalentequery-usernet aquialgum tempo atrás.

No meu caso, é simples expor o monitor usando -monitor unix:$PWD/mon.sock,server,nowait, conectar, enviar o comando, ler a saída e analisá-la para obter os valores desejados.

Então, em resumo, para usar o QEMU para testar aplicativos de rede sem exigir sudo ou outros pré-requisitos (além do próprio QEMU) e evitando conflitos de porta no host:

  1. Para cada aplicação, crie um mapeamento de porta através da linha de comando ( -netdev) ou monitor ( netdev_add), fornecendo 0a porta do host
  2. Exponha o monitorcomo um soquete unix para o aplicativo de consumo
  3. Execute info usernetatravés do soquete e extraia a porta de origem para cada mapeamento, que é a porta real alocada pelo sistema operacional

informação relacionada