Skip to main content

cw917x on catalyst hardware

··9 mins·
xeylou
Author
xeylou
Table of Contents
booting a cw917x cisco ap on catalyst software && onrolling it on a c9k8 wlc

the cw917x ap series came out end 2k24 w/ wi-fi 7 + a global-use “day 0” meraki “guap” algorithm to configure the ap either on on meraki || catalyst internal software at first boot

for this cw917x guap global use ap onboarding software, cisco has add an extension to the dhcp 43 option (the f3) && remove the ability to instruct commands via cli (only the offline-migration-info command is available)

the c9k8 must run ios-xe 17.15.2 or higher. licensing moved to the “cisco networking subscription” (wireless essentials && advantage, not the “dna” ones anymore). c9k8 needs capwap-discovery onboarding all command on the ap join profile to accept broadcast discovery on the same subnet

single global sku is not baked-in regulatory domain. country code needs to be resolved at runtime (gps, proximity ap that has one, migration or raf), else radios are down w/ -UN country code

it took me days to troubleshoot && add a c9176i ap to a c9k8 wlc, taking wireshark traces, reading docs that misses some parts (and is mostly meraki oriented). so i decided to post here my findings, troubleshoot && how i made a lab running on my laptop

the setup
#

here are the elements of my lab, i ran the vms on my laptop (ryzen 7 7840hs, 32gb, debian 13)

i used a meraki ms130-12x for a l2 sw w/ poe capability

i used my room wi-fi only because i only have one rj45 nic (enxf4a80d5addf0), any default gw will do

%%{init: {'theme':'dark'}}%%
graph TD
internet(("the global cat videos kingdom (internet)"))
subgraph laptop
wifi(wlan
personal wlan, cause i only have 1 rj45 nic) virbr0(virbr0 libvirt nat
192.168.122.1/24) br0(br0
10.0.0.2/24) win["win server 2k25
(dhcp + dns)
10.0.0.5"] wlc[c9k8-cl
gi1 -> no use
gi2 -> svi vlan10 10.0.0.10
] end sw("ms130-12x
no config, for poe only") ap[cw9176i
10.0.0.101
degraded @ 30w
] clients[wi-fi clients
dhcp 10.0.0.100-200] internet --- wifi virbr0 -. nat masquerade .- wifi wlc ---|virtio| virbr0 wlc ---|virtio| br0 win ---|virtio| br0 br0 ---|enxf4a80d5addf0 untagged| sw sw ---|mgig 2.5g poe+ 30w| ap ap .- clients ap -. capwap dtls on udp 5246/5247 .- wlc

here are the commands i used to setup my lab

checking kvm acceleration

egrep -c '(vmx|svm)' /proc/cpuinfo      # 0 == unsupported, >=1 == supported (enabled in bios)
kvm-ok                                  # expect "KVM acceleration can be used" (package cpu-checker)
ls -l /dev/kvm                          # device must exist
lsmod | grep kvm                        # kvm_intel or kvm_amd loaded
systemctl status libvirtd               # active (running)
virsh -c qemu:///system version

packages i used specifically

you can the add the genisoimage package if you want to boostrap the iosxe_config.txt file into the wlc

apt install -y --no-install-recommends \
  qemu-system-x86 qemu-utils libvirt-daemon-system \
  libvirt-daemon-config-network dnsmasq-base libvirt-clients \
  virtinst ovmf virt-viewer swtpm-tools

add yourself to the appropriate groups to avoid using root for the actions regarding the vms

usermod -aG libvirt,kvm <your_user>
# log out && back in, then
groups  # should list libvirt + kvm

to list the vms + see their states, you can use the
virsh list --all command

you can load the vhost_net (virtio network accelerator) kernel module w/ out restarting like this + enable it at start

modprobe vhost_net && echo vhost_net | tee /etc/modules-load.d/vhost_net.conf

bridge br0 creation

nmcli connection add type bridge ifname br0 con-name br0
nmcli connection modify br0 ipv4.method manual ipv4.addresses 10.0.0.2/24
nmcli connection modify br0 bridge.stp no

attaching the nic as a bridge port to the rj45 nic + activating it

nmcli connection add type ethernet ifname enxf4a80d5addf0 master br0 con-name br0-port
nmcli connection up br0
nmcli connection up br0-port

look if br0 has the associated ip using ip addr show br0 && if your nic is attached to br0 using bridge link

you can download the latest virtio here && the c9k8 virtual iso here

i used a windows server for the dhcp server because various docs from cisco was using it && showing configuration pictures

virt-install \
  --name srv2k25 \
  --os-variant win2k22 \
  --cpu host-passthrough \
  --vcpus 4 \
  --memory 8192 \
  --disk path=/var/lib/libvirt/images/srv2k25.qcow2,size=64,bus=virtio,format=qcow2,cache=none,io=native,discard=unmap \
  --disk path=<your_windows_srv_install_iso_here>,device=cdrom,bus=sata \
  --disk path=<your_virtiowin_iso_here>,device=cdrom,bus=sata \
  --network bridge=br0,model=virtio \
  --graphics vnc \
  --video virtio \
  --tpm backend.type=emulator,backend.version=2.0,model=tpm-crb \
  --boot uefi
virt-viewer --connect qemu:///system --wait srv2k25

i installed windows, virtiotools, setup ip, enabled dhcp (Install-WindowsFeature DHCP -IncludeManagementTools), config via dhcpmgmt.msc -> adding a scope && add options

going through the options later

for the access to the kingdom of cat pictures for the c9k8

virsh net-define /usr/share/libvirt/networks/default.xml
virsh net-autostart default
virsh net-start default

&& the installation of the c9k8 vm

virt-install \
  --name c9k8-cl \
  --virt-type kvm \
  --os-variant rhel8.0 \
  --cpu host-passthrough \
  --vcpus 4 \
  --memory 8192 \
  --import \
  --disk path=/<your_c9k8_qcow2_image_here>,bus=virtio,format=qcow2,cache=none,io=native \
  --network network=default,model=virtio \
  --network bridge=br0,model=virtio \
  --rng /dev/urandom,model=virtio \
  --graphics vnc \
  --console pty,target_type=serial \
  --noautoconsole
click to see my wlc config
en
conf t
hostname c9k8-xeylou
!
aaa new-model
aaa authentication login default local
aaa authorization exec default local
aaa authentication login CONSOLE none
aaa authorization exec CONSOLE none
username admin privilege 15 secret Cisco123!
enable secret Cisco123!
ip domain name xeyloulab.local
service password-encryption
clock timezone CET 1 0
clock summer-time CEST recurring last Sun Mar 2:00 last Sun Oct 3:00
vlan 10
 name WLAB
vlan 999
 name CAT_KINGDOM
!
interface GigabitEthernet1
 description nat-virbr0
 switchport mode access
 switchport access vlan 999
 no shutdown
!
interface GigabitEthernet2
 description LAB
 switchport mode access
 switchport access vlan 10
 spanning-tree portfast
 no shutdown
exit
!
interface Vlan10
 description Wireless-Management
 ip address 10.0.0.10 255.255.255.0
 no shutdown
exit
interface Vlan999
  description CAT_+_access
  ip address 192.168.122.10 255.255.255.0
  no shutdown
exit
ip route 0.0.0.0 0.0.0.0 192.168.122.1
ip name-server 8.8.8.8
!
ip http server
ip http secure-server
ip http authentication local

wireless management interface Vlan10
wireless country FR

crypto key generate rsa modulus 2048
wireless config vwlc-ssc key-size 2048 signature-algo sha256 password 0 Soleil123

capwap-discovery onboarding all

!
line vty 0 4
 login authentication default
 transport input ssh
end
wr

option 43
#

option 43 vendor-specific information is an opaque byte string, it stores && ships. the parsing happens on the client : the option carries a sequence of tlv blocks (type, length, value), each ap walks the sequence, consumes the tlv it knows, ignores the rest

f1 classic capwap discovery
#

historical “241” (hexa f1), read by every ap (including the c91xx) && by the “standard” offline-migration path of global-use ap

its organization:

fieldsizevalue
type1 bytef1 (241)
length1 byten * 4 - n = number of wlcs (04 1 wlc, for 2 wlcs length become 08)
valuen * 4 byteswlc ipv4s, raw hex

e.g. for the wlc 10.0.0.10
(10 -> 0a, 0 -> 00, 0 -> 00, 10 -> 0a)

it would be : f1 (option) + 04 (length) + 0a 00 00 0a (ip in hex) => f1 04 0a 00 00 0a

for 2 wlcs, relace the length by 08 && add the 2nd wlc ipv4 address in hex after the other one

the ap adds these ips to its capwap discovery list && unicasts discovery requests to them, starting w/ the 1st ip

f3 fast offline migration
#

introduced w/ the cw917x guap firmware, same idea but extra byte

fieldsizevalue
type1 bytef3 (243)
length1 byten * 4 + 1 (bc of the mode byte)
valuen * 4 byteswlc ipv4s
mode1 byte01 == meraki, 02 == catalyst

for the same 10.0.0.10 wlc, it would become :
f3 (option) + 05 (length) + 0a 00 00 0a (ip in hex) + 02 (mode) => f3 05 0a 00 00 0a 02

the f3 is processed right at dhcp time in the day 0 state, it skips the 8 mn offline wait. the ap validates the wlc target by a ping (if the ap had a proper ip, gw even if same network, fw OK…), then flips persona && reboots on the mode (02 catalyst or 01 meraki)

if you do not use f3, it would try to reach 8.8.8.8 or 1.1.1.1 confirm the access to the meraki cloud later, all the time and spams it. so it would first join the meraki cloud if you have f1 option configured, even though you have configured the f1 option properly. whereas for catalyst wlc, you have to wait a full 8 mn, && if it fails, wait another 8 mn for it to retry

worth repeating : f3 only gets a chance if the state machine reaches it. no gateway in the scope (option 3) -> uplink validation fails (blink red twices) -> the ap loops forever without ever parsing f1 nor f3. ask me how i know (my take, bc it could never ever reach the meraki cloud, it refuses to read the f1/f3 either…)

(it stops completely if no gw, even if in the same network)

concatenation
#

both tlvs can live happily inside the same option 43

for our f1 && f3 examples, we could type

f1 04 0a 00 00 0a f3 05 0a 00 00 0a 02

&& both c91xx + cw917x aps could be on the same network

bc each ap would read itw own tlv && skips the unknown one, could also filter on the vendor class (option 60) in the dhcp discover, && the server would answer a different 43 option based on “Cisco Wireless AP” for cw917x or “Cisco AP” for the c91xx family. it is a cleaner solution, for a lab i choose the concatenation

feeding the win dhcp server
#

doing it by powershell, bc the gui binary editor once shipped 5e 00 on the wire instead of my string, sry windows but wireshark don’t lie

so it would be :

Set-DhcpServerv4OptionValue -ScopeId 10.0.0.0 -OptionId 43 `
    -Value 0xF1,0x04,0x0A,0x00,0x00,0x0A,0xF3,0x05,0x0A,0x00,0x00,0x0A,0x02

on infoblox, two clean ways:

  • raw vendor-encapsulated-options (43), type string, colon-separated hex, no quotes — quotes = ascii = the same trap again : f1:04:0a:00:00:0a:f3:05:0a:00:00:0a:02
  • typed a custom option space w/ sub-option 241 as array of ip-address (nios computes type + length for you, multi-wlc friendly) && sub-option 243 as string w/ payload 0a:00:00:0a:02 only. the trailing mode byte breaks the ip-array typing, so f3 stays hex. never both raw && option-space on the same object : encapsulation conflict

my only trust source was wireshark, where you should see on the offer + the ack:

Option: (43) Vendor-Specific Information
    Length: 13
    Value: f1040a00000af3050a00000a02