Collect from a wireless controller (WLC)
When you need this
Section titled “When you need this”- “A laptop shows up in switch CAM on the AP’s uplink trunk — but which radio, on which AP, is it actually associated to?”
- “We have a mix of new Catalyst 9800s and older 5508s. I want wireless visibility from both without running two different tools.”
- “Security wants every rogue AP the controller hears pinned to a switch port.”
A switch only ever sees an access point’s wired MAC on the uplink; it has no idea which client sits behind which radio, and a client roaming between APs is a sub-second L2 move a 5-minute polling fabric misses entirely. The controller is the only place that truth lives. l2trace’s wireless collector binds to one controller, registers every AP as a child device (by its wired ethernet MAC — the identity the upstream switch sees in CAM), and emits each client association on its AP’s radio port. The result flows into the exact same bitemporal CAM path as every other MAC observation.
Two transports, one source
Section titled “Two transports, one source”Both controller families use the single wireless collector source — one
device_collector row per controller. The wire protocol is chosen per device
with extras.transport:
transport | Controller | Protocol |
|---|---|---|
restconf (default) | Catalyst 9800 (IOS-XE) | RESTCONF / JSON over HTTPS |
airespace-snmp | AireOS (5508 / 8540 / 2504 / vWLC) | AIRESPACE-WIRELESS-MIB over SNMPv2c |
Aliases aireos-snmp, airespace, and snmp also select the SNMP path.
Anything else (or nothing) is the RESTCONF path, so existing 9800 rows need
no change.
What each poll collects
Section titled “What each poll collects”Both transports emit the same three things, so the reconciler and every downstream view treat a wireless client identically regardless of controller:
- One
CAM_SNAPSHOTper AP — the AP’s currently-associated clients (MAC + VLAN) on its radio port. Snapshot semantics close a client that roamed away, so a roam from AP1 to AP2 shows as a close-here / learn-there across two AP devices. - One
ARP_SNAPSHOTon the controller — every client’s IP↔MAC binding, the wifi analogue of a router ARP cache. - One
ROGUE_AP_SNAPSHOTon the controller (opt-in, default on) — the rogues it heard over the air, each classified and flagged on-wire, for the find-a-rogue-ap correlation.
The VLAN problem, and the SNMP cross-fill
Section titled “The VLAN problem, and the SNMP cross-fill”On IOS-XE 17.12 the RESTCONF path can’t read a wireless client’s VLAN from
any oper leaf — it derives the VLAN from the client’s IP via a subnet→VLAN
map in extras, and a client whose IP doesn’t match the map is dropped from
CAM. In practice that map is rarely complete: on one production 9800 the
RESTCONF path was placing only ~44% of associated clients, dropping the other
~56% for no resolvable VLAN.
AireOS reports the client VLAN directly (bsnMobileStationVlanId), with
no map and no guessing — validated against a live controller at 415/415
clients placed. A Catalyst 9800 serves that same AIRESPACE VLAN column over
SNMP for backward compat, so the RESTCONF collector can cross-fill it:
set an SNMP community in auth and each poll does one cheap extra walk of
just that column, supplying the VLAN for exactly the clients the subnet map
misses. It’s fallback-only — a client the map already resolves keeps its
mapped VLAN, so existing placements never churn — and best-effort, so a failed
SNMP walk degrades to subnet-map-only rather than losing the poll. This keeps
the 9800 on its richer RESTCONF rogue-oper while closing the client-VLAN gap.
Opt out with extras.vlan_from_snmp = false; with no community the
cross-fill is simply inert (existing rows are unchanged).
Configuration
Section titled “Configuration”Catalyst 9800 (RESTCONF — the default)
Section titled “Catalyst 9800 (RESTCONF — the default)”from l2trace.collectors.base import CollectorConfigfrom l2trace.events.schema import Source
cfg = CollectorConfig( device_id=42, hostname="wlc-9800-1", mgmt_ip="10.0.0.9", source=Source.WIRELESS, auth={ "username": "l2trace-ro", "password": "...", # or {"token": "..."} "community": "your-read-community", # enables the SNMP VLAN cross-fill }, extras={ # transport defaults to "restconf" — no need to set it. "subnet_vlan_map": [ # primary VLAN source (best-effort) {"cidr": "10.20.0.0/16", "vlan": 20}, {"cidr": "10.30.0.0/16", "vlan": 30}, ], # "vlan_from_snmp": True is the default when a community is present; # set False to disable the cross-fill and use the map alone. },)With a community set, the map can even be left empty — the SNMP cross-fill
supplies every client’s VLAN directly. Keep the map for controllers you can’t
give SNMP access to.
AireOS (AIRESPACE-SNMP)
Section titled “AireOS (AIRESPACE-SNMP)”cfg = CollectorConfig( device_id=43, hostname="wlc-5508-1", mgmt_ip="10.0.0.8", source=Source.WIRELESS, auth={"community": "your-read-community"}, extras={ "transport": "airespace-snmp", "snmp_port": 161, "snmp_timeout_seconds": 5.0, "poll_interval_seconds": 120, # SNMP walks a big controller heavily "rogue_detection": True, # default; set false to skip the rogue walk },)In production these come from a device_collector row (with auth values as
env:// / file:// secret URIs), spawned by the orchestrator — you don’t
construct the collector by hand. The SNMP path polls on a gentler default
cadence (120s) than RESTCONF (60s) because a full AIRESPACE walk of a
controller with hundreds of APs and thousands of clients is heavy.
What the SNMP path walks
Section titled “What the SNMP path walks”The AireOS collector GETBULK-walks a targeted set of AIRESPACE-WIRELESS-MIB
columns (Cisco PEN 1.3.6.1.4.1.14179), not whole tables:
| Table | Purpose |
|---|---|
bsnAPTable (…2.2.1.1) | Managed-AP inventory; row index is the AP’s wired ethernet MAC |
bsnMobileStationTable (…2.1.4.1) | Associated clients: AP MAC (the wired-MAC join key), VLAN, SSID, IP |
bsnRogueAPTable (…2.1.7.1) + sub-table | Detected rogues, on-network flag, RSSI/channel |
The client’s AP MAC is the same wired ethernet MAC that indexes bsnAPTable
and appears in the switch CAM, so one identity threads the whole chain:
client → AP-eth-MAC → registered AP device → wired switch port.
Caveat: rogue layout differs on the 9800
Section titled “Caveat: rogue layout differs on the 9800”A Catalyst 9800 also exposes a backward-compat AIRESPACE tree over SNMP, but
its rogue table reorders columns versus classic AireOS (RSSI/channel move
into the main table, dates become strings). The SNMP rogue parser targets the
classic AireOS layout, so keep 9800s on the restconf transport — its native
rogue oper data is richer anyway. The client and AP tables are consistent
across both.
See also
Section titled “See also”- The collectors:
src/l2trace/collectors/wlc.py(RESTCONF) andsrc/l2trace/collectors/wlc_snmp.py(AireOS SNMP) - Find a rogue AP’s switch port — the security
view fed by the
ROGUE_AP_SNAPSHOT - Collect from an SNMP-only device — the switch CAM collector the AP wired MAC is correlated against