VM Sees the Dongle but Has No Internet Without a Windows ECM Driver
When someone reports no internet, the SIM or signal usually gets blamed first. But in some cases, the hardware is already working properly, and the only problem is that the guest operating system does not recognize the interface the device exposes.
I had a host running several Windows VMs, each paired with one USB LTE dongle to give it its own internet connection. One day, someone reported that one machine had no internet, even after restarting it, but when I checked Device Manager, the device was there. I assumed the SIM had expired, as usual. Once I investigated, it turned out to be something else entirely. The SIM was fine, the signal was full, and even the host could connect to the internet through that dongle, but Windows in the guest had no driver for the interface this dongle exposed.
In this article, I describe how I worked through each layer to find the cause, along with three other pitfalls I encountered along the way. I have removed all device identification numbers, machine numbers, and carrier names, leaving only the vendor and product IDs needed in the commands.
If you are looking for a way to pass a USB device from Windows into Linux in WSL, I covered that separately in Passing USB into WSL2 with usbipd-win. This case goes in the opposite direction and concerns a different layer: a Linux host passes a device to a Windows guest, and the guest does not recognize it. It is purely a driver issue, rather than a port passthrough issue.
First, identify which layer has the problem
The report had three parts: the device was visible, there was no internet, and restarting did not fix it. Those three symptoms cover four possible layers, so I worked from the bottom up, because the lower layers were quicker to verify and did not require interrupting the user.
The first layer was the quickest to settle. I brought up the interface on the host itself, enabled mobile data, and told it to connect. If the host could connect, that ruled out the SIM and signal.
from huawei_lte_api.Client import Client
from huawei_lte_api.Connection import Connection
with Connection("http://192.168.8.1/") as conn: # ที่อยู่ตั้งต้นของ dongle
c = Client(conn)
c.dial_up.set_mobile_dataswitch(1) # เปิดสวิตช์ข้อมูล
c.dial_up.dial()
print(c.monitoring.status()["ConnectionStatus"]) # 901 = ต่อแล้ว
print(c.device.signal()["rsrp"], c.net.current_plmn()["State"])
$ ip addr add 10.10.10.50/24 dev enx0
$ ip link set enx0 up
$ python probe.py
901
-84dBm 0
$ ping -I enx0 -c3 203.0.113.1
3 packets transmitted, 3 received, 0% packet loss
The host successfully reached the internet through that dongle, with full signal and no SIM issues. That left the three upper layers.
This family of dongles also supplies its own default route and DNS server address. Running dhclient on the host will overwrite /etc/resolv.conf for the entire machine to point to the dongle. The result is that the host can no longer resolve domain names, run updates, or pull images, and it looks as though the host's internet connection has suddenly failed. The safe approach is to configure the address manually with ip addr add, then bind the test to that interface with ping -I, leaving the machine's default route untouched.
Read the interface the device exposes: the answer is here
Reading the USB device descriptors directly reveals which layer is failing. Devices like this can expose several different interfaces to the host, and each requires a different driver.
lsusb | grep -i 12d1
# Bus 003 Device 018: ID 12d1:14db USB LTE dongle (network mode)
lsusb -v -d 12d1:14db 2>/dev/null | grep -E \
"bNumConfigurations|bInterfaceClass|bInterfaceSubClass|iInterface"
bNumConfigurations 1
bInterfaceClass 2 Communications
bInterfaceSubClass 6 Ethernet Networking Control Model (ECM)
bInterfaceClass 10 CDC Data
bInterfaceSubClass 0
The decisive line is bInterfaceSubClass 6, which means the device exposes a CDC ECM interface, rather than RNDIS or NCM, and bNumConfigurations 1 means there is only one configuration, with no alternative to switch to. Another thing to check is whether a serial port appears.
ls /dev/ttyUSB* 2>/dev/null || echo "ไม่มีพอร์ตอนุกรมเลย"
# ไม่มีพอร์ตอนุกรมเลย
No serial port means no way to send AT commands, which was the only remaining channel for switching modes from the host. With that route closed too, there was really only one option left: fix it in the guest.
Plug and play on Windows
Both have drivers built into the operating system, so a network adapter appears as soon as the device is plugged in. Most commonly sold dongles are configured this way, so people get used to expecting them to always work.
Windows has no built-in driver
Linux and Mac have drivers in the kernel from the start, but Windows does not. The device ends up under Other devices with an exclamation mark: the system sees something plugged in, but no network adapter appears. That matched the reported symptoms exactly.
Trying to switch to RNDIS from the host hit a dead end every time
Before moving on to a fix in the guest, I tried every option available from the host, because switching modes would let me fix it in one place for all the machines. Every attempt hit a dead end.
$ curl -s -X POST http://192.168.8.1/api/device/usb-tethering-switch ...
<error><code>100002</code><message>No support</message></error>
$ usb_modeswitch -v 12d1 -p 14db -J
Look for default devices ...
No devices in default mode found. Nothing to do. Bye!
$ ls /dev/ttyUSB*
(ไม่มี)
These three lines represent three doors closing at once: the device's web API returned an unsupported response, the mode-switching tool had no data for this product ID, and there was no serial port for sending AT commands. The only remaining option was flashing new firmware, which I do not recommend on a device in active use. One mistake and it becomes a paperweight.
Keep the dongle from taking over the host's default route
Before getting into modes, there is another issue to settle during testing: do not let the dongle's interface become the host's primary internet connection. This can easily happen on a host with multiple network adapters. If the dongle comes up with a lower route metric than the existing adapter, all of the machine's internet traffic immediately goes through the dongle, making it several times slower and steadily consuming the SIM's data allowance without anyone noticing.
ip route show | head -3
# default via 10.10.10.1 dev enx0 metric 100 <- dongle แย่งไปแล้ว
# default via 10.10.20.1 dev vmbr0 metric 200
ip route replace default via 10.10.20.1 dev vmbr0 metric 100
ip route del default dev enx0 2>/dev/null # ไม่ให้ออกทาง dongle เลย
grep nameserver /etc/resolv.conf # ต้องไม่ใช่ที่อยู่ของ dongle
I made it a rule that the dongle's interface on the host serves only two purposes: checking the device's condition and telling it to connect. It is not there to provide internet access for the host. Whenever I test, I therefore bind the command directly to that interface instead of letting it choose a route automatically.
The storage-mode trap that breaks passthrough
Along the way, I ran into another trap that cost me half a day: when this family of dongles is first plugged in, it appears as a storage drive so a machine without drivers can install them from the device itself, then switches to network mode afterward.
I had thought that if I left it in storage mode and passed it directly into the VM, Windows would see the drive, install the drivers, and switch modes itself. That failed for a reason that had nothing to do with Windows.
When switching from storage to network mode, the device disconnects from the bus and returns as a different device. Passthrough bound to the original port does not follow it. Windows sees the device disappear mid-session, and nothing returns until the VM is stopped and started again. The approach that actually works is to put the dongle in network mode and establish its connection before starting the VM, then pass it through. The device passed into the VM will remain stable for the entire run.
The script I use to put every dongle into network mode before starting the VMs looks roughly like this. The key is to find devices by vendor ID, rather than remembering their ports.
#!/usr/bin/env bash
# กวาด dongle ทุกตัวให้เข้าโหมดเน็ตเวิร์ก แล้วสั่งต่อ
export PATH=/usr/sbin:/usr/bin:/sbin:/bin # หลังบูต PATH ไม่มี /usr/sbin
for d in /sys/bus/usb/devices/*; do
[ -f "$d/idVendor" ] || continue
[ "$(cat "$d/idVendor")" = "12d1" ] || continue
pid=$(cat "$d/idProduct")
bus=$(cat "$d/busnum"); dev=$(cat "$d/devnum")
if [ "$pid" = "1f01" ]; then
echo "[$d] โหมดสตอเรจ กำลังสลับ"
usb_modeswitch -b "$bus" -g "$dev" -v 12d1 -p 1f01 -J
sleep 8 # รอให้มันนับตัวใหม่จนจบ
fi
done
grep -q 0 /sys/bus/usb/drivers/usb_modeswitch/DisableSwitching 2>/dev/null
echo "เสร็จแล้วค่อยสั่ง qm start"
A path like 3-1.1.3 is not a permanent device name. It is a position in the bus tree. After a reboot or unplugging and reconnecting, the order may change. Binding VM passthrough directly to a port path leaves it pointing to a stale value as soon as that path shifts, and worse, the VM may get another machine's dongle instead. The correct approach is to use the vendor and product IDs, then have a script resolve them to a path each time the VM is about to start.
Bind passthrough by device ID, not port path
A short script can handle the shifting ports. The principle is to read the actual device path from the system before every VM startup, then write that value into the VM configuration, rather than setting it once and leaving it there for years.
#!/usr/bin/env bash
# map-usb.sh <vmid> <ลำดับที่เท่าไรของ dongle ที่ยังว่าง>
vmid="$1"; want="${2:-1}"; n=0
for d in /sys/bus/usb/devices/*; do
[ -f "$d/idVendor" ] || continue
[ "$(cat "$d/idVendor")" = "12d1" ] || continue
[ "$(cat "$d/idProduct")" = "14db" ] || continue # เอาเฉพาะโหมดเน็ต
n=$((n+1))
[ "$n" = "$want" ] || continue
port=$(basename "$d") # เช่น 3-1.1.3
echo "ผูก $port เข้ากับ VM $vmid"
qm set "$vmid" -usb0 "host=$port,usb3=1"
exit 0
done
echo "ไม่เจอ dongle ที่อยู่โหมดเน็ตและยังว่าง" >&2
exit 1
The usb3=1 option affects some devices when they re-enumerate. Some return to the VM with this option enabled; others still do not. I do not consider it a fix, just a bonus. The actual fix is still to have the device settled in network mode before starting the VM.
Another requirement to build into the script is skipping devices already reserved by other VMs. Otherwise, scanning and reading every device will take one away from a running machine, immediately dropping its internet connection without its user having any idea why.
The actual fix belongs in the guest, not the host
After ruling out every other option, only one answer remained: install a CDC ECM driver in the Windows guest. Verifying that conclusion was easy: compare it with another VM that worked normally, because the hardware was exactly the same. The only difference was that someone had already installed this driver on the working machine.
เครื่องที่ใช้งานได้ Network adapters > USB Ethernet (CDC ECM) ปกติ
เครื่องที่มีปัญหา Other devices > CDC ECM มีเครื่องหมายตกใจ
Another thing worth knowing to avoid wasting time: the installation disc supplied by the dongle in storage mode cannot fix this case. I mounted it and checked its contents. It contained only a Mac installer and autorun files, with no Windows driver information file for ECM at all.
The sequence I followed to completion had five steps.
- Verify on the host first Bring up the interface, enable mobile data, connect, then ping through that interface. If it works, the SIM and signal are not the issue.
- Read the descriptors Check which interface is exposed. If it is ECM and there is no serial port, stop looking for a host-side fix.
- Put the dongle in network mode and connect it before starting the VM Do not let it switch modes while the VM is running.
- Install the ECM driver in the guest Then confirm that it actually appears under Network adapters, rather than Other devices.
- Include the driver in the template So the next VM does not require the entire investigation again.
The last step is the most worthwhile, because this problem will certainly happen again. Every new machine built from the old template will lack this driver, and one day someone will report that there is no internet again.
A short checklist for the same symptoms
[ ] โฮสต์ต่อเน็ตผ่าน dongle ตัวนั้นได้ไหม -> ไม่ได้ = เรื่องซิม/สัญญาณ
[ ] lsusb -v เจอ InterfaceSubClass 6 ไหม -> เจอ = วินโดวส์ไม่มีไดรเวอร์
[ ] มี /dev/ttyUSB* ไหม -> ไม่มี = สั่ง AT ไม่ได้
[ ] bNumConfigurations เท่ากับ 1 ไหม -> ใช่ = สลับหน้าตาไม่ได้
[ ] idProduct ตอนนี้เป็น 1f01 หรือ 14db -> 1f01 = ยังไม่ได้สลับโหมด
[ ] resolv.conf ของโฮสต์ยังชี้ตัวแปลชื่อเดิมไหม -> ไม่ = โดน dhclient ทับ
[ ] เส้นทางพอร์ตใน config ของ VM ตรงกับของจริงไหม -> ไม่ตรง = ค้างค่าเก่าหลังบูต
[ ] VM ตัวอื่นที่ฮาร์ดแวร์เหมือนกันใช้ได้ไหม -> ได้ = ต่างกันที่ไดรเวอร์ในแขก
Personally, I took one lesson from this case: seeing a device and being able to use it are separate things at separate layers. When someone reports no internet, blaming the SIM first wastes the most time, because it is the hardest layer to verify yet the least likely to fail. Working through the layers from the bottom up, a case like this takes less than twenty minutes. Feel free to adapt the checklist for your own device model. If I have left anything out or made a typo, I will come back and correct it later.
Want us to set this up?
Get help with server setup, configuration, migration, or troubleshooting. Save time for the work that matters.
Hit something like this? Stuck somewhere?
Come trade notes on the LinuxThai board



Comments 0
No comments yet — be the first