3
0
mirror of https://git.kernel.org/pub/scm/network/wireless/iwd.git synced 2026-02-07 10:47:57 +01:00

Compare commits

...

24 Commits
3.9 ... master

Author SHA1 Message Date
Ronan Pigott
b49ed01626 dbus: register interface before acquiring name
If the interface isn't available by the time we acquire the well-known
name, clients can get confused when the expected interfaces are missing
during bus activation.
2026-02-05 12:29:46 -06:00
Marcel Holtmann
3e7a8feee0 Release 3.10 2025-09-25 15:36:37 +02:00
James Prestwood
3760a49650 station: print vendor quirks (if any) when connecting/roaming
This makes it clear the BSS being selected for a connection/roam has
any quirks associated with its OUI(s) and that IWD may behave
differently based on these.
2025-08-27 12:40:50 -05:00
James Prestwood
c4d114d804 auto-t: add AP roam test for bad neighbor reports/candidate lists 2025-08-27 12:40:44 -05:00
James Prestwood
ffe79bfada station: check vendor quirk for BSS TM request candidate list
If the AP vendor has known issues with the preferred candidate list
ignore it and jump directly to requesting a neighbor report.
2025-08-27 12:40:28 -05:00
James Prestwood
c0efaf21ad station: get neighbor report on BSS TM request
If a BSS is requesting IWD roam elsewhere but does not include a
preferred candidate list try getting a neighbor report before doing
a full scan.

If the limited scan based on the candidate list comes up empty this
would previously result in IWD giving up on the AP roam entirely.
This patch also improves that behavior slightly by doing a full
scan afterwards as a last ditch effort. If no BSS's are found after
that, IWD will give up on the AP roam.
2025-08-27 12:38:34 -05:00
James Prestwood
cee079da5b handshake: use vendor quirk to disable check of replay counters
This has been a long standing issue on Aruba APs where the scan
IEs differ from the IEs received during FT. For compatibility we
have been carrying a patch to disable the replay counter check but
this isn't something that was ever acceptable for upstream. Now
with the addition of vendor quirks this check can be disabled only
for the OUI of Aruba APs.

Reported-by: Michael Johnson <mjohnson459@gmail.com>
Co-authored-by: Michael Johnson <<mjohnson459@gmail.com>
2025-08-27 12:37:54 -05:00
James Prestwood
df30309aac station: set vendor quirks into handshake object 2025-08-27 12:37:49 -05:00
James Prestwood
2fe8c13016 scan: store vendor quirks in scan_bss
As each vendor IE is parsed lookup if there are any quirks associated
with it, and store these in a bit mask.
2025-08-27 12:37:21 -05:00
James Prestwood
84666b9703 handshake: add vendor quirks into handshake object 2025-08-27 12:37:00 -05:00
James Prestwood
54c0dbb3c8 handshake: pass object to handshake_util_ap_ie_matches
This is to prepare for supporting a vendor quirk, where we'll need
the handshake to lookup if the quirk to disable a specific check.
2025-08-27 12:36:13 -05:00
James Prestwood
6e9e0928b0 vendor_quirks: implement two vendor quirks
ignore_bss_tm_candidates:
  When a BSS requests a station roam it can optionally include a
  list of BSS's that can be roamed to. IWD uses this list and only
  scans on those frequencies. In some cases though the AP's list
  contains very poor options and it would be better for IWD to
  request a full neighbor report.

replay_counter_mismatch:
  On some Aruba APs there is a mismatch in the replay counters
  between what is seen in scans versus authentications/associations.
  This difference is not allowed in the spec, therefore IWD will
  not connect. This quirk is intended to relax that check.
2025-08-27 12:31:57 -05:00
James Prestwood
a1247fe46e vendor_quirks: initial skeleton
This module will provide a database for known issues or quirks with
wireless vendors.

The vendor_quirks_append_for_oui() API is intended to be called from
scan.c when parsing vendor attributes. This will lookup any quirks
associated with the OUI provided and combine them into an existing
vendor_quirk structure. This can be repeated against all the vendor
OUI's seen in the scan then referenced later to alter IWD behavior.

In the future more critera could be added such as MAC address prefix
or more generalized IE matches e.g.

vendor_quirks_append_for_mac()
vendor_quirks_append_for_ie()
etc.
2025-08-27 12:26:03 -05:00
James Prestwood
088bb2e308 station: clear roam_freqs on delayed roam
If there were no BSS candidates found after trying to roam make
sure the old roam_freqs list gets cleared so IWD doesn't end up
scanning potentially old frequencies on the next retry.
2025-08-26 10:00:32 -05:00
James Prestwood
57dc5d843c monitor: add Cisco Meraki as a printable vendor 2025-08-26 09:38:01 -05:00
James Prestwood
8cb134f935 scan: check support before using colocated flag 2025-08-26 09:37:17 -05:00
James Prestwood
46037c428c wiphy: add comments around the driver quirks 2025-08-26 09:36:44 -05:00
James Prestwood
161de4a3ad wiphy: add driver quirk for the colocated scan flag
Some drivers do not handle the colocated scan flag very well and this
results in BSS's not being seen in scans. This of course results in
very poor behavior.

This has been seen on ath11k specifically but after some
conversations [1] on the linux-wireless mailing list others have
reported issues with iwlwifi acting similarly. Since there are many
hardware variants that use both ath11k and iwlwifi this new quirk
isn't being forced to those drivers, but let users configure IWD to
disable the flag if needed.

[1] https://lore.kernel.org/linux-wireless/d1e75a08-047d-7947-d51a-2e486efead77@candelatech.com/
2025-08-26 09:35:35 -05:00
James Prestwood
77ee863f04 auto-t: add test for channel switch during roam
In kernel 6.8 a new CMD_ASSOCIATE failure path was added which checks
if the AP has a channel switch in progress. Eariler patches update
IWD into handling this case better, and this new test exercises that.
2025-08-20 11:18:51 -05:00
James Prestwood
405d1ab77c auto-t: make waiting for channel switch configurable 2025-08-20 11:18:45 -05:00
James Prestwood
dc1589f3fe netdev: disconnect rather than deauth in FT association failure
After CSA IE parsing was added to the kernel this opened up the
possibility that associations could be rejected locally based on
the contents of this CSA IE in the AP's beacons. Overall, it was
always possible for a local rejection but this case was never
considered by IWD. The CSA-based rejection is something that can
and does happen out in the wild.

When this association rejection happens it desync's IWD and the
kernel's state:

1. IWD begins an FT roam. Authenticates successfully, then proceeds
   to calling netdev_ft_reassociate().
2. Immediately IWD transitions to a ft-roaming state and waits for
   an association response.
3. CMD_ASSOCIATE is rejected by the kernel in the ACK which IWD
   handles by sending a deauthenticate command to the kernel (since
   we have a valid authentication to the new BSS).
4. Due to a bug IWD uses the target BSSID to deauthenticate which
   the kernel rejects since it has no knowledge of this auth. This
   error is not handled or logged.
5. IWD proceeds, assuming its deauthenticated, and transitions to a
   disconnected state. The kernel remains "connected" which of course
   prevents any future connections.

A simple fix for this is to address the bug (4) in IWD that deauths
using the current BSS roam target. This is actually legacy behavior
from back when IWD used CMD_AUTHENTICATE. Today the kernel is unaware
that IWD authenticated so a deauth is not going to be effective.
Instead we can issue a CMD_DISCONNECT. This is somewhat of a large
hammer, but since the handshake and internal state has already been
modified to use the new target BSS we cannot go back and maintain the
existing connect (though it is _possible_, see the TODO in the
patch).
2025-08-20 11:18:01 -05:00
James Prestwood
755280a4cc netdev: check connected in channel switch event
In an ideal world userspace should never be getting a channel switch
event unless connected to an AP, but alas this has been seen at least
with ath10k hardware. This causes IWD to crash since the logic
assumes netdev->handshake is set.
2025-08-20 11:17:54 -05:00
Gokul Sivakumar
df2c5cf7fa doc: add STA inactive and connected time duration info to diagnostics 2025-08-07 17:59:15 -05:00
Gokul Sivakumar
fee0e5de33 netdev: parse INACTIVE_TIME and CONNECTED_TIME in netdev_get_station
These two newly parsed station info params "inactive time" and the
"connected time" would be helpful to track the duration (in ms) for
which the station was last inactive and the total duration (in s) for
which the station is currently connected to the AP.

When the wlan device is in STA mode, these fields represent the info
of this station device. And when wlan device is in AP mode, then these
fields repesents the stations that are connected to this AP device.
2025-08-06 09:33:42 -05:00
25 changed files with 596 additions and 48 deletions

View File

@ -1,3 +1,8 @@
ver 3.10:
Fix issue with handling neighbor report on BSS TM request.
Fix issue with handling deauth and FT association failure.
Fix issue with handling roaming and old frequencies.
ver 3.9:
Fix issue with Access Point mode and frequency unlocking.
Fix issue with network configuration and BSS retry logic.

View File

@ -274,6 +274,8 @@ src_iwd_SOURCES = src/main.c linux/nl80211.h src/iwd.h \
src/dpp.c \
src/udev.c \
src/pmksa.h src/pmksa.c \
src/vendor_quirks.h \
src/vendor_quirks.c \
$(eap_sources) \
$(builtin_sources)

View File

@ -0,0 +1,156 @@
#!/usr/bin/python3
import unittest
import sys
sys.path.append('../util')
import iwd
from iwd import IWD
from iwd import NetworkType
from hostapd import HostapdCLI
class Test(unittest.TestCase):
def initial_connection(self):
ordered_network = self.device.get_ordered_network('TestAPRoam')
self.assertEqual(ordered_network.type, NetworkType.psk)
condition = 'not obj.connected'
self.wd.wait_for_object_condition(ordered_network.network_object, condition)
self.device.connect_bssid(self.bss_hostapd[0].bssid)
condition = 'obj.state == DeviceState.connected'
self.wd.wait_for_object_condition(self.device, condition)
self.bss_hostapd[0].wait_for_event('AP-STA-CONNECTED')
self.assertFalse(self.bss_hostapd[1].list_sta())
def test_full_scan(self):
"""
Tests that IWD first tries a limited scan, then a full scan after
an AP directed roam. After the full scan yields no results IWD
should stop trying to roam.
"""
self.initial_connection()
# Disable other APs, so the scans come up empty
self.bss_hostapd[1].disable()
self.bss_hostapd[2].disable()
# Send a bad candidate list with the BSS TM request which contains a
# channel with no AP operating on it.
self.bss_hostapd[0].send_bss_transition(
self.device.address,
[(self.bss_hostapd[1].bssid, "8f0000005105060603000000")]
)
self.device.wait_for_event("roam-scan-triggered")
self.device.wait_for_event("no-roam-candidates")
# IWD should then trigger a full scan
self.device.wait_for_event("full-roam-scan")
self.device.wait_for_event("no-roam-candidates", timeout=30)
# IWD should not trigger a roam again after the above 2 failures.
with self.assertRaises(TimeoutError):
self.device.wait_for_event("roam-scan-triggered", timeout=60)
def test_bad_candidate_list(self):
"""
Tests behavior when the AP sends a candidate list but the scan
finds no BSS's. IWD should fall back to a full scan after.
"""
self.initial_connection()
# Send a bad candidate list with the BSS TM request which contains a
# channel with no AP operating on it.
self.bss_hostapd[0].send_bss_transition(
self.device.address,
[(self.bss_hostapd[1].bssid, "8f0000005105060603000000")]
)
self.device.wait_for_event("roam-scan-triggered")
self.device.wait_for_event("no-roam-candidates")
# IWD should then trigger a full scan
self.device.wait_for_event("full-roam-scan")
self.device.wait_for_event("roaming", timeout=30)
self.device.wait_for_event("connected")
def test_bad_neighbor_report(self):
"""
Tests behavior when the AP sends no candidate list. IWD should
request a neighbor report. If the limited scan yields no BSS's IWD
should fall back to a full scan.
"""
# Set a bad neighbor (channel that no AP is on) to force the limited
# roam scan to fail
self.bss_hostapd[0].set_neighbor(
self.bss_hostapd[1].bssid,
"TestAPRoam",
'%s8f000000%s%s060603000000' % (self.bss_hostapd[1].bssid.replace(':', ''), "51", "0b")
)
self.initial_connection()
self.bss_hostapd[0].send_bss_transition(self.device.address, [])
self.device.wait_for_event("roam-scan-triggered")
# The AP will have sent a neighbor report with a single BSS but on
# channel 11 which no AP is on. This should result in a limited scan
# picking up no candidates.
self.device.wait_for_event("no-roam-candidates", timeout=30)
# IWD should then trigger a full scan
self.device.wait_for_event("full-roam-scan")
self.device.wait_for_event("roaming", timeout=30)
self.device.wait_for_event("connected")
def test_ignore_candidate_list_quirk(self):
"""
Tests that IWD ignores the candidate list sent by the AP since its
OUI indicates it should be ignored.
"""
# Set the OUI so the candidate list should be ignored
for hapd in self.bss_hostapd:
hapd.set_value('vendor_elements', 'dd0400180a01')
self.initial_connection()
# Send with a candidate list (should be ignored)
self.bss_hostapd[0].send_bss_transition(
self.device.address,
[(self.bss_hostapd[1].bssid, "8f0000005105060603000000")]
)
# IWD should ignore the list and trigger a full scan since we have not
# set any neighbors
self.device.wait_for_event("full-roam-scan")
self.device.wait_for_event("roaming", timeout=30)
self.device.wait_for_event("connected")
def setUp(self):
self.wd = IWD(True)
devices = self.wd.list_devices(1)
self.device = devices[0]
def tearDown(self):
self.wd = None
self.device = None
for hapd in self.bss_hostapd:
hapd.reload()
@classmethod
def setUpClass(cls):
IWD.copy_to_storage('TestAPRoam.psk')
cls.bss_hostapd = [ HostapdCLI(config='ssid1.conf'),
HostapdCLI(config='ssid2.conf'),
HostapdCLI(config='ssid3.conf') ]
@classmethod
def tearDownClass(cls):
IWD.clear_storage()
if __name__ == '__main__':
unittest.main(exit=True)

View File

@ -0,0 +1,114 @@
#! /usr/bin/python3
import unittest
import sys, os
sys.path.append('../util')
from iwd import IWD
from iwd import NetworkType
from hostapd import HostapdCLI
from packaging import version
from subprocess import run
import re
import testutil
#
# The CSA handling was added in kernel 6.8, so for any earlier kernel this test
# won't pass.
#
def kernel_is_newer(min_version="6.8"):
proc = run(["uname", "-r"], capture_output=True)
version_str = proc.stdout.decode("utf-8")
match = re.match(r"(\d+\.\d+)", version_str)
if not match:
return False
return version.parse(match.group(1)) >= version.parse(min_version)
class Test(unittest.TestCase):
def test_channel_switch_during_roam(self):
wd = self.wd
device = wd.list_devices(1)[0]
ordered_network = device.get_ordered_network('TestFT', full_scan=True)
self.assertEqual(ordered_network.type, NetworkType.psk)
condition = 'not obj.connected'
wd.wait_for_object_condition(ordered_network.network_object, condition)
self.assertFalse(self.bss_hostapd[0].list_sta())
self.assertFalse(self.bss_hostapd[1].list_sta())
device.connect_bssid(self.bss_hostapd[0].bssid)
condition = 'obj.state == DeviceState.connected'
wd.wait_for_object_condition(device, condition)
self.bss_hostapd[0].wait_for_event('AP-STA-CONNECTED %s' % device.address)
testutil.test_iface_operstate(device.name)
testutil.test_ifaces_connected(self.bss_hostapd[0].ifname, device.name)
self.assertRaises(Exception, testutil.test_ifaces_connected,
(self.bss_hostapd[1].ifname, device.name, True, True))
# Start a channel switch and wait for it to begin
self.bss_hostapd[1].chan_switch(6, wait=False)
self.bss_hostapd[1].wait_for_event("CTRL-EVENT-STARTED-CHANNEL-SWITCH")
# Initiate a roam immediately which should get rejected by the kernel
device.roam(self.bss_hostapd[1].bssid)
# IWD should authenticate, then proceed to association
device.wait_for_event("ft-authenticating")
device.wait_for_event("ft-roaming")
# The kernel should reject the association, which should trigger a
# disconnect
condition = 'obj.state == DeviceState.disconnected'
wd.wait_for_object_condition(device, condition)
condition = 'obj.state == DeviceState.connected'
wd.wait_for_object_condition(device, condition)
def tearDown(self):
os.system('ip link set "' + self.bss_hostapd[0].ifname + '" down')
os.system('ip link set "' + self.bss_hostapd[1].ifname + '" down')
os.system('ip link set "' + self.bss_hostapd[0].ifname + '" up')
os.system('ip link set "' + self.bss_hostapd[1].ifname + '" up')
for hapd in self.bss_hostapd:
hapd.default()
self.wd.stop()
self.wd = None
def setUp(self):
self.wd = IWD(True)
@classmethod
def setUpClass(cls):
if not kernel_is_newer():
raise unittest.SkipTest()
IWD.copy_to_storage('TestFT.psk')
cls.bss_hostapd = [ HostapdCLI(config='ft-psk-ccmp-1.conf'),
HostapdCLI(config='ft-psk-ccmp-2.conf'),
HostapdCLI(config='ft-psk-ccmp-3.conf') ]
unused = HostapdCLI(config='ft-psk-ccmp-3.conf')
unused.disable()
cls.bss_hostapd[0].set_address('12:00:00:00:00:01')
cls.bss_hostapd[1].set_address('12:00:00:00:00:02')
cls.bss_hostapd[2].set_address('12:00:00:00:00:03')
HostapdCLI.group_neighbors(*cls.bss_hostapd)
@classmethod
def tearDownClass(cls):
IWD.clear_storage()
cls.bss_hostapd = None

View File

@ -288,13 +288,15 @@ class HostapdCLI(object):
cmd = 'RESEND_M3 %s' % address
self.ctrl_sock.sendall(cmd.encode('utf-8'))
def chan_switch(self, channel):
def chan_switch(self, channel, wait=True):
if channel > len(chan_freq_map):
raise Exception("Only 2.4GHz channels supported for chan_switch")
cmd = self.cmdline + ['chan_switch', '50', str(chan_freq_map[channel])]
ctx.start_process(cmd).wait()
self.wait_for_event('AP-CSA-FINISHED')
if wait:
self.wait_for_event('AP-CSA-FINISHED')
def _get_status(self):
ret = {}

View File

@ -95,6 +95,8 @@ static const struct diagnostic_dict_mapping diagnostic_mapping[] = {
{ "Frequency", 'u' },
{ "Channel", 'q' },
{ "Security", 's' },
{ "InactiveTime", 'u', "ms" },
{ "ConnectedTime", 'u', "s" },
{ NULL }
};

View File

@ -1,5 +1,5 @@
AC_PREREQ([2.69])
AC_INIT([iwd],[3.9])
AC_INIT([iwd],[3.10])
AC_CONFIG_HEADERS(config.h)
AC_CONFIG_AUX_DIR(build-aux)

View File

@ -31,6 +31,12 @@ Methods array{dict} GetDiagnostics()
TxMCS [optional] - Transmitting MCS index
InactiveTime [optional] - Time duration (in ms) for which the STA
connected to this BSS is currently inactive.
ConnectedTime [optional] - Time duration (in s) for which the STA
remains connected to this BSS.
Possible errors: net.connman.iwd.Failed
net.connman.iwd.NotConnected
net.connman.iwd.NotFound

View File

@ -53,6 +53,12 @@ Methods dict GetDiagnostics()
- GCMP-256
- CCMP-256
InactiveTime [optional] - Time duration (in ms) for which this STA
is currently inactive.
ConnectedTime [optional] - Time Duration (in s) for which this STA
remains connected to the BSS.
Possible errors: net.connman.iwd.Busy
net.connman.iwd.Failed
net.connman.iwd.NotConnected

View File

@ -404,6 +404,7 @@ static const struct {
{ { 0x00, 0x50, 0xf2 }, "Microsoft" },
{ { 0x00, 0x90, 0x4c }, "Epigram" },
{ { 0x50, 0x6f, 0x9a }, "Wi-Fi Alliance" },
{ { 0x00, 0x18, 0x0a }, "Cisco Meraki" },
{ }
};

View File

@ -110,6 +110,14 @@ bool diagnostic_info_to_dict(const struct diagnostic_station_info *info,
dbus_append_dict_basic(builder, "ExpectedThroughput", 'u',
&info->expected_throughput);
if (info->have_inactive_time)
dbus_append_dict_basic(builder, "InactiveTime", 'u',
&info->inactive_time);
if (info->have_connected_time)
dbus_append_dict_basic(builder, "ConnectedTime", 'u',
&info->connected_time);
return true;
}

View File

@ -43,6 +43,9 @@ struct diagnostic_station_info {
uint32_t expected_throughput;
uint32_t inactive_time;
uint32_t connected_time;
bool have_cur_rssi : 1;
bool have_avg_rssi : 1;
bool have_rx_mcs : 1;
@ -50,6 +53,8 @@ struct diagnostic_station_info {
bool have_rx_bitrate : 1;
bool have_tx_bitrate : 1;
bool have_expected_throughput : 1;
bool have_inactive_time : 1;
bool have_connected_time : 1;
};
bool diagnostic_info_to_dict(const struct diagnostic_station_info *info,

View File

@ -1810,7 +1810,7 @@ static void eapol_handle_ptk_3_of_4(struct eapol_sm *sm,
if ((rsne[1] != hs->authenticator_ie[1] ||
memcmp(rsne + 2, hs->authenticator_ie + 2, rsne[1])) &&
!handshake_util_ap_ie_matches(&rsn_info,
!handshake_util_ap_ie_matches(hs, &rsn_info,
hs->authenticator_ie,
hs->wpa_ie))
goto error_ie_different;

View File

@ -223,7 +223,8 @@ static bool ft_parse_associate_resp_frame(const uint8_t *frame, size_t frame_len
return true;
}
static bool ft_verify_rsne(const uint8_t *rsne, const uint8_t *pmk_r0_name,
static bool ft_verify_rsne(struct handshake_state *hs,
const uint8_t *rsne, const uint8_t *pmk_r0_name,
const uint8_t *authenticator_ie)
{
/*
@ -253,7 +254,7 @@ static bool ft_verify_rsne(const uint8_t *rsne, const uint8_t *pmk_r0_name,
memcmp(msg2_rsne.pmkids, pmk_r0_name, 16))
return false;
if (!handshake_util_ap_ie_matches(&msg2_rsne, authenticator_ie, false))
if (!handshake_util_ap_ie_matches(hs, &msg2_rsne, authenticator_ie, false))
return false;
return true;
@ -301,7 +302,8 @@ static int parse_ies(struct handshake_state *hs,
is_rsn = hs->supplicant_ie != NULL;
if (is_rsn) {
if (!ft_verify_rsne(rsne, hs->pmk_r0_name, authenticator_ie))
if (!ft_verify_rsne(hs, rsne, hs->pmk_r0_name,
authenticator_ie))
goto ft_error;
} else if (rsne)
goto ft_error;
@ -480,7 +482,7 @@ int __ft_rx_associate(uint32_t ifindex, const uint8_t *frame, size_t frame_len)
memcmp(msg4_rsne.pmkids, hs->pmk_r1_name, 16))
return -EBADMSG;
if (!handshake_util_ap_ie_matches(&msg4_rsne,
if (!handshake_util_ap_ie_matches(hs, &msg4_rsne,
hs->authenticator_ie,
false))
return -EBADMSG;

View File

@ -368,6 +368,12 @@ void handshake_state_set_vendor_ies(struct handshake_state *s,
}
}
void handshake_state_set_vendor_quirks(struct handshake_state *s,
struct vendor_quirk quirks)
{
s->vendor_quirks = quirks;
}
void handshake_state_set_kh_ids(struct handshake_state *s,
const uint8_t *r0khid, size_t r0khid_len,
const uint8_t *r1khid)
@ -877,7 +883,8 @@ void handshake_state_set_igtk(struct handshake_state *s, const uint8_t *key,
* results vs the RSN/WPA IE obtained as part of the 4-way handshake. If they
* don't match, the EAPoL packet must be silently discarded.
*/
bool handshake_util_ap_ie_matches(const struct ie_rsn_info *msg_info,
bool handshake_util_ap_ie_matches(struct handshake_state *s,
const struct ie_rsn_info *msg_info,
const uint8_t *scan_ie, bool is_wpa)
{
struct ie_rsn_info scan_info;
@ -907,11 +914,15 @@ bool handshake_util_ap_ie_matches(const struct ie_rsn_info *msg_info,
if (msg_info->no_pairwise != scan_info.no_pairwise)
return false;
if (msg_info->ptksa_replay_counter != scan_info.ptksa_replay_counter)
return false;
if (!(s->vendor_quirks.replay_counter_mismatch)) {
if (msg_info->ptksa_replay_counter !=
scan_info.ptksa_replay_counter)
return false;
if (msg_info->gtksa_replay_counter != scan_info.gtksa_replay_counter)
return false;
if (msg_info->gtksa_replay_counter !=
scan_info.gtksa_replay_counter)
return false;
}
if (msg_info->mfpr != scan_info.mfpr)
return false;

View File

@ -26,6 +26,8 @@
#include <linux/types.h>
#include <ell/cleanup.h>
#include "src/vendor_quirks.h"
struct handshake_state;
enum crypto_cipher;
struct eapol_frame;
@ -107,6 +109,7 @@ struct handshake_state {
uint8_t *authenticator_fte;
uint8_t *supplicant_fte;
uint8_t *vendor_ies;
struct vendor_quirk vendor_quirks;
size_t vendor_ies_len;
enum ie_rsn_cipher_suite pairwise_cipher;
enum ie_rsn_cipher_suite group_cipher;
@ -237,6 +240,9 @@ void handshake_state_set_vendor_ies(struct handshake_state *s,
const struct iovec *iov,
size_t n_iovs);
void handshake_state_set_vendor_quirks(struct handshake_state *s,
struct vendor_quirk quirks);
void handshake_state_set_kh_ids(struct handshake_state *s,
const uint8_t *r0khid, size_t r0khid_len,
const uint8_t *r1khid);
@ -312,7 +318,8 @@ bool handshake_state_set_pmksa(struct handshake_state *s, struct pmksa *pmksa);
void handshake_state_cache_pmksa(struct handshake_state *s);
bool handshake_state_remove_pmksa(struct handshake_state *s);
bool handshake_util_ap_ie_matches(const struct ie_rsn_info *msg_info,
bool handshake_util_ap_ie_matches(struct handshake_state *s,
const struct ie_rsn_info *msg_info,
const uint8_t *scan_ie, bool is_wpa);
const uint8_t *handshake_util_find_kde(enum handshake_kde selector,

View File

@ -197,29 +197,12 @@ static void request_name_callback(struct l_dbus *dbus, bool success,
{
if (!success) {
l_error("Name request failed");
goto fail_exit;
l_main_quit();
}
if (!l_dbus_object_manager_enable(dbus, "/"))
l_warn("Unable to register the ObjectManager");
if (!l_dbus_object_add_interface(dbus, IWD_BASE_PATH,
IWD_DAEMON_INTERFACE,
NULL) ||
!l_dbus_object_add_interface(dbus, IWD_BASE_PATH,
L_DBUS_INTERFACE_PROPERTIES,
NULL))
l_info("Unable to add %s and/or %s at %s",
IWD_DAEMON_INTERFACE, L_DBUS_INTERFACE_PROPERTIES,
IWD_BASE_PATH);
/* TODO: Always request nl80211 for now, ignoring auto-loading */
l_genl_request_family(genl, NL80211_GENL_NAME, nl80211_appeared,
NULL, NULL);
return;
fail_exit:
l_main_quit();
}
static struct l_dbus_message *iwd_dbus_get_info(struct l_dbus *dbus,
@ -249,12 +232,25 @@ static void dbus_ready(void *user_data)
{
struct l_dbus *dbus = user_data;
l_dbus_name_acquire(dbus, "net.connman.iwd", false, false, false,
request_name_callback, NULL);
l_dbus_register_interface(dbus, IWD_DAEMON_INTERFACE,
iwd_setup_deamon_interface,
NULL, false);
if (!l_dbus_object_manager_enable(dbus, "/"))
l_warn("Unable to register the ObjectManager");
if (!l_dbus_object_add_interface(dbus, IWD_BASE_PATH,
IWD_DAEMON_INTERFACE,
NULL) ||
!l_dbus_object_add_interface(dbus, IWD_BASE_PATH,
L_DBUS_INTERFACE_PROPERTIES,
NULL))
l_info("Unable to add %s and/or %s at %s",
IWD_DAEMON_INTERFACE, L_DBUS_INTERFACE_PROPERTIES,
IWD_BASE_PATH);
l_dbus_name_acquire(dbus, "net.connman.iwd", false, false, false,
request_name_callback, NULL);
}
static void dbus_disconnected(void *user_data)

View File

@ -637,7 +637,6 @@ static bool netdev_parse_sta_info(struct l_genl_attr *attr,
info->have_tx_mcs = true;
break;
case NL80211_STA_INFO_EXPECTED_THROUGHPUT:
if (len != 4)
return false;
@ -645,6 +644,22 @@ static bool netdev_parse_sta_info(struct l_genl_attr *attr,
info->expected_throughput = l_get_u32(data);
info->have_expected_throughput = true;
break;
case NL80211_STA_INFO_INACTIVE_TIME:
if (len != 4)
return false;
info->inactive_time = l_get_u32(data);
info->have_inactive_time = true;
break;
case NL80211_STA_INFO_CONNECTED_TIME:
if (len != 4)
return false;
info->connected_time = l_get_u32(data);
info->have_connected_time = true;
break;
}
}
@ -2993,13 +3008,26 @@ static void netdev_cmd_ft_reassociate_cb(struct l_genl_msg *msg,
void *user_data)
{
struct netdev *netdev = user_data;
int err = l_genl_msg_get_error(msg);
netdev->connect_cmd_id = 0;
if (l_genl_msg_get_error(msg) >= 0)
l_debug("%d", err);
if (err >= 0)
return;
netdev_deauth_and_fail_connection(netdev,
/*
* TODO: It is possible to not trigger a disconnect here and maintain
* the current connection. The issue is that IWD has already
* modified the handshake and we've lost all reference to the old
* BSS keys.
*
* This could be remedied in the future by creating an entirely
* new handshake_state object for the association and only when
* the ack indicates success do we clear out the old object.
*/
netdev_disconnect_and_fail_connection(netdev,
NETDEV_RESULT_ASSOCIATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
}
@ -5402,6 +5430,9 @@ static void netdev_channel_switch_event(struct l_genl_msg *msg,
if (netdev->type != NL80211_IFTYPE_STATION)
return;
if (L_WARN_ON(!netdev->connected))
return;
chandef = l_new(struct band_chandef, 1);
if (nl80211_parse_chandef(msg, chandef) < 0) {

View File

@ -51,6 +51,7 @@
#include "src/mpdu.h"
#include "src/band.h"
#include "src/scan.h"
#include "src/vendor_quirks.h"
/* User configurable options */
static double RANK_2G_FACTOR;
@ -414,7 +415,8 @@ static struct l_genl_msg *scan_build_cmd(struct scan_context *sc,
if (params->ap_scan)
flags |= NL80211_SCAN_FLAG_AP;
flags |= NL80211_SCAN_FLAG_COLOCATED_6GHZ;
if (wiphy_supports_colocated_flag(sc->wiphy))
flags |= NL80211_SCAN_FLAG_COLOCATED_6GHZ;
if (flags)
l_genl_msg_append_attr(msg, NL80211_ATTR_SCAN_FLAGS, 4, &flags);
@ -1220,6 +1222,11 @@ static void scan_parse_vendor_specific(struct scan_bss *bss, const void *data,
uint16_t cost_flags;
bool dgaf_disable;
if (L_WARN_ON(len < 3))
return;
vendor_quirks_append_for_oui(data, &bss->vendor_quirks);
if (!bss->wpa && is_ie_wpa_ie(data, len)) {
bss->wpa = l_memdup(data - 2, len + 2);
return;

View File

@ -21,6 +21,7 @@
*/
#include "src/defs.h"
#include "src/vendor_quirks.h"
struct scan_freq_set;
struct ie_rsn_info;
@ -79,6 +80,7 @@ struct scan_bss {
uint8_t *wfd; /* Concatenated WFD IEs */
ssize_t wfd_size; /* Size of Concatenated WFD IEs */
int8_t snr;
struct vendor_quirk vendor_quirks;
bool mde_present : 1;
bool cc_present : 1;
bool cap_rm_neighbor_report : 1;

View File

@ -64,6 +64,7 @@
#include "src/eap-tls-common.h"
#include "src/storage.h"
#include "src/pmksa.h"
#include "src/vendor_quirks.h"
#define STATION_RECENT_NETWORK_LIMIT 5
#define STATION_RECENT_FREQS_LIMIT 5
@ -1446,6 +1447,8 @@ static struct handshake_state *station_handshake_setup(struct station *station,
vendor_ies = network_info_get_extra_ies(info, bss, &iov_elems);
handshake_state_set_vendor_ies(hs, vendor_ies, iov_elems);
handshake_state_set_vendor_quirks(hs, bss->vendor_quirks);
/*
* It can't hurt to try the FILS IP Address Assignment independent of
* which auth-proto is actually used.
@ -2404,6 +2407,11 @@ static void station_roam_retry(struct station *station)
station->roam_scan_full = false;
station->ap_directed_roaming = false;
if (station->roam_freqs) {
scan_freq_set_free(station->roam_freqs);
station->roam_freqs = NULL;
}
if (station->signal_low)
station_roam_timeout_rearm(station, roam_retry_interval);
}
@ -2433,8 +2441,16 @@ static void station_roam_failed(struct station *station)
* We were told by the AP to roam, but failed. Try ourselves or
* wait for the AP to tell us to roam again
*/
if (station->ap_directed_roaming)
if (station->ap_directed_roaming) {
/*
* The candidate list from the AP (or neighbor report) found
* no BSS's. Force a full scan
*/
if (!station->roam_scan_full)
goto full_scan;
goto delayed_retry;
}
/*
* If we tried a limited scan, failed and the signal is still low,
@ -2446,6 +2462,7 @@ static void station_roam_failed(struct station *station)
* the scan here, so that the destroy callback is not called
* after the return of this function
*/
full_scan:
scan_cancel(netdev_get_wdev_id(station->netdev),
station->roam_scan_id);
@ -2733,11 +2750,15 @@ static bool station_try_next_transition(struct station *station,
enum security security = network_get_security(connected);
struct handshake_state *new_hs;
struct ie_rsn_info cur_rsne, target_rsne;
const char *vendor_quirks = vendor_quirks_to_string(bss->vendor_quirks);
iwd_notice(IWD_NOTICE_ROAM_INFO, "bss: "MAC", signal: %d, load: %d/255",
MAC_STR(bss->addr),
bss->signal_strength / 100,
bss->utilization);
if (vendor_quirks)
l_debug("vendor quirks for "MAC": %s",
MAC_STR(bss->addr), vendor_quirks);
/* Reset AP roam flag, at this point the roaming behaves the same */
station->ap_directed_roaming = false;
@ -3038,6 +3059,7 @@ static int station_roam_scan(struct station *station,
if (!freq_set) {
station->roam_scan_full = true;
params.freqs = allowed;
station_debug_event(station, "full-roam-scan");
} else
scan_freq_set_constrain(freq_set, allowed);
@ -3249,6 +3271,8 @@ static void station_ap_directed_roam(struct station *station,
uint16_t dtimer;
uint8_t valid_interval;
bool can_roam = !station_cannot_roam(station);
bool ignore_candidates =
station->connected_bss->vendor_quirks.ignore_bss_tm_candidates;
l_debug("ifindex: %u", netdev_get_ifindex(station->netdev));
@ -3366,12 +3390,21 @@ static void station_ap_directed_roam(struct station *station,
l_timeout_remove(station->roam_trigger_timeout);
station->roam_trigger_timeout = NULL;
if (req_mode & WNM_REQUEST_MODE_PREFERRED_CANDIDATE_LIST) {
if ((req_mode & WNM_REQUEST_MODE_PREFERRED_CANDIDATE_LIST) &&
!ignore_candidates) {
l_debug("roam: AP sent a preferred candidate list");
station_neighbor_report_cb(station->netdev, 0, body + pos,
body_len - pos, station);
} else {
l_debug("roam: AP did not include a preferred candidate list");
if (station->connected_bss->cap_rm_neighbor_report) {
if (!netdev_neighbor_report_req(station->netdev,
station_neighbor_report_cb))
return;
l_warn("failed to request neighbor report!");
}
l_debug("full scan after BSS transition request");
if (station_roam_scan(station, NULL) < 0)
station_roam_failed(station);
}
@ -3884,6 +3917,7 @@ int __station_connect_network(struct station *station, struct network *network,
{
struct handshake_state *hs;
int r;
const char *vendor_quirks = vendor_quirks_to_string(bss->vendor_quirks);
/*
* If we already have a handshake_state ref this is due to a retry,
@ -3918,6 +3952,10 @@ int __station_connect_network(struct station *station, struct network *network,
bss->signal_strength / 100,
bss->utilization);
if (vendor_quirks)
l_debug("vendor quirks for "MAC": %s",
MAC_STR(bss->addr), vendor_quirks);
station->connected_bss = bss;
station->connected_network = network;
station->hs = handshake_state_ref(hs);

84
src/vendor_quirks.c Normal file
View File

@ -0,0 +1,84 @@
/*
*
* Wireless daemon for Linux
*
* Copyright (C) 2025 Locus Robotics Corporation. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <stdio.h>
#include <ell/ell.h>
#include "src/vendor_quirks.h"
static const struct {
uint8_t oui[3];
struct vendor_quirk quirks;
} oui_quirk_db[] = {
{
/* Cisco Meraki */
{ 0x00, 0x18, 0x0a },
{ .ignore_bss_tm_candidates = true },
},
{
/* Hewlett Packard, owns Aruba */
{ 0x00, 0x0b, 0x86 },
{ .replay_counter_mismatch = true },
},
};
void vendor_quirks_append_for_oui(const uint8_t *oui,
struct vendor_quirk *quirks)
{
size_t i;
for (i = 0; i < L_ARRAY_SIZE(oui_quirk_db); i++) {
const struct vendor_quirk *quirk = &oui_quirk_db[i].quirks;
if (memcmp(oui_quirk_db[i].oui, oui, 3))
continue;
quirks->ignore_bss_tm_candidates |=
quirk->ignore_bss_tm_candidates;
quirks->replay_counter_mismatch |=
quirk->replay_counter_mismatch;
}
}
const char *vendor_quirks_to_string(struct vendor_quirk quirks)
{
static char out[1024];
char *pos = out;
size_t s = 0;
if (quirks.ignore_bss_tm_candidates)
s += snprintf(pos, sizeof(out) - s, "IgnoreBssTmCandidateList");
if (quirks.replay_counter_mismatch)
s += snprintf(pos, sizeof(out) - s, "ReplayCounterMismatch");
if (!s)
return NULL;
return out;
}

39
src/vendor_quirks.h Normal file
View File

@ -0,0 +1,39 @@
/*
*
* Wireless daemon for Linux
*
* Copyright (C) 2025 Locus Robotics Corporation. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef __IWD_VENDOR_QUIRKS_H
#define __IWD_VENDOR_QUIRKS_H
#include <stdint.h>
struct vendor_quirk {
bool ignore_bss_tm_candidates : 1;
bool replay_counter_mismatch : 1;
};
void vendor_quirks_append_for_oui(const uint8_t *oui,
struct vendor_quirk *quirks);
const char *vendor_quirks_to_string(struct vendor_quirk quirks);
#endif /* __IWD_VENDOR_QUIRKS_H */

View File

@ -69,12 +69,26 @@ static uint32_t work_ids;
static unsigned int wiphy_dump_id;
enum driver_flag {
/* Force the use of the default interface created by the kernel */
DEFAULT_IF = 0x1,
/*
* Force the use of the PAE socket rather than control port, even if
* control port is supported
*/
FORCE_PAE = 0x2,
/* Disable power save on the adapter during initialization */
POWER_SAVE_DISABLE = 0x4,
/* Don't use OWE when connecting to open networks */
OWE_DISABLE = 0x8,
/* Disables multicast RX frame registration */
MULTICAST_RX_DISABLE = 0x10,
/*
* Don't use SAE (WPA3) when connecting to hybrid networks. This will
* prevent IWD from connecting to WPA3-only networks
*/
SAE_DISABLE = 0x20,
/* Disables use of the NL80211_SCAN_FLAG_COLOCATED_6GHZ flag in scans */
COLOCATED_SCAN_DISABLE = 0x40,
};
struct driver_flag_name {
@ -103,12 +117,13 @@ static const struct driver_info driver_infos[] = {
};
static const struct driver_flag_name driver_flag_names[] = {
{ "DefaultInterface", DEFAULT_IF },
{ "ForcePae", FORCE_PAE },
{ "PowerSaveDisable", POWER_SAVE_DISABLE },
{ "OweDisable", OWE_DISABLE },
{ "MulticastRxDisable", MULTICAST_RX_DISABLE },
{ "SaeDisable", SAE_DISABLE },
{ "DefaultInterface", DEFAULT_IF },
{ "ForcePae", FORCE_PAE },
{ "PowerSaveDisable", POWER_SAVE_DISABLE },
{ "OweDisable", OWE_DISABLE },
{ "MulticastRxDisable", MULTICAST_RX_DISABLE },
{ "SaeDisable", SAE_DISABLE },
{ "ColocatedScanDisable", COLOCATED_SCAN_DISABLE },
};
struct wiphy {
@ -963,6 +978,11 @@ bool wiphy_supports_multicast_rx(const struct wiphy *wiphy)
!(wiphy->driver_flags & MULTICAST_RX_DISABLE);
}
bool wiphy_supports_colocated_flag(const struct wiphy *wiphy)
{
return !(wiphy->driver_flags & COLOCATED_SCAN_DISABLE);
}
const uint8_t *wiphy_get_ht_capabilities(const struct wiphy *wiphy,
enum band_freq band,
size_t *size)
@ -1382,6 +1402,9 @@ static void wiphy_print_basic_info(struct wiphy *wiphy)
if (wiphy->driver_flags & SAE_DISABLE)
flags = l_strv_append(flags, "SaeDisable");
if (wiphy->driver_flags & COLOCATED_SCAN_DISABLE)
flags = l_strv_append(flags, "ColocatedScanDisable");
joined = l_strjoinv(flags, ' ');
l_info("\tDriver Flags: %s", joined);

View File

@ -144,6 +144,7 @@ bool wiphy_country_is_unknown(struct wiphy *wiphy);
bool wiphy_supports_uapsd(const struct wiphy *wiphy);
bool wiphy_supports_cmd_offchannel(const struct wiphy *wiphy);
bool wiphy_supports_multicast_rx(const struct wiphy *wiphy);
bool wiphy_supports_colocated_flag(const struct wiphy *wiphy);
const uint8_t *wiphy_get_ht_capabilities(const struct wiphy *wiphy,
enum band_freq band,