This exporter allows a prometheus instance to scrape metrics from Cisco Catalyst 9800 Wireless Controllers.
- 🛡️ Critical State Monitoring: Detects changes such as AP mis-configurations or WLAN enable/disable
- 🌐 Client Connectivity Tracking: Monitors client signal strength, speed, protocols, traffic and latency
- 📊 Long-Term Observability: Extends metric retention for historical analysis and wireless trend tracking
- ↩️ Pull-Based Telemetry: Alternative to the Streaming Telemetry feature using RESTCONF APIs
Please enable RESTCONF and HTTPS on the C9800 before using this exporter. Please see:
Encode your controller credentials as Base64.
# username:password → Base64
echo -n "admin:your-password" | base64
# Output: YWRtaW46eW91ci1wYXNzd29yZA==export WNC_CONTROLLER="wnc1.example.internal"
export WNC_ACCESS_TOKEN="YWRtaW46eW91ci1wYXNzd29yZA=="docker run -p 10039:10039 -e WNC_CONTROLLER -e WNC_ACCESS_TOKEN \
ghcr.io/umatare5/cisco-wnc-exporter-p: Maps container port10039/tcpto host port10039/tcp.-e: Passes the environment variables into the container.
Tip
If you prefer using binaries, download them from the release page.
- Supported Platforms:
linux_amd64,linux_arm64,darwin_amd64,darwin_arm64andwindows_amd64
NAME:
cisco-wnc-exporter - Prometheus exporter for Cisco WNC
USAGE:
cisco-wnc-exporter [global options]
VERSION:
0.1.0
OPTIONS:
--dry-run Validate configuration without starting the server
--help, -h show help
--log.format string Log format (json, text) (default: "json")
--log.level string Log level (debug, info, warn, error) (default: "info")
--version, -v print the version
--web.listen-address string Address to bind the HTTP server to (default: "0.0.0.0")
--web.listen-port int Port number to bind the HTTP server to (default: 10039)
--web.telemetry-path string Path for the metrics endpoint (default: "/metrics")
--wnc.access-token string WNC API access token [$WNC_ACCESS_TOKEN]
--wnc.cache-ttl duration WNC API response cache TTL in seconds (default: 55s)
--wnc.controller string WNC controller hostname or IP address [$WNC_CONTROLLER]
--wnc.timeout duration WNC API request timeout (default: 55s)
--wnc.tls-skip-verify Skip TLS certificate verification (default: false)
# AP Collector Options
--collector.ap.errors Enable AP error metrics
--collector.ap.general Enable AP general metrics
--collector.ap.info Enable AP info metrics
--collector.ap.info-labels string Comma-separated list of AP info labels (default: "name,ip")
--collector.ap.radio Enable AP radio metrics
--collector.ap.traffic Enable AP traffic metrics
# Client Collector Options
--collector.client.errors Enable Client error metrics
--collector.client.general Enable Client general metrics
--collector.client.info Enable Client info metrics
--collector.client.info-labels string Comma-separated list of Client info labels (default: "name,ipv4")
--collector.client.radio Enable Client radio metrics
--collector.client.traffic Enable Client traffic metrics
# WLAN Collector Options
--collector.wlan.config Enable WLAN config metrics
--collector.wlan.general Enable WLAN general metrics
--collector.wlan.info Enable WLAN info metrics
--collector.wlan.info-labels string Comma-separated list of WLAN info labels (default: "name")
--collector.wlan.traffic Enable WLAN traffic metrics
* Collector Wide Options
--collector.info-cache-ttl duration Cache TTL for collector info metrics (default: 30m0s)
* Internal Collector Options
--collector.internal.go-runtime Enable Go runtime metrics collector
--collector.internal.process Enable process metrics collectorCaution
The --wnc.tls-skip-verify flag disables TLS certificate verification. This should only be used in development environments or when connecting to controllers with self-signed certificates. Never use this option in production environments as it compromises security.
This exporter supports following environment variables:
| Environment Variable | Description |
|---|---|
WNC_CONTROLLER |
WNC controller hostname or IP address (required) |
WNC_ACCESS_TOKEN |
WNC API access token (required) |
This exporter collects wireless network metrics from Cisco C9800 WNC using following collectors:
- AP Collector - For RF foundation and radio performance
- Client Collector - For user experience quality and connection performance
- WLAN Collector - For logical SSID performance and parameter checks
All collectors have multiple modules to allow fine-grained control over which metrics to collect.
Important
All collectors are disabled by default to reduce load on both Prometheus and the Cisco C9800 WNC. Because a Cisco C9800 WNC typically manages hundreds or even thousands of APs and clients, selective monitoring is essential to maintain performance and stability.
Cisco C9800 WNC caching (55 seconds)
- API responses are cached for 55 seconds
- This TTL is tuned for both Prometheus scrape intervals and AP metric reporting intervals
- You can adjust it with the
--wnc.cache-ttlflag
Prometheus info metrics caching (1800 seconds)
- Info metrics are cached for 1800 seconds (30 minutes)
- Wireless clients often roam between APs, causing the
aplabel to change frequently - You can adjust it with the
--collector.info-cache-ttlflag
AP collector focus on RF foundation and radio performance.
| Module | Metric | Type | Description |
|---|---|---|---|
| general | wnc_ap_admin_state |
Gauge | Admin state (0=disabled, 1=enabled) |
| general | wnc_ap_oper_state |
Gauge | Operational state (0=down, 1=up) |
| general | wnc_ap_radio_state |
Gauge | Radio state (0=down, 1=up) |
| general | wnc_ap_config_state |
Gauge | Tag config state (0=valid, 1=invalid) |
| general | wnc_ap_uptime_seconds |
Gauge | AP uptime in seconds |
| general | wnc_ap_cpu_utilization_percent |
Gauge | CPU utilization percentage |
| general | wnc_ap_memory_utilization_percent |
Gauge | Memory utilization percentage |
| radio | wnc_ap_channel_number |
Gauge | Operating channel number |
| radio | wnc_ap_channel_width_mhz |
Gauge | Channel bandwidth (MHz) |
| radio | wnc_ap_tx_power_dbm |
Gauge | Current transmit power (dBm) |
| radio | wnc_ap_tx_power_max_dbm |
Gauge | Maximum TX power capability (dBm) |
| radio | wnc_ap_noise_floor_dbm |
Gauge | Channel noise floor (dBm) |
| radio | wnc_ap_channel_utilization_percent |
Gauge | Channel utilization percentage (CCA) |
| radio | wnc_ap_rx_utilization_percent |
Gauge | RX utilization percentage |
| radio | wnc_ap_tx_utilization_percent |
Gauge | TX utilization percentage |
| radio | wnc_ap_noise_utilization_percent |
Gauge | Noise channel utilization percentage |
| traffic | wnc_ap_clients_total |
Gauge | Associated clients count (calclulated) |
| traffic | wnc_ap_rx_bytes_total |
Counter | Total received bytes (calculated) |
| traffic | wnc_ap_tx_bytes_total |
Counter | Total transmitted bytes (calculated) |
| traffic | wnc_ap_rx_packets_total |
Counter | Total received packets |
| traffic | wnc_ap_tx_packets_total |
Counter | Total transmitted packets |
| traffic | wnc_ap_total_tx_frames_total |
Counter | Total TX frames |
| traffic | wnc_ap_data_rx_frames_total |
Counter | Data RX frames |
| traffic | wnc_ap_data_tx_frames_total |
Counter | Data TX frames |
| traffic | wnc_ap_management_rx_frames_total |
Counter | Management RX frames |
| traffic | wnc_ap_management_tx_frames_total |
Counter | Management TX frames |
| traffic | wnc_ap_control_rx_frames_total |
Counter | Control RX frames (*1) |
| traffic | wnc_ap_control_tx_frames_total |
Counter | Control TX frames (*1) |
| traffic | wnc_ap_multicast_rx_frames_total |
Counter | Multicast RX frames (*1) |
| traffic | wnc_ap_multicast_tx_frames_total |
Counter | Multicast TX frames (*1) |
| traffic | wnc_ap_rts_success_total |
Counter | Successful RTS transmissions (*1) |
| errors | wnc_ap_tx_errors_total |
Counter | Total TX errors (*1) |
| errors | wnc_ap_rx_errors_total |
Counter | Total RX errors (*1) |
| errors | wnc_ap_tx_drops_total |
Counter | Total TX drops (calculated) |
| errors | wnc_ap_tx_retries_total |
Counter | Total TX retries (calculated) |
| errors | wnc_ap_transmission_failures_total |
Counter | Failed transmission attempts (*1) (*2) |
| errors | wnc_ap_duplicate_frames_total |
Counter | Duplicate frames received |
| errors | wnc_ap_fcs_errors_total |
Counter | Frame Check Sequence errors |
| errors | wnc_ap_fragmentation_rx_total |
Counter | RX fragmented packets (*1) |
| errors | wnc_ap_fragmentation_tx_total |
Counter | TX fragmented packets (*1) |
| errors | wnc_ap_rts_failures_total |
Counter | RTS failures (*1) |
| errors | wnc_ap_decryption_errors_total |
Counter | Decryption errors (*1) |
| errors | wnc_ap_mic_errors_total |
Counter | MIC errors (*1) |
| errors | wnc_ap_wep_undecryptable_total |
Counter | WEP undecryptable frames (*1) |
| errors | wnc_ap_coverage_hole_events_total |
Counter | Coverage hole events |
| errors | wnc_ap_last_radar_on_radio_at |
Gauge | Last radar detection unix timestamp |
| errors | wnc_ap_radio_reset_total |
Counter | Radio reset count |
*1 Metrics consistently returning zero values on Cisco IOS-XE 17.12.6a with FlexConnect AP
The following metrics consistently return zero values due to implementation limitations:
wnc_ap_(tx|rx)_errors_totalwnc_ap_control_(rx|tx)_frames_totalwnc_ap_decryption_errors_totalwnc_ap_fragmentation_(rx|tx)_totalwnc_ap_mic_errors_totalwnc_ap_multicast_(rx|tx)_frames_totalwnc_ap_rts_(success|failures)_totalwnc_ap_transmission_failures_totalwnc_ap_wep_undecryptable_total
This was verified through direct RESTCONF API access to the live WNC environment:
❯ curl -sS -k -H "Authorization: Basic $WNC_ACCESS_TOKEN" \
-H "Accept: application/yang-data+json" \
"https://$WNC_CONTROLLER/restconf/data/Cisco-IOS-XE-wireless-access-point-oper:access-point-oper-data/radio-oper-stats" \
| jq '.["Cisco-IOS-XE-wireless-access-point-oper:radio-oper-stats"]'
[
{
"ap-mac": "aa:bb:cc:11:22:30",
"slot-id": 0,
"aid-user-list": 2,
"tx-fragment-count": 0,
"multicast-tx-frame-cnt": 0,
"failed-count": 0,
"retry-count": 79738,
"multiple-retry-count": 0,
"frame-duplicate-count": 0,
"rts-success-count": 0,
"rts-failure-count": 0,
"ack-failure-count": 0,
"rx-fragment-count": 0,
"multicast-rx-frame-cnt": 0,
"fcs-error-count": 324978,
"tx-frame-count": 2457172,
"wep-undecryptable-count": 0,
"rx-error-frame-count": 0,
"mac-mic-err-frame-count": 0,
"rx-mgmt-frame-count": 2084196,
"rx-ctrl-frame-count": 0,
"rx-data-frame-count": 3121831,
"tx-mgmt-frame-count": 2345422,
"tx-ctrl-frame-count": 0,
"tx-data-frame-count": 2457172,
"rx-data-pkt-count": 0,
"tx-data-pkt-count": 0,
"noise-floor": 0,
"ap-radio-stats": {
"stuck-ts": "1970-01-01T00:00:00+00:00",
"last-ts": "2025-09-23T10:25:19+00:00",
"num-radio-stuck-reset": 0
},
"mac-decry-err-frame-count": 0
},
{
"ap-mac": "aa:bb:cc:11:22:30",
"slot-id": 1,
"aid-user-list": 3,
"tx-fragment-count": 0,
"multicast-tx-frame-cnt": 0,
"failed-count": 0,
"retry-count": 216205,
"multiple-retry-count": 0,
"frame-duplicate-count": 0,
"rts-success-count": 0,
"rts-failure-count": 0,
"ack-failure-count": 0,
"rx-fragment-count": 0,
"multicast-rx-frame-cnt": 0,
"fcs-error-count": 15398676,
"tx-frame-count": 17767278,
"wep-undecryptable-count": 0,
"rx-error-frame-count": 0,
"mac-mic-err-frame-count": 0,
"rx-mgmt-frame-count": 19155,
"rx-ctrl-frame-count": 0,
"rx-data-frame-count": 6552508,
"tx-mgmt-frame-count": 4628753,
"tx-ctrl-frame-count": 0,
"tx-data-frame-count": 17767278,
"rx-data-pkt-count": 0,
"tx-data-pkt-count": 0,
"noise-floor": 0,
"ap-radio-stats": {
"stuck-ts": "1970-01-01T00:00:00+00:00",
"last-ts": "2025-09-23T10:25:19+00:00",
"num-radio-stuck-reset": 0
},
"mac-decry-err-frame-count": 0
},
<snip>
]*2 Cisco Bug CSCwn96363 - AckFailureCount vs FailedCount
According to Cisco Bug CSCwn96363, there are redundant counters in the wireless statistics:
- Issue:
AckFailureCountalways returns 0 and does not increment - Root Cause:
AckFailureCountandFailedCountrepresent the same counter - Solution: Use
FailedCountinstead ofAckFailureCountfor accurate transmission failure statistics
This exporter implements the recommended workaround by using failed-count from the RESTCONF API for the wnc_ap_transmission_failures_total metric.
Tip
info module provides wnc_ap_info contains following labels to join with other metrics:
| Labels | Description | Example Value | Default | Required |
|---|---|---|---|---|
mac |
AP wireless MAC address | aa:bb:cc:dd:ee:f0 |
Yes | Yes |
name |
AP hostname | TEST-AP01 |
Yes | No |
ip |
AP IP address | 192.168.1.10 |
Yes | No |
radio |
Radio identifier | 0, 1, 2 |
Yes | Yes |
band |
Radio band | 2.4, 5, 6 |
No | No |
model |
AP model | AIR-AP1815I-Q-K9 |
No | No |
serial |
AP serial number | FGL1234ABCD |
No | No |
sw_version |
Software version | 17.12.5.41 |
No | No |
eth_mac |
Ethernet MAC address | aa:bb:cc:00:11:22 |
No | No |
Use this info metric to add contextual labels to other metrics in PromQL queries:
wnc_ap_radio_admin_state * on(mac,radio) group_left(name,ip) wnc_ap_infoClient collector focus on user experience quality and connection performance.
| Module | Metric | Type | Description |
|---|---|---|---|
| general | wnc_client_state |
Gauge | Client state (0-2) |
| general | wnc_client_state_transition_seconds |
Gauge | State transition latency |
| general | wnc_client_power_save_state |
Gauge | Power save state (0=active, 1=save) |
| radio | wnc_client_protocol |
Gauge | 802.11 protocol (5=ac, 6=ax) |
| general | wnc_client_uptime_seconds |
Gauge | Connection duration |
| radio | wnc_client_mcs_index |
Gauge | MCS index (-1=legacy, 0-11) |
| radio | wnc_client_spatial_streams |
Gauge | Spatial streams count |
| radio | wnc_client_speed_mbps |
Gauge | Connection throughput |
| radio | wnc_client_rssi_dbm |
Gauge | Signal strength (dBm) |
| radio | wnc_client_snr_decibels |
Gauge | Signal-to-noise ratio (dB) |
| traffic | wnc_client_rx_bytes_total |
Counter | Received bytes |
| traffic | wnc_client_tx_bytes_total |
Counter | Transmitted bytes |
| traffic | wnc_client_rx_packets_total |
Counter | Received packets |
| traffic | wnc_client_tx_packets_total |
Counter | Transmitted packets |
| errors | wnc_client_retry_ratio_percent |
Gauge | Retry rate percentage |
| errors | wnc_client_tx_retries_total |
Counter | TX retries count (*3) |
| errors | wnc_client_data_retries_total |
Counter | Data retries by mobile station |
| errors | wnc_client_excessive_retries_total |
Counter | Excessive retries count (*3) |
| errors | wnc_client_rts_retries_total |
Counter | RTS retries count (*3) |
| errors | wnc_client_duplicate_received_total |
Counter | Duplicate packets received (*3) |
| errors | wnc_client_tx_drops_total |
Counter | TX drops count |
| errors | wnc_client_decryption_failed_total |
Counter | Decryption failures |
| errors | wnc_client_mic_mismatch_total |
Counter | MIC mismatch errors (*3) |
| errors | wnc_client_mic_missing_total |
Counter | MIC missing errors (*3) |
| errors | wnc_client_policy_errors_total |
Counter | Policy errors (*3) |
| errors | wnc_client_rx_group_counter_total |
Counter | RX group counter (*3) |
*3 Client error metrics consistently returning zero values on Cisco IOS-XE 17.12.6a with FlexConnect AP
The following client error metrics consistently return zero values due to implementation limitations:
wnc_client_duplicate_received_totalwnc_client_excessive_retries_totalwnc_client_mic_mismatch_totalwnc_client_mic_missing_totalwnc_client_policy_errors_totalwnc_client_rts_retries_totalwnc_client_rx_group_counter_totalwnc_client_tx_retries_total
This was verified through direct RESTCONF API access to the live WNC environment:
❯ curl -sS -k -H "Authorization: Basic $WNC_ACCESS_TOKEN" \
-H "Accept: application/yang-data+json" \
"https://$WNC_CONTROLLER/restconf/data/Cisco-IOS-XE-wireless-client-oper:client-oper-data/traffic-stats" \
| jq '.["Cisco-IOS-XE-wireless-client-oper:traffic-stats"][0]' | \
jq '{duplicate_rcv, tx_excessive_retries, mic_mismatch, mic_missing, policy_errs, rts_retries, rx_group_counter, tx_retries}'
{
"duplicate_rcv": "0",
"tx_excessive_retries": "0",
"mic_mismatch": "0",
"mic_missing": "0",
"policy_errs": "0",
"rts_retries": "0",
"rx_group_counter": "0",
"tx_retries": "0"
}Tip
info module provides wnc_client_info contains following labels to join with other metrics:
| Labels | Description | Example Value | Default | Required |
|---|---|---|---|---|
mac |
MAC address | aa:bb:cc:12:34:56 |
Yes | Yes |
ap |
Access point identifier | TEST-AP01 |
No | No |
band |
Radio band | 2.4, 5, 6 |
No | No |
wlan |
WLAN ESSID name | labo-wifi |
No | No |
name |
Device Classification Name | MacBook Pro (14-inch, 2021) |
Yes | No |
username |
EAP authentication identity | [email protected] |
No | No |
ipv4 |
Client IPv4 address | 192.168.1.100 |
Yes | No |
ipv6 |
Client IPv6 address | 2001:db8::1 |
No | No |
Use this info metric to add contextual labels to other metrics in PromQL queries:
wnc_client_state * on(mac) group_left(ap,wlan,name) wnc_client_infoWLAN collector focus on logical SSID performance and parameter checks.
| Module | Metric | Type | Description |
|---|---|---|---|
| general | wnc_wlan_enabled |
Gauge | WLAN status |
| traffic | wnc_wlan_clients_total |
Gauge | Connected clients count (calclulated) |
| traffic | wnc_wlan_rx_bytes_total |
Counter | WLAN received bytes (calclulated) |
| traffic | wnc_wlan_tx_bytes_total |
Counter | WLAN transmitted bytes (calclulated) |
| config | wnc_wlan_auth_psk_enabled |
Gauge | PSK authentication enabled |
| config | wnc_wlan_auth_dot1x_enabled |
Gauge | 802.1x authentication enabled |
| config | wnc_wlan_auth_dot1x_sha256_enabled |
Gauge | 802.1x SHA256 auth enabled |
| config | wnc_wlan_wpa2_enabled |
Gauge | WPA2 support enabled |
| config | wnc_wlan_wpa3_enabled |
Gauge | WPA3 support enabled |
| config | wnc_wlan_session_timeout_seconds |
Gauge | Session timeout duration |
| config | wnc_wlan_load_balance_enabled |
Gauge | Load balancing enabled |
| config | wnc_wlan_11k_neighbor_list_enabled |
Gauge | 802.11k neighbor list enabled |
| config | wnc_wlan_client_steering_enabled |
Gauge | 6GHz client steering enabled |
| config | wnc_wlan_central_switching_enabled |
Gauge | Central switching enabled |
| config | wnc_wlan_central_authentication_enabled |
Gauge | Central authentication enabled |
| config | wnc_wlan_central_dhcp_enabled |
Gauge | Central DHCP enabled |
| config | wnc_wlan_central_association_enabled |
Gauge | Central association enabled |
Tip
info module provides wnc_wlan_info contains following labels to join with other metrics:
| Labels | Description | Example Value | Default | Required |
|---|---|---|---|---|
id |
WLAN identifier | 1, 5, 10 |
Yes | Yes |
name |
WLAN ESSID name | labo-wifi |
Yes | No |
Use this info metric to add contextual labels to other metrics in PromQL queries:
wnc_wlan_enabled * on(id) group_left(name) wnc_wlan_infoThere are multiple ways to run the exporter, including direct binary execution and Docker containerization.
Visit http://localhost:10039/ to verify the exporter is running.
The exporter starts without any collectors enabled by default:
$ WNC_CONTROLLER="wnc1.example.internal"
$ WNC_ACCESS_TOKEN="foobarbaz"
$ ./cisco-wnc-exporter
time="2025-04-13T18:50:54Z" level=info msg="Starting the cisco-wnc-exporter on port 10039."Enable essential collectors for basic monitoring:
$ WNC_CONTROLLER="wnc1.example.internal"
$ WNC_ACCESS_TOKEN="foobarbaz"
$ ./cisco-wnc-exporter \
--collector.ap.general --collector.client.general --collector.wlan.generalFor complete monitoring, see .air.toml which enables all collectors with maximum info-labels.
This section describes how to configure Prometheus to scrape metrics from the controld-exporter.
- Add the job config to your Prometheus YAML file using examples/prometheus.yml as a reference.
The following make commands are available for development and testing:
| Command | Description |
|---|---|
make help |
Display available targets and requirements |
make build |
Build the binary to ./tmp/cisco-wnc-exporter |
make lint |
Run golangci-lint and tidy go.mod |
make test-unit |
Run unit tests with coverage using gotestsum |
make test-unit-coverage |
Generate HTML coverage report |
make clean |
Remove build artifacts and backup files |
make image |
Build Docker image |
The repository includes a ready to use Dockerfile. To build a new Docker image:
make imageThis creates an image named ghcr.io/$USER/cisco-wnc-exporter and exposes 10039/tcp.
To release a new version, follow these steps:
- Update the version in the
VERSIONfile. - Submit a pull request with the updated
VERSIONfile.
Once the pull request is merged, the GitHub Workflow automatically creates and pushes a new tag, after which I manually publish a release using the GitHub Actions release workflow.
- Fork (https://github.com/umatare5/cisco-wnc-exporter/fork)
- Create a feature branch
- Commit your changes
- Rebase your local changes against the master branch
- Create a new Pull Request
I launched this project with the help of GitHub Copilot Coding Agent, and I am grateful to the global developer community for their contributions to open source projects and public repositories.