Vấn đề: Thiết bị USB vật lý không dùng được trong máy ảo
Mình có một con dongle bảo mật (USB hardware key) dùng để kích hoạt phần mềm kế toán. Vấn đề là phần mềm đó chỉ chạy được trên Windows, trong khi host mình dùng Ubuntu với KVM. Cắm dongle vào máy host thì host nhận ra, nhưng Windows VM không thấy gì hết.
Tương tự với modem 3G/4G USB — bạn muốn gán cố định cho một VM router để nó tự dial-up, không phụ thuộc host. Hay camera USB muốn đưa thẳng vào VM chạy NVR (phần mềm quản lý camera).
Tất cả đều giải quyết bằng USB Passthrough: máy ảo chiếm quyền kiểm soát hoàn toàn thiết bị USB vật lý, giao tiếp trực tiếp với firmware của nó — không qua trung gian.
Tại sao mặc định máy ảo không thấy USB vật lý?
KVM/QEMU mặc định tạo ra USB controller ảo (emulated). VM nhìn vào thấy một controller giả lập sạch bóng, không có thiết bị vật lý nào. Hypervisor đứng giữa host và VM — và mặc định nó không chuyển tiếp USB hardware xuống.
Để passthrough hoạt động, QEMU dùng cơ chế usb-host: ánh xạ trực tiếp device file /dev/bus/usb/... từ host vào VM qua VFIO hoặc libusb. VM khi đó nhận đủ USB descriptor, endpoint, và giao tiếp thẳng với firmware thiết bị — giống hệt cắm vật lý.
Proxmox bọc thêm Web UI cho dễ dùng, nhưng bên dưới vẫn là cùng cơ chế QEMU đó.
Hai cách passthrough, chọn cái nào?
Cách 1: Passthrough theo Vendor ID và Product ID (nên dùng)
Gán thiết bị theo model — Vendor:Product ID. Cắm vào cổng nào VM vẫn nhận đúng thiết bị. Đây là cách phù hợp cho dongle bảo mật, modem 3G, hoặc bất kỳ thiết bị nào bạn cần gán lâu dài.
Cách 2: Passthrough theo địa chỉ Bus:Device
Gán theo vị trí vật lý — bus số mấy, port số mấy. Rút thiết bị ra rồi cắm lại là địa chỉ thay đổi, VM mất kết nối ngay. Chỉ dùng khi thực sự cần kiểm soát cổng cụ thể, không phải thiết bị cụ thể.
Thực hiện trên KVM/libvirt
Bước 1: Tìm Vendor ID và Product ID
Cắm thiết bị vào host, chạy:
lsusb
Kết quả mẫu:
Bus 001 Device 003: ID 0403:6001 Future Technology Devices International, Ltd FT232 Serial (UART) IC
Bus 001 Device 004: ID 12d1:1506 Huawei Technologies Co., Ltd. Modem/Networkcard
Bus 002 Device 002: ID 0529:0001 Aladdin Knowledge Systems HASP v0.06
Dòng cuối là dongle HASP — 0529 là Vendor ID, 0001 là Product ID. Ghi lại hai số này.
Bước 2: Thêm USB device vào XML của VM
Mình dùng virsh edit cho nhanh:
virsh edit ten-may-ao
Tìm section <devices>, thêm vào trong đó:
<hostdev mode='subsystem' type='usb' managed='yes'>
<source>
<vendor id='0x0529'/>
<product id='0x0001'/>
</source>
</hostdev>
Lưu lại rồi restart VM:
virsh shutdown ten-may-ao
virsh start ten-may-ao
Bước 3: Kiểm tra trong VM
Windows VM: mở Device Manager, thiết bị sẽ xuất hiện ngay. Linux VM thì kiểm tra bằng:
lsusb # chạy bên trong VM
Hot-plug: thêm USB khi VM đang chạy
Không cần restart — tạo file XML tạm rồi attach trực tiếp:
cat > /tmp/usb-dongle.xml <<EOF
<hostdev mode='subsystem' type='usb' managed='yes'>
<source>
<vendor id='0x0529'/>
<product id='0x0001'/>
</source>
</hostdev>
EOF
virsh attach-device ten-may-ao /tmp/usb-dongle.xml --live
Gỡ ra khi VM vẫn chạy:
virsh detach-device ten-may-ao /tmp/usb-dongle.xml --live
Thực hiện trên Proxmox VE
Homelab của mình chạy Proxmox quản lý 12 VM và container — đây là nơi mình test mọi thứ trước khi đưa lên production. USB passthrough là tính năng dùng gần như hằng tuần, đặc biệt khi cần test phần mềm yêu cầu dongle license.
Qua Web UI (nhanh nhất)
- Vào Proxmox Web UI → chọn VM → tab Hardware
- Click Add → chọn USB Device
- Chọn Use USB Vendor/Device ID → dropdown liệt kê các thiết bị USB đang cắm vào host
- Chọn thiết bị cần passthrough → OK
- Start (hoặc restart) VM
Proxmox tự ghi config vào /etc/pve/qemu-server/<VMID>.conf:
usb0: host=0529:0001
Qua config file (cho automation hoặc scripting)
SSH vào Proxmox node, mở file config trực tiếp:
nano /etc/pve/qemu-server/100.conf # 100 là VMID
Thêm:
# Dongle kế toán
usb0: host=0529:0001
# Modem Huawei thứ hai
usb1: host=12d1:1506
Restart VM:
qm stop 100 && qm start 100
Passthrough toàn bộ cổng USB vật lý
Muốn gán cố định một cổng USB (bất kể cắm thiết bị gì vào đó), dùng option Use USB Port trong Proxmox UI. Config tương ứng:
usb0: host=1-2 # Bus 1, Port 2
Xác định bus/port bằng:
lsusb -t # Xem cây USB topology
Xử lý lỗi thường gặp
Permission denied khi attach USB
Libvirt cần quyền đọc device file. Kiểm tra ownership trước:
ls -la /dev/bus/usb/001/004 # thay số bus/device cho đúng
Owner là root:root trong khi QEMU chạy user khác? Thêm udev rule:
cat > /etc/udev/rules.d/99-usb-passthrough.rules <<EOF
SUBSYSTEM=="usb", ATTR{idVendor}=="0529", ATTR{idProduct}=="0001", MODE="0666"
EOF
udevadm control --reload-rules
udevadm trigger
Thiết bị mất kết nối sau khi VM restart
Gần như chắc chắn là đang dùng passthrough theo Bus:Device thay vì Vendor:Product. Địa chỉ Bus:Device thay đổi mỗi lần thiết bị được enum lại. Chuyển sang Vendor:Product ID là xong.
USB 3.0 không nhận trên VM
QEMU mặc định dùng USB 2.0 controller (EHCI). Thiết bị USB 3.0 cần xHCI. Trong Proxmox UI: Hardware → Add → USB Controller → chọn xHCI. Hoặc trong libvirt XML:
<controller type='usb' model='qemu-xhci'/>
Script tự động attach USB khi cắm vào
Script nhỏ dùng cho homelab — attach USB vào VM ngay khi thiết bị được cắm, không cần can thiệp thủ công:
#!/bin/bash
# usb-attach.sh — Attach USB device vào VM khi cắm vào
# Dùng với udev rule hoặc chạy thủ công
VENDOR_ID="0529"
PRODUCT_ID="0001"
VM_NAME="windows-kế-toán"
if virsh list --state-running | grep -q "$VM_NAME"; then
echo "VM đang chạy, attach USB..."
virsh attach-device "$VM_NAME" - --live <<EOF
<hostdev mode='subsystem' type='usb' managed='yes'>
<source>
<vendor id='0x${VENDOR_ID}'/>
<product id='0x${PRODUCT_ID}'/>
</source>
</hostdev>
EOF
echo "Attach thành công!"
else
echo "VM chưa chạy, bỏ qua."
fi
Tổng kết
Toàn bộ flow chỉ có hai bước: tìm Vendor:Product ID của thiết bị, khai báo vào config VM. Xong. Proxmox làm điều đó qua vài click trong Web UI. KVM/libvirt thì virsh edit và virsh attach-device cho kiểm soát chi tiết hơn — bao gồm cả hot-plug khi VM đang chạy.
Một điểm quan trọng: luôn dùng Vendor:Product ID, đừng dùng Bus:Device. Mình từng mất cả buổi debug vì dongle “biến mất” sau mỗi lần restart — hóa ra chỉ vì địa chỉ Bus:Device thay đổi. Chuyển sang Vendor:Product là cố định vĩnh viễn, dù cắm vào cổng nào.

