[PATCH 21/21] tests: Add CIP tests
Benjamin Berg
benjamin at sipsolutions.net
Tue Oct 27 06:17:07 PDT 2026
From: Benjamin Berg <benjamin.berg at intel.com>
Add a number of tests to check CIP is working correctly. Check the key
installation explicitly using debugfs as hwsim will never actually use
the installed keys and it is not possible to indirectly verify correct
key installation.
Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
Reviewed-by: Andrei Otcheretianski <andrei.otcheretianski at intel.com>
---
tests/hwsim/test_cip.py | 243 ++++++++++++++++++++++++++++++++++
tests/hwsim/test_he_mbssid.py | 3 +-
tests/hwsim/wpasupplicant.py | 3 +-
3 files changed, 247 insertions(+), 2 deletions(-)
create mode 100644 tests/hwsim/test_cip.py
diff --git a/tests/hwsim/test_cip.py b/tests/hwsim/test_cip.py
new file mode 100644
index 0000000000..8e4b73d52f
--- /dev/null
+++ b/tests/hwsim/test_cip.py
@@ -0,0 +1,243 @@
+# CIP tests
+# Copyright (C) 2026 Intel Corporation
+#
+# This software may be distributed under the terms of the BSD license.
+# See README for more details.
+
+import binascii
+import os
+import subprocess
+import tempfile
+import time
+from functools import partial
+
+import hostapd
+from utils import *
+from hwsim import HWSimRadio
+import hwsim_utils
+from wpasupplicant import WpaSupplicant
+from test_eht import eht_mld_enable_ap, eht_verify_status
+from test_ap_pmf import mac80211_read_key
+from test_he_mbssid import mbssid_create_cfg_file, mbssid_write_bss_params
+from test_he_mbssid import mbssid_start_ap, mbssid_stop_ap
+
+
+cip_params = {
+ 'cip': '1',
+ 'wpa_pairwise': 'GCMP-256',
+ 'rsn_pairwise': 'GCMP-256',
+ 'group_cipher': 'GCMP-256',
+ 'group_mgmt_cipher': 'BIP-GMAC-256',
+ 'ieee80211w': '2',
+ 'beacon_prot': '1',
+}
+
+
+def get_mac80211_cigtks(dev):
+ phy = dev.get_driver_status_field("phyname")
+ keys = f"/sys/kernel/debug/ieee80211/{phy}/keys"
+
+ res = []
+
+ try:
+ for key in os.listdir(keys):
+ vals = mac80211_read_key(os.path.join(keys, key))
+
+ if 'link_id' not in vals or 'conf_flags' not in vals:
+ raise HwsimSkip('debugfs does not have conf_flags/link_id support')
+
+ conf_flags = int(vals.get('conf_flags'), 0)
+
+ # group key (continue if pairwise)
+ if conf_flags & (1 << 3):
+ continue
+
+ # CIP flag should be set
+ if not conf_flags & (1 << 12):
+ continue
+
+ assert int(vals.get('keyidx')) in (0, 1), 'Invalid CIGTK key index'
+ assert vals.get('algorithm') == "00-0f-ac:9", 'Invalid CIGTK algorithm'
+
+ res.append(vals)
+ except OSError:
+ raise HwsimSkip("debugfs not supported in mac80211")
+
+ return res
+
+
+def cip_verify_status(wpas, hapd, valid_links=1, sta_expect_cip=True):
+ sta = hapd.get_sta(wpas.own_addr())
+ logger.info("hostapd STA: " + str(sta))
+ if sta['addr'] == 'FAIL':
+ raise Exception("hostapd " + hapd.ifname + " did not have a STA entry for the STA " + wpas.own_addr())
+
+ if "[CIP]" not in sta['flags']:
+ if sta_expect_cip:
+ raise Exception("Missing STA flag: CIP")
+ elif not sta_expect_cip:
+ raise Exception("Erroneous STA flag: CIP")
+
+ cigtks_wpas = get_mac80211_cigtks(wpas)
+ cigtks_hapd = get_mac80211_cigtks(hapd)
+
+ cigtks_wpas = [(c['link_id'], c['keyidx'], c['key']) for c in cigtks_wpas]
+ cigtks_hapd = [(c['link_id'], c['keyidx'], c['key']) for c in cigtks_hapd]
+
+ logger.info(f"CIGTKs on supplicant: {cigtks_wpas}")
+ logger.info(f"CIGTKs on hostapd: {cigtks_hapd}")
+
+ if sta_expect_cip:
+ links = 0
+ if not cigtks_wpas or not cigtks_hapd:
+ raise Exception('Expected CIGTKs per interface')
+
+ for cigtk in cigtks_wpas:
+ assert cigtk in cigtks_hapd, 'CIGTK does not match hostapd key'
+ assert int(cigtk[0]) != -1, 'CIGTK has no proper link ID set'
+
+ links |= 1 << int(cigtk[0])
+ assert links == valid_links, f'CIGTK installed on {links}, expected {valid_links}'
+ else:
+ if cigtks_wpas:
+ raise Exception('Expected to have no CIGTK installed in supplicant')
+
+
+def _test_cip_simple(dev, apdev, disable_cip=False, mlo=False):
+ """Simple HE connection with/without CIP"""
+
+ # Currently we need MLO for CIP support in hwsim
+ with HWSimRadio(use_mlo=True) as (hapd_radio, hapd_iface), \
+ HWSimRadio(use_mlo=True) as (wpas_radio, wpas_iface):
+
+ wpas = WpaSupplicant(global_iface='/tmp/wpas-wlan5')
+ wpas.interface_add(wpas_iface)
+
+ passphrase = '12345678'
+
+ params = {
+ 'ssid': 'he',
+ 'hw_mode': 'g',
+ 'channel': '1',
+ 'ieee80211n': '1',
+ 'ieee80211ax': '1',
+ 'sae_pwe': '1',
+ 'wpa': '2',
+ 'wpa_key_mgmt': 'SAE',
+ 'sae_password': passphrase,
+ }
+ params.update(cip_params)
+
+ try:
+ if mlo:
+ hapd = eht_mld_enable_ap(hapd_iface, 0, params)
+
+ params['channel'] = '6'
+ hapd1 = eht_mld_enable_ap(hapd_iface, 1, params)
+ else:
+ hapd = hostapd.add_ap(hapd_iface, params)
+ except:
+ raise HwsimSkip('hostapd did not start, CIP likely not supported')
+
+ wpas.set('sae_pwe', '1')
+ wpas.set('sae_groups', '')
+ wpas.connect("he", key_mgmt="SAE", sae_password=passphrase,
+ pairwise='GCMP-256', group='GCMP-256', group_mgmt='BIP-GMAC-256',
+ ieee80211w='2', disable_cip='1' if disable_cip else '0')
+
+ if mlo:
+ cip_verify_status(wpas, hapd, valid_links=3, sta_expect_cip=not disable_cip)
+ eht_verify_status(wpas, hapd, 2412, 20, is_ht=True, mld=True,
+ valid_links=3, active_links=3)
+ else:
+ cip_verify_status(wpas, hapd, sta_expect_cip=not disable_cip)
+
+
+def test_cip_simple(dev, apdev):
+ """Simple HE connection with CIP"""
+ _test_cip_simple(dev, apdev)
+
+
+def test_cip_mlo_simple(dev, apdev):
+ """Simple EHT MLO connection with CIP"""
+ _test_cip_simple(dev, apdev, mlo=True)
+
+
+def test_cip_disable(dev, apdev):
+ """Simple HE connection with non-CIP client"""
+ _test_cip_simple(dev, apdev, disable_cip=True)
+
+
+def run_cip_mbssid_same_security_cigtk(params, non_tx_first=False):
+ """HE MBSSID same-security CIP with matching CIGTK"""
+ with HWSimRadio(use_mlo=True) as (hapd_radio, hapd_iface), \
+ HWSimRadio(use_mlo=True) as (wpas0_radio, wpas0_iface), \
+ HWSimRadio(use_mlo=True) as (wpas1_radio, wpas1_iface):
+
+ # Use the MLO AP device
+ mlo_apdev = [{'ifname': hapd_iface}]
+
+ wpas0 = WpaSupplicant(global_iface='/tmp/wpas-wlan5')
+ wpas0.interface_add(wpas0_iface)
+ wpas1 = WpaSupplicant(global_iface='/tmp/wpas-wlan6')
+ wpas1.interface_add(wpas1_iface)
+
+ f, fname, ifname = mbssid_create_cfg_file(mlo_apdev, params)
+ sae_cip_params = {
+ "wpa": "2",
+ "wpa_passphrase": "12345678",
+ "wpa_key_mgmt": "SAE",
+ "sae_pwe": "1",
+ }
+ sae_cip_params.update(cip_params)
+
+ for idx in range(0, 2):
+ mbssid_write_bss_params(f, ifname, idx, sae_cip_params)
+ f.close()
+
+ hapd, pid = mbssid_start_ap([wpas0], mlo_apdev, params, fname,
+ ifname, {}, only_start_ap=True)
+ hapd1 = hostapd.Hostapd(ifname + "-1")
+
+ try:
+ wpas0.set("sae_pwe", "1")
+ wpas1.set("sae_pwe", "1")
+ wpas0.set("sae_groups", "")
+ wpas1.set("sae_groups", "")
+
+ sta_params = {
+ "psk": "12345678",
+ "key_mgmt": "SAE",
+ "ieee80211w": "2",
+ "pairwise": "GCMP-256",
+ "group": "GCMP-256",
+ "scan_freq": "2412",
+ "disable_cip": "0",
+ "beacon_prot": "1",
+ }
+
+ if non_tx_first:
+ wpas1.connect("bss-1", **sta_params)
+ wpas0.connect("bss-0", **sta_params)
+ else:
+ wpas0.connect("bss-0", **sta_params)
+ wpas1.connect("bss-1", **sta_params)
+
+ cip_verify_status(wpas0, hapd)
+ cip_verify_status(wpas1, hapd1)
+ finally:
+ wpas0.request("DISCONNECT")
+ wpas1.request("DISCONNECT")
+ wpas0.set("sae_pwe", "0")
+ wpas1.set("sae_pwe", "0")
+ mbssid_stop_ap(hapd, pid)
+
+
+def test_cip_mbssid_same_security_cigtk(dev, apdev, params):
+ """HE MBSSID same-security CIP with matching CIGTK, TX first"""
+ run_cip_mbssid_same_security_cigtk(params)
+
+
+def test_cip_mbssid_same_security_cigtk_nontx_first(dev, apdev, params):
+ """HE MBSSID same-security CIP with matching CIGTK, non-TX first"""
+ run_cip_mbssid_same_security_cigtk(params, non_tx_first=True)
diff --git a/tests/hwsim/test_he_mbssid.py b/tests/hwsim/test_he_mbssid.py
index 422a39cd7c..fa5fa8a579 100644
--- a/tests/hwsim/test_he_mbssid.py
+++ b/tests/hwsim/test_he_mbssid.py
@@ -52,7 +52,8 @@ def mbssid_write_bss_params(f, ifname, idx, params=None, single_ssid=False):
# Add BSS specific characteristics
fields = ["wpa", "wpa_pairwise", "rsn_pairwise", "wpa_passphrase",
"wpa_key_mgmt", "ieee80211w", "sae_pwe", "beacon_prot",
- "sae_password"]
+ "sae_password", "group_cipher", "group_mgmt_cipher",
+ "cip"]
if idx == 0:
f.write("interface=%s\n" % ifname)
diff --git a/tests/hwsim/wpasupplicant.py b/tests/hwsim/wpasupplicant.py
index e5aeec2007..794e14778d 100644
--- a/tests/hwsim/wpasupplicant.py
+++ b/tests/hwsim/wpasupplicant.py
@@ -1153,7 +1153,8 @@ class WpaSupplicant:
"eap_over_auth_frame",
"security_profiles",
"disable_pmksa_caching",
- "disable_uhr"]
+ "disable_uhr",
+ "disable_cip"]
for field in not_quoted:
if field in kwargs and kwargs[field]:
self.set_network(id, field, kwargs[field])
--
2.56.0
More information about the Hostap
mailing list