From ea1d387262918062273267382b2ea0a4c98abb1c Mon Sep 17 00:00:00 2001 From: Aleks Man Date: Wed, 15 Jul 2026 01:48:47 +0300 Subject: [PATCH 1/3] network: expose RSN capabilities on BSS Network.Type intentionally groups all personal networks as "psk". Consumers therefore cannot distinguish PSK, SAE, and transition BSSes even though scan results retain the RSN IE. Add an optional RSN dictionary to BasicServiceSet with the advertised key management suites and pairwise and group ciphers. Use the same field and value names as wpa_supplicant's BSS.RSN property so consumers can share parsing logic. Parse bss->rsne directly so legacy WPA capabilities are not exposed as RSN. --- src/network.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/src/network.c b/src/network.c index a5a2375a..aa9e1281 100644 --- a/src/network.c +++ b/src/network.c @@ -2198,10 +2198,120 @@ static bool network_bss_property_get_address(struct l_dbus *dbus, return true; } +struct network_bss_suite_name { + uint32_t suite; + const char *name; +}; + +static const struct network_bss_suite_name network_bss_akm_names[] = { + { IE_RSN_AKM_SUITE_PSK, "wpa-psk" }, + { IE_RSN_AKM_SUITE_FT_USING_PSK, "wpa-ft-psk" }, + { IE_RSN_AKM_SUITE_PSK_SHA256, "wpa-psk-sha256" }, + { IE_RSN_AKM_SUITE_8021X, "wpa-eap" }, + { IE_RSN_AKM_SUITE_FT_OVER_8021X, "wpa-ft-eap" }, + { IE_RSN_AKM_SUITE_8021X_SHA256, "wpa-eap-sha256" }, + { IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA256, "wpa-eap-suite-b" }, + { IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA384, "wpa-eap-suite-b-192" }, + { IE_RSN_AKM_SUITE_FT_OVER_8021X_SHA384, "wpa-ft-eap-sha384" }, + { IE_RSN_AKM_SUITE_FILS_SHA256, "wpa-fils-sha256" }, + { IE_RSN_AKM_SUITE_FILS_SHA384, "wpa-fils-sha384" }, + { IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256, "wpa-ft-fils-sha256" }, + { IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA384, "wpa-ft-fils-sha384" }, + { IE_RSN_AKM_SUITE_SAE_SHA256, "sae" }, + { IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256, "ft-sae" }, + { IE_RSN_AKM_SUITE_OWE, "owe" }, +}; + +static const struct network_bss_suite_name network_bss_pairwise_names[] = { + { IE_RSN_CIPHER_SUITE_TKIP, "tkip" }, + { IE_RSN_CIPHER_SUITE_CCMP, "ccmp" }, + { IE_RSN_CIPHER_SUITE_GCMP, "gcmp" }, + { IE_RSN_CIPHER_SUITE_CCMP_256, "ccmp-256" }, + { IE_RSN_CIPHER_SUITE_GCMP_256, "gcmp-256" }, +}; + +static const struct network_bss_suite_name network_bss_group_names[] = { + { IE_RSN_CIPHER_SUITE_WEP40, "wep40" }, + { IE_RSN_CIPHER_SUITE_WEP104, "wep104" }, + { IE_RSN_CIPHER_SUITE_TKIP, "tkip" }, + { IE_RSN_CIPHER_SUITE_CCMP, "ccmp" }, + { IE_RSN_CIPHER_SUITE_GCMP, "gcmp" }, + { IE_RSN_CIPHER_SUITE_CCMP_256, "ccmp-256" }, + { IE_RSN_CIPHER_SUITE_GCMP_256, "gcmp-256" }, +}; + +static void network_bss_append_suite_names( + struct l_dbus_message_builder *builder, + const char *name, uint32_t suites, + const struct network_bss_suite_name *names, + size_t names_len) +{ + size_t i; + + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_append_basic(builder, 's', name); + l_dbus_message_builder_enter_variant(builder, "as"); + l_dbus_message_builder_enter_array(builder, "s"); + + for (i = 0; i < names_len; i++) + if (suites & names[i].suite) + l_dbus_message_builder_append_basic(builder, 's', + names[i].name); + + l_dbus_message_builder_leave_array(builder); + l_dbus_message_builder_leave_variant(builder); + l_dbus_message_builder_leave_dict(builder); +} + +static const char *network_bss_group_cipher_name( + enum ie_rsn_cipher_suite cipher) +{ + size_t i; + + for (i = 0; i < L_ARRAY_SIZE(network_bss_group_names); i++) + if (network_bss_group_names[i].suite == cipher) + return network_bss_group_names[i].name; + + return ""; +} + +static bool network_bss_property_get_rsn(struct l_dbus *dbus, + struct l_dbus_message *message, + struct l_dbus_message_builder *builder, + void *user_data) +{ + struct scan_bss *bss = user_data; + struct ie_rsn_info info; + const char *group; + + if (!bss->rsne || ie_parse_rsne_from_data(bss->rsne, bss->rsne[1] + 2, + &info) < 0) + return false; + + l_dbus_message_builder_enter_array(builder, "{sv}"); + + network_bss_append_suite_names(builder, "KeyMgmt", info.akm_suites, + network_bss_akm_names, + L_ARRAY_SIZE(network_bss_akm_names)); + network_bss_append_suite_names(builder, "Pairwise", + info.pairwise_ciphers, + network_bss_pairwise_names, + L_ARRAY_SIZE(network_bss_pairwise_names)); + + group = network_bss_group_cipher_name(info.group_cipher); + dbus_append_dict_basic(builder, "Group", 's', group); + + l_dbus_message_builder_leave_array(builder); + + return true; +} + static void setup_bss_interface(struct l_dbus_interface *interface) { l_dbus_interface_property(interface, "Address", 0, "s", network_bss_property_get_address, NULL); + l_dbus_interface_property(interface, "RSN", 0, "a{sv}", + network_bss_property_get_rsn, NULL); } static int network_init(void) From 114539200c5a8a2b3afa891c135e2c695d4722eb Mon Sep 17 00:00:00 2001 From: Aleks Man Date: Wed, 15 Jul 2026 01:48:48 +0300 Subject: [PATCH 2/3] doc: document BasicServiceSet RSN property Describe the optional RSN dictionary and its KeyMgmt, Pairwise, and Group entries. --- doc/basic-service-set.txt | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/doc/basic-service-set.txt b/doc/basic-service-set.txt index f827ca02..b4f2486f 100644 --- a/doc/basic-service-set.txt +++ b/doc/basic-service-set.txt @@ -8,3 +8,26 @@ Object path /net/connman/iwd/{phy0,phy1,...}/{1,2,...}/Xxx Properties string Address [readonly] MAC address of BSS + + dict RSN [readonly, optional] + + Security capabilities advertised in the RSN information + element. The dictionary contains the following entries: + + array(string) KeyMgmt + + Key management suites. Values use the same names as + the wpa_supplicant D-Bus BSS.RSN property, such as + "wpa-psk", "wpa-ft-psk", "sae", "ft-sae", + "wpa-eap", and "owe". + + array(string) Pairwise + + Pairwise ciphers. Possible values are "tkip", + "ccmp", "gcmp", "ccmp-256", and "gcmp-256". + + string Group + + Group cipher. Possible values are "wep40", "wep104", + "tkip", "ccmp", "gcmp", "ccmp-256", and + "gcmp-256". From 378bd7c0bfebd2e150c57590426eb78e4fa5d37b Mon Sep 17 00:00:00 2001 From: Aleks Man Date: Wed, 15 Jul 2026 01:48:49 +0300 Subject: [PATCH 3/3] auto-t: test BasicServiceSet RSN property Verify the exact D-Bus key management and cipher capabilities for WPA2-PSK, SAE, and transition BSSes. --- .../testBasicServiceSetRSN/bss_rsn_test.py | 70 +++++++++++++++++++ autotests/testBasicServiceSetRSN/hw.conf | 9 +++ autotests/testBasicServiceSetRSN/ssidSAE.conf | 11 +++ .../ssidTransition.conf | 12 ++++ .../testBasicServiceSetRSN/ssidWPA2.conf | 8 +++ 5 files changed, 110 insertions(+) create mode 100644 autotests/testBasicServiceSetRSN/bss_rsn_test.py create mode 100644 autotests/testBasicServiceSetRSN/hw.conf create mode 100644 autotests/testBasicServiceSetRSN/ssidSAE.conf create mode 100644 autotests/testBasicServiceSetRSN/ssidTransition.conf create mode 100644 autotests/testBasicServiceSetRSN/ssidWPA2.conf diff --git a/autotests/testBasicServiceSetRSN/bss_rsn_test.py b/autotests/testBasicServiceSetRSN/bss_rsn_test.py new file mode 100644 index 00000000..20e0b62c --- /dev/null +++ b/autotests/testBasicServiceSetRSN/bss_rsn_test.py @@ -0,0 +1,70 @@ +#! /usr/bin/python3 + +import unittest +import sys + +import dbus + +sys.path.append('../util') +import iwd +from iwd import IWD +from hwsim import Hwsim +from hostapd import HostapdCLI + +BSS_INTERFACE = 'net.connman.iwd.BasicServiceSet' + + +class Test(unittest.TestCase): + def _get_rsn(self, device, ssid): + ordered_network = device.get_ordered_network(ssid, full_scan=True) + network = ordered_network.network_object + self.assertEqual(len(network.extended_service_set), 1) + + path = network.extended_service_set[0] + proxy = self.wd._bus.get_object(iwd.IWD_SERVICE, path) + properties = dbus.Interface(proxy, iwd.DBUS_PROPERTIES) + + return properties.GetAll(BSS_INTERFACE)['RSN'] + + def test_rsn(self): + device = self.wd.list_devices(1)[0] + + rsn = self._get_rsn(device, 'ssidWPA2') + self.assertEqual(set(map(str, rsn['KeyMgmt'])), {'wpa-psk'}) + self.assertEqual(set(map(str, rsn['Pairwise'])), {'ccmp'}) + self.assertEqual(str(rsn['Group']), 'ccmp') + + rsn = self._get_rsn(device, 'ssidSAE') + self.assertEqual(set(map(str, rsn['KeyMgmt'])), {'sae'}) + self.assertEqual(set(map(str, rsn['Pairwise'])), {'ccmp'}) + self.assertEqual(str(rsn['Group']), 'ccmp') + + rsn = self._get_rsn(device, 'ssidTransition') + self.assertEqual(set(map(str, rsn['KeyMgmt'])), + {'wpa-psk', 'sae'}) + self.assertEqual(set(map(str, rsn['Pairwise'])), {'ccmp'}) + self.assertEqual(str(rsn['Group']), 'ccmp') + + def setUp(self): + self.wd = IWD(True) + + def tearDown(self): + self.wd.stop() + self.wd = None + + @classmethod + def setUpClass(cls): + Hwsim() + cls.hostapd = [ + HostapdCLI(config='ssidWPA2.conf'), + HostapdCLI(config='ssidSAE.conf'), + HostapdCLI(config='ssidTransition.conf'), + ] + + @classmethod + def tearDownClass(cls): + cls.hostapd = None + + +if __name__ == '__main__': + unittest.main(exit=True) diff --git a/autotests/testBasicServiceSetRSN/hw.conf b/autotests/testBasicServiceSetRSN/hw.conf new file mode 100644 index 00000000..a43062d9 --- /dev/null +++ b/autotests/testBasicServiceSetRSN/hw.conf @@ -0,0 +1,9 @@ +[SETUP] +num_radios=4 +hwsim_medium=yes +start_iwd=no + +[HOSTAPD] +rad0=ssidWPA2.conf +rad1=ssidSAE.conf +rad2=ssidTransition.conf diff --git a/autotests/testBasicServiceSetRSN/ssidSAE.conf b/autotests/testBasicServiceSetRSN/ssidSAE.conf new file mode 100644 index 00000000..2ec9ae61 --- /dev/null +++ b/autotests/testBasicServiceSetRSN/ssidSAE.conf @@ -0,0 +1,11 @@ +hw_mode=g +channel=2 +ssid=ssidSAE + +wpa=2 +wpa_key_mgmt=SAE +wpa_pairwise=CCMP +sae_password=secret123 +sae_groups=19 +ieee80211w=2 +sae_pwe=0 diff --git a/autotests/testBasicServiceSetRSN/ssidTransition.conf b/autotests/testBasicServiceSetRSN/ssidTransition.conf new file mode 100644 index 00000000..00170772 --- /dev/null +++ b/autotests/testBasicServiceSetRSN/ssidTransition.conf @@ -0,0 +1,12 @@ +hw_mode=g +channel=3 +ssid=ssidTransition + +wpa=2 +wpa_key_mgmt=WPA-PSK SAE +wpa_pairwise=CCMP +wpa_passphrase=secret123 +sae_password=secret123 +sae_groups=19 +ieee80211w=1 +sae_pwe=0 diff --git a/autotests/testBasicServiceSetRSN/ssidWPA2.conf b/autotests/testBasicServiceSetRSN/ssidWPA2.conf new file mode 100644 index 00000000..d706142b --- /dev/null +++ b/autotests/testBasicServiceSetRSN/ssidWPA2.conf @@ -0,0 +1,8 @@ +hw_mode=g +channel=1 +ssid=ssidWPA2 + +wpa=2 +wpa_key_mgmt=WPA-PSK +wpa_pairwise=CCMP +wpa_passphrase=secret123