Use IOL for routed labs, and choose between IOL-L2 and IOSv-L2 by the switching feature, because neither switch image passed every test. Built in my home lab on Containerlab, twelve feature labs split cleanly: IOSv-L2 learned dynamic DHCP snooping bindings, ran DAI with DHCP clients and completed wired EAP-TLS, while IOL-L2 passed IP Source Guard with a static binding, cut rogue IPv6 RAs from 15/15 to 0/15 and fed a SPAN port that Zeek could see. Use ASAv or FTDv only when the lab needs that firewall.
These results are for the exact builds below (IOL and IOL-L2 on IOS XE 17.18.2, IOSv-L2 15.2 from 2020; some people call it vIOS). A command that parses is only the start: every row below sent allowed and forbidden traffic before it counted.

IOL vs IOSv-L2: which image passed each feature test
"Verified" means the lab exercised the behavior, including its control or recovery. "Qualified" narrows what worked. Each row gives the result and its practical limit. These are results on the builds I tested, not a claim about every Cisco image, physical switch or later release.
| Feature or lab goal | Image that passed | Result and practical limit |
|---|---|---|
| Routed asymmetric-path and uRPF exercise | IOL: verified | uRPF strict vs loose: strict return-path checking lost 5/5 legitimate pings with five added RPF drops; loose restored ping and HTTP 200. allow-default was required for the tested default-route case. |
| NTP authentication and bad-key diagnosis | IOL: qualified | NTP authentication: the wrong key produced bad_auth Invalid_NAK; the control selected an authenticated peer, but the IOL NTP client never synced its local clock in the observation windows. |
| SNMPv3 authPriv, engine-ID break and repair | IOL: verified | SNMPv3 traps and informs: SHA/AES128 polling broke after the engine-ID change and recovered after the user was recreated; three authPriv traps were decrypted. Informs were not delivery-tested. |
| TACACS+ login, command authorization and local fallback | IOL: verified | TACACS+ device administration: show permit, configure deny and accounting worked; restoring local EXEC and command fallback recovered fresh SSH and console access with the server stopped. |
| Flexible NetFlow v9 export and collector-source fault | IOL: verified | Flexible NetFlow: the wrong exporter source gave zero accepted flows; the fix gave 12 flows, 1,239,588 bytes and 1,473 packets. Cache tuning commands had parser limits. |
| Dynamic DHCP snooping binding for a DAI lab | IOSv-L2: verified for learning | DAI and IP Source Guard: IOSv-L2 learned a dynamic binding; IOL-L2 delivered the lease but left its dynamic table empty. Cisco's own IOL-L2 test only checks that rogue DHCP is blocked on untrusted ports, not that the binding table fills. This is not a complete DHCP-server enforcement comparison. |
| DAI with DHCP clients and a static-host ARP ACL exception | IOSv-L2: verified; IOL-L2 also works with a static binding | Same DAI lab: eight forged replies reached the unprotected client, zero with DAI; an ARP ACL restored static-host pings 4/4. Replies carrying the real client IP and MAC from another port still passed. |
| IP Source Guard with a deliberate static source binding | IOL-L2: verified | Same DAI lab: valid UDP arrived 8/8 and spoofed UDP 0/8; IOSv-L2 blocked both the valid and the spoofed traffic, including with a static binding. The command is ip source binding, not ip source-binding. |
| IPv6 RA Guard | IOL-L2: verified | RA Guard and DHCPv6 Guard: rogue RAs went from 15/15 received to 0/15, with legitimate RAs still passing; IOSv-L2 rejected attaching the interface policy. Fragmentation and evasion were untested. |
| DHCPv6 Guard, crafted server Advertise filtering | IOL-L2: qualified | Same RA Guard lab: 15/15 control Advertises arrived and 0/15 guarded ones did; IOSv-L2 accepted the policy syntax but passed all 15. Client DNS changes and other DHCPv6 messages were not tested. |
| Private VLAN gateway access and isolated-host ping tests | IOL-L2: qualified | Private VLANs: gateway pings passed 3/3, isolated-peer pings failed, removing the mapping broke gateway access and restoring it recovered it. IOSv-L2 got a CLI probe only; some frames crossed capture points. |
| BPDU Guard trip and manual recovery | IOL-L2: verified | BPDU Guard lab: a captured rogue-bridge BPDU matched err-disabled bpduguard; manual recovery restored 5/5 pings. No physical loop or timed recovery was tested. |
| Storm-control rates, enforcement and counters | No verified choice among these builds | Storm control: IOL-L2 and IOSv-L2 both rejected the percentage, pps, bps, action and verification commands. There is no enforcement proof, and another installed image is not an automatic fallback. |
| Wired EAP-TLS and VLAN authorization | IOSv-L2: worked in this build | FreeRADIUS dynamic VLAN assignment: fresh EAP-TLS sessions completed and VLAN 20 was restored with bidirectional 3/3 forwarding. Neither run shows IOL-L2 failing every 802.1X case. |
| RADIUS-selected per-user local ACL | IOSv-L2: verified | Filter-Id = "BLOCK-WEB.in" denied TCP 80 and permitted TCP 8080 and ICMP; removing it permitted TCP 80 and restoring it blocked it again. Downloading ACL text from RADIUS was untested. |
| RADIUS server-dead critical VLAN versus explicit reject VLAN | IOSv-L2: verified | RADIUS server unreachable: stopping the server selected VLAN 50, an explicit Access-Reject selected VLAN 60 and restoration selected VLAN 20; active VLAN probes passed 3/3 in both directions. Fixed timing was not established. |
| SPAN visibility for Zeek L2 evidence | IOL-L2: verified | Without SPAN, Zeek missed six rogue DHCP replies and three false ARPs that reached the client; restoring SPAN produced two custom notices. Mirroring gave visibility, not prevention. |
| ASA firewall, NAT, IPsec and RADIUS AAA exercise | ASAv: verified | ASAv: verified for IKEv2 site-to-site and identity NAT (ASA LAN-to-LAN IKEv2), and for RADIUS authentication against FreeRADIUS, including wrong-password, wrong-NAS and wrong-secret faults (ASA AAA for VPN); AAA success there was not a data tunnel. Boot notes in ASAv in Containerlab. |
| FMC-managed Threat Defense policy and deployment | FTDv | FTDv: verified for a single FMC-managed FTD (FMC REST API deploy). My FTDv HA attempt never formed a pair, so HA is not verified. For FTD without FMC, see FTDv with local FDM. |
Cisco's CML documentation lists SPAN and Private VLANs as unsupported on IOSvL2, and lists 802.1X only as passthrough. The EAP-TLS result is what this build did, not a Cisco-supported feature. Cisco's IOL-L2 notes cover the DHCP snooping test mentioned in the table.
Two of those results, straight from the lab output. IOSv-L2 learned the DHCP client's binding dynamically:
02:00:00:44:00:10 10.144.0.100 3593 dhcp-snooping 144 GigabitEthernet0/2
Total number of bindings: 1On IOL-L2, the RA Guard capture counts (rogue RA, legitimate RA, DHCPv6 Advertise) show the receiver H1 got none of the 15 rogue RAs, while the capture on the attacker H2 shows all 15 it sent:
iol-guarded-H1.pcap | 0 | 3 | 0
iol-guarded-H2.pcap | 15 | 3 | 0Three traps when an image accepts the command
A populated status display is not a forwarding test. IOSv-L2 displayed an active IPSG address while blocking the valid control traffic. Its DHCPv6 Guard syntax also loaded without blocking the crafted messages. For protection features, send an allowed control and a forbidden test, then capture at both ends.
A working variant needs its prerequisite in the label. The IOL-L2 IPSG result used a static source binding because dynamic snooping learning failed. The IOSv-L2 ACL result selected an existing local ACL by Filter-Id; it did not download ACL text from RADIUS. The PVLAN result verified ICMP isolation, not complete frame confidentiality. Those distinctions decide whether you can repeat the exercise.
Installed is a separate claim from working. A parser accepting a command, a status display and an installed image are all separate from verified forwarding. ASAv and FTDv were present as images on the lab host, but the inventory behind this post did not log in to either appliance or test forwarding. The ASAv and FTDv rows rest on the labs they link to, and only on what those labs tested.
Exact builds tested
Tags, interface maps and boot gotchas are in Cisco images in Containerlab. The results above came from these builds:
| Name used here | Image tag | Guest version |
|---|---|---|
| IOL | vrnetlab/cisco_iol:17.18.02 |
IOS XE 17.18.2 |
| IOL-L2 | vrnetlab/cisco_iol:L2-17.18.02 |
IOS XE 17.18.2 |
| IOSv-L2 | vrnetlab/cisco_vios:L2-20200929 |
IOS 15.2(20200924:215240) |
| ASAv | vrnetlab/cisco_asav:9-24-1 |
Not checked in this inventory |
| FTDv | vrnetlab/cisco_ftdv:10.0.0 |
Not checked in this inventory |
When you document your own result, keep both the image tag and the guest version. Two images with similar names are not interchangeable proof.
How to test a feature on a new image
Pick the row that matches your experiment and reuse that lab's topology and configs. In Containerlab, IOL and IOL-L2 use Ethernet names, with eth1 mapped to Ethernet0/1; IOSv-L2 uses GigabitEthernet0/1 for eth1. Leave the management interface out of the test links, and check the real interface names before you paste configuration.
For a new comparison, keep the endpoints, addresses, traffic and success criteria the same. Record the image tag, show clock, guest version, baseline, fault, recovery and endpoint captures. A parser probe can show that syntax is missing; proving enforcement also needs traffic.
Cisco's CML FAQ lists nominal sizes of 1 vCPU and 1 GB for IOL and IOL-L2, 512 MB for IOSv, 768 MB for IOSv-L2, 2 GB for ASAv and 8 GB for FTDv. On my 63 GiB lab host, the IOL and IOSv-L2 labs in this matrix all ran beside a persistent FMCv.