mirror of
				https://git.kernel.org/pub/scm/network/wireless/iwd.git
				synced 2025-10-31 13:17:25 +01:00 
			
		
		
		
	Compare commits
	
		
			143 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 3e7a8feee0 | ||
|   | 3760a49650 | ||
|   | c4d114d804 | ||
|   | ffe79bfada | ||
|   | c0efaf21ad | ||
|   | cee079da5b | ||
|   | df30309aac | ||
|   | 2fe8c13016 | ||
|   | 84666b9703 | ||
|   | 54c0dbb3c8 | ||
|   | 6e9e0928b0 | ||
|   | a1247fe46e | ||
|   | 088bb2e308 | ||
|   | 57dc5d843c | ||
|   | 8cb134f935 | ||
|   | 46037c428c | ||
|   | 161de4a3ad | ||
|   | 77ee863f04 | ||
|   | 405d1ab77c | ||
|   | dc1589f3fe | ||
|   | 755280a4cc | ||
|   | df2c5cf7fa | ||
|   | fee0e5de33 | ||
|   | 601d9b0e02 | ||
|   | f209e00dde | ||
|   | 86523b0597 | ||
|   | 85a2637fc5 | ||
|   | 2f991918b1 | ||
|   | 5b5a9b60fb | ||
|   | 5287809043 | ||
|   | ea9ff2dcaf | ||
|   | 9dce36fe3d | ||
|   | c4718a5355 | ||
|   | c9c8790ff2 | ||
|   | d135bfc4b8 | ||
|   | e269beadba | ||
|   | 3e55fc855a | ||
|   | c8d9936f9d | ||
|   | 79940956ef | ||
|   | 9f98c6c3c8 | ||
|   | 93eef7b02d | ||
|   | 26ed1f8b9f | ||
|   | 243db1d256 | ||
|   | 5224b0b0e7 | ||
|   | 3267d356d2 | ||
|   | 78f4e6240e | ||
|   | 36b1086f60 | ||
|   | 266eb405f2 | ||
|   | 0a8e646231 | ||
|   | 8ebc4780ea | ||
|   | 4ded663e68 | ||
|   | c00bc3a065 | ||
|   | f469db8a95 | ||
|   | c3a27354ff | ||
|   | d927fd07c1 | ||
|   | 8dff156eb6 | ||
|   | e5c41a8024 | ||
|   | 603d6b2881 | ||
|   | d1aa4009bc | ||
|   | 3c5081c7a6 | ||
|   | 7d5bcd738b | ||
|   | 0a93c55552 | ||
|   | 7f9ea7640d | ||
|   | c52d913f20 | ||
|   | 651b647570 | ||
|   | 8cf9734d2b | ||
|   | d70fbade44 | ||
|   | f0e515b6ff | ||
|   | 47ef40d645 | ||
|   | 9e10efbef5 | ||
|   | 224afbb9ca | ||
|   | bf69e6210c | ||
|   | 258482d509 | ||
|   | 1caad4ca88 | ||
|   | 59464a0ca4 | ||
|   | 93b25c87d6 | ||
|   | e971ef71d5 | ||
|   | bff5006b38 | ||
|   | ea571861d6 | ||
|   | f3e4263f51 | ||
|   | 77639d2d45 | ||
|   | 1662707f22 | ||
|   | 5f4bf2a5e5 | ||
|   | 40af18f96a | ||
|   | ab4fa30c7e | ||
|   | 43f73823ec | ||
|   | f4439fd2b6 | ||
|   | bf82aff039 | ||
|   | 4b535cee1f | ||
|   | 7144741537 | ||
|   | 64b872f363 | ||
|   | 83a2457550 | ||
|   | 43f895142c | ||
|   | c352b35bf1 | ||
|   | d4bba5c838 | ||
|   | 1dd9f94713 | ||
|   | c458e6612d | ||
|   | 45db339dcd | ||
|   | 887d8c8fe8 | ||
|   | c6932efa30 | ||
|   | f3ba82b0e1 | ||
|   | a26fcd8f2d | ||
|   | ab49b404fd | ||
|   | 9bc71b2853 | ||
|   | 5b104967ce | ||
|   | 4680c0c13b | ||
|   | c36358cc7c | ||
|   | 235f6e5f14 | ||
|   | 980e132f48 | ||
|   | 900aa5810e | ||
|   | 8cf83d6620 | ||
|   | ee52bc60ff | ||
|   | 3f4a29651e | ||
|   | f58cad8cd9 | ||
|   | b9c3feb198 | ||
|   | 94ebc9d90b | ||
|   | b0759ebbb2 | ||
|   | f2ac45eb52 | ||
|   | 7c5b40ff6b | ||
|   | 7465abe5f8 | ||
|   | a910a21beb | ||
|   | c40e665094 | ||
|   | bb57d61add | ||
|   | fc2965649c | ||
|   | fa25de4ad1 | ||
|   | 901305dcdd | ||
|   | b4a4495537 | ||
|   | ccd91fe556 | ||
|   | e89e4d692c | ||
|   | 0868418ad1 | ||
|   | d81de65533 | ||
|   | 65073ffcfa | ||
|   | d0b9fc84b5 | ||
|   | e0727bfeb6 | ||
|   | 8e10e00904 | ||
|   | a2b2f66c4c | ||
|   | ca9b7ccaf6 | ||
|   | 354bce64dd | ||
|   | ff4edacb42 | ||
|   | a6edf6f31e | ||
|   | 31787e3788 | ||
|   | e98a76aefb | ||
|   | 4a04d41409 | 
							
								
								
									
										3
									
								
								.codespellrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.codespellrc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| [codespell] | ||||
| ignore-words-list = fils, FILS, SME, assertIn, OCE, clen, aci, ELL, sav, ths | ||||
| skip = build-aux, linux, autom4te.cache, aclocal.m4, AUTHORS, libtool, configure*, *.5, ell | ||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -67,6 +67,8 @@ unit/test-band | ||||
| unit/test-dpp | ||||
| unit/test-json | ||||
| unit/test-nl80211util | ||||
| unit/test-pmksa | ||||
| unit/test-storage | ||||
| unit/cert-*.pem | ||||
| unit/cert-*.csr | ||||
| unit/cert-*.srl | ||||
|  | ||||
							
								
								
									
										45
									
								
								ChangeLog
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								ChangeLog
									
									
									
									
									
								
							| @ -1,3 +1,48 @@ | ||||
| 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. | ||||
| 	Fix issue with handling busy notification from Access Point. | ||||
| 	Fix issue with handling P-192, P-224 and P-521 for SAE. | ||||
| 
 | ||||
| ver 3.8: | ||||
| 	Fix issue with handling unit tests and missing kernel features. | ||||
| 
 | ||||
| ver 3.7: | ||||
| 	Fix issue with handling length of EncryptedSecurity. | ||||
| 	Fix issue with handling empty affinities lists. | ||||
| 	Fix issue with handling survey scanning results. | ||||
| 	Fix issue with handling duplicate values in DPP URI. | ||||
| 
 | ||||
| ver 3.6: | ||||
| 	Fix issue with handling blacklisting and roaming requests. | ||||
| 	Fix issue with handling CQM thresholds for FullMAC devices. | ||||
| 	Add support for PMKSA when using FullMAC devices. | ||||
| 
 | ||||
| ver 3.5: | ||||
| 	Add support for option to disable blacklist handling. | ||||
| 	Add support for option to disable SAE for broken drivers. | ||||
| 
 | ||||
| ver 3.4: | ||||
| 	Add support for the Test Anything Protocol. | ||||
| 
 | ||||
| ver 3.3: | ||||
| 	Fix issue with handling External Authentication. | ||||
| 
 | ||||
| ver 3.2: | ||||
| 	Fix issue with GCC 15 and -std=c23 build errors. | ||||
| 	Add support for using PMKSA over SAE if available. | ||||
| 	Add support for HighUtilization/StationCount thresholds. | ||||
| 	Add support for disabling Multicast RX option. | ||||
| 
 | ||||
| ver 3.1: | ||||
| 	Fix issue with handling OWE transition BSS selection. | ||||
| 	Fix issue with handling oper class 136 starting frequency. | ||||
| 
 | ||||
| ver 3.0: | ||||
| 	Fix issue with handling alpha2 code for United Kingdom. | ||||
| 	Fix issue with handling empty TX/RX bitrate attributes. | ||||
|  | ||||
							
								
								
									
										30
									
								
								Makefile.am
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								Makefile.am
									
									
									
									
									
								
							| @ -65,12 +65,15 @@ ell_headers = ell/util.h \ | ||||
| 			ell/cleanup.h \
 | ||||
| 			ell/netconfig.h \
 | ||||
| 			ell/sysctl.h \
 | ||||
| 			ell/notifylist.h | ||||
| 			ell/notifylist.h \
 | ||||
| 			ell/minheap.h | ||||
| 
 | ||||
| ell_sources = ell/private.h \
 | ||||
| 			ell/missing.h \
 | ||||
| 			ell/util.c \
 | ||||
| 			ell/test-private.h \
 | ||||
| 			ell/test.c \
 | ||||
| 			ell/test-dbus.c \
 | ||||
| 			ell/strv.c \
 | ||||
| 			ell/utf8.c \
 | ||||
| 			ell/queue.c \
 | ||||
| @ -147,7 +150,8 @@ ell_sources = ell/private.h \ | ||||
| 			ell/acd.c \
 | ||||
| 			ell/netconfig.c \
 | ||||
| 			ell/sysctl.c \
 | ||||
| 			ell/notifylist.c | ||||
| 			ell/notifylist.c \
 | ||||
| 			ell/minheap.c | ||||
| 
 | ||||
| ell_shared = ell/useful.h ell/asn1-private.h | ||||
| 
 | ||||
| @ -269,6 +273,9 @@ src_iwd_SOURCES = src/main.c linux/nl80211.h src/iwd.h \ | ||||
| 					src/json.h src/json.c \
 | ||||
| 					src/dpp.c \
 | ||||
| 					src/udev.c \
 | ||||
| 					src/pmksa.h src/pmksa.c \
 | ||||
| 					src/vendor_quirks.h \
 | ||||
| 					src/vendor_quirks.c \
 | ||||
| 					$(eap_sources) \
 | ||||
| 					$(builtin_sources) | ||||
| 
 | ||||
| @ -435,7 +442,8 @@ unit_tests += unit/test-cmac-aes \ | ||||
| 		unit/test-ie unit/test-util unit/test-ssid-security \
 | ||||
| 		unit/test-arc4 unit/test-wsc unit/test-eap-mschapv2 \
 | ||||
| 		unit/test-eap-sim unit/test-sae unit/test-p2p unit/test-band \
 | ||||
| 		unit/test-dpp unit/test-json unit/test-nl80211util | ||||
| 		unit/test-dpp unit/test-json unit/test-nl80211util \
 | ||||
| 		unit/test-pmksa unit/test-storage | ||||
| endif | ||||
| 
 | ||||
| if CLIENT | ||||
| @ -454,6 +462,7 @@ unit_test_eap_sim_SOURCES = unit/test-eap-sim.c \ | ||||
| 		src/eapol.h src/eapol.c \
 | ||||
| 		src/eapolutil.h src/eapolutil.c \
 | ||||
| 		src/handshake.h src/handshake.c \
 | ||||
| 		src/pmksa.h src/pmksa.c \
 | ||||
| 		src/eap.h src/eap.c src/eap-private.h \
 | ||||
| 		src/util.h src/util.c \
 | ||||
| 		src/simauth.h src/simauth.c \
 | ||||
| @ -513,6 +522,7 @@ unit_test_eapol_SOURCES = unit/test-eapol.c \ | ||||
| 				src/eapol.h src/eapol.c \
 | ||||
| 				src/eapolutil.h src/eapolutil.c \
 | ||||
| 				src/handshake.h src/handshake.c \
 | ||||
| 				src/pmksa.h src/pmksa.c \
 | ||||
| 				src/eap.h src/eap.c src/eap-private.h \
 | ||||
| 				src/eap-tls.c src/eap-ttls.c \
 | ||||
| 				src/eap-md5.c src/util.c \
 | ||||
| @ -543,6 +553,7 @@ unit_test_wsc_SOURCES = unit/test-wsc.c src/wscutil.h src/wscutil.c \ | ||||
| 				src/eapol.h src/eapol.c \
 | ||||
| 				src/eapolutil.h src/eapolutil.c \
 | ||||
| 				src/handshake.h src/handshake.c \
 | ||||
| 				src/pmksa.h src/pmksa.c \
 | ||||
| 				src/eap.h src/eap.c src/eap-private.h \
 | ||||
| 				src/util.h src/util.c \
 | ||||
| 				src/erp.h src/erp.c \
 | ||||
| @ -561,6 +572,7 @@ unit_test_sae_SOURCES = unit/test-sae.c \ | ||||
| 				src/crypto.h src/crypto.c \
 | ||||
| 				src/ie.h src/ie.c \
 | ||||
| 				src/handshake.h src/handshake.c \
 | ||||
| 				src/pmksa.h src/pmksa.c \
 | ||||
| 				src/erp.h src/erp.c \
 | ||||
| 				src/band.h src/band.c \
 | ||||
| 				src/util.h src/util.c \
 | ||||
| @ -591,6 +603,15 @@ unit_test_nl80211util_SOURCES = unit/test-nl80211util.c \ | ||||
| 				src/ie.h src/ie.c \
 | ||||
| 				src/util.h src/util.c | ||||
| unit_test_nl80211util_LDADD = $(ell_ldadd) | ||||
| 
 | ||||
| unit_test_pmksa_SOURCES = unit/test-pmksa.c src/pmksa.c src/pmksa.h \
 | ||||
| 				src/module.h src/util.h | ||||
| unit_test_pmksa_LDADD = $(ell_ldadd) | ||||
| 
 | ||||
| unit_test_storage_SOURCES = unit/test-storage.c src/storage.c src/storage.h \
 | ||||
| 				src/crypto.c src/crypto.h \
 | ||||
| 				src/common.c src/common.h | ||||
| unit_test_storage_LDADD = $(ell_ldadd) | ||||
| endif | ||||
| 
 | ||||
| if CLIENT | ||||
| @ -606,6 +627,9 @@ unit_test_client_SOURCES = unit/test-client.c \ | ||||
| unit_test_client_LDADD = $(ell_ldadd) $(client_ldadd) | ||||
| endif | ||||
| 
 | ||||
| LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) \
 | ||||
| 			$(top_srcdir)/build-aux/tap-driver.sh | ||||
| 
 | ||||
| TESTS = $(unit_tests) | ||||
| 
 | ||||
| EXTRA_DIST = src/genbuiltin src/iwd.service.in src/net.connman.iwd.service \
 | ||||
|  | ||||
							
								
								
									
										4
									
								
								TODO
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								TODO
									
									
									
									
									
								
							| @ -110,7 +110,7 @@ Wireless monitor | ||||
| 
 | ||||
| - Subscribe to all nl80211 multicast groups at startup | ||||
| 
 | ||||
|   It seems the nlmon packets are limited to actual subscribed mutlicast | ||||
|   It seems the nlmon packets are limited to actual subscribed multicast | ||||
|   groups.  To get a complete picture of all the nl80211 commands and | ||||
|   events, it is required that iwmon adds membership to all multicast | ||||
|   groups that the nl80211 lists. | ||||
| @ -234,7 +234,7 @@ Wireless daemon | ||||
| 
 | ||||
| - Implement Enrollee Session Overlap Detection after WSC Protocol Run | ||||
| 
 | ||||
|   WSC Best Practices v2.0.1, Section 3.15 describes an enhacement to detect | ||||
|   WSC Best Practices v2.0.1, Section 3.15 describes an enhancement to detect | ||||
|   PBC session overlaps.  The Enrollee is asked to perform an extra scan without | ||||
|   the PBC request in the ProbeRequest frames after EAP-WSC completes | ||||
|   successfully.  If another AP in PBC mode is found, then a SessionOverlap | ||||
|  | ||||
							
								
								
									
										156
									
								
								autotests/testAPRoam/bad_neighbor_report_test.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								autotests/testAPRoam/bad_neighbor_report_test.py
									
									
									
									
									
										Normal 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) | ||||
| @ -11,52 +11,58 @@ from iwd import NetworkType | ||||
| from hostapd import HostapdCLI | ||||
| 
 | ||||
| class Test(unittest.TestCase): | ||||
| 
 | ||||
|     def validate(self, expect_roam=True): | ||||
|         wd = IWD() | ||||
| 
 | ||||
|         devices = wd.list_devices(1) | ||||
|         device = devices[0] | ||||
| 
 | ||||
|         ordered_network = device.get_ordered_network('TestAPRoam') | ||||
|     def initial_connection(self): | ||||
|         ordered_network = self.device.get_ordered_network('TestAPRoam') | ||||
| 
 | ||||
|         self.assertEqual(ordered_network.type, NetworkType.psk) | ||||
| 
 | ||||
|         condition = 'not obj.connected' | ||||
|         wd.wait_for_object_condition(ordered_network.network_object, condition) | ||||
|         self.wd.wait_for_object_condition(ordered_network.network_object, condition) | ||||
| 
 | ||||
|         device.connect_bssid(self.bss_hostapd[0].bssid) | ||||
|         self.device.connect_bssid(self.bss_hostapd[0].bssid) | ||||
| 
 | ||||
|         condition = 'obj.state == DeviceState.connected' | ||||
|         wd.wait_for_object_condition(device, condition) | ||||
|         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()) | ||||
| 
 | ||||
|         self.bss_hostapd[0].send_bss_transition(device.address, | ||||
|                 [(self.bss_hostapd[1].bssid, '8f0000005102060603000000')], | ||||
|     def validate_roam(self, from_bss, to_bss, expect_roam=True): | ||||
|         from_bss.send_bss_transition(self.device.address, | ||||
|                 self.neighbor_list, | ||||
|                 disassoc_imminent=expect_roam) | ||||
| 
 | ||||
|         if expect_roam: | ||||
|             from_condition = 'obj.state == DeviceState.roaming' | ||||
|             to_condition = 'obj.state == DeviceState.connected' | ||||
|             wd.wait_for_object_change(device, from_condition, to_condition) | ||||
|             self.wd.wait_for_object_change(self.device, from_condition, to_condition) | ||||
| 
 | ||||
|             self.bss_hostapd[1].wait_for_event('AP-STA-CONNECTED %s' % device.address) | ||||
|             to_bss.wait_for_event('AP-STA-CONNECTED %s' % self.device.address) | ||||
|         else: | ||||
|             device.wait_for_event("no-roam-candidates") | ||||
| 
 | ||||
|         device.disconnect() | ||||
| 
 | ||||
|         condition = 'not obj.connected' | ||||
|         wd.wait_for_object_condition(ordered_network.network_object, condition) | ||||
|             self.device.wait_for_event("no-roam-candidates") | ||||
| 
 | ||||
|     def test_disassoc_imminent(self): | ||||
|         self.validate(expect_roam=True) | ||||
|         self.initial_connection() | ||||
|         self.validate_roam(self.bss_hostapd[0], self.bss_hostapd[1]) | ||||
| 
 | ||||
|     def test_no_candidates(self): | ||||
|         self.validate(expect_roam=False) | ||||
|         self.initial_connection() | ||||
|         # We now have BSS0 roam blacklisted | ||||
|         self.validate_roam(self.bss_hostapd[0], self.bss_hostapd[1]) | ||||
|         # Try and trigger another roam back, which shouldn't happen since now | ||||
|         # both BSS's are roam blacklisted | ||||
|         self.validate_roam(self.bss_hostapd[1], self.bss_hostapd[0], expect_roam=False) | ||||
| 
 | ||||
|     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 | ||||
| 
 | ||||
|     @classmethod | ||||
|     def setUpClass(cls): | ||||
| @ -65,6 +71,10 @@ class Test(unittest.TestCase): | ||||
|         cls.bss_hostapd = [ HostapdCLI(config='ssid1.conf'), | ||||
|                             HostapdCLI(config='ssid2.conf'), | ||||
|                             HostapdCLI(config='ssid3.conf') ] | ||||
|         cls.neighbor_list = [ | ||||
|             (cls.bss_hostapd[0].bssid, "8f0000005101060603000000"), | ||||
|             (cls.bss_hostapd[1].bssid, "8f0000005102060603000000"), | ||||
|         ] | ||||
| 
 | ||||
|     @classmethod | ||||
|     def tearDownClass(cls): | ||||
|  | ||||
| @ -1,5 +1,7 @@ | ||||
| [SETUP] | ||||
| num_radios=4 | ||||
| hwsim_medium=true | ||||
| start_iwd=false | ||||
| 
 | ||||
| [HOSTAPD] | ||||
| rad0=ssid1.conf | ||||
|  | ||||
							
								
								
									
										6
									
								
								autotests/testAPRoam/main.conf.roaming
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								autotests/testAPRoam/main.conf.roaming
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| [General] | ||||
| RoamThreshold=-72 | ||||
| CriticalRoamThreshold=-72 | ||||
| 
 | ||||
| [Blacklist] | ||||
| InitialAccessPointBusyTimeout=20 | ||||
							
								
								
									
										183
									
								
								autotests/testAPRoam/roam_blacklist_test.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								autotests/testAPRoam/roam_blacklist_test.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,183 @@ | ||||
| #!/usr/bin/python3 | ||||
| 
 | ||||
| import unittest | ||||
| import sys | ||||
| 
 | ||||
| sys.path.append('../util') | ||||
| import iwd | ||||
| from iwd import IWD, IWD_CONFIG_DIR | ||||
| from iwd import NetworkType | ||||
| 
 | ||||
| from hostapd import HostapdCLI | ||||
| from hwsim import Hwsim | ||||
| 
 | ||||
| class Test(unittest.TestCase): | ||||
|     def validate_connected(self, hostapd): | ||||
|         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(hostapd.bssid) | ||||
| 
 | ||||
|         condition = 'obj.state == DeviceState.connected' | ||||
|         self.wd.wait_for_object_condition(self.device, condition) | ||||
| 
 | ||||
|         hostapd.wait_for_event('AP-STA-CONNECTED') | ||||
| 
 | ||||
|     def validate_ap_roamed(self, from_hostapd, to_hostapd): | ||||
|         from_hostapd.send_bss_transition( | ||||
|             self.device.address, self.neighbor_list, disassoc_imminent=True | ||||
|         ) | ||||
| 
 | ||||
|         from_condition = 'obj.state == DeviceState.roaming' | ||||
|         to_condition = 'obj.state == DeviceState.connected' | ||||
|         self.wd.wait_for_object_change(self.device, from_condition, to_condition) | ||||
| 
 | ||||
|         to_hostapd.wait_for_event('AP-STA-CONNECTED %s' % self.device.address) | ||||
| 
 | ||||
|         self.device.wait_for_event("ap-roam-blacklist-added") | ||||
| 
 | ||||
|     def test_roam_to_optimal_candidates(self): | ||||
|         # In this test IWD will naturally transition down the list after each | ||||
|         # BSS gets roam blacklisted. All BSS's are above the RSSI thresholds. | ||||
|         self.rule_ssid1.signal = -5000 | ||||
|         self.rule_ssid2.signal = -6500 | ||||
|         self.rule_ssid3.signal = -6900 | ||||
| 
 | ||||
|         # Connect to BSS0 | ||||
|         self.validate_connected(self.bss_hostapd[0]) | ||||
| 
 | ||||
|         # AP directed roam to BSS1 | ||||
|         self.validate_ap_roamed(self.bss_hostapd[0], self.bss_hostapd[1]) | ||||
| 
 | ||||
|         # AP directed roam to BSS2 | ||||
|         self.validate_ap_roamed(self.bss_hostapd[1], self.bss_hostapd[2]) | ||||
| 
 | ||||
|     def test_avoiding_under_threshold_bss(self): | ||||
|         # In this test IWD will blacklist BSS0, then roam the BSS1. BSS1 will | ||||
|         # then tell IWD to roam, but it should go back to BSS0 since the only | ||||
|         # non-blacklisted BSS is under the roam threshold. | ||||
|         self.rule_ssid1.signal = -5000 | ||||
|         self.rule_ssid2.signal = -6500 | ||||
|         self.rule_ssid3.signal = -7300 | ||||
| 
 | ||||
|         # Connect to BSS0 | ||||
|         self.validate_connected(self.bss_hostapd[0]) | ||||
| 
 | ||||
|         # AP directed roam to BSS1 | ||||
|         self.validate_ap_roamed(self.bss_hostapd[0], self.bss_hostapd[1]) | ||||
| 
 | ||||
|         # AP directed roam, but IWD should choose BSS0 since BSS2 is -73dB | ||||
|         self.validate_ap_roamed(self.bss_hostapd[1], self.bss_hostapd[0]) | ||||
| 
 | ||||
|     def test_connect_to_roam_blacklisted_bss(self): | ||||
|         # In this test a BSS will be roam blacklisted, but all other options are | ||||
|         # below the RSSI threshold so IWD should roam back to the blacklisted | ||||
|         # BSS. | ||||
|         self.rule_ssid1.signal = -5000 | ||||
|         self.rule_ssid2.signal = -8000 | ||||
|         self.rule_ssid3.signal = -8500 | ||||
| 
 | ||||
|         # Connect to BSS0 | ||||
|         self.validate_connected(self.bss_hostapd[0]) | ||||
| 
 | ||||
|         # AP directed roam, should connect to BSS1 as its the next best | ||||
|         self.validate_ap_roamed(self.bss_hostapd[0], self.bss_hostapd[1]) | ||||
| 
 | ||||
|         # Connected to BSS1, but the signal is bad, so IWD should try to roam | ||||
|         # again. BSS0 is still blacklisted, but its the only reasonable option | ||||
|         # since both BSS1 and BSS2 are below the set RSSI threshold (-72dB) | ||||
| 
 | ||||
|         from_condition = 'obj.state == DeviceState.roaming' | ||||
|         to_condition = 'obj.state == DeviceState.connected' | ||||
|         self.wd.wait_for_object_change(self.device, from_condition, to_condition) | ||||
| 
 | ||||
|         # IWD should have connected to BSS0, even though its roam blacklisted | ||||
|         self.bss_hostapd[0].wait_for_event('AP-STA-CONNECTED %s' % self.device.address) | ||||
| 
 | ||||
|     def test_blacklist_during_roam_scan(self): | ||||
|         # Tests that an AP roam request mid-roam results in the AP still being | ||||
|         # blacklisted even though the request itself doesn't directly trigger | ||||
|         # a roam. | ||||
|         self.rule_ssid1.signal = -7300 | ||||
|         self.rule_ssid2.signal = -7500 | ||||
|         self.rule_ssid3.signal = -8500 | ||||
| 
 | ||||
|         # Connect to BSS0 under the roam threshold so IWD will immediately try | ||||
|         # roaming elsewhere | ||||
|         self.validate_connected(self.bss_hostapd[0]) | ||||
| 
 | ||||
|         self.device.wait_for_event("roam-scan-triggered") | ||||
| 
 | ||||
|         self.bss_hostapd[0].send_bss_transition( | ||||
|             self.device.address, self.neighbor_list, disassoc_imminent=True | ||||
|         ) | ||||
|         self.device.wait_for_event("ap-roam-blacklist-added") | ||||
| 
 | ||||
|         # BSS0 should have gotten blacklisted even though IWD was mid-roam, | ||||
|         # causing IWD to choose BSS1 when it gets is results. | ||||
| 
 | ||||
|         from_condition = 'obj.state == DeviceState.roaming' | ||||
|         to_condition = 'obj.state == DeviceState.connected' | ||||
|         self.wd.wait_for_object_change(self.device, from_condition, to_condition) | ||||
| 
 | ||||
|         self.bss_hostapd[1].wait_for_event('AP-STA-CONNECTED %s' % self.device.address) | ||||
| 
 | ||||
|     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 | ||||
| 
 | ||||
| 
 | ||||
|     @classmethod | ||||
|     def setUpClass(cls): | ||||
|         IWD.copy_to_storage("main.conf.roaming", IWD_CONFIG_DIR, "main.conf") | ||||
|         IWD.copy_to_storage('TestAPRoam.psk') | ||||
|         hwsim = Hwsim() | ||||
| 
 | ||||
|         cls.bss_hostapd = [ HostapdCLI(config='ssid1.conf'), | ||||
|                             HostapdCLI(config='ssid2.conf'), | ||||
|                             HostapdCLI(config='ssid3.conf') ] | ||||
|         HostapdCLI.group_neighbors(*cls.bss_hostapd) | ||||
| 
 | ||||
|         rad0 = hwsim.get_radio('rad0') | ||||
|         rad1 = hwsim.get_radio('rad1') | ||||
|         rad2 = hwsim.get_radio('rad2') | ||||
| 
 | ||||
|         cls.neighbor_list = [ | ||||
|             (cls.bss_hostapd[0].bssid, "8f0000005101060603000000"), | ||||
|             (cls.bss_hostapd[1].bssid, "8f0000005102060603000000"), | ||||
|             (cls.bss_hostapd[2].bssid, "8f0000005103060603000000"), | ||||
|         ] | ||||
| 
 | ||||
| 
 | ||||
|         cls.rule_ssid1 = hwsim.rules.create() | ||||
|         cls.rule_ssid1.source = rad0.addresses[0] | ||||
|         cls.rule_ssid1.bidirectional = True | ||||
|         cls.rule_ssid1.enabled = True | ||||
| 
 | ||||
|         cls.rule_ssid2 = hwsim.rules.create() | ||||
|         cls.rule_ssid2.source = rad1.addresses[0] | ||||
|         cls.rule_ssid2.bidirectional = True | ||||
|         cls.rule_ssid2.enabled = True | ||||
| 
 | ||||
|         cls.rule_ssid3 = hwsim.rules.create() | ||||
|         cls.rule_ssid3.source = rad2.addresses[0] | ||||
|         cls.rule_ssid3.bidirectional = True | ||||
|         cls.rule_ssid3.enabled = True | ||||
| 
 | ||||
|     @classmethod | ||||
|     def tearDownClass(cls): | ||||
|         IWD.clear_storage() | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     unittest.main(exit=True) | ||||
							
								
								
									
										2
									
								
								autotests/testBSSBlacklist/TestBlacklist.psk
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								autotests/testBSSBlacklist/TestBlacklist.psk
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| [Security] | ||||
| Passphrase=secret123 | ||||
| @ -260,12 +260,69 @@ class Test(unittest.TestCase): | ||||
| 
 | ||||
|         self.wd.unregister_psk_agent(psk_agent) | ||||
| 
 | ||||
|     def test_blacklist_disabled(self): | ||||
|         wd = self.wd | ||||
|         bss_hostapd = self.bss_hostapd | ||||
| 
 | ||||
|         rule0 = self.rule0 | ||||
|         rule1 = self.rule1 | ||||
|         rule2 = self.rule2 | ||||
| 
 | ||||
|         psk_agent = PSKAgent(["secret123", 'secret123']) | ||||
|         wd.register_psk_agent(psk_agent) | ||||
| 
 | ||||
|         devices = wd.list_devices(1) | ||||
|         device = devices[0] | ||||
| 
 | ||||
|         rule0.drop = True | ||||
|         rule0.enabled = True | ||||
| 
 | ||||
|         device.autoconnect = True | ||||
| 
 | ||||
|         condition = 'obj.state == DeviceState.connected' | ||||
|         wd.wait_for_object_condition(device, condition) | ||||
| 
 | ||||
|         ordered_network = device.get_ordered_network("TestBlacklist", full_scan=True) | ||||
| 
 | ||||
|         self.assertEqual(ordered_network.type, NetworkType.psk) | ||||
| 
 | ||||
|         # The first BSS should fail, and we should connect to the second. This | ||||
|         # should not result in a connection blacklist though since its disabled. | ||||
|         bss_hostapd[1].wait_for_event('AP-STA-CONNECTED %s' % device.address) | ||||
| 
 | ||||
|         device.disconnect() | ||||
| 
 | ||||
|         rule0.drop = False | ||||
|         device.autoconnect = True | ||||
| 
 | ||||
|         # Verify the first BSS wasn't blacklisted. | ||||
|         bss_hostapd[0].wait_for_event('AP-STA-CONNECTED %s' % device.address) | ||||
| 
 | ||||
|     def setUp(self): | ||||
|         _, _, name = self.id().split(".") | ||||
| 
 | ||||
|         # TODO: If we have this pattern elsewhere it might be nice to turn this | ||||
|         # into a decorator e.g. | ||||
|         # | ||||
|         # @config("main.conf.disabled") | ||||
|         # @profile("TestBlacklist.psk") | ||||
|         # def test_blacklist_disabled(self) | ||||
|         #    ... | ||||
|         # | ||||
|         if name == "test_blacklist_disabled": | ||||
|             IWD.copy_to_storage("main.conf.disabled", IWD_CONFIG_DIR, "main.conf") | ||||
|             IWD.copy_to_storage("TestBlacklist.psk") | ||||
|         else: | ||||
|             IWD.copy_to_storage("main.conf.default", IWD_CONFIG_DIR, "main.conf") | ||||
| 
 | ||||
|         self.wd = IWD(True) | ||||
| 
 | ||||
|     def tearDown(self): | ||||
|         IWD.clear_storage() | ||||
|         self.wd = None | ||||
|         self.rule0.drop = False | ||||
|         self.rule1.drop = False | ||||
|         self.rule2.drop = False | ||||
| 
 | ||||
|     @classmethod | ||||
|     def setUpClass(cls): | ||||
|  | ||||
							
								
								
									
										2
									
								
								autotests/testBSSBlacklist/main.conf.disabled
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								autotests/testBSSBlacklist/main.conf.disabled
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| [Blacklist] | ||||
| InitialTimeout=0 | ||||
| @ -11,7 +11,7 @@ from iwd import IWD | ||||
| 
 | ||||
| class Test8021xNetwork(unittest.TestCase): | ||||
|     ''' | ||||
|     The bellow test cases excesise the following connection scenarios: | ||||
|     The below test cases excesise the following connection scenarios: | ||||
| 
 | ||||
|     Network config is | ||||
|     present at start time:  Connect:  AutoConnect:  Result: | ||||
|  | ||||
| @ -11,7 +11,7 @@ from iwd import IWD | ||||
| 
 | ||||
| class TestOpenNetwork(unittest.TestCase): | ||||
|     ''' | ||||
|     The bellow test cases excesise the following connection scenarios: | ||||
|     The below test cases excesise the following connection scenarios: | ||||
| 
 | ||||
|     Network config is | ||||
|     present at start time:  Connect:  AutoConnect:  Result: | ||||
|  | ||||
| @ -11,7 +11,7 @@ from iwd import IWD | ||||
| 
 | ||||
| class TestWpaNetwork(unittest.TestCase): | ||||
|     ''' | ||||
|     The bellow test cases exercise the following connection scenarios: | ||||
|     The below test cases exercise the following connection scenarios: | ||||
| 
 | ||||
|     Network config is | ||||
|     present at start time:  Connect:  AutoConnect:  Result: | ||||
|  | ||||
| @ -104,7 +104,7 @@ class Test(unittest.TestCase): | ||||
| 
 | ||||
|         self.assertTrue(self.profile_is_encrypted('ssidCCMP.psk')) | ||||
| 
 | ||||
|     # Tests that a profile that doesn't decrypt wont become a known network | ||||
|     # Tests that a profile that doesn't decrypt won't become a known network | ||||
|     def test_decryption_failure(self): | ||||
|         bad_config = \ | ||||
| ''' | ||||
|  | ||||
| @ -3,7 +3,11 @@ import sys | ||||
| import sys | ||||
| import os | ||||
| from scapy.layers.dot11 import * | ||||
| from scapy.arch import str2mac, get_if_raw_hwaddr | ||||
| from scapy.arch import str2mac | ||||
| try: | ||||
|     from scapy.arch import get_if_raw_hwaddr | ||||
| except: | ||||
|     from scapy.arch.unix import get_if_raw_hwaddr | ||||
| from time import time, sleep | ||||
| from threading import Thread | ||||
| 
 | ||||
|  | ||||
| @ -12,7 +12,7 @@ from hostapd import HostapdCLI | ||||
| 
 | ||||
| class TestWpaNetwork(unittest.TestCase): | ||||
|     ''' | ||||
|     The bellow test cases excesise the following connection scenarios: | ||||
|     The below test cases excesise the following connection scenarios: | ||||
| 
 | ||||
|     Network config is | ||||
|     present at start time:  Connect:  AutoConnect:  Result: | ||||
|  | ||||
| @ -12,7 +12,7 @@ from hostapd import HostapdCLI | ||||
| 
 | ||||
| class TestOpenNetwork(unittest.TestCase): | ||||
|     ''' | ||||
|     The bellow test cases excesise the following connection scenarios: | ||||
|     The below test cases excesise the following connection scenarios: | ||||
| 
 | ||||
|     Network config is | ||||
|     present at start time:  Connect:  AutoConnect:  Result: | ||||
|  | ||||
| @ -12,7 +12,7 @@ from hostapd import HostapdCLI | ||||
| 
 | ||||
| class TestWpaNetwork(unittest.TestCase): | ||||
|     ''' | ||||
|     The bellow test cases excesise the following connection scenarios: | ||||
|     The below test cases excesise the following connection scenarios: | ||||
| 
 | ||||
|     Network config is | ||||
|     present at start time:  Connect:  AutoConnect:  Result: | ||||
|  | ||||
| @ -11,7 +11,7 @@ from iwd import NetworkType | ||||
| 
 | ||||
| class TestMFP(unittest.TestCase): | ||||
|     ''' | ||||
|     The bellow test cases excesise the following MFP option setting scenarios: | ||||
|     The below test cases excesise the following MFP option setting scenarios: | ||||
| 
 | ||||
|     IWD_MFP: AP_MFP:  Result: | ||||
|     0        0        No MFP, connection succeeds | ||||
|  | ||||
| @ -137,7 +137,7 @@ class Test(unittest.TestCase): | ||||
|         # since (T2 - T1) / 2 is shorter than 60s.  It is now about 10s since the last | ||||
|         # renewal or 5s before the next DHCPREQUEST frame that is going to be lost.  We'll | ||||
|         # wait T1 seconds, so until about 10s after the failed attempt, we'll check that | ||||
|         # there was no renewal by that time, just in case, and we'll reenable frame delivery. | ||||
|         # there was no renewal by that time, just in case, and we'll re-enable frame delivery. | ||||
|         # We'll then wait another 60s and we should see the lease has been successfully | ||||
|         # renewed some 10 seconds earlier on the 1st DHCPREQUEST retransmission. | ||||
|         # | ||||
|  | ||||
| @ -91,16 +91,11 @@ class Test(unittest.TestCase): | ||||
|         # using the same static config.  The new client's ACD client should | ||||
|         # detect an IP conflict and not allow the device to reach the | ||||
|         # "connected" state although the DBus .Connect call will succeed. | ||||
|         ordered_network.network_object.connect() | ||||
|         self.assertEqual(dev2.state, iwd.DeviceState.connecting) | ||||
|         try: | ||||
|             # We should either stay in "connecting" indefinitely or move to | ||||
|             # "disconnecting" | ||||
|             condition = 'obj.state != DeviceState.connecting' | ||||
|             iwd_ns0_1.wait_for_object_condition(dev2, condition, max_wait=21) | ||||
|             self.assertEqual(dev2.state, iwd.DeviceState.disconnecting) | ||||
|         except TimeoutError: | ||||
|             dev2.disconnect() | ||||
|         with self.assertRaises(iwd.FailedEx): | ||||
|             ordered_network.network_object.connect(timeout=500) | ||||
| 
 | ||||
|         condition = 'obj.state == DeviceState.disconnected' | ||||
|         iwd_ns0_1.wait_for_object_condition(dev2, condition, max_wait=21) | ||||
| 
 | ||||
|         iwd_ns0_1.unregister_psk_agent(psk_agent_ns0_1) | ||||
|         del dev2 | ||||
|  | ||||
| @ -8,6 +8,8 @@ from iwd import PSKAgent | ||||
| from iwd import NetworkType | ||||
| 
 | ||||
| class Test(unittest.TestCase): | ||||
|     def connect_failure(self, ex): | ||||
|         self.failure_triggered = True | ||||
| 
 | ||||
|     def test_netconfig_timeout(self): | ||||
|         IWD.copy_to_storage('autoconnect.psk', name='ap-ns1.psk') | ||||
| @ -27,23 +29,34 @@ class Test(unittest.TestCase): | ||||
|         condition = 'not obj.connected' | ||||
|         wd.wait_for_object_condition(ordered_network.network_object, condition) | ||||
| 
 | ||||
|         ordered_network.network_object.connect() | ||||
|         self.failure_triggered = False | ||||
| 
 | ||||
|         condition = 'obj.state == DeviceState.connecting' | ||||
|         # Set our error handler here so we can check if it fails | ||||
|         ordered_network.network_object.connect( | ||||
|             wait=False, | ||||
|             timeout=1000, | ||||
|             error_handler=self.connect_failure | ||||
|         ) | ||||
| 
 | ||||
|         # IWD should attempt to try both BSS's with both failing netconfig. | ||||
|         # Then the autoconnect list should be exhausted, and IWD should | ||||
|         # transition to a disconnected state, then proceed to full autoconnect. | ||||
|         device.wait_for_event("netconfig-failed", timeout=1000) | ||||
|         device.wait_for_event("netconfig-failed", timeout=1000) | ||||
|         device.wait_for_event("disconnected") | ||||
| 
 | ||||
|         device.wait_for_event("autoconnect_full") | ||||
| 
 | ||||
|         # The connect call should have failed | ||||
|         self.assertTrue(self.failure_triggered) | ||||
| 
 | ||||
|         condition = "obj.scanning" | ||||
|         wd.wait_for_object_condition(device, condition) | ||||
|         condition = "not obj.scanning" | ||||
|         wd.wait_for_object_condition(device, condition) | ||||
| 
 | ||||
|         device.wait_for_event("connecting (netconfig)") | ||||
| 
 | ||||
|         # Netconfig should fail, and IWD should disconnect | ||||
|         from_condition = 'obj.state == DeviceState.connecting' | ||||
|         to_condition = 'obj.state == DeviceState.disconnecting' | ||||
|         wd.wait_for_object_change(device, from_condition, to_condition, max_wait=60) | ||||
| 
 | ||||
|         # Autoconnect should then try again | ||||
|         condition = 'obj.state == DeviceState.connecting' | ||||
|         wd.wait_for_object_condition(device, condition) | ||||
| 
 | ||||
|         device.wait_for_event("connecting (netconfig)") | ||||
|         # IWD should attempt to connect, but it will of course fail again. | ||||
|         device.wait_for_event("netconfig-failed", timeout=1000) | ||||
| 
 | ||||
|         device.disconnect() | ||||
|         condition = 'obj.state == DeviceState.disconnected' | ||||
|  | ||||
| @ -19,7 +19,7 @@ class Test(unittest.TestCase): | ||||
| 
 | ||||
|         device = wd.list_devices(1)[0] | ||||
|         device.get_ordered_network('TestFT', full_scan=True) | ||||
|         device.connect_bssid(self.bss_hostapd[1].bssid) | ||||
|         device.connect_bssid(self.bss_hostapd[1].bssid, wait=False) | ||||
| 
 | ||||
|         self.bss_hostapd[1].wait_for_event(f'AP-STA-CONNECTED {device.address}') | ||||
|         device.wait_for_event("connecting (netconfig)") | ||||
|  | ||||
							
								
								
									
										114
									
								
								autotests/testPMKSA-SAE/connection_test.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								autotests/testPMKSA-SAE/connection_test.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,114 @@ | ||||
| #!/usr/bin/python3 | ||||
| 
 | ||||
| import unittest | ||||
| import sys | ||||
| 
 | ||||
| sys.path.append('../util') | ||||
| from iwd import IWD | ||||
| from iwd import PSKAgent | ||||
| from iwd import NetworkType | ||||
| from hostapd import HostapdCLI | ||||
| import testutil | ||||
| 
 | ||||
| class Test(unittest.TestCase): | ||||
| 
 | ||||
|     def validate_connection(self, wd, ssid, hostapd, expected_group): | ||||
|         psk_agent = PSKAgent("secret123") | ||||
|         wd.register_psk_agent(psk_agent) | ||||
| 
 | ||||
|         devices = wd.list_devices(1) | ||||
|         self.assertIsNotNone(devices) | ||||
|         device = devices[0] | ||||
| 
 | ||||
|         device.disconnect() | ||||
| 
 | ||||
|         network = device.get_ordered_network(ssid, full_scan=True) | ||||
| 
 | ||||
|         self.assertEqual(network.type, NetworkType.psk) | ||||
| 
 | ||||
|         network.network_object.connect() | ||||
| 
 | ||||
|         condition = 'obj.state == DeviceState.connected' | ||||
|         wd.wait_for_object_condition(device, condition) | ||||
| 
 | ||||
|         wd.wait(2) | ||||
| 
 | ||||
|         testutil.test_iface_operstate(intf=device.name) | ||||
|         testutil.test_ifaces_connected(if0=device.name, if1=hostapd.ifname) | ||||
| 
 | ||||
|         # Initial connection PMKSA should not be used. So we should see the | ||||
|         # SAE group set. | ||||
|         sta_status = hostapd.sta_status(device.address) | ||||
|         self.assertEqual(int(sta_status["sae_group"]), expected_group) | ||||
| 
 | ||||
|         device.disconnect() | ||||
| 
 | ||||
|         condition = 'not obj.connected' | ||||
|         wd.wait_for_object_condition(network.network_object, condition) | ||||
| 
 | ||||
|         wd.unregister_psk_agent(psk_agent) | ||||
| 
 | ||||
|         network.network_object.connect(wait=False) | ||||
| 
 | ||||
|         condition = 'obj.state == DeviceState.connected' | ||||
|         wd.wait_for_object_condition(device, condition) | ||||
| 
 | ||||
|         wd.wait(2) | ||||
| 
 | ||||
|         testutil.test_iface_operstate(intf=device.name) | ||||
|         testutil.test_ifaces_connected(if0=device.name, if1=hostapd.ifname) | ||||
| 
 | ||||
|         # Having connected once prior we should have a PMKSA and SAE should not | ||||
|         # have been used. | ||||
|         sta_status = hostapd.sta_status(device.address) | ||||
|         self.assertNotIn("sae_group", sta_status.keys()) | ||||
| 
 | ||||
|         device.disconnect() | ||||
| 
 | ||||
|         condition = 'not obj.connected' | ||||
|         wd.wait_for_object_condition(network.network_object, condition) | ||||
| 
 | ||||
|         hostapd.pmksa_flush() | ||||
| 
 | ||||
|         wd.wait(5) | ||||
| 
 | ||||
|         network.network_object.connect() | ||||
| 
 | ||||
|         device.wait_for_event("pmksa-invalid-pmkid") | ||||
| 
 | ||||
|         condition = 'obj.state == DeviceState.connected' | ||||
|         wd.wait_for_object_condition(device, condition) | ||||
| 
 | ||||
|         wd.wait(2) | ||||
| 
 | ||||
|         testutil.test_iface_operstate(intf=device.name) | ||||
|         testutil.test_ifaces_connected(if0=device.name, if1=hostapd.ifname) | ||||
| 
 | ||||
|         # Manually flushing the PMKSA from the AP then reconnecting we should | ||||
|         # have failed (INVALID_PMKID) then retried the same BSS with SAE, not | ||||
|         # PMKSA. | ||||
|         sta_status = hostapd.sta_status(device.address) | ||||
|         self.assertEqual(int(sta_status["sae_group"]), expected_group) | ||||
| 
 | ||||
|     def test_pmksa_sae(self): | ||||
|         self.hostapd.wait_for_event("AP-ENABLED") | ||||
|         self.validate_connection(self.wd, "ssidSAE", self.hostapd, 19) | ||||
| 
 | ||||
|     def setUp(self): | ||||
|         self.hostapd.default() | ||||
|         self.wd = IWD(True) | ||||
| 
 | ||||
|     def tearDown(self): | ||||
|         self.wd.clear_storage() | ||||
|         self.wd = None | ||||
| 
 | ||||
|     @classmethod | ||||
|     def setUpClass(cls): | ||||
|         cls.hostapd = HostapdCLI(config='ssidSAE.conf') | ||||
| 
 | ||||
|     @classmethod | ||||
|     def tearDownClass(cls): | ||||
|         pass | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     unittest.main(exit=True) | ||||
							
								
								
									
										7
									
								
								autotests/testPMKSA-SAE/hw.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								autotests/testPMKSA-SAE/hw.conf
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| [SETUP] | ||||
| num_radios=2 | ||||
| start_iwd=0 | ||||
| hwsim_medium=yes | ||||
| 
 | ||||
| [HOSTAPD] | ||||
| rad0=ssidSAE.conf | ||||
							
								
								
									
										12
									
								
								autotests/testPMKSA-SAE/ssidSAE.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								autotests/testPMKSA-SAE/ssidSAE.conf
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| hw_mode=g | ||||
| channel=1 | ||||
| ssid=ssidSAE | ||||
| 
 | ||||
| wpa=2 | ||||
| wpa_key_mgmt=SAE | ||||
| wpa_pairwise=CCMP | ||||
| sae_password=secret123 | ||||
| sae_groups=19 | ||||
| ieee80211w=2 | ||||
| sae_pwe=0 | ||||
| rsn_preauth=1 | ||||
							
								
								
									
										114
									
								
								autotests/testPSK-roam/chan_switch_test.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								autotests/testPSK-roam/chan_switch_test.py
									
									
									
									
									
										Normal 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 | ||||
| @ -17,7 +17,7 @@ from hwsim import Hwsim | ||||
| class Test(unittest.TestCase): | ||||
|     # Normally the time between a failed roam attempt and the next roam attempt | ||||
|     # is 60 seconds (default RoamRetryInterval). Test that we retry roaming | ||||
|     # faster if the transision looks like this: LOW [roam] [same bss] HIGH LOW. | ||||
|     # faster if the transition looks like this: LOW [roam] [same bss] HIGH LOW. | ||||
|     def test_fast_retry(self): | ||||
|         hwsim = Hwsim() | ||||
| 
 | ||||
|  | ||||
| @ -15,7 +15,7 @@ from hostapd import HostapdCLI | ||||
| from hwsim import Hwsim | ||||
| 
 | ||||
| class Test(unittest.TestCase): | ||||
|     # Test that we do not periodically retry roaming if the transision looks | ||||
|     # Test that we do not periodically retry roaming if the transition looks | ||||
|     # like this: LOW [roam] [new bss] HIGH. | ||||
|     def test_stop_retry(self): | ||||
|         hwsim = Hwsim() | ||||
|  | ||||
| @ -13,7 +13,7 @@ import testutil | ||||
| from config import ctx | ||||
| 
 | ||||
| class Test(unittest.TestCase): | ||||
|     def validate_connection(self, wd, ft=True): | ||||
|     def validate_connection(self, wd, ft=True, check_used_pmksa=False): | ||||
|         device = wd.list_devices(1)[0] | ||||
| 
 | ||||
|         # This won't guarantee all BSS's are found, but at least ensures that | ||||
| @ -37,6 +37,14 @@ class Test(unittest.TestCase): | ||||
|         self.assertRaises(Exception, testutil.test_ifaces_connected, | ||||
|                           (self.bss_hostapd[1].ifname, device.name, True, True)) | ||||
| 
 | ||||
|         # If PMKSA was used, hostapd should not include the sae_group key in | ||||
|         # its status for the station. | ||||
|         sta_status = self.bss_hostapd[0].sta_status(device.address) | ||||
|         if check_used_pmksa: | ||||
|             self.assertNotIn("sae_group", sta_status.keys()) | ||||
|         else: | ||||
|             self.assertIn("sae_group", sta_status.keys()) | ||||
| 
 | ||||
|         device.roam(self.bss_hostapd[1].bssid) | ||||
| 
 | ||||
|         # Check that iwd is on BSS 1 once out of roaming state and doesn't | ||||
| @ -88,6 +96,31 @@ class Test(unittest.TestCase): | ||||
| 
 | ||||
|         self.validate_connection(wd, True) | ||||
| 
 | ||||
|     def test_ft_roam_pmksa(self): | ||||
|         wd = IWD(True) | ||||
| 
 | ||||
|         self.bss_hostapd[0].set_value('wpa_key_mgmt', 'FT-SAE SAE') | ||||
|         self.bss_hostapd[0].reload() | ||||
|         self.bss_hostapd[0].wait_for_event("AP-ENABLED") | ||||
|         self.bss_hostapd[1].set_value('wpa_key_mgmt', 'FT-SAE SAE') | ||||
|         self.bss_hostapd[1].reload() | ||||
|         self.bss_hostapd[1].wait_for_event("AP-ENABLED") | ||||
|         self.bss_hostapd[2].set_value('wpa_key_mgmt', 'FT-PSK') | ||||
|         self.bss_hostapd[2].reload() | ||||
|         self.bss_hostapd[2].wait_for_event("AP-ENABLED") | ||||
| 
 | ||||
|         self.validate_connection(wd, True) | ||||
| 
 | ||||
|         device = wd.list_devices(1)[0] | ||||
|         device.disconnect() | ||||
| 
 | ||||
|         for hapd in self.bss_hostapd: | ||||
|             hapd.deauthenticate(device.address) | ||||
| 
 | ||||
|         wd.wait(5) | ||||
| 
 | ||||
|         self.validate_connection(wd, True, check_used_pmksa=True) | ||||
| 
 | ||||
|     def test_reassociate_roam_success(self): | ||||
|         wd = IWD(True) | ||||
| 
 | ||||
| @ -103,6 +136,31 @@ class Test(unittest.TestCase): | ||||
| 
 | ||||
|         self.validate_connection(wd, False) | ||||
| 
 | ||||
|     def test_reassociate_roam_pmksa(self): | ||||
|         wd = IWD(True) | ||||
| 
 | ||||
|         self.bss_hostapd[0].set_value('wpa_key_mgmt', 'SAE') | ||||
|         self.bss_hostapd[0].reload() | ||||
|         self.bss_hostapd[0].wait_for_event("AP-ENABLED") | ||||
|         self.bss_hostapd[1].set_value('wpa_key_mgmt', 'SAE') | ||||
|         self.bss_hostapd[1].reload() | ||||
|         self.bss_hostapd[1].wait_for_event("AP-ENABLED") | ||||
|         self.bss_hostapd[2].set_value('wpa_key_mgmt', 'WPA-PSK') | ||||
|         self.bss_hostapd[2].reload() | ||||
|         self.bss_hostapd[2].wait_for_event("AP-ENABLED") | ||||
| 
 | ||||
|         self.validate_connection(wd, False) | ||||
| 
 | ||||
|         device = wd.list_devices(1)[0] | ||||
|         device.disconnect() | ||||
| 
 | ||||
|         for hapd in self.bss_hostapd: | ||||
|             hapd.deauthenticate(device.address) | ||||
| 
 | ||||
|         wd.wait(5) | ||||
| 
 | ||||
|         self.validate_connection(wd, False, check_used_pmksa=True) | ||||
| 
 | ||||
|     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') | ||||
|  | ||||
| @ -4,3 +4,6 @@ | ||||
| # hardware, but fails when used in simulated environment with mac80211_hwsim. | ||||
| # Disable MAC randomization for the tests with hidden networks. | ||||
| DisableMacAddressRandomization=true | ||||
| 
 | ||||
| [General] | ||||
| DisablePMKSA=true | ||||
|  | ||||
| @ -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 = {} | ||||
| @ -368,3 +370,7 @@ class HostapdCLI(object): | ||||
|             others = [h for h in args if h != hapd] | ||||
| 
 | ||||
|             hapd._add_neighbors(*others) | ||||
| 
 | ||||
|     def pmksa_flush(self): | ||||
|         cmd = self.cmdline + ['pmksa_flush'] | ||||
|         ctx.start_process(cmd).wait() | ||||
|  | ||||
| @ -7,7 +7,10 @@ from weakref import WeakValueDictionary | ||||
| from abc import ABCMeta, abstractmethod | ||||
| from enum import Enum | ||||
| from scapy.all import * | ||||
| from scapy.contrib.wpa_eapol import WPA_key | ||||
| try: | ||||
|     from scapy.contrib.wpa_eapol import WPA_key | ||||
| except: | ||||
|     from scapy.layers.eap import EAPOL_KEY | ||||
| 
 | ||||
| import iwd | ||||
| from config import ctx | ||||
| @ -444,9 +447,15 @@ class Hwsim(iwd.AsyncOpAbstract): | ||||
| 
 | ||||
|         # NOTE: Expected key_info is 0x008a, with the install flag | ||||
|         # this becomes 0x00ca. | ||||
|         eapol = WPA_key( descriptor_type = 2, | ||||
|                         key_info = 0x00ca, # Includes an invalid install flag! | ||||
|                         replay_counter = struct.pack(">Q", 100)) | ||||
|         try: | ||||
|             eapol = WPA_key( descriptor_type = 2, | ||||
|                             key_info = 0x00ca, # Includes an invalid install flag! | ||||
|                             replay_counter = struct.pack(">Q", 100)) | ||||
|         except: | ||||
|             eapol = EAPOL_KEY( key_descriptor_type = 2, | ||||
|                             install = 1, | ||||
|                             key_ack = 1, | ||||
|                             key_replay_counter = 1) | ||||
|         frame /= LLC()/SNAP()/EAPOL(version="802.1X-2004", type="EAPOL-Key") | ||||
|         frame /= eapol | ||||
| 
 | ||||
|  | ||||
| @ -112,8 +112,8 @@ class AsyncOpAbstract(object): | ||||
|         self._is_completed = True | ||||
|         self._exception = _convert_dbus_ex(ex) | ||||
| 
 | ||||
|     def _wait_for_async_op(self): | ||||
|         ctx.non_block_wait(lambda s: s._is_completed, 30, self, exception=None) | ||||
|     def _wait_for_async_op(self, timeout=50): | ||||
|         ctx.non_block_wait(lambda s: s._is_completed, timeout, self, exception=None) | ||||
| 
 | ||||
|         self._is_completed = False | ||||
|         if self._exception is not None: | ||||
| @ -280,8 +280,15 @@ class StationDebug(IWDDBusAbstract): | ||||
|     def autoconnect(self): | ||||
|         return self._properties['AutoConnect'] | ||||
| 
 | ||||
|     def connect_bssid(self, address): | ||||
|         self._iface.ConnectBssid(dbus.ByteArray.fromhex(address.replace(':', ''))) | ||||
|     def connect_bssid(self, address, wait=True): | ||||
|         self._iface.ConnectBssid( | ||||
|             dbus.ByteArray.fromhex(address.replace(':', '')), | ||||
|             reply_handler=self._success, | ||||
|             error_handler=self._failure | ||||
|         ) | ||||
| 
 | ||||
|         if wait: | ||||
|             self._wait_for_async_op() | ||||
| 
 | ||||
|     def roam(self, address): | ||||
|         self._iface.Roam(dbus.ByteArray.fromhex(address.replace(':', ''))) | ||||
| @ -450,13 +457,15 @@ class Device(IWDDBusAbstract): | ||||
|         self._wps_manager_if = None | ||||
|         self._station_if = None | ||||
|         self._station_props = None | ||||
|         self._station_debug_obj = None | ||||
|         self._dpp_obj = None | ||||
|         self._sc_dpp_obj = None | ||||
|         self._ap_obj = None | ||||
| 
 | ||||
|         IWDDBusAbstract.__init__(self, *args, **kwargs) | ||||
| 
 | ||||
|         self._station_debug_obj = StationDebug(object_path=self._object_path, | ||||
|                                                     namespace=self._namespace) | ||||
| 
 | ||||
|     @property | ||||
|     def _wps_manager(self): | ||||
|         if self._wps_manager_if is None: | ||||
| @ -868,8 +877,8 @@ class Device(IWDDBusAbstract): | ||||
|     def stop_adhoc(self): | ||||
|         self._prop_proxy.Set(IWD_DEVICE_INTERFACE, 'Mode', 'station') | ||||
| 
 | ||||
|     def connect_bssid(self, address): | ||||
|         self._station_debug.connect_bssid(address) | ||||
|     def connect_bssid(self, address, wait=True): | ||||
|         self._station_debug.connect_bssid(address, wait=wait) | ||||
| 
 | ||||
|     def roam(self, address): | ||||
|         self._station_debug.roam(address) | ||||
| @ -997,7 +1006,7 @@ class Network(IWDDBusAbstract): | ||||
|     def extended_service_set(self): | ||||
|         return self._properties['ExtendedServiceSet'] | ||||
| 
 | ||||
|     def connect(self, wait=True): | ||||
|     def connect(self, wait=True, timeout=50, reply_handler=None, error_handler=None): | ||||
|         ''' | ||||
|             Connect to the network. Request the device implied by the object | ||||
|             path to connect to specified network. | ||||
| @ -1012,12 +1021,19 @@ class Network(IWDDBusAbstract): | ||||
|             @rtype: void | ||||
|         ''' | ||||
| 
 | ||||
|         if not reply_handler: | ||||
|             reply_handler = self._success | ||||
| 
 | ||||
|         if not error_handler: | ||||
|             error_handler = self._failure | ||||
| 
 | ||||
|         self._iface.Connect(dbus_interface=self._iface_name, | ||||
|                             reply_handler=self._success, | ||||
|                             error_handler=self._failure) | ||||
|                             reply_handler=reply_handler, | ||||
|                             error_handler=error_handler, | ||||
|                             timeout=timeout) | ||||
| 
 | ||||
|         if wait: | ||||
|             self._wait_for_async_op() | ||||
|             self._wait_for_async_op(timeout=timeout) | ||||
| 
 | ||||
|     def __str__(self, prefix = ''): | ||||
|         return prefix + 'Network:\n' \ | ||||
| @ -1481,10 +1497,10 @@ class IWD(AsyncOpAbstract): | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def create_in_storage(file_name, file_content, storage_dir=IWD_STORAGE_DIR): | ||||
|         fo = open(storage_dir + '/' + file_name, 'w') | ||||
|         f = open(storage_dir + '/' + file_name, 'w') | ||||
| 
 | ||||
|         fo.write(file_content) | ||||
|         fo.close() | ||||
|         f.write(file_content) | ||||
|         f.close() | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def _ensure_storage_dir_exists(storage_dir): | ||||
|  | ||||
| @ -237,7 +237,7 @@ class Wpas: | ||||
|                         ('' if go_intent is None else ' go_intent=' + str(go_intent))) | ||||
|         self.wait_for_event('OK') | ||||
| 
 | ||||
|     # Pre-accept the next GO Negotiation Request from this peer to avoid the extra Respone + Request frames | ||||
|     # Pre-accept the next GO Negotiation Request from this peer to avoid the extra Response + Request frames | ||||
|     def p2p_authorize(self, peer, pin=None, go_intent=None): | ||||
|         self._rx_data = [] | ||||
|         self._ctrl_request('P2P_CONNECT ' + peer['p2p_dev_addr'] + ' ' + ('pbc' if pin is None else pin) + | ||||
|  | ||||
| @ -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 } | ||||
| }; | ||||
| 
 | ||||
| @ -186,5 +188,5 @@ void diagnostic_display(struct l_dbus_message_iter *dict, | ||||
| 	return; | ||||
| 
 | ||||
| parse_error: | ||||
| 	display_error("Error parsing dignostics"); | ||||
| 	display_error("Error parsing diagnostics"); | ||||
| } | ||||
|  | ||||
| @ -219,7 +219,7 @@ static void network_display_inline(const char *margin, const void *data) | ||||
| 
 | ||||
| 	display("%s%s %s %s\n", margin, network->name ? network->name : "", | ||||
| 			network->type ? network->type : "", | ||||
| 			network->connected ? "connected" : "diconnected"); | ||||
| 			network->connected ? "connected" : "disconnected"); | ||||
| } | ||||
| 
 | ||||
| static void *network_create(void) | ||||
|  | ||||
| @ -1,10 +1,12 @@ | ||||
| AC_PREREQ([2.69]) | ||||
| AC_INIT([iwd],[3.0]) | ||||
| AC_INIT([iwd],[3.10]) | ||||
| 
 | ||||
| AC_CONFIG_HEADERS(config.h) | ||||
| AC_CONFIG_AUX_DIR(build-aux) | ||||
| AC_CONFIG_MACRO_DIR(build-aux) | ||||
| 
 | ||||
| AC_REQUIRE_AUX_FILE([tap-driver.sh]) | ||||
| 
 | ||||
| AM_INIT_AUTOMAKE([foreign subdir-objects color-tests silent-rules | ||||
| 					tar-pax no-dist-gzip dist-xz]) | ||||
| 
 | ||||
| @ -29,6 +31,7 @@ AC_PROG_CC_GCOV | ||||
| AC_PROG_INSTALL | ||||
| AC_PROG_MKDIR_P | ||||
| AC_PROG_LN_S | ||||
| AC_PROG_AWK | ||||
| 
 | ||||
| AC_SYS_LARGEFILE | ||||
| 
 | ||||
| @ -297,7 +300,7 @@ if (test "${enable_external_ell}" = "yes"); then | ||||
| 			test "${enable_monitor}" != "no" || | ||||
| 			test "${enable_wired}" = "yes" || | ||||
| 			test "${enable_hwsim}" = "yes"); then | ||||
| 		ell_min_version="0.69" | ||||
| 		ell_min_version="0.77" | ||||
| 	else | ||||
| 		ell_min_version="0.5" | ||||
| 	fi | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -322,10 +322,10 @@ M18: Use appropriate logging levels | ||||
| An appropriate log level should be used depending on the type of message | ||||
| being logged. Logging is done using the l_log APIs in ELL: | ||||
| 
 | ||||
| l_error   An unexpected condition ocurred. These are generally fatal to the | ||||
| l_error   An unexpected condition occurred. These are generally fatal to the | ||||
|           current connection/protocol that is running but not generally to IWD's | ||||
|           overall operation. | ||||
| l_warn    An unexpected, but non-fatal condition ocurred | ||||
| l_warn    An unexpected, but non-fatal condition occurred | ||||
| l_notice  Should not be used directly. This log level is reserved for special | ||||
|           event type notifications which is handled by iwd_notice(). | ||||
| l_info    Information that is expected during normal operation. l_info's use | ||||
|  | ||||
| @ -135,7 +135,7 @@ Object path	/net/connman/iwd/{phy0,phy1,...}/{1,2,...} | ||||
| 		void StartConfigurator(object agent_path) | ||||
| 
 | ||||
| 			Start a shared code configurator using an agent | ||||
| 			(distingushed by 'agent_path') to obtain the shared | ||||
| 			(distinguished by 'agent_path') to obtain the shared | ||||
| 			code. This method is meant for an automated use case | ||||
| 			where a configurator is capable of configuring multiple | ||||
| 			enrollees, and distinguishing between them by their | ||||
| @ -196,7 +196,7 @@ Methods		void Release() [noreply] | ||||
| 		string RequestSharedCode(string identifier) | ||||
| 
 | ||||
| 			This method gets called when a shared code is requested | ||||
| 			for a particular enrollee, distingushed by the | ||||
| 			for a particular enrollee, distinguished by the | ||||
| 			identifier. The shared code agent should lookup the | ||||
| 			identifier and return the shared code, or return an | ||||
| 			error if not found. | ||||
|  | ||||
| @ -5,7 +5,7 @@ credentials for your e.g. cable/cellular provider, or via a dedicated account | ||||
| like Boingo. Lots of these services also allow you to roam between networks. | ||||
| 
 | ||||
| The underlying authentication is standard WPA2-Enterprise but Hotspot 2.0 adds a | ||||
| 'discovery' stage to identifiying networks. This discovery is done using ANQP, | ||||
| 'discovery' stage to identifying networks. This discovery is done using ANQP, | ||||
| which queries the network for additional information to determine if the client | ||||
| has the credentials to connect. | ||||
| 
 | ||||
|  | ||||
| @ -11,6 +11,12 @@ Methods		void Connect() | ||||
| 			the object path to connect to specified network. | ||||
| 			Connecting to WEP networks is not supported. | ||||
| 
 | ||||
| 			Note: When [General].EnableNetworkConfiguration is set | ||||
| 			to true a call to Connect() has the potential to take | ||||
| 			a significant amount of time. Specifically if DHCP is | ||||
| 			either slow, or is unable to complete. The timeout for | ||||
| 			DHCP is roughly 30 seconds per BSS. | ||||
| 
 | ||||
| 			Possible errors: net.connman.iwd.Aborted | ||||
| 					 net.connman.iwd.Busy | ||||
| 					 net.connman.iwd.Failed | ||||
|  | ||||
| @ -56,7 +56,7 @@ Methods		array(on) GetPeers() | ||||
| 			between requested threshold values is a compromise | ||||
| 			between resolution and the frequency of system | ||||
| 			wakeups and context-switches that are going to be | ||||
| 			occuring to update the client's signal meter.  Only | ||||
| 			occurring to update the client's signal meter.  Only | ||||
| 			one agent can be registered at any time. | ||||
| 
 | ||||
| 			Possible errors: [service].Error.InvalidArguments | ||||
|  | ||||
| @ -166,7 +166,7 @@ Properties	string State [readonly] | ||||
| 
 | ||||
| 		object ConnectedAccessPoint [readonly, optional] | ||||
| 
 | ||||
| 			net.connman.iwd.BasicServiceSet object represeting the | ||||
| 			net.connman.iwd.BasicServiceSet object representing the | ||||
| 			BSS the device is currently connected to or to which | ||||
| 			a connection is in progress. | ||||
| 
 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -218,7 +218,7 @@ supplicant running IWD: | ||||
| #~~~~~~~~~~~~~~~~~~~~~~~~~ hw.conf ~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| # Lines starting with # are ignored | ||||
| 
 | ||||
| # 'SETUP' is a manditory configuration group. | ||||
| # 'SETUP' is a mandatory configuration group. | ||||
| [SETUP] | ||||
| # | ||||
| # Total number of radios requested per network setup. This includes | ||||
|  | ||||
| @ -578,7 +578,7 @@ static int analyze_pcap(const char *pathname) | ||||
| 	printf("\n"); | ||||
| 	printf(" Number of packets: %lu\n", pkt_count); | ||||
| 	printf("     Short packets: %lu\n", pkt_short); | ||||
| 	printf("  Tuncated packets: %lu\n", pkt_trunc); | ||||
| 	printf("  Truncated packets: %lu\n", pkt_trunc); | ||||
| 	printf("\n"); | ||||
| 	printf("  Ethernet packets: %lu\n", pkt_ether); | ||||
| 	printf("       PAE packets: %lu\n", pkt_pae); | ||||
| @ -718,29 +718,36 @@ static void usage(void) | ||||
| 		"Usage:\n"); | ||||
| 	printf("\tiwmon [options]\n"); | ||||
| 	printf("Options:\n" | ||||
| 		"\t-r, --read <file>      Read netlink PCAP trace file\n" | ||||
| 		"\t-w, --write <file>     Write netlink PCAP trace file\n" | ||||
| 		"\t-a, --analyze <file>   Analyze netlink PCAP trace file\n" | ||||
| 		"\t-i, --interface <dev>  Use specified netlink monitor\n" | ||||
| 		"\t-n, --nortnl           Don't show RTNL output\n" | ||||
| 		"\t-y, --nowiphy          Don't show 'New Wiphy' output\n" | ||||
| 		"\t-s, --noscan           Don't show scan result output\n" | ||||
| 		"\t-e, --noies            Don't show IEs except SSID\n" | ||||
| 		"\t-h, --help             Show help options\n"); | ||||
| 		"\t-r, --read <file>          Read netlink PCAP trace file\n" | ||||
| 		"\t-w, --write <file>         Write netlink PCAP trace file\n" | ||||
| 		"\t-a, --analyze <file>       Analyze netlink PCAP trace file\n" | ||||
| 		"\t-i, --interface <dev>      Use specified netlink monitor\n" | ||||
| 		"\t-n, --nortnl               Don't show RTNL output\n" | ||||
| 		"\t-y, --nowiphy              Don't show 'New Wiphy' output\n" | ||||
| 		"\t-s, --noscan               Don't show scan result output\n" | ||||
| 		"\t-e, --noies                Don't show IEs except SSID\n" | ||||
| 		"\t-t, --time-format <format> Time format to display. Either\n" | ||||
| 						"\t\t\t\t   'delta' or 'utc'.\n" | ||||
| 		"\t-W,--pcap-count            Maximum number of PCAP files\n" | ||||
| 		"\t-C,--pcap-size             Maximum size (MB) of PCAP files\n" | ||||
| 		"\t-h, --help                 Show help options\n"); | ||||
| } | ||||
| 
 | ||||
| static const struct option main_options[] = { | ||||
| 	{ "read",      required_argument, NULL, 'r' }, | ||||
| 	{ "write",     required_argument, NULL, 'w' }, | ||||
| 	{ "analyze",   required_argument, NULL, 'a' }, | ||||
| 	{ "nl80211",   required_argument, NULL, 'F' }, | ||||
| 	{ "interface", required_argument, NULL, 'i' }, | ||||
| 	{ "nortnl",    no_argument,       NULL, 'n' }, | ||||
| 	{ "nowiphy",   no_argument,       NULL, 'y' }, | ||||
| 	{ "noscan",    no_argument,       NULL, 's' }, | ||||
| 	{ "noies",     no_argument,       NULL, 'e' }, | ||||
| 	{ "version",   no_argument,       NULL, 'v' }, | ||||
| 	{ "help",      no_argument,       NULL, 'h' }, | ||||
| 	{ "read",        required_argument, NULL, 'r' }, | ||||
| 	{ "write",       required_argument, NULL, 'w' }, | ||||
| 	{ "analyze",     required_argument, NULL, 'a' }, | ||||
| 	{ "nl80211",     required_argument, NULL, 'F' }, | ||||
| 	{ "interface",   required_argument, NULL, 'i' }, | ||||
| 	{ "nortnl",      no_argument,       NULL, 'n' }, | ||||
| 	{ "nowiphy",     no_argument,       NULL, 'y' }, | ||||
| 	{ "noscan",      no_argument,       NULL, 's' }, | ||||
| 	{ "noies",       no_argument,       NULL, 'e' }, | ||||
| 	{ "time-format", required_argument, NULL, 't' }, | ||||
| 	{ "pcap-count",  required_argument, NULL, 'W' }, | ||||
| 	{ "pcap-size",   required_argument, NULL, 'C' }, | ||||
| 	{ "version",     no_argument,       NULL, 'v' }, | ||||
| 	{ "help",        no_argument,       NULL, 'h' }, | ||||
| 	{ } | ||||
| }; | ||||
| 
 | ||||
| @ -754,7 +761,7 @@ int main(int argc, char *argv[]) | ||||
| 	for (;;) { | ||||
| 		int opt; | ||||
| 
 | ||||
| 		opt = getopt_long(argc, argv, "r:w:a:i:nvhyse", | ||||
| 		opt = getopt_long(argc, argv, "r:w:a:i:t:W:C:nvhyse", | ||||
| 						main_options, NULL); | ||||
| 		if (opt < 0) | ||||
| 			break; | ||||
| @ -784,6 +791,35 @@ int main(int argc, char *argv[]) | ||||
| 			break; | ||||
| 		case 'e': | ||||
| 			config.noies = true; | ||||
| 			break; | ||||
| 		case 't': | ||||
| 			if (!strcmp(optarg, "delta")) | ||||
| 				config.time_format = TIME_FORMAT_DELTA; | ||||
| 			else if (!strcmp(optarg, "utc")) | ||||
| 				config.time_format = TIME_FORMAT_UTC; | ||||
| 			else { | ||||
| 				printf("Invalid time format '%s'", optarg); | ||||
| 				return EXIT_FAILURE; | ||||
| 			} | ||||
| 
 | ||||
| 			break; | ||||
| 		case 'W': | ||||
| 			if (l_safe_atou32(optarg, | ||||
| 					&config.pcap_file_count) < 0 || | ||||
| 					config.pcap_file_count == 0) { | ||||
| 				printf("Invalid file count '%s'\n", optarg); | ||||
| 				return EXIT_FAILURE; | ||||
| 			} | ||||
| 
 | ||||
| 			break; | ||||
| 		case 'C': | ||||
| 			if (l_safe_atou32(optarg, | ||||
| 					&config.pcap_file_size) < 0 || | ||||
| 					config.pcap_file_size == 0) { | ||||
| 				printf("Invalid file size '%s'\n", optarg); | ||||
| 				return EXIT_FAILURE; | ||||
| 			} | ||||
| 
 | ||||
| 			break; | ||||
| 		case 'v': | ||||
| 			printf("%s\n", VERSION); | ||||
|  | ||||
							
								
								
									
										158
									
								
								monitor/nlmon.c
									
									
									
									
									
								
							
							
						
						
									
										158
									
								
								monitor/nlmon.c
									
									
									
									
									
								
							| @ -29,6 +29,7 @@ | ||||
| #include <errno.h> | ||||
| #include <ctype.h> | ||||
| #include <unistd.h> | ||||
| #include <time.h> | ||||
| #include <sys/ioctl.h> | ||||
| #include <sys/socket.h> | ||||
| #include <arpa/inet.h> | ||||
| @ -42,6 +43,7 @@ | ||||
| #include <linux/genetlink.h> | ||||
| #include <linux/rtnetlink.h> | ||||
| #include <linux/filter.h> | ||||
| #include <linux/limits.h> | ||||
| #include <ell/ell.h> | ||||
| 
 | ||||
| #ifndef ARPHRD_NETLINK | ||||
| @ -93,6 +95,8 @@ | ||||
| #define BSS_CAPABILITY_APSD		(1<<11) | ||||
| #define BSS_CAPABILITY_DSSS_OFDM	(1<<13) | ||||
| 
 | ||||
| #define BYTES_PER_MB			1000000 | ||||
| 
 | ||||
| struct nlmon *cur_nlmon; | ||||
| 
 | ||||
| enum msg_type { | ||||
| @ -113,6 +117,12 @@ struct nlmon { | ||||
| 	bool noscan; | ||||
| 	bool noies; | ||||
| 	bool read; | ||||
| 	enum time_format time_format; | ||||
| 
 | ||||
| 	char *file_prefix; | ||||
| 	unsigned int file_idx; | ||||
| 	unsigned int max_files; | ||||
| 	unsigned int max_size; | ||||
| }; | ||||
| 
 | ||||
| struct nlmon_req { | ||||
| @ -185,11 +195,15 @@ static void nlmon_req_free(void *data) | ||||
| } | ||||
| 
 | ||||
| static time_t time_offset = ((time_t) -1); | ||||
| static enum time_format time_format; | ||||
| 
 | ||||
| static inline void update_time_offset(const struct timeval *tv) | ||||
| static inline void update_time_offset(const struct timeval *tv, | ||||
| 					enum time_format tf) | ||||
| { | ||||
| 	if (tv && time_offset == ((time_t) -1)) | ||||
| 	if (tv && time_offset == ((time_t) -1)) { | ||||
| 		time_offset = tv->tv_sec; | ||||
| 		time_format = tf; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #define print_indent(indent, color1, prefix, title, color2, fmt, args...) \ | ||||
| @ -225,15 +239,38 @@ static void print_packet(const struct timeval *tv, char ident, | ||||
| 	int n, ts_len = 0, ts_pos = 0, len = 0, pos = 0; | ||||
| 
 | ||||
| 	if (tv) { | ||||
| 		struct tm *tm; | ||||
| 
 | ||||
| 		if (use_color()) { | ||||
| 			n = sprintf(ts_str + ts_pos, "%s", COLOR_TIMESTAMP); | ||||
| 			if (n > 0) | ||||
| 				ts_pos += n; | ||||
| 		} | ||||
| 
 | ||||
| 		n = sprintf(ts_str + ts_pos, " %" PRId64 ".%06" PRId64, | ||||
| 		switch (time_format) { | ||||
| 		case TIME_FORMAT_DELTA: | ||||
| 			n = sprintf(ts_str + ts_pos, " %" PRId64 ".%06" PRId64, | ||||
| 					(int64_t)tv->tv_sec - time_offset, | ||||
| 					(int64_t)tv->tv_usec); | ||||
| 			break; | ||||
| 		case TIME_FORMAT_UTC: | ||||
| 			tm = gmtime(&tv->tv_sec); | ||||
| 			if (!tm) { | ||||
| 				n = sprintf(ts_str + ts_pos, "%s", | ||||
| 						"Time error"); | ||||
| 				break; | ||||
| 			} | ||||
| 
 | ||||
| 			n = strftime(ts_str + ts_pos, sizeof(ts_str) - ts_pos, | ||||
| 					"%b %d %H:%M:%S", tm); | ||||
| 			break; | ||||
| 		default: | ||||
| 			/* Should never happen */ | ||||
| 			printf("Unknown time format"); | ||||
| 			l_main_quit(); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		if (n > 0) { | ||||
| 			ts_pos += n; | ||||
| 			ts_len += n; | ||||
| @ -367,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"		}, | ||||
| 	{ } | ||||
| }; | ||||
| 
 | ||||
| @ -491,7 +529,30 @@ static void print_ie_country(unsigned int level, const char *label, | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	print_attr(level, "%s: %c%c%c", label, code[0], code[1], code[2]); | ||||
| 	print_attr(level, "%s: %c%c", label, code[0], code[1]); | ||||
| 
 | ||||
| 	switch (code[2]) { | ||||
| 	case ' ': | ||||
| 		print_attr(level + 1, | ||||
| 			"3rd octet: 0x%02x: All environments", code[2]); | ||||
| 		break; | ||||
| 	case 'O': | ||||
| 		print_attr(level + 1, | ||||
| 			"3rd octet: 0x%02x: Outdoor environments", code[2]); | ||||
| 		break; | ||||
| 	case 'I': | ||||
| 		print_attr(level + 1, | ||||
| 			"3rd octet: 0x%02x: Indoor environments", code[2]); | ||||
| 		break; | ||||
| 	case 'X': | ||||
| 		print_attr(level + 1, | ||||
| 			"3rd octet: 0x%02x: Non-country entity", code[2]); | ||||
| 		break; | ||||
| 	default: | ||||
| 		print_attr(level + 1, | ||||
| 			"3rd octet: 0x%02x: Annex E table", code[2]); | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	while (i < size) { | ||||
| 		if (code[i] > 200) { | ||||
| @ -1658,7 +1719,7 @@ static void print_ie_vht_capabilities(unsigned int level, | ||||
| 		[21] = "TXOP PS", | ||||
| 		[22] = "+HTC-VHT Capable", | ||||
| 		[23 ... 25] = "Maximum A-MPDU Length Exponent", | ||||
| 		[26 ... 27] = "VHT Link Adapation Capable", | ||||
| 		[26 ... 27] = "VHT Link Adaptation Capable", | ||||
| 		[28] = "RX Antenna Pattern Consistency", | ||||
| 		[29] = "TX Antenna Pattern Consistency", | ||||
| 		[30 ... 31] = "Extended NSS BW Support", | ||||
| @ -1855,7 +1916,7 @@ static void print_ie_interworking(unsigned int level, | ||||
| 	size--; | ||||
| 	ptr++; | ||||
| 
 | ||||
| 	if (!size) | ||||
| 	if (size < 2) | ||||
| 		return; | ||||
| 
 | ||||
| 	/*
 | ||||
| @ -7341,6 +7402,64 @@ static bool nlmon_req_match(const void *a, const void *b) | ||||
| 	return (req->seq == match->seq && req->pid == match->pid); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Ensures that PCAP names are zero padded when needed. This makes the files | ||||
|  * sort correctly. | ||||
|  */ | ||||
| static void next_pcap_name(char *buf, size_t size, const char *prefix, | ||||
| 				unsigned int idx, unsigned int max) | ||||
| { | ||||
| 	unsigned int ndigits = 1; | ||||
| 
 | ||||
| 	while (max > 9) { | ||||
| 		max /= 10; | ||||
| 		ndigits++; | ||||
| 	} | ||||
| 
 | ||||
| 	snprintf(buf, size, "%s%.*u", prefix, ndigits, idx); | ||||
| } | ||||
| 
 | ||||
| static bool check_pcap(struct nlmon *nlmon, size_t next_size) | ||||
| { | ||||
| 	char path[PATH_MAX]; | ||||
| 
 | ||||
| 	if (!nlmon->pcap) | ||||
| 		return false; | ||||
| 
 | ||||
| 	if (!nlmon->max_size) | ||||
| 		return true; | ||||
| 
 | ||||
| 	if (pcap_get_size(nlmon->pcap) + next_size <= nlmon->max_size) | ||||
| 		return true; | ||||
| 
 | ||||
| 	pcap_close(nlmon->pcap); | ||||
| 
 | ||||
| 	/* Exhausted the single PCAP file */ | ||||
| 	if (nlmon->max_files < 2) { | ||||
| 		printf("Reached maximum size of PCAP, exiting\n"); | ||||
| 		nlmon->pcap = NULL; | ||||
| 		l_main_quit(); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	next_pcap_name(path, sizeof(path), nlmon->file_prefix, | ||||
| 			++nlmon->file_idx, nlmon->max_files); | ||||
| 
 | ||||
| 	nlmon->pcap = pcap_create(path); | ||||
| 
 | ||||
| 	if (nlmon->max_files > nlmon->file_idx) | ||||
| 		return true; | ||||
| 
 | ||||
| 	/* Remove oldest PCAP file */ | ||||
| 	next_pcap_name(path, sizeof(path), nlmon->file_prefix, | ||||
| 		nlmon->file_idx - nlmon->max_files, nlmon->max_files); | ||||
| 
 | ||||
| 	if (remove(path) < 0) | ||||
| 		printf("Failed to remove old PCAP file %s\n", path); | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| static void store_packet(struct nlmon *nlmon, const struct timeval *tv, | ||||
| 					uint16_t pkt_type, | ||||
| 					uint16_t arphrd_type, | ||||
| @ -7349,7 +7468,7 @@ static void store_packet(struct nlmon *nlmon, const struct timeval *tv, | ||||
| { | ||||
| 	uint8_t sll_hdr[16], *buf = sll_hdr; | ||||
| 
 | ||||
| 	if (!nlmon->pcap) | ||||
| 	if (!check_pcap(nlmon, sizeof(sll_hdr) + size)) | ||||
| 		return; | ||||
| 
 | ||||
| 	memset(sll_hdr, 0, sizeof(sll_hdr)); | ||||
| @ -7474,6 +7593,10 @@ struct nlmon *nlmon_create(uint16_t id, const struct nlmon_config *config) | ||||
| 	nlmon->noscan = config->noscan; | ||||
| 	nlmon->noies = config->noies; | ||||
| 	nlmon->read = config->read_only; | ||||
| 	nlmon->time_format = config->time_format; | ||||
| 	nlmon->max_files = config->pcap_file_count; | ||||
| 	/* Command line expects MB, but use bytes internally */ | ||||
| 	nlmon->max_size = config->pcap_file_size * BYTES_PER_MB; | ||||
| 
 | ||||
| 	return nlmon; | ||||
| } | ||||
| @ -8310,7 +8433,10 @@ void nlmon_print_rtnl(struct nlmon *nlmon, const struct timeval *tv, | ||||
| 	int64_t aligned_size = NLMSG_ALIGN(size); | ||||
| 	const struct nlmsghdr *nlmsg; | ||||
| 
 | ||||
| 	update_time_offset(tv); | ||||
| 	if (nlmon->nortnl) | ||||
| 		return; | ||||
| 
 | ||||
| 	update_time_offset(tv, nlmon->time_format); | ||||
| 
 | ||||
| 	for (nlmsg = data; NLMSG_OK(nlmsg, aligned_size); | ||||
| 				nlmsg = NLMSG_NEXT(nlmsg, aligned_size)) { | ||||
| @ -8348,7 +8474,7 @@ void nlmon_print_genl(struct nlmon *nlmon, const struct timeval *tv, | ||||
| { | ||||
| 	const struct nlmsghdr *nlmsg; | ||||
| 
 | ||||
| 	update_time_offset(tv); | ||||
| 	update_time_offset(tv, nlmon->time_format); | ||||
| 
 | ||||
| 	for (nlmsg = data; NLMSG_OK(nlmsg, size); | ||||
| 				nlmsg = NLMSG_NEXT(nlmsg, size)) { | ||||
| @ -8371,7 +8497,7 @@ void nlmon_print_pae(struct nlmon *nlmon, const struct timeval *tv, | ||||
| { | ||||
| 	char extra_str[16]; | ||||
| 
 | ||||
| 	update_time_offset(tv); | ||||
| 	update_time_offset(tv, nlmon->time_format); | ||||
| 
 | ||||
| 	sprintf(extra_str, "len %u", size); | ||||
| 
 | ||||
| @ -8498,13 +8624,20 @@ struct nlmon *nlmon_open(uint16_t id, const char *pathname, | ||||
| 	struct nlmon *nlmon; | ||||
| 	struct l_io *pae_io; | ||||
| 	struct pcap *pcap; | ||||
| 	char path[PATH_MAX]; | ||||
| 
 | ||||
| 	pae_io = open_pae(); | ||||
| 	if (!pae_io) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	if (pathname) { | ||||
| 		pcap = pcap_create(pathname); | ||||
| 		if (config->pcap_file_count > 1) | ||||
| 			next_pcap_name(path, sizeof(path), pathname, | ||||
| 					0, config->pcap_file_count); | ||||
| 		else | ||||
| 			snprintf(path, sizeof(path), "%s", pathname); | ||||
| 
 | ||||
| 		pcap = pcap_create(path); | ||||
| 		if (!pcap) { | ||||
| 			l_io_destroy(pae_io); | ||||
| 			return NULL; | ||||
| @ -8517,6 +8650,7 @@ struct nlmon *nlmon_open(uint16_t id, const char *pathname, | ||||
| 
 | ||||
| 	nlmon->pae_io = pae_io; | ||||
| 	nlmon->pcap = pcap; | ||||
| 	nlmon->file_prefix = l_strdup(pathname); | ||||
| 
 | ||||
| 	l_io_set_read_handler(nlmon->pae_io, pae_receive, nlmon, NULL); | ||||
| 
 | ||||
| @ -8539,5 +8673,7 @@ void nlmon_close(struct nlmon *nlmon) | ||||
| 	if (nlmon->pcap) | ||||
| 		pcap_close(nlmon->pcap); | ||||
| 
 | ||||
| 	l_free(nlmon->file_prefix); | ||||
| 
 | ||||
| 	l_free(nlmon); | ||||
| } | ||||
|  | ||||
| @ -25,12 +25,22 @@ | ||||
| 
 | ||||
| struct nlmon; | ||||
| 
 | ||||
| enum time_format { | ||||
| 	TIME_FORMAT_DELTA, | ||||
| 	TIME_FORMAT_UTC, | ||||
| }; | ||||
| 
 | ||||
| struct nlmon_config { | ||||
| 	bool nortnl; | ||||
| 	bool nowiphy; | ||||
| 	bool noscan; | ||||
| 	bool noies; | ||||
| 	bool read_only; | ||||
| 	enum time_format time_format; | ||||
| 
 | ||||
| 	/* File size in MB */ | ||||
| 	uint32_t pcap_file_size; | ||||
| 	uint32_t pcap_file_count; | ||||
| }; | ||||
| 
 | ||||
| struct nlmon *nlmon_open(uint16_t id, const char *pathname, | ||||
|  | ||||
| @ -60,6 +60,7 @@ struct pcap { | ||||
| 	bool closed; | ||||
| 	uint32_t type; | ||||
| 	uint32_t snaplen; | ||||
| 	size_t size; | ||||
| }; | ||||
| 
 | ||||
| struct pcap *pcap_open(const char *pathname) | ||||
| @ -152,6 +153,8 @@ struct pcap *pcap_create(const char *pathname) | ||||
| 		goto failed; | ||||
| 	} | ||||
| 
 | ||||
| 	pcap->size += len; | ||||
| 
 | ||||
| 	return pcap; | ||||
| 
 | ||||
| failed: | ||||
| @ -188,6 +191,11 @@ uint32_t pcap_get_snaplen(struct pcap *pcap) | ||||
| 	return pcap->snaplen; | ||||
| } | ||||
| 
 | ||||
| size_t pcap_get_size(struct pcap *pcap) | ||||
| { | ||||
| 	return pcap->size; | ||||
| } | ||||
| 
 | ||||
| bool pcap_read(struct pcap *pcap, struct timeval *tv, | ||||
| 		void *data, uint32_t size, uint32_t *len, uint32_t *real_len) | ||||
| { | ||||
| @ -279,5 +287,7 @@ bool pcap_write(struct pcap *pcap, const struct timeval *tv, | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	pcap->size += written; | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| @ -36,6 +36,7 @@ void pcap_close(struct pcap *pcap); | ||||
| 
 | ||||
| uint32_t pcap_get_type(struct pcap *pcap); | ||||
| uint32_t pcap_get_snaplen(struct pcap *pcap); | ||||
| size_t pcap_get_size(struct pcap *pcap); | ||||
| 
 | ||||
| bool pcap_read(struct pcap *pcap, struct timeval *tv, | ||||
| 		void *data, uint32_t size, uint32_t *len, uint32_t *real_len); | ||||
|  | ||||
| @ -94,13 +94,13 @@ static void adhoc_sta_free(void *data) | ||||
| 		eapol_sm_free(sta->sm); | ||||
| 
 | ||||
| 	if (sta->hs_sta) | ||||
| 		handshake_state_free(sta->hs_sta); | ||||
| 		handshake_state_unref(sta->hs_sta); | ||||
| 
 | ||||
| 	if (sta->sm_a) | ||||
| 		eapol_sm_free(sta->sm_a); | ||||
| 
 | ||||
| 	if (sta->hs_auth) | ||||
| 		handshake_state_free(sta->hs_auth); | ||||
| 		handshake_state_unref(sta->hs_auth); | ||||
| 
 | ||||
| end: | ||||
| 	l_free(sta); | ||||
|  | ||||
| @ -234,7 +234,7 @@ uint32_t anqp_request(uint64_t wdev_id, const uint8_t *addr, | ||||
| 	request->anqp_cb = cb; | ||||
| 	request->anqp_destroy = destroy; | ||||
| 	/*
 | ||||
| 	 * WPA3 Specificiation version 3, Section 9.4: | ||||
| 	 * WPA3 Specification version 3, Section 9.4: | ||||
| 	 * "A STA shall use a randomized dialog token for every new GAS | ||||
| 	 * exchange." | ||||
| 	 */ | ||||
|  | ||||
| @ -131,7 +131,7 @@ char **anqp_parse_nai_realms(const unsigned char *anqp, unsigned int len) | ||||
| 	uint16_t count; | ||||
| 
 | ||||
| 	if (len < 2) | ||||
| 		return false; | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	count = l_get_le16(anqp); | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										100
									
								
								src/ap.c
									
									
									
									
									
								
							
							
						
						
									
										100
									
								
								src/ap.c
									
									
									
									
									
								
							| @ -109,6 +109,8 @@ struct ap_state { | ||||
| 	struct l_timeout *rekey_timeout; | ||||
| 	unsigned int rekey_time; | ||||
| 
 | ||||
| 	uint32_t pre_scan_cmd_id; | ||||
| 
 | ||||
| 	bool started : 1; | ||||
| 	bool gtk_set : 1; | ||||
| 	bool netconfig_set_addr4 : 1; | ||||
| @ -230,7 +232,7 @@ static void ap_stop_handshake(struct sta_state *sta) | ||||
| 	} | ||||
| 
 | ||||
| 	if (sta->hs) { | ||||
| 		handshake_state_free(sta->hs); | ||||
| 		handshake_state_unref(sta->hs); | ||||
| 		sta->hs = NULL; | ||||
| 	} | ||||
| 
 | ||||
| @ -354,6 +356,12 @@ static void ap_reset(struct ap_state *ap) | ||||
| 		l_timeout_remove(ap->rekey_timeout); | ||||
| 		ap->rekey_timeout = NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	if (ap->pre_scan_cmd_id) { | ||||
| 		scan_cancel(netdev_get_wdev_id(ap->netdev), | ||||
| 				ap->pre_scan_cmd_id); | ||||
| 		ap->pre_scan_cmd_id = 0; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static bool ap_event_done(struct ap_state *ap, bool prev_in_event) | ||||
| @ -3852,6 +3860,70 @@ static int ap_load_config(struct ap_state *ap, const struct l_settings *config, | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void ap_pre_scan_trigger(int err, void *user_data) | ||||
| { | ||||
| 	struct ap_state *ap = user_data; | ||||
| 
 | ||||
| 	if (err < 0) { | ||||
| 		l_error("AP pre-scan failed: %i", err); | ||||
| 		ap_start_failed(ap, err); | ||||
| 		return; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static bool ap_check_channel(struct ap_state *ap) | ||||
| { | ||||
| 	const struct band_freq_attrs *freq_attr; | ||||
| 
 | ||||
| 	freq_attr = wiphy_get_frequency_info(netdev_get_wiphy(ap->netdev), | ||||
| 				band_channel_to_freq(ap->channel, ap->band)); | ||||
| 	if (L_WARN_ON(!freq_attr)) | ||||
| 		return false; | ||||
| 
 | ||||
| 	/* Check if disabled/no-IR */ | ||||
| 	if (freq_attr->disabled || freq_attr->no_ir) | ||||
| 		return false; | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| static bool ap_pre_scan_notify(int err, struct l_queue *bss_list, | ||||
| 				const struct scan_freq_set *freqs, | ||||
| 				void *user_data) | ||||
| { | ||||
| 	struct ap_state *ap = user_data; | ||||
| 
 | ||||
| 	if (!ap_check_channel(ap)) { | ||||
| 		l_error("Unable to channel %u even after pre-scan", | ||||
| 			ap->channel); | ||||
| 		goto error; | ||||
| 	} | ||||
| 
 | ||||
| 	if (ap_start_send(ap)) | ||||
| 		return false; | ||||
| 
 | ||||
| error: | ||||
| 	ap_start_failed(ap, -ENOTSUP); | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| static void ap_pre_scan_destroy(void *user_data) | ||||
| { | ||||
| 	struct ap_state *ap = user_data; | ||||
| 
 | ||||
| 	ap->pre_scan_cmd_id = 0; | ||||
| } | ||||
| 
 | ||||
| static bool ap_pre_scan(struct ap_state *ap) | ||||
| { | ||||
| 	ap->pre_scan_cmd_id = scan_passive(netdev_get_wdev_id(ap->netdev), | ||||
| 						NULL, ap_pre_scan_trigger, | ||||
| 						ap_pre_scan_notify, | ||||
| 						ap, ap_pre_scan_destroy); | ||||
| 
 | ||||
| 	return ap->pre_scan_cmd_id != 0; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Start a simple independent WPA2 AP on given netdev. | ||||
|  * | ||||
| @ -3914,34 +3986,34 @@ struct ap_state *ap_start(struct netdev *netdev, struct l_settings *config, | ||||
| 
 | ||||
| 	if (!frame_watch_add(wdev_id, 0, 0x0000 | | ||||
| 			(MPDU_MANAGEMENT_SUBTYPE_ASSOCIATION_REQUEST << 4), | ||||
| 			NULL, 0, ap_assoc_req_cb, ap, NULL)) | ||||
| 			NULL, 0, false, ap_assoc_req_cb, ap, NULL)) | ||||
| 		goto error; | ||||
| 
 | ||||
| 	if (!frame_watch_add(wdev_id, 0, 0x0000 | | ||||
| 			(MPDU_MANAGEMENT_SUBTYPE_REASSOCIATION_REQUEST << 4), | ||||
| 			NULL, 0, ap_reassoc_req_cb, ap, NULL)) | ||||
| 			NULL, 0, false, ap_reassoc_req_cb, ap, NULL)) | ||||
| 		goto error; | ||||
| 
 | ||||
| 	if (!wiphy_supports_probe_resp_offload(wiphy)) { | ||||
| 		if (!frame_watch_add(wdev_id, 0, 0x0000 | | ||||
| 				(MPDU_MANAGEMENT_SUBTYPE_PROBE_REQUEST << 4), | ||||
| 				NULL, 0, ap_probe_req_cb, ap, NULL)) | ||||
| 				NULL, 0, false, ap_probe_req_cb, ap, NULL)) | ||||
| 			goto error; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!frame_watch_add(wdev_id, 0, 0x0000 | | ||||
| 				(MPDU_MANAGEMENT_SUBTYPE_DISASSOCIATION << 4), | ||||
| 				NULL, 0, ap_disassoc_cb, ap, NULL)) | ||||
| 				NULL, 0, false, ap_disassoc_cb, ap, NULL)) | ||||
| 		goto error; | ||||
| 
 | ||||
| 	if (!frame_watch_add(wdev_id, 0, 0x0000 | | ||||
| 				(MPDU_MANAGEMENT_SUBTYPE_AUTHENTICATION << 4), | ||||
| 				NULL, 0, ap_auth_cb, ap, NULL)) | ||||
| 				NULL, 0, false, ap_auth_cb, ap, NULL)) | ||||
| 		goto error; | ||||
| 
 | ||||
| 	if (!frame_watch_add(wdev_id, 0, 0x0000 | | ||||
| 				(MPDU_MANAGEMENT_SUBTYPE_DEAUTHENTICATION << 4), | ||||
| 				NULL, 0, ap_deauth_cb, ap, NULL)) | ||||
| 				NULL, 0, false, ap_deauth_cb, ap, NULL)) | ||||
| 		goto error; | ||||
| 
 | ||||
| 	ap->mlme_watch = l_genl_family_register(ap->nl80211, "mlme", | ||||
| @ -3962,6 +4034,20 @@ struct ap_state *ap_start(struct netdev *netdev, struct l_settings *config, | ||||
| 		return ap; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!ap_check_channel(ap)) { | ||||
| 		l_debug("Channel %u is disabled/no-IR, pre-scanning", | ||||
| 			ap->channel); | ||||
| 
 | ||||
| 		if (ap_pre_scan(ap)) { | ||||
| 			if (err_out) | ||||
| 				*err_out = 0; | ||||
| 
 | ||||
| 			return ap; | ||||
| 		} | ||||
| 
 | ||||
| 		goto error; | ||||
| 	} | ||||
| 
 | ||||
| 	if (ap_start_send(ap)) { | ||||
| 		if (err_out) | ||||
| 			*err_out = 0; | ||||
|  | ||||
							
								
								
									
										2
									
								
								src/ap.h
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								src/ap.h
									
									
									
									
									
								
							| @ -79,7 +79,7 @@ struct ap_ops { | ||||
| 					void *user_data); | ||||
| 	/*
 | ||||
| 	 * If not null, writes extra IEs to be added to the outgoing frame of | ||||
| 	 * given type and, if it's not a beacon frame, in reponse to a given | ||||
| 	 * given type and, if it's not a beacon frame, in response to a given | ||||
| 	 * client frame.  May also react to the extra IEs in that frame. | ||||
| 	 * Returns the number of bytes written which must be less than or | ||||
| 	 * equal to the number returned by .get_extra_ies_len when called | ||||
|  | ||||
| @ -54,7 +54,7 @@ void __iwd_backtrace_print(unsigned int offset) | ||||
| 	int pathlen; | ||||
| 	pid_t pid; | ||||
| 
 | ||||
| 	if (program_exec == NULL) | ||||
| 	if (!program_exec) | ||||
| 		return; | ||||
| 
 | ||||
| 	pathlen = strlen(program_path); | ||||
| @ -186,7 +186,7 @@ void __iwd_backtrace_init(void) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (program_exec == NULL) | ||||
| 	if (!program_exec) | ||||
| 		return; | ||||
| 
 | ||||
| 	program_path = getcwd(cwd, sizeof(cwd)); | ||||
|  | ||||
| @ -896,7 +896,7 @@ static const struct operating_class_info e4_operating_classes[] = { | ||||
| 	}, | ||||
| 	{ | ||||
| 		.operating_class = 136, | ||||
| 		.starting_frequency = 5950, | ||||
| 		.starting_frequency = 5925, | ||||
| 		.channel_spacing = 20, | ||||
| 		.center_frequencies = { 2 }, | ||||
| 	} | ||||
| @ -1352,6 +1352,10 @@ check_e4: | ||||
| 		const struct operating_class_info *info = | ||||
| 						&e4_operating_classes[i]; | ||||
| 
 | ||||
| 		if (band != band_oper_class_to_band(NULL, | ||||
| 							info->operating_class)) | ||||
| 			continue; | ||||
| 
 | ||||
| 		if (e4_has_frequency(info, freq) == 0 || | ||||
| 					e4_has_ccfi(info, freq) == 0) { | ||||
| 			if (out_band) | ||||
|  | ||||
							
								
								
									
										128
									
								
								src/blacklist.c
									
									
									
									
									
								
							
							
						
						
									
										128
									
								
								src/blacklist.c
									
									
									
									
									
								
							| @ -45,22 +45,42 @@ | ||||
| 
 | ||||
| static uint64_t blacklist_multiplier; | ||||
| static uint64_t blacklist_initial_timeout; | ||||
| static uint64_t blacklist_ap_busy_initial_timeout; | ||||
| static uint64_t blacklist_max_timeout; | ||||
| 
 | ||||
| struct blacklist_entry { | ||||
| 	uint8_t addr[6]; | ||||
| 	uint64_t added_time; | ||||
| 	uint64_t expire_time; | ||||
| 	enum blacklist_reason reason; | ||||
| }; | ||||
| 
 | ||||
| struct blacklist_search { | ||||
| 	const uint8_t *addr; | ||||
| 	enum blacklist_reason reason; | ||||
| }; | ||||
| 
 | ||||
| static struct l_queue *blacklist; | ||||
| 
 | ||||
| static uint64_t get_reason_timeout(enum blacklist_reason reason) | ||||
| { | ||||
| 	switch (reason) { | ||||
| 	case BLACKLIST_REASON_CONNECT_FAILED: | ||||
| 		return blacklist_initial_timeout; | ||||
| 	case BLACKLIST_REASON_AP_BUSY: | ||||
| 		return blacklist_ap_busy_initial_timeout; | ||||
| 	default: | ||||
| 		l_warn("Unhandled blacklist reason: %u", reason); | ||||
| 		return 0; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static bool check_if_expired(void *data, void *user_data) | ||||
| { | ||||
| 	struct blacklist_entry *entry = data; | ||||
| 	uint64_t now = l_get_u64(user_data); | ||||
| 
 | ||||
| 	if (l_time_diff(now, entry->added_time) > blacklist_max_timeout) { | ||||
| 	if (l_time_after(now, entry->expire_time)) { | ||||
| 		l_debug("Removing entry "MAC" on prune", MAC_STR(entry->addr)); | ||||
| 		l_free(entry); | ||||
| 		return true; | ||||
| @ -87,17 +107,53 @@ static bool match_addr(const void *a, const void *b) | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| void blacklist_add_bss(const uint8_t *addr) | ||||
| static bool match_addr_and_reason(const void *a, const void *b) | ||||
| { | ||||
| 	const struct blacklist_entry *entry = a; | ||||
| 	const struct blacklist_search *search = b; | ||||
| 
 | ||||
| 	if (entry->reason != search->reason) | ||||
| 		return false; | ||||
| 
 | ||||
| 	if (!memcmp(entry->addr, search->addr, 6)) | ||||
| 		return true; | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| void blacklist_add_bss(const uint8_t *addr, enum blacklist_reason reason) | ||||
| { | ||||
| 	struct blacklist_entry *entry; | ||||
| 	uint64_t timeout; | ||||
| 
 | ||||
| 	blacklist_prune(); | ||||
| 
 | ||||
| 	timeout = get_reason_timeout(reason); | ||||
| 	if (!timeout) | ||||
| 		return; | ||||
| 
 | ||||
| 	entry = l_queue_find(blacklist, match_addr, addr); | ||||
| 
 | ||||
| 	if (entry) { | ||||
| 		uint64_t offset = l_time_diff(entry->added_time, | ||||
| 							entry->expire_time); | ||||
| 		uint64_t offset; | ||||
| 
 | ||||
| 		if (reason < entry->reason) { | ||||
| 			l_debug("Promoting "MAC" blacklist to reason %u", | ||||
| 					MAC_STR(addr), reason); | ||||
| 			/* Reset this to the new timeout and reason */ | ||||
| 			entry->reason = reason; | ||||
| 			entry->added_time = l_time_now(); | ||||
| 			entry->expire_time = l_time_offset(entry->added_time, | ||||
| 								timeout); | ||||
| 			return; | ||||
| 		} else if (reason > entry->reason) { | ||||
| 			l_debug("Ignoring blacklist extension of "MAC", " | ||||
| 				"current blacklist status is more severe!", | ||||
| 				MAC_STR(addr)); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		offset = l_time_diff(entry->added_time, entry->expire_time); | ||||
| 
 | ||||
| 		offset *= blacklist_multiplier; | ||||
| 
 | ||||
| @ -112,40 +168,36 @@ void blacklist_add_bss(const uint8_t *addr) | ||||
| 	entry = l_new(struct blacklist_entry, 1); | ||||
| 
 | ||||
| 	entry->added_time = l_time_now(); | ||||
| 	entry->expire_time = l_time_offset(entry->added_time, | ||||
| 						blacklist_initial_timeout); | ||||
| 	entry->expire_time = l_time_offset(entry->added_time, timeout); | ||||
| 	entry->reason = reason; | ||||
| 	memcpy(entry->addr, addr, 6); | ||||
| 
 | ||||
| 	l_queue_push_tail(blacklist, entry); | ||||
| } | ||||
| 
 | ||||
| bool blacklist_contains_bss(const uint8_t *addr) | ||||
| bool blacklist_contains_bss(const uint8_t *addr, enum blacklist_reason reason) | ||||
| { | ||||
| 	bool ret; | ||||
| 	uint64_t time_now; | ||||
| 	struct blacklist_entry *entry; | ||||
| 	struct blacklist_search search = { | ||||
| 		.addr = addr, | ||||
| 		.reason = reason | ||||
| 	}; | ||||
| 
 | ||||
| 	blacklist_prune(); | ||||
| 
 | ||||
| 	entry = l_queue_find(blacklist, match_addr, addr); | ||||
| 
 | ||||
| 	if (!entry) | ||||
| 		return false; | ||||
| 
 | ||||
| 	time_now = l_time_now(); | ||||
| 
 | ||||
| 	ret = l_time_after(time_now, entry->expire_time) ? false : true; | ||||
| 
 | ||||
| 	return ret; | ||||
| 	return l_queue_find(blacklist, match_addr_and_reason, &search) != NULL; | ||||
| } | ||||
| 
 | ||||
| void blacklist_remove_bss(const uint8_t *addr) | ||||
| void blacklist_remove_bss(const uint8_t *addr, enum blacklist_reason reason) | ||||
| { | ||||
| 	struct blacklist_entry *entry; | ||||
| 	struct blacklist_search search = { | ||||
| 		.addr = addr, | ||||
| 		.reason = reason | ||||
| 	}; | ||||
| 
 | ||||
| 	blacklist_prune(); | ||||
| 
 | ||||
| 	entry = l_queue_remove_if(blacklist, match_addr, addr); | ||||
| 	entry = l_queue_remove_if(blacklist, match_addr_and_reason, &search); | ||||
| 
 | ||||
| 	if (!entry) | ||||
| 		return; | ||||
| @ -162,19 +214,47 @@ static int blacklist_init(void) | ||||
| 		blacklist_initial_timeout = BLACKLIST_DEFAULT_TIMEOUT; | ||||
| 
 | ||||
| 	/* For easier user configuration the timeout values are in seconds */ | ||||
| 	blacklist_initial_timeout *= 1000000; | ||||
| 	blacklist_initial_timeout *= L_USEC_PER_SEC; | ||||
| 
 | ||||
| 	if (!l_settings_get_uint64(config, "Blacklist", | ||||
| 					"InitialRoamRequestedTimeout", | ||||
| 					&blacklist_ap_busy_initial_timeout)) | ||||
| 		blacklist_ap_busy_initial_timeout = BLACKLIST_DEFAULT_TIMEOUT; | ||||
| 	else | ||||
| 		l_warn("[Blacklist].InitialRoamRequestedTimeout is deprecated, " | ||||
| 			"use [Blacklist].InitialAccessPointBusyTimeout"); | ||||
| 
 | ||||
| 	if (!l_settings_get_uint64(config, "Blacklist", | ||||
| 					"InitialAccessPointBusyTimeout", | ||||
| 					&blacklist_ap_busy_initial_timeout)) | ||||
| 		blacklist_ap_busy_initial_timeout = BLACKLIST_DEFAULT_TIMEOUT; | ||||
| 
 | ||||
| 	/* For easier user configuration the timeout values are in seconds */ | ||||
| 	blacklist_ap_busy_initial_timeout *= L_USEC_PER_SEC; | ||||
| 
 | ||||
| 	if (!l_settings_get_uint64(config, "Blacklist", | ||||
| 					"Multiplier", | ||||
| 					&blacklist_multiplier)) | ||||
| 		blacklist_multiplier = BLACKLIST_DEFAULT_MULTIPLIER; | ||||
| 
 | ||||
| 	if (blacklist_multiplier == 0) { | ||||
| 		l_warn("[Blacklist].Multiplier cannot be zero, setting to 1"); | ||||
| 		blacklist_multiplier = 1; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!l_settings_get_uint64(config, "Blacklist", | ||||
| 					"MaximumTimeout", | ||||
| 					&blacklist_max_timeout)) | ||||
| 		blacklist_max_timeout = BLACKLIST_DEFAULT_MAX_TIMEOUT; | ||||
| 
 | ||||
| 	blacklist_max_timeout *= 1000000; | ||||
| 	blacklist_max_timeout *= L_USEC_PER_SEC; | ||||
| 
 | ||||
| 	if (blacklist_initial_timeout > blacklist_max_timeout) | ||||
| 		l_warn("[Blacklist].InitialTimeout exceeded " | ||||
| 			"[Blacklist].MaximumTimeout!"); | ||||
| 
 | ||||
| 	if (!blacklist_initial_timeout) | ||||
| 		l_debug("initial timeout was zero, blacklist will be disabled"); | ||||
| 
 | ||||
| 	blacklist = l_queue_new(); | ||||
| 
 | ||||
|  | ||||
| @ -20,6 +20,23 @@ | ||||
|  * | ||||
|  */ | ||||
| 
 | ||||
| void blacklist_add_bss(const uint8_t *addr); | ||||
| bool blacklist_contains_bss(const uint8_t *addr); | ||||
| void blacklist_remove_bss(const uint8_t *addr); | ||||
| enum blacklist_reason { | ||||
| 	/*
 | ||||
| 	 * When a BSS is blacklisted using this reason IWD will refuse to | ||||
| 	 * connect to it via autoconnect | ||||
| 	 */ | ||||
| 	BLACKLIST_REASON_CONNECT_FAILED, | ||||
| 	/*
 | ||||
| 	 * This type of blacklist is added when an AP indicates that its unable | ||||
| 	 * to handle more connections. This is done via BSS-TM requests or | ||||
| 	 * denied authentications/associations with certain status codes. | ||||
| 	 * | ||||
| 	 * Once this type of blacklist is applied to a BSS IWD will attempt to | ||||
| 	 * avoid roaming to it for a configured period of time. | ||||
| 	 */ | ||||
| 	BLACKLIST_REASON_AP_BUSY, | ||||
| }; | ||||
| 
 | ||||
| void blacklist_add_bss(const uint8_t *addr, enum blacklist_reason reason); | ||||
| bool blacklist_contains_bss(const uint8_t *addr, enum blacklist_reason reason); | ||||
| void blacklist_remove_bss(const uint8_t *addr, enum blacklist_reason reason); | ||||
|  | ||||
| @ -1212,7 +1212,7 @@ struct l_ecc_point *crypto_derive_sae_pwe_from_pt_ecc(const uint8_t *mac1, | ||||
| 	struct l_ecc_point *pwe; | ||||
| 
 | ||||
| 	if (!pt || !curve) | ||||
| 		return false; | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	hash = crypto_sae_hash_from_ecc_prime_len(CRYPTO_SAE_HASH_TO_ELEMENT, | ||||
| 					l_ecc_curve_get_scalar_bytes(curve)); | ||||
|  | ||||
| @ -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; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -1166,21 +1166,34 @@ struct dpp_uri_info *dpp_parse_uri(const char *uri) | ||||
| 
 | ||||
| 		switch (*pos) { | ||||
| 		case 'C': | ||||
| 			if (L_WARN_ON(info->freqs)) | ||||
| 				goto free_info; | ||||
| 
 | ||||
| 			info->freqs = dpp_parse_class_and_channel(pos + 2, len); | ||||
| 			if (!info->freqs) | ||||
| 				goto free_info; | ||||
| 			break; | ||||
| 		case 'M': | ||||
| 			if (L_WARN_ON(!l_memeqzero(info->mac, | ||||
| 							sizeof(info->mac)))) | ||||
| 				goto free_info; | ||||
| 
 | ||||
| 			ret = dpp_parse_mac(pos + 2, len, info->mac); | ||||
| 			if (ret < 0) | ||||
| 				goto free_info; | ||||
| 			break; | ||||
| 		case 'V': | ||||
| 			if (L_WARN_ON(info->version != 0)) | ||||
| 				goto free_info; | ||||
| 
 | ||||
| 			ret = dpp_parse_version(pos + 2, len, &info->version); | ||||
| 			if (ret < 0) | ||||
| 				goto free_info; | ||||
| 			break; | ||||
| 		case 'K': | ||||
| 			if (L_WARN_ON(info->boot_public)) | ||||
| 				goto free_info; | ||||
| 
 | ||||
| 			info->boot_public = dpp_parse_key(pos + 2, len); | ||||
| 			if (!info->boot_public) | ||||
| 				goto free_info; | ||||
|  | ||||
							
								
								
									
										163
									
								
								src/dpp.c
									
									
									
									
									
								
							
							
						
						
									
										163
									
								
								src/dpp.c
									
									
									
									
									
								
							| @ -66,7 +66,6 @@ static struct l_genl_family *nl80211; | ||||
| static uint8_t broadcast[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; | ||||
| static struct l_queue *dpp_list; | ||||
| static uint32_t mlme_watch; | ||||
| static uint32_t unicast_watch; | ||||
| 
 | ||||
| static uint8_t dpp_prefix[] = { 0x04, 0x09, 0x50, 0x6f, 0x9a, 0x1a, 0x01 }; | ||||
| 
 | ||||
| @ -552,6 +551,8 @@ static void dpp_reset(struct dpp_sm *dpp) | ||||
| 
 | ||||
| 	dpp_property_changed_notify(dpp); | ||||
| 
 | ||||
| 	frame_watch_group_remove(dpp->wdev_id, FRAME_GROUP_DPP); | ||||
| 
 | ||||
| 	dpp->interface = DPP_INTERFACE_UNBOUND; | ||||
| 
 | ||||
| 	if (station) { | ||||
| @ -2000,7 +2001,7 @@ static void dpp_offchannel_timeout(int error, void *user_data) | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * We have a pending agent request but it did not arrive in | ||||
| 		 * time, we cant assume the enrollee will be waiting around | ||||
| 		 * time, we can't assume the enrollee will be waiting around | ||||
| 		 * for our response so cancel the request and continue waiting | ||||
| 		 * for another request | ||||
| 		 */ | ||||
| @ -2721,7 +2722,7 @@ static void dpp_handle_pkex_exchange_response(struct dpp_sm *dpp, | ||||
| 	} | ||||
| 
 | ||||
| 	if (version && version != dpp->pkex_version) { | ||||
| 		l_debug("PKEX version does not match, igoring"); | ||||
| 		l_debug("PKEX version does not match, ignoring"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| @ -3437,7 +3438,7 @@ static void dpp_handle_pkex_commit_reveal_request(struct dpp_sm *dpp, | ||||
| 	dpp->peer_boot_public = l_ecc_point_from_data(dpp->curve, | ||||
| 					L_ECC_POINT_TYPE_FULL, key, key_len); | ||||
| 	if (!dpp->peer_boot_public) { | ||||
| 		l_debug("peers boostrapping key did not validate"); | ||||
| 		l_debug("peers bootstrapping key did not validate"); | ||||
| 		goto failed; | ||||
| 	} | ||||
| 
 | ||||
| @ -3475,10 +3476,11 @@ failed: | ||||
| 	dpp_reset(dpp); | ||||
| } | ||||
| 
 | ||||
| static void dpp_handle_frame(struct dpp_sm *dpp, | ||||
| 				const struct mmpdu_header *frame, | ||||
| 				const void *body, size_t body_len) | ||||
| static void dpp_handle_frame(const struct mmpdu_header *frame, | ||||
| 				const void *body, size_t body_len, | ||||
| 				int rssi, void *user_data) | ||||
| { | ||||
| 	struct dpp_sm *dpp = user_data; | ||||
| 	const uint8_t *ptr; | ||||
| 
 | ||||
| 	/*
 | ||||
| @ -3596,7 +3598,7 @@ static void dpp_mlme_notify(struct l_genl_msg *msg, void *user_data) | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Only want to handle the no-ACK case. Re-transmitting an ACKed | ||||
| 	 * frame likely wont do any good, at least in the case of DPP. | ||||
| 	 * frame likely won't do any good, at least in the case of DPP. | ||||
| 	 */ | ||||
| 	if (!ack) | ||||
| 		goto retransmit; | ||||
| @ -3638,99 +3640,6 @@ retransmit: | ||||
| 						dpp_frame_timeout, dpp, NULL); | ||||
| } | ||||
| 
 | ||||
| static void dpp_unicast_notify(struct l_genl_msg *msg, void *user_data) | ||||
| { | ||||
| 	struct dpp_sm *dpp; | ||||
| 	const uint64_t *wdev_id = NULL; | ||||
| 	struct l_genl_attr attr; | ||||
| 	uint16_t type, len, frame_len; | ||||
| 	const void *data; | ||||
| 	const struct mmpdu_header *mpdu = NULL; | ||||
| 	const uint8_t *body; | ||||
| 	size_t body_len; | ||||
| 
 | ||||
| 	if (l_genl_msg_get_command(msg) != NL80211_CMD_FRAME) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (!l_genl_attr_init(&attr, msg)) | ||||
| 		return; | ||||
| 
 | ||||
| 	while (l_genl_attr_next(&attr, &type, &len, &data)) { | ||||
| 		switch (type) { | ||||
| 		case NL80211_ATTR_WDEV: | ||||
| 			if (len != 8) | ||||
| 				break; | ||||
| 
 | ||||
| 			wdev_id = data; | ||||
| 			break; | ||||
| 
 | ||||
| 		case NL80211_ATTR_FRAME: | ||||
| 			mpdu = mpdu_validate(data, len); | ||||
| 			if (!mpdu) { | ||||
| 				l_warn("Frame didn't validate as MMPDU"); | ||||
| 				return; | ||||
| 			} | ||||
| 
 | ||||
| 			frame_len = len; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (!wdev_id) { | ||||
| 		l_warn("Bad wdev attribute"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	dpp = l_queue_find(dpp_list, match_wdev, wdev_id); | ||||
| 	if (!dpp) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (!mpdu) { | ||||
| 		l_warn("Missing frame data"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	body = mmpdu_body(mpdu); | ||||
| 	body_len = (const uint8_t *) mpdu + frame_len - body; | ||||
| 
 | ||||
| 	if (body_len < sizeof(dpp_prefix) || | ||||
| 			memcmp(body, dpp_prefix, sizeof(dpp_prefix)) != 0) | ||||
| 		return; | ||||
| 
 | ||||
| 	dpp_handle_frame(dpp, mpdu, body, body_len); | ||||
| } | ||||
| 
 | ||||
| static void dpp_frame_watch_cb(struct l_genl_msg *msg, void *user_data) | ||||
| { | ||||
| 	if (l_genl_msg_get_error(msg) < 0) | ||||
| 		l_error("Could not register frame watch type %04x: %i", | ||||
| 			L_PTR_TO_UINT(user_data), l_genl_msg_get_error(msg)); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Special case the frame watch which includes the presence frames since they | ||||
|  * require multicast support. This is only supported by ath9k, so adding | ||||
|  * general support to frame-xchg isn't desireable. | ||||
|  */ | ||||
| static void dpp_frame_watch(struct dpp_sm *dpp, uint16_t frame_type, | ||||
| 				const uint8_t *prefix, size_t prefix_len) | ||||
| { | ||||
| 	struct l_genl_msg *msg; | ||||
| 
 | ||||
| 	msg = l_genl_msg_new_sized(NL80211_CMD_REGISTER_FRAME, 32 + prefix_len); | ||||
| 
 | ||||
| 	l_genl_msg_append_attr(msg, NL80211_ATTR_WDEV, 8, &dpp->wdev_id); | ||||
| 	l_genl_msg_append_attr(msg, NL80211_ATTR_FRAME_TYPE, 2, &frame_type); | ||||
| 	l_genl_msg_append_attr(msg, NL80211_ATTR_FRAME_MATCH, | ||||
| 				prefix_len, prefix); | ||||
| 	if (dpp->mcast_support) | ||||
| 		l_genl_msg_append_attr(msg, NL80211_ATTR_RECEIVE_MULTICAST, | ||||
| 					0, NULL); | ||||
| 
 | ||||
| 	l_genl_family_send(nl80211, msg, dpp_frame_watch_cb, | ||||
| 					L_UINT_TO_PTR(frame_type), NULL); | ||||
| } | ||||
| 
 | ||||
| static void dpp_start_enrollee(struct dpp_sm *dpp); | ||||
| static bool dpp_start_pkex_enrollee(struct dpp_sm *dpp); | ||||
| 
 | ||||
| @ -3820,8 +3729,6 @@ static void dpp_create(struct netdev *netdev) | ||||
| { | ||||
| 	struct l_dbus *dbus = dbus_get_bus(); | ||||
| 	struct dpp_sm *dpp = l_new(struct dpp_sm, 1); | ||||
| 	uint8_t dpp_conf_response_prefix[] = { 0x04, 0x0b }; | ||||
| 	uint8_t dpp_conf_request_prefix[] = { 0x04, 0x0a }; | ||||
| 	uint64_t wdev_id = netdev_get_wdev_id(netdev); | ||||
| 
 | ||||
| 	dpp->netdev = netdev; | ||||
| @ -3832,9 +3739,8 @@ static void dpp_create(struct netdev *netdev) | ||||
| 	dpp->key_len = l_ecc_curve_get_scalar_bytes(dpp->curve); | ||||
| 	dpp->nonce_len = dpp_nonce_len_from_key_len(dpp->key_len); | ||||
| 	dpp->max_roc = wiphy_get_max_roc_duration(wiphy_find_by_wdev(wdev_id)); | ||||
| 	dpp->mcast_support = wiphy_has_ext_feature( | ||||
| 				wiphy_find_by_wdev(dpp->wdev_id), | ||||
| 				NL80211_EXT_FEATURE_MULTICAST_REGISTRATIONS); | ||||
| 	dpp->mcast_support = wiphy_supports_multicast_rx( | ||||
| 				wiphy_find_by_wdev(dpp->wdev_id)); | ||||
| 
 | ||||
| 	l_ecdh_generate_key_pair(dpp->curve, &dpp->boot_private, | ||||
| 					&dpp->boot_public); | ||||
| @ -3857,17 +3763,6 @@ static void dpp_create(struct netdev *netdev) | ||||
| 	 */ | ||||
| 	dpp->refcount = 2; | ||||
| 
 | ||||
| 	dpp_frame_watch(dpp, 0x00d0, dpp_prefix, sizeof(dpp_prefix)); | ||||
| 
 | ||||
| 	frame_watch_add(netdev_get_wdev_id(netdev), 0, 0x00d0, | ||||
| 				dpp_conf_response_prefix, | ||||
| 				sizeof(dpp_conf_response_prefix), | ||||
| 				dpp_handle_config_response_frame, dpp, NULL); | ||||
| 	frame_watch_add(netdev_get_wdev_id(netdev), 0, 0x00d0, | ||||
| 				dpp_conf_request_prefix, | ||||
| 				sizeof(dpp_conf_request_prefix), | ||||
| 				dpp_handle_config_request_frame, dpp, NULL); | ||||
| 
 | ||||
| 	dpp->known_network_watch = known_networks_watch_add( | ||||
| 					dpp_known_network_watch, dpp, NULL); | ||||
| 
 | ||||
| @ -4012,6 +3907,26 @@ done: | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void dpp_add_frame_watches(struct dpp_sm *dpp, bool multicast_rx) | ||||
| { | ||||
| 	uint8_t dpp_conf_response_prefix[] = { 0x04, 0x0b }; | ||||
| 	uint8_t dpp_conf_request_prefix[] = { 0x04, 0x0a }; | ||||
| 
 | ||||
| 	frame_watch_add(dpp->wdev_id, FRAME_GROUP_DPP, 0x00d0, | ||||
| 			dpp_prefix, sizeof(dpp_prefix), multicast_rx, | ||||
| 			dpp_handle_frame, dpp, NULL); | ||||
| 
 | ||||
| 	frame_watch_add(dpp->wdev_id, FRAME_GROUP_DPP, 0x00d0, | ||||
| 			dpp_conf_response_prefix, | ||||
| 			sizeof(dpp_conf_response_prefix), false, | ||||
| 			dpp_handle_config_response_frame, dpp, NULL); | ||||
| 
 | ||||
| 	frame_watch_add(dpp->wdev_id, FRAME_GROUP_DPP, 0x00d0, | ||||
| 			dpp_conf_request_prefix, | ||||
| 			sizeof(dpp_conf_request_prefix), false, | ||||
| 			dpp_handle_config_request_frame, dpp, NULL); | ||||
| } | ||||
| 
 | ||||
| static void dpp_start_enrollee(struct dpp_sm *dpp) | ||||
| { | ||||
| 	uint32_t freq = band_channel_to_freq(6, BAND_FREQ_2_4_GHZ); | ||||
| @ -4051,6 +3966,8 @@ static struct l_dbus_message *dpp_dbus_start_enrollee(struct l_dbus *dbus, | ||||
| 	dpp->role = DPP_CAPABILITY_ENROLLEE; | ||||
| 	dpp->interface = DPP_INTERFACE_DPP; | ||||
| 
 | ||||
| 	dpp_add_frame_watches(dpp, false); | ||||
| 
 | ||||
| 	ret = dpp_try_disconnect_station(dpp, &wait_for_disconnect); | ||||
| 	if (ret < 0) { | ||||
| 		dpp_reset(dpp); | ||||
| @ -4188,6 +4105,8 @@ static struct l_dbus_message *dpp_start_configurator_common( | ||||
| 	} else | ||||
| 		dpp->current_freq = bss->frequency; | ||||
| 
 | ||||
| 	dpp_add_frame_watches(dpp, responder && dpp->mcast_support); | ||||
| 
 | ||||
| 	dpp->uri = dpp_generate_uri(dpp->own_asn1, dpp->own_asn1_len, 2, | ||||
| 					netdev_get_address(dpp->netdev), | ||||
| 					&bss->frequency, 1, NULL, NULL); | ||||
| @ -4517,6 +4436,8 @@ static struct l_dbus_message *dpp_dbus_pkex_start_enrollee(struct l_dbus *dbus, | ||||
| 	dpp->state = DPP_STATE_PKEX_EXCHANGE; | ||||
| 	dpp->interface = DPP_INTERFACE_PKEX; | ||||
| 
 | ||||
| 	dpp_add_frame_watches(dpp, false); | ||||
| 
 | ||||
| 	ret = dpp_try_disconnect_station(dpp, &wait_for_disconnect); | ||||
| 	if (ret < 0) { | ||||
| 		dpp_reset(dpp); | ||||
| @ -4613,6 +4534,7 @@ static struct l_dbus_message *dpp_start_pkex_configurator(struct dpp_sm *dpp, | ||||
| 	dpp->config = dpp_configuration_new(network_get_settings(network), | ||||
| 						network_get_ssid(network), | ||||
| 						hs->akm_suite); | ||||
| 	dpp_add_frame_watches(dpp, dpp->mcast_support); | ||||
| 
 | ||||
| 	dpp_reset_protocol_timer(dpp, DPP_PKEX_PROTO_TIMEOUT); | ||||
| 	dpp_property_changed_notify(dpp); | ||||
| @ -4741,11 +4663,6 @@ static int dpp_init(void) | ||||
| 	mlme_watch = l_genl_family_register(nl80211, "mlme", dpp_mlme_notify, | ||||
| 						NULL, NULL); | ||||
| 
 | ||||
| 	unicast_watch = l_genl_add_unicast_watch(iwd_get_genl(), | ||||
| 						NL80211_GENL_NAME, | ||||
| 						dpp_unicast_notify, | ||||
| 						NULL, NULL); | ||||
| 
 | ||||
| 	dpp_list = l_queue_new(); | ||||
| 
 | ||||
| 	return 0; | ||||
| @ -4760,8 +4677,6 @@ static void dpp_exit(void) | ||||
| 
 | ||||
| 	netdev_watch_remove(netdev_watch); | ||||
| 
 | ||||
| 	l_genl_remove_unicast_watch(iwd_get_genl(), unicast_watch); | ||||
| 
 | ||||
| 	l_genl_family_unregister(nl80211, mlme_watch); | ||||
| 	mlme_watch = 0; | ||||
| 
 | ||||
|  | ||||
| @ -544,7 +544,8 @@ static bool eap_mschapv2_load_settings(struct eap_state *eap, | ||||
| 	return true; | ||||
| 
 | ||||
| error: | ||||
| 	free(state); | ||||
| 	l_free(state->user); | ||||
| 	l_free(state); | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -165,7 +165,7 @@ static void __eap_tls_common_state_reset(struct eap_state *eap) | ||||
| 	 * | ||||
| 	 * Drop the cache even if we have no indication that the | ||||
| 	 * method failed but it just didn't succeed, to handle cases like | ||||
| 	 * the server getting stuck and a timout occuring at a higher | ||||
| 	 * the server getting stuck and a timeout occurring at a higher | ||||
| 	 * layer.  The risk is that we may occasionally flush the session | ||||
| 	 * data when there was only a momentary radio issue, invalid | ||||
| 	 * phase2 credentials or decision to abort.  Those are not hot | ||||
|  | ||||
| @ -425,8 +425,8 @@ static void eap_handle_response(struct eap_state *eap, const uint8_t *pkt, | ||||
| 				size_t len) | ||||
| { | ||||
| 	enum eap_type type; | ||||
| 	uint32_t vendor_id; | ||||
| 	uint32_t vendor_type; | ||||
| 	uint32_t vendor_id = 0; | ||||
| 	uint32_t vendor_type = 0; | ||||
| 	enum eap_type our_type = eap->method->request_type; | ||||
| 	uint32_t our_vendor_id = (eap->method->vendor_id[0] << 16) | | ||||
| 				(eap->method->vendor_id[1] << 8) | | ||||
| @ -974,7 +974,7 @@ static void __eap_method_enable(struct eap_method_desc *start, | ||||
| 
 | ||||
| 	l_debug(""); | ||||
| 
 | ||||
| 	if (start == NULL || stop == NULL) | ||||
| 	if (!start || !stop) | ||||
| 		return; | ||||
| 
 | ||||
| 	for (desc = start; desc < stop; desc++) { | ||||
| @ -992,7 +992,7 @@ static void __eap_method_disable(struct eap_method_desc *start, | ||||
| 
 | ||||
| 	l_debug(""); | ||||
| 
 | ||||
| 	if (start == NULL || stop == NULL) | ||||
| 	if (!start || !stop) | ||||
| 		return; | ||||
| 
 | ||||
| 	for (desc = start; desc < stop; desc++) { | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -342,8 +342,7 @@ static bool frame_watch_group_io_read(struct l_io *io, void *user_data) | ||||
| 
 | ||||
| 	nlmsg_len = bytes_read; | ||||
| 
 | ||||
| 	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; | ||||
| 					cmsg = CMSG_NXTHDR(&msg, cmsg)) { | ||||
| 	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { | ||||
| 		struct nl_pktinfo pktinfo; | ||||
| 
 | ||||
| 		if (cmsg->cmsg_level != SOL_NETLINK) | ||||
| @ -575,6 +574,7 @@ drop: | ||||
| 
 | ||||
| bool frame_watch_add(uint64_t wdev_id, uint32_t group_id, uint16_t frame_type, | ||||
| 			const uint8_t *prefix, size_t prefix_len, | ||||
| 			bool multicast_rx, | ||||
| 			frame_watch_cb_t handler, void *user_data, | ||||
| 			frame_xchg_destroy_func_t destroy) | ||||
| { | ||||
| @ -614,6 +614,10 @@ bool frame_watch_add(uint64_t wdev_id, uint32_t group_id, uint16_t frame_type, | ||||
| 	l_genl_msg_append_attr(msg, NL80211_ATTR_FRAME_MATCH, | ||||
| 				prefix_len, prefix); | ||||
| 
 | ||||
| 	if (multicast_rx) | ||||
| 		l_genl_msg_append_attr(msg, NL80211_ATTR_RECEIVE_MULTICAST, | ||||
| 					0, NULL); | ||||
| 
 | ||||
| 	if (group->id == 0) | ||||
| 		l_genl_family_send(nl80211, msg, frame_watch_register_cb, | ||||
| 					L_UINT_TO_PTR(frame_type), NULL); | ||||
| @ -1194,7 +1198,7 @@ uint32_t frame_xchg_startv(uint64_t wdev_id, struct iovec *frame, uint32_t freq, | ||||
| 		watch->prefix = prefix; | ||||
| 		watch->cb = va_arg(resp_args, void *); | ||||
| 		frame_watch_add(wdev_id, group_id, prefix->frame_type, | ||||
| 				prefix->data, prefix->len, | ||||
| 				prefix->data, prefix->len, false, | ||||
| 				frame_xchg_resp_cb, fx, NULL); | ||||
| 
 | ||||
| 		if (!fx->rx_watches) | ||||
|  | ||||
| @ -41,10 +41,12 @@ enum frame_xchg_group { | ||||
| 	FRAME_GROUP_DEFAULT = 0, | ||||
| 	FRAME_GROUP_P2P_LISTEN, | ||||
| 	FRAME_GROUP_P2P_CONNECT, | ||||
| 	FRAME_GROUP_DPP, | ||||
| }; | ||||
| 
 | ||||
| bool frame_watch_add(uint64_t wdev_id, uint32_t group, uint16_t frame_type, | ||||
| 			const uint8_t *prefix, size_t prefix_len, | ||||
| 			bool multicast_rx, | ||||
| 			frame_watch_cb_t handler, void *user_data, | ||||
| 			frame_xchg_destroy_func_t destroy); | ||||
| bool frame_watch_group_remove(uint64_t wdev_id, uint32_t group); | ||||
|  | ||||
							
								
								
									
										10
									
								
								src/ft.c
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/ft.c
									
									
									
									
									
								
							| @ -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; | ||||
|  | ||||
							
								
								
									
										128
									
								
								src/handshake.c
									
									
									
									
									
								
							
							
						
						
									
										128
									
								
								src/handshake.c
									
									
									
									
									
								
							| @ -43,6 +43,7 @@ | ||||
| #include "src/handshake.h" | ||||
| #include "src/erp.h" | ||||
| #include "src/band.h" | ||||
| #include "src/pmksa.h" | ||||
| 
 | ||||
| static inline unsigned int n_ecc_groups(void) | ||||
| { | ||||
| @ -103,7 +104,14 @@ void __handshake_set_install_ext_tk_func(handshake_install_ext_tk_func_t func) | ||||
| 	install_ext_tk = func; | ||||
| } | ||||
| 
 | ||||
| void handshake_state_free(struct handshake_state *s) | ||||
| struct handshake_state *handshake_state_ref(struct handshake_state *s) | ||||
| { | ||||
| 	__sync_fetch_and_add(&s->refcount, 1); | ||||
| 
 | ||||
| 	return s; | ||||
| } | ||||
| 
 | ||||
| void handshake_state_unref(struct handshake_state *s) | ||||
| { | ||||
| 	__typeof__(s->free) destroy; | ||||
| 
 | ||||
| @ -117,6 +125,9 @@ void handshake_state_free(struct handshake_state *s) | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (__sync_sub_and_fetch(&s->refcount, 1)) | ||||
| 		return; | ||||
| 
 | ||||
| 	l_free(s->authenticator_ie); | ||||
| 	l_free(s->supplicant_ie); | ||||
| 	l_free(s->authenticator_rsnxe); | ||||
| @ -128,6 +139,9 @@ void handshake_state_free(struct handshake_state *s) | ||||
| 	l_free(s->fils_ip_resp_ie); | ||||
| 	l_free(s->vendor_ies); | ||||
| 
 | ||||
| 	if (s->have_pmksa) | ||||
| 		l_free(s->pmksa); | ||||
| 
 | ||||
| 	if (s->erp_cache) | ||||
| 		erp_cache_put(s->erp_cache); | ||||
| 
 | ||||
| @ -354,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) | ||||
| @ -691,6 +711,11 @@ void handshake_state_install_ptk(struct handshake_state *s) | ||||
| { | ||||
| 	s->ptk_complete = true; | ||||
| 
 | ||||
| 	if (!s->have_pmksa && IE_AKM_IS_SAE(s->akm_suite)) { | ||||
| 		l_debug("Adding PMKSA expiration"); | ||||
| 		s->expiration = l_time_now() + pmksa_lifetime(); | ||||
| 	} | ||||
| 
 | ||||
| 	if (install_tk) { | ||||
| 		uint32_t cipher = ie_rsn_cipher_suite_to_cipher( | ||||
| 							s->pairwise_cipher); | ||||
| @ -858,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; | ||||
| @ -888,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; | ||||
| @ -1193,3 +1223,89 @@ done: | ||||
| 
 | ||||
| 	return r; | ||||
| } | ||||
| 
 | ||||
| bool handshake_state_set_pmksa(struct handshake_state *s, | ||||
| 					struct pmksa *pmksa) | ||||
| { | ||||
| 	/* checks for both expiration || pmksa being set */ | ||||
| 	if (s->expiration) | ||||
| 		return false; | ||||
| 
 | ||||
| 	s->pmksa = pmksa; | ||||
| 	s->have_pmksa = true; | ||||
| 
 | ||||
| 	handshake_state_set_pmkid(s, pmksa->pmkid); | ||||
| 	handshake_state_set_pmk(s, pmksa->pmk, pmksa->pmk_len); | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| static struct pmksa *handshake_state_steal_pmksa(struct handshake_state *s) | ||||
| { | ||||
| 	struct pmksa *pmksa; | ||||
| 	uint64_t now = l_time_now(); | ||||
| 
 | ||||
| 	if (s->have_pmksa) { | ||||
| 		pmksa = l_steal_ptr(s->pmksa); | ||||
| 		s->have_pmksa = false; | ||||
| 
 | ||||
| 		if (l_time_after(now, pmksa->expiration)) { | ||||
| 			pmksa_cache_free(pmksa); | ||||
| 			pmksa = NULL; | ||||
| 		} | ||||
| 
 | ||||
| 		return pmksa; | ||||
| 	} | ||||
| 
 | ||||
| 	if (s->expiration && l_time_after(now, s->expiration)) { | ||||
| 		s->expiration = 0; | ||||
| 		return NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!s->have_pmkid) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	pmksa = l_new(struct pmksa, 1); | ||||
| 	pmksa->expiration = s->expiration; | ||||
| 	memcpy(pmksa->spa, s->spa, sizeof(s->spa)); | ||||
| 	memcpy(pmksa->aa, s->aa, sizeof(s->aa)); | ||||
| 	memcpy(pmksa->ssid, s->ssid, s->ssid_len); | ||||
| 	pmksa->ssid_len = s->ssid_len; | ||||
| 	pmksa->akm = s->akm_suite; | ||||
| 	memcpy(pmksa->pmkid, s->pmkid, sizeof(s->pmkid)); | ||||
| 	pmksa->pmk_len = s->pmk_len; | ||||
| 	memcpy(pmksa->pmk, s->pmk, s->pmk_len); | ||||
| 
 | ||||
| 	return pmksa; | ||||
| } | ||||
| 
 | ||||
| void handshake_state_cache_pmksa(struct handshake_state *s) | ||||
| { | ||||
| 	struct pmksa *pmksa = handshake_state_steal_pmksa(s); | ||||
| 
 | ||||
| 	if (!pmksa) { | ||||
| 		l_debug("No PMKSA for "MAC, MAC_STR(s->aa)); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	l_debug("Caching PMKSA for "MAC, MAC_STR(s->aa)); | ||||
| 
 | ||||
| 	if (L_WARN_ON(pmksa_cache_put(pmksa) < 0)) | ||||
| 		pmksa_cache_free(pmksa); | ||||
| } | ||||
| 
 | ||||
| bool handshake_state_remove_pmksa(struct handshake_state *s) | ||||
| { | ||||
| 	struct pmksa *pmksa; | ||||
| 
 | ||||
| 	if (!s->have_pmksa) | ||||
| 		return false; | ||||
| 
 | ||||
| 	pmksa = handshake_state_steal_pmksa(s); | ||||
| 	if (!pmksa) | ||||
| 		return false; | ||||
| 
 | ||||
| 	pmksa_cache_free(pmksa); | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| @ -26,9 +26,12 @@ | ||||
| #include <linux/types.h> | ||||
| #include <ell/cleanup.h> | ||||
| 
 | ||||
| #include "src/vendor_quirks.h" | ||||
| 
 | ||||
| struct handshake_state; | ||||
| enum crypto_cipher; | ||||
| struct eapol_frame; | ||||
| struct pmksa; | ||||
| 
 | ||||
| enum handshake_kde { | ||||
| 	/* 802.11-2020 Table 12-9 in section 12.7.2 */ | ||||
| @ -106,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; | ||||
| @ -141,6 +145,11 @@ struct handshake_state { | ||||
| 	bool supplicant_ocvc : 1; | ||||
| 	bool ext_key_id_capable : 1; | ||||
| 	bool force_default_ecc_group : 1; | ||||
| 	bool have_pmksa : 1; | ||||
| 	union { | ||||
| 		struct pmksa *pmksa; | ||||
| 		uint64_t expiration; | ||||
| 	}; | ||||
| 	uint8_t ssid[SSID_MAX_SIZE]; | ||||
| 	size_t ssid_len; | ||||
| 	char *passphrase; | ||||
| @ -170,6 +179,8 @@ struct handshake_state { | ||||
| 	bool in_event; | ||||
| 
 | ||||
| 	handshake_event_func_t event_func; | ||||
| 
 | ||||
| 	int refcount; | ||||
| }; | ||||
| 
 | ||||
| #define HSID(x) UNIQUE_ID(handshake_, x) | ||||
| @ -186,7 +197,7 @@ struct handshake_state { | ||||
| 					##__VA_ARGS__);			\ | ||||
| 									\ | ||||
| 			if (!HSID(hs)->in_event) {			\ | ||||
| 				handshake_state_free(HSID(hs));		\ | ||||
| 				handshake_state_unref(HSID(hs));	\ | ||||
| 				HSID(freed) = true;			\ | ||||
| 			} else						\ | ||||
| 				HSID(hs)->in_event = false;		\ | ||||
| @ -194,7 +205,8 @@ struct handshake_state { | ||||
| 		HSID(freed);						\ | ||||
| 	}) | ||||
| 
 | ||||
| void handshake_state_free(struct handshake_state *s); | ||||
| struct handshake_state *handshake_state_ref(struct handshake_state *s); | ||||
| void handshake_state_unref(struct handshake_state *s); | ||||
| 
 | ||||
| void handshake_state_set_supplicant_address(struct handshake_state *s, | ||||
| 						const uint8_t *spa); | ||||
| @ -228,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); | ||||
| @ -299,7 +314,12 @@ void handshake_state_set_chandef(struct handshake_state *s, | ||||
| int handshake_state_verify_oci(struct handshake_state *s, const uint8_t *oci, | ||||
| 				size_t oci_len); | ||||
| 
 | ||||
| bool handshake_util_ap_ie_matches(const struct ie_rsn_info *msg_info, | ||||
| 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(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, | ||||
| @ -316,4 +336,4 @@ void handshake_util_build_gtk_kde(enum crypto_cipher cipher, const uint8_t *key, | ||||
| void handshake_util_build_igtk_kde(enum crypto_cipher cipher, const uint8_t *key, | ||||
| 					unsigned int key_index, uint8_t *to); | ||||
| 
 | ||||
| DEFINE_CLEANUP_FUNC(handshake_state_free); | ||||
| DEFINE_CLEANUP_FUNC(handshake_state_unref); | ||||
|  | ||||
| @ -225,6 +225,11 @@ The group ``[General]`` contains general settings. | ||||
|        request is just a 'hint' and ultimately left up to the kernel to set the | ||||
|        country. | ||||
| 
 | ||||
|    * - DisablePMKSA | ||||
|      - Value: **false**, true | ||||
| 
 | ||||
|        Disable PMKSA support in IWD | ||||
| 
 | ||||
| Network | ||||
| ------- | ||||
| 
 | ||||
| @ -285,9 +290,27 @@ control how long a misbehaved BSS spends on the blacklist. | ||||
|    * - InitialTimeout | ||||
|      - Values: uint64 value in seconds (default: **60**) | ||||
| 
 | ||||
|        The initial time that a BSS spends on the blacklist. | ||||
|        The initial time that a BSS spends on the blacklist. Setting this to zero | ||||
|        will disable blacklisting functionality in IWD. | ||||
|    * - InitialRoamRequestedTimeout (**deprecated**) | ||||
|      - Values: uint64 value in seconds (default: **30**) | ||||
| 
 | ||||
|        This setting is deprecated, please use | ||||
|        [Blacklist].InitialAccessPointBusyTimeout instead. | ||||
|    * - InitialAccessPointBusyTimeout | ||||
|      - Values: uint64 value in seconds (default: **30**) | ||||
| 
 | ||||
|        The initial time that a BSS will be blacklisted after indicating it | ||||
|        cannot handle more connections. This is triggered by either a BSS | ||||
|        transition management request (telling IWD to roam elsewhere) or by a | ||||
|        denied authentication or association with the NO_MORE_STAS status code. | ||||
| 
 | ||||
|        Once a BSS is blacklisted in this manor IWD will attempt to avoid it for | ||||
|        the configured amount of time. | ||||
| 
 | ||||
|    * - Multiplier | ||||
|      - Values: unsigned int value in seconds (default: **30**) | ||||
|      - Values: unsigned int value greater than zero, in seconds | ||||
|        (default: **30**) | ||||
| 
 | ||||
|        If the BSS was blacklisted previously and another connection attempt | ||||
|        has failed after the initial timeout has expired, then the BSS blacklist | ||||
| @ -341,6 +364,28 @@ autoconnect purposes. | ||||
|        A value of 0.0 will disable the 6GHz band and prevent scanning or | ||||
|        connecting on those frequencies. | ||||
| 
 | ||||
|    * - HighUtilizationThreshold | ||||
|      - Values: unsigned integer value 0 - 255 (default: **0**, disabled) | ||||
| 
 | ||||
|        **Warning: This is an experimental feature** | ||||
| 
 | ||||
|        The BSS utilization threshold at which a negative rank factor begins to | ||||
|        be applied to the BSS. As the load increases for a BSS the ranking factor | ||||
|        decays exponentially, meaning the ranking factor will decrease | ||||
|        exponentially. Setting this can have very drastic effects on the BSS rank | ||||
|        if its utilization is high, use with care. | ||||
| 
 | ||||
|    * - HighStationCountThreshold | ||||
|      - Values: unsigned integer value 0 - 255 (default: **0**, disabled) | ||||
| 
 | ||||
|        **Warning: This is an experimental feature** | ||||
| 
 | ||||
|        The BSS station count threshold at which a negative rank factor begins to | ||||
|        be applied to the BSS. As the station count increases for a BSS the | ||||
|        ranking factor decays exponentially, meaning the ranking factor will | ||||
|        decrease exponentially. Setting this can have very drastic effects on the | ||||
|        BSS rank if its station count is high, use with care. | ||||
| 
 | ||||
| Scan | ||||
| ---- | ||||
| 
 | ||||
| @ -432,6 +477,20 @@ are buggy or just don't behave similar enough to the majority of other drivers. | ||||
| 
 | ||||
|        If a driver in user matches one in this list power save will be disabled. | ||||
| 
 | ||||
|    * - MulticastRxDisable | ||||
|      - Values: comma-separated list of drivers or glob matches | ||||
| 
 | ||||
|        If a driver in use matches one in this list, multicast RX will be | ||||
|        disabled. | ||||
| 
 | ||||
|    * - SaeDisable | ||||
|      - Values: comma-separated list of drivers or glob matches | ||||
| 
 | ||||
|        If a driver in use matches one in this list, SAE/WPA3 will be disabled | ||||
|        for connections. This will prevent connections to WPA3-only networks, but | ||||
|        will allow for connections to WPA3/WPA2 hybrid networks by utilizing | ||||
|        WPA2. | ||||
| 
 | ||||
| SEE ALSO | ||||
| ======== | ||||
| 
 | ||||
|  | ||||
| @ -209,7 +209,7 @@ connect to that network. | ||||
|    * - PasswordIdentifier | ||||
|      - string | ||||
| 
 | ||||
|        An identifer string to be used with the passphrase. This is used for | ||||
|        An identifier string to be used with the passphrase. This is used for | ||||
|        WPA3-Personal (SAE) networks if the security has enabled password | ||||
|        identifiers for clients. | ||||
|    * - PreSharedKey | ||||
|  | ||||
| @ -82,7 +82,7 @@ bool mschap_challenge_response(const uint8_t *challenge, | ||||
| 
 | ||||
| /**
 | ||||
|  * Hash the utf8 encoded nt password. | ||||
|  * It is asumed, that the password is valid utf8! | ||||
|  * It is assumed, that the password is valid utf8! | ||||
|  * The rfc says "unicode-char", but never specifies which encoding. | ||||
|  * This function converts the password to ucs-2. | ||||
|  * The example in the code uses LE for the unicode chars, so it is forced here. | ||||
|  | ||||
| @ -622,7 +622,7 @@ bool netconfig_get_fils_ip_req(struct netconfig *netconfig, | ||||
| 				struct ie_fils_ip_addr_request_info *info) | ||||
| { | ||||
| 	/*
 | ||||
| 	 * Fill in the fields used for building the FILS IP Address Assigment | ||||
| 	 * Fill in the fields used for building the FILS IP Address Assignment | ||||
| 	 * IE during connection if we're configured to do automatic network | ||||
| 	 * configuration (usually DHCP).  If we're configured with static | ||||
| 	 * values return false to mean the IE should not be sent. | ||||
| @ -710,7 +710,7 @@ struct netconfig *netconfig_new(uint32_t ifindex) | ||||
| 	netconfig_commit_init(netconfig); | ||||
| 
 | ||||
| 	debug_level = getenv("IWD_DHCP_DEBUG"); | ||||
| 	if (debug_level != NULL) { | ||||
| 	if (debug_level) { | ||||
| 		if (!strcmp("debug", debug_level)) | ||||
| 			dhcp_priority = L_LOG_DEBUG; | ||||
| 		else if (!strcmp("info", debug_level)) | ||||
|  | ||||
							
								
								
									
										273
									
								
								src/netdev.c
									
									
									
									
									
								
							
							
						
						
									
										273
									
								
								src/netdev.c
									
									
									
									
									
								
							| @ -65,6 +65,7 @@ | ||||
| #include "src/frame-xchg.h" | ||||
| #include "src/diagnostic.h" | ||||
| #include "src/band.h" | ||||
| #include "src/pmksa.h" | ||||
| 
 | ||||
| #ifndef ENOTSUPP | ||||
| #define ENOTSUPP 524 | ||||
| @ -376,6 +377,7 @@ struct handshake_state *netdev_handshake_state_new(struct netdev *netdev) | ||||
| 
 | ||||
| 	nhs->super.ifindex = netdev->index; | ||||
| 	nhs->super.free = netdev_handshake_state_free; | ||||
| 	nhs->super.refcount = 1; | ||||
| 
 | ||||
| 	nhs->netdev = netdev; | ||||
| 	/*
 | ||||
| @ -461,6 +463,14 @@ uint8_t netdev_get_rssi_level_idx(struct netdev *netdev) | ||||
| 	return netdev->cur_rssi_level_idx; | ||||
| } | ||||
| 
 | ||||
| int netdev_get_low_signal_threshold(uint32_t frequency) | ||||
| { | ||||
| 	if (frequency > 4000) | ||||
| 		return LOW_SIGNAL_THRESHOLD_5GHZ; | ||||
| 
 | ||||
| 	return LOW_SIGNAL_THRESHOLD; | ||||
| } | ||||
| 
 | ||||
| static void netdev_set_powered_result(int error, uint16_t type, | ||||
| 					const void *data, | ||||
| 					uint32_t len, void *user_data) | ||||
| @ -627,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; | ||||
| @ -635,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; | ||||
| 		} | ||||
| 	} | ||||
| @ -828,7 +853,7 @@ static void netdev_connect_free(struct netdev *netdev) | ||||
| 	eapol_preauth_cancel(netdev->index); | ||||
| 
 | ||||
| 	if (netdev->handshake) { | ||||
| 		handshake_state_free(netdev->handshake); | ||||
| 		handshake_state_unref(netdev->handshake); | ||||
| 		netdev->handshake = NULL; | ||||
| 	} | ||||
| 
 | ||||
| @ -1099,6 +1124,7 @@ static void netdev_free(void *data) | ||||
| 		l_timeout_remove(netdev->rssi_poll_timeout); | ||||
| 
 | ||||
| 	scan_wdev_remove(netdev->wdev_id); | ||||
| 	frame_watch_wdev_remove(netdev->wdev_id); | ||||
| 
 | ||||
| 	watchlist_destroy(&netdev->station_watches); | ||||
| 
 | ||||
| @ -1496,6 +1522,105 @@ static void netdev_setting_keys_failed(struct netdev_handshake_state *nhs, | ||||
| 	handshake_event(&nhs->super, HANDSHAKE_EVENT_SETTING_KEYS_FAILED, &err); | ||||
| } | ||||
| 
 | ||||
| static bool netdev_match_addr(const void *a, const void *b) | ||||
| { | ||||
| 	const struct netdev *netdev = a; | ||||
| 	const uint8_t *addr = b; | ||||
| 
 | ||||
| 	return memcmp(netdev->addr, addr, ETH_ALEN) == 0; | ||||
| } | ||||
| 
 | ||||
| static struct netdev *netdev_find_by_address(const uint8_t *addr) | ||||
| { | ||||
| 	return l_queue_find(netdev_list, netdev_match_addr, addr); | ||||
| } | ||||
| 
 | ||||
| static void netdev_pmksa_driver_add(const struct pmksa *pmksa) | ||||
| { | ||||
| 	struct l_genl_msg *msg; | ||||
| 	struct netdev *netdev = netdev_find_by_address(pmksa->spa); | ||||
| 	uint32_t expiration = (uint32_t)pmksa->expiration; | ||||
| 
 | ||||
| 	if (!netdev) | ||||
| 		return; | ||||
| 
 | ||||
| 	/* Only need to set the PMKSA into the kernel for fullmac drivers */ | ||||
| 	if (wiphy_supports_cmds_auth_assoc(netdev->wiphy)) | ||||
| 		return; | ||||
| 
 | ||||
| 	l_debug("Adding PMKSA to kernel"); | ||||
| 
 | ||||
| 	msg = l_genl_msg_new(NL80211_CMD_SET_PMKSA); | ||||
| 
 | ||||
| 	l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); | ||||
| 	l_genl_msg_append_attr(msg, NL80211_ATTR_PMKID, 16, pmksa->pmkid); | ||||
| 	l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN, pmksa->aa); | ||||
| 	l_genl_msg_append_attr(msg, NL80211_ATTR_SSID, | ||||
| 				pmksa->ssid_len, pmksa->ssid); | ||||
| 	l_genl_msg_append_attr(msg, NL80211_ATTR_PMK_LIFETIME, 4, &expiration); | ||||
| 	l_genl_msg_append_attr(msg, NL80211_ATTR_PMK, | ||||
| 				pmksa->pmk_len, pmksa->pmk); | ||||
| 
 | ||||
| 	if (!l_genl_family_send(nl80211, msg, NULL, NULL, NULL)) | ||||
| 		l_error("error sending SET_PMKSA"); | ||||
| } | ||||
| 
 | ||||
| static void netdev_pmksa_driver_remove(const struct pmksa *pmksa) | ||||
| { | ||||
| 	struct l_genl_msg *msg; | ||||
| 	struct netdev *netdev = netdev_find_by_address(pmksa->spa); | ||||
| 
 | ||||
| 	if (!netdev) | ||||
| 		return; | ||||
| 
 | ||||
| 	/* Only need to set the PMKSA into the kernel for fullmac drivers */ | ||||
| 	if (wiphy_supports_cmds_auth_assoc(netdev->wiphy)) | ||||
| 		return; | ||||
| 
 | ||||
| 	l_debug("Removing PMKSA from kernel"); | ||||
| 
 | ||||
| 	msg = l_genl_msg_new(NL80211_CMD_DEL_PMKSA); | ||||
| 
 | ||||
| 	l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); | ||||
| 	l_genl_msg_append_attr(msg, NL80211_ATTR_PMKID, 16, pmksa->pmkid); | ||||
| 	l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN, pmksa->aa); | ||||
| 	l_genl_msg_append_attr(msg, NL80211_ATTR_SSID, | ||||
| 				pmksa->ssid_len, pmksa->ssid); | ||||
| 
 | ||||
| 	if (!l_genl_family_send(nl80211, msg, NULL, NULL, NULL)) | ||||
| 		l_error("error sending DEL_PMKSA"); | ||||
| } | ||||
| 
 | ||||
| static void netdev_flush_pmksa(struct netdev *netdev) | ||||
| { | ||||
| 	struct l_genl_msg *msg; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	* We only utilize the kernel's PMKSA cache for fullmac cards, | ||||
| 	* so no need to flush if this is a softmac. | ||||
| 	*/ | ||||
| 	if (wiphy_supports_cmds_auth_assoc(netdev->wiphy)) | ||||
| 		return; | ||||
| 
 | ||||
| 	msg = l_genl_msg_new(NL80211_CMD_FLUSH_PMKSA); | ||||
| 
 | ||||
| 	l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); | ||||
| 
 | ||||
| 	if (!l_genl_family_send(nl80211, msg, NULL, NULL, NULL)) | ||||
| 		l_error("Failed to flush PMKSA for %u", netdev->index); | ||||
| } | ||||
| 
 | ||||
| static void netdev_pmksa_driver_flush(void) | ||||
| { | ||||
| 	const struct l_queue_entry *e; | ||||
| 
 | ||||
| 	for (e = l_queue_get_entries(netdev_list); e; e = e->next) { | ||||
| 		struct netdev *netdev = e->data; | ||||
| 
 | ||||
| 		netdev_flush_pmksa(netdev); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static void try_handshake_complete(struct netdev_handshake_state *nhs) | ||||
| { | ||||
| 	l_debug("ptk_installed: %u, gtk_installed: %u, igtk_installed: %u", | ||||
| @ -1516,6 +1641,8 @@ static void try_handshake_complete(struct netdev_handshake_state *nhs) | ||||
| 
 | ||||
| 		l_debug("Invoking handshake_event()"); | ||||
| 
 | ||||
| 		handshake_state_cache_pmksa(&nhs->super); | ||||
| 
 | ||||
| 		if (handshake_event(&nhs->super, HANDSHAKE_EVENT_COMPLETE)) | ||||
| 			return; | ||||
| 
 | ||||
| @ -2169,7 +2296,7 @@ static void netdev_set_pmk(struct handshake_state *hs, const uint8_t *pmk, | ||||
| 				struct netdev_handshake_state, super); | ||||
| 	struct netdev *netdev = nhs->netdev; | ||||
| 
 | ||||
| 	/* Only relevent for 8021x offload */ | ||||
| 	/* Only relevant for 8021x offload */ | ||||
| 	if (nhs->type != CONNECTION_TYPE_8021X_OFFLOAD) | ||||
| 		return; | ||||
| 
 | ||||
| @ -2281,7 +2408,7 @@ static void netdev_qos_map_cb(struct l_genl_msg *msg, void *user_data) | ||||
| 		return; | ||||
| 
 | ||||
| 	ext_error = l_genl_msg_get_extended_error(msg); | ||||
| 	l_error("Couuld not set QoS Map in kernel: %s", | ||||
| 	l_error("Could not set QoS Map in kernel: %s", | ||||
| 			ext_error ? ext_error : strerror(-err)); | ||||
| } | ||||
| 
 | ||||
| @ -2354,7 +2481,7 @@ static void netdev_get_oci_cb(struct l_genl_msg *msg, void *user_data) | ||||
| done: | ||||
| 	if (netdev->ap) { | ||||
| 		/*
 | ||||
| 		 * Cant do much here. IWD assumes every kernel/driver supports | ||||
| 		 * Can't do much here. IWD assumes every kernel/driver supports | ||||
| 		 * this. There is no way of detecting support either. | ||||
| 		 */ | ||||
| 		if (L_WARN_ON(err < 0)) | ||||
| @ -2457,7 +2584,19 @@ static struct l_genl_msg *netdev_build_cmd_connect(struct netdev *netdev, | ||||
| { | ||||
| 	struct netdev_handshake_state *nhs = | ||||
| 		l_container_of(hs, struct netdev_handshake_state, super); | ||||
| 	uint32_t auth_type = IE_AKM_IS_SAE(hs->akm_suite) ? | ||||
| 	/*
 | ||||
| 	 * Choose Open system auth type if PMKSA caching is used for an SAE AKM: | ||||
| 	 * | ||||
| 	 * IEEE 802.11-2020 Table 9-151 | ||||
| 	 *   - SAE authentication: | ||||
| 	 *       3 (SAE) for SAE Authentication | ||||
| 	 *       0 (open) for PMKSA caching | ||||
| 	 *   - FT authentication over SAE: | ||||
| 	 *       3 (SAE) for FT Initial Mobility Domain Association | ||||
| 	 *       0 (open) for FT Initial Mobility Domain Association over | ||||
| 	 *         PMKSA caching | ||||
| 	 */ | ||||
| 	uint32_t auth_type = IE_AKM_IS_SAE(hs->akm_suite) && !hs->have_pmksa ? | ||||
| 					NL80211_AUTHTYPE_SAE : | ||||
| 					NL80211_AUTHTYPE_OPEN_SYSTEM; | ||||
| 	enum mpdu_management_subtype subtype = prev_bssid ? | ||||
| @ -2869,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); | ||||
| } | ||||
| @ -3467,6 +3619,13 @@ static void netdev_external_auth_sae_tx_associate(void *user_data) | ||||
| 
 | ||||
| 	netdev_send_external_auth(netdev, MMPDU_STATUS_CODE_SUCCESS); | ||||
| 	netdev_ensure_eapol_registered(netdev); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Free the auth proto now. With external auth there is no associate | ||||
| 	 * event which is where this normally gets cleaned up. | ||||
| 	 */ | ||||
| 	auth_proto_free(netdev->ap); | ||||
| 	netdev->ap = NULL; | ||||
| } | ||||
| 
 | ||||
| struct rtnl_data { | ||||
| @ -3793,6 +3952,15 @@ static void netdev_cmd_set_cqm_cb(struct l_genl_msg *msg, void *user_data) | ||||
| static int netdev_cqm_rssi_update(struct netdev *netdev) | ||||
| { | ||||
| 	struct l_genl_msg *msg; | ||||
| 	struct netdev_handshake_state *nhs = l_container_of(netdev->handshake, | ||||
| 				struct netdev_handshake_state, super); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Fullmac cards handle roaming in firmware, there is no need to set | ||||
| 	 * CQM thresholds | ||||
| 	 */ | ||||
| 	if (nhs->type == CONNECTION_TYPE_FULLMAC) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	l_debug(""); | ||||
| 
 | ||||
| @ -4026,6 +4194,15 @@ static void netdev_connect_common(struct netdev *netdev, | ||||
| 		goto done; | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * If SAE, and we have a valid PMKSA cache we can skip the entire SAE | ||||
| 	 * protocol and authenticate using the cached keys. | ||||
| 	 */ | ||||
| 	if (IE_AKM_IS_SAE(hs->akm_suite) && hs->have_pmksa) { | ||||
| 		l_debug("Skipping SAE by using PMKSA cache"); | ||||
| 		goto build_cmd_connect; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!IE_AKM_IS_SAE(hs->akm_suite) || | ||||
| 				nhs->type == CONNECTION_TYPE_SAE_OFFLOAD) | ||||
| 		goto build_cmd_connect; | ||||
| @ -4239,7 +4416,7 @@ int netdev_reassociate(struct netdev *netdev, const struct scan_bss *target_bss, | ||||
| 		eapol_sm_free(old_sm); | ||||
| 
 | ||||
| 	if (old_hs) | ||||
| 		handshake_state_free(old_hs); | ||||
| 		handshake_state_unref(old_hs); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| @ -5253,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) { | ||||
| @ -5302,6 +5482,39 @@ static void netdev_michael_mic_failure(struct l_genl_msg *msg, | ||||
| 	l_debug("ifindex=%u key_idx=%u type=%u", netdev->index, idx, type); | ||||
| } | ||||
| 
 | ||||
| #define MAX_COMEBACK_DELAY 1200 | ||||
| 
 | ||||
| static void netdev_assoc_comeback(struct l_genl_msg *msg, | ||||
| 					struct netdev *netdev) | ||||
| { | ||||
| 	const uint8_t *mac; | ||||
| 	uint32_t timeout; | ||||
| 
 | ||||
| 	if (L_WARN_ON(!netdev->connected)) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (nl80211_parse_attrs(msg, NL80211_ATTR_MAC, &mac, | ||||
| 				NL80211_ATTR_TIMEOUT, &timeout, | ||||
| 				NL80211_ATTR_UNSPEC) < 0) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (L_WARN_ON(memcmp(mac, netdev->handshake->aa, ETH_ALEN))) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (timeout <= MAX_COMEBACK_DELAY) { | ||||
| 		l_debug(MAC" requested an association comeback delay of %u TU", | ||||
| 			MAC_STR(netdev->handshake->aa), timeout); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	l_debug("Comeback delay of %u exceeded maximum of %u, deauthenticating", | ||||
| 		timeout, MAX_COMEBACK_DELAY); | ||||
| 
 | ||||
| 	netdev_deauth_and_fail_connection(netdev, | ||||
| 					NETDEV_RESULT_ASSOCIATION_FAILED, | ||||
| 					MMPDU_STATUS_CODE_REFUSED_TEMPORARILY); | ||||
| } | ||||
| 
 | ||||
| static void netdev_mlme_notify(struct l_genl_msg *msg, void *user_data) | ||||
| { | ||||
| 	struct netdev *netdev = NULL; | ||||
| @ -5355,6 +5568,9 @@ static void netdev_mlme_notify(struct l_genl_msg *msg, void *user_data) | ||||
| 	case NL80211_CMD_MICHAEL_MIC_FAILURE: | ||||
| 		netdev_michael_mic_failure(msg, netdev); | ||||
| 		break; | ||||
| 	case NL80211_CMD_ASSOC_COMEBACK: | ||||
| 		netdev_assoc_comeback(msg, netdev); | ||||
| 		break; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -5472,23 +5688,18 @@ static void netdev_external_auth_event(struct l_genl_msg *msg, | ||||
| 	} | ||||
| 
 | ||||
| 	if (action == NL80211_EXTERNAL_AUTH_ABORT) { | ||||
| 		iwd_notice(IWD_NOTICE_CONNECT_INFO, "External Auth Aborted"); | ||||
| 		l_warn("External Auth Aborted"); | ||||
| 		goto error; | ||||
| 	} | ||||
| 
 | ||||
| 	iwd_notice(IWD_NOTICE_CONNECT_INFO, | ||||
| 			"External Auth to SSID: %s, bssid: "MAC, | ||||
| 			util_ssid_to_utf8(ssid.iov_len, ssid.iov_base), | ||||
| 			MAC_STR(bssid)); | ||||
| 
 | ||||
| 	if (hs->ssid_len != ssid.iov_len || | ||||
| 			memcmp(hs->ssid, ssid.iov_base, hs->ssid_len)) { | ||||
| 		iwd_notice(IWD_NOTICE_CONNECT_INFO, "Target SSID mismatch"); | ||||
| 		l_warn("Target SSID mismatch"); | ||||
| 		goto error; | ||||
| 	} | ||||
| 
 | ||||
| 	if (memcmp(hs->aa, bssid, ETH_ALEN)) { | ||||
| 		iwd_notice(IWD_NOTICE_CONNECT_INFO, "Target BSSID mismatch"); | ||||
| 		l_warn("Target BSSID mismatch"); | ||||
| 		goto error; | ||||
| 	} | ||||
| 
 | ||||
| @ -5720,35 +5931,35 @@ static void netdev_add_station_frame_watches(struct netdev *netdev) | ||||
| 
 | ||||
| 	/* Subscribe to Management -> Action -> RM -> Neighbor Report frames */ | ||||
| 	frame_watch_add(wdev, 0, 0x00d0, action_neighbor_report_prefix, | ||||
| 			sizeof(action_neighbor_report_prefix), | ||||
| 			sizeof(action_neighbor_report_prefix), false, | ||||
| 			netdev_neighbor_report_frame_event, netdev, NULL); | ||||
| 
 | ||||
| 	frame_watch_add(wdev, 0, 0x00d0, action_sa_query_resp_prefix, | ||||
| 			sizeof(action_sa_query_resp_prefix), | ||||
| 			sizeof(action_sa_query_resp_prefix), false, | ||||
| 			netdev_sa_query_resp_frame_event, netdev, NULL); | ||||
| 
 | ||||
| 	frame_watch_add(wdev, 0, 0x00d0, action_sa_query_req_prefix, | ||||
| 			sizeof(action_sa_query_req_prefix), | ||||
| 			sizeof(action_sa_query_req_prefix), false, | ||||
| 			netdev_sa_query_req_frame_event, netdev, NULL); | ||||
| 
 | ||||
| 	frame_watch_add(wdev, 0, 0x00d0, action_ft_response_prefix, | ||||
| 			sizeof(action_ft_response_prefix), | ||||
| 			sizeof(action_ft_response_prefix), false, | ||||
| 			netdev_ft_response_frame_event, netdev, NULL); | ||||
| 
 | ||||
| 	frame_watch_add(wdev, 0, 0x00b0, auth_ft_response_prefix, | ||||
| 			sizeof(auth_ft_response_prefix), | ||||
| 			sizeof(auth_ft_response_prefix), false, | ||||
| 			netdev_ft_auth_response_frame_event, netdev, NULL); | ||||
| 
 | ||||
| 	if (wiphy_supports_qos_set_map(netdev->wiphy)) | ||||
| 		frame_watch_add(wdev, 0, 0x00d0, action_qos_map_prefix, | ||||
| 				sizeof(action_qos_map_prefix), | ||||
| 				sizeof(action_qos_map_prefix), false, | ||||
| 				netdev_qos_map_frame_event, netdev, NULL); | ||||
| 
 | ||||
| 	if (!wiphy_supports_cmds_auth_assoc(netdev->wiphy) && | ||||
| 			wiphy_has_feature(netdev->wiphy, NL80211_FEATURE_SAE)) | ||||
| 		frame_watch_add(wdev, 0, 0x00b0, | ||||
| 				auth_sae_prefix, sizeof(auth_sae_prefix), | ||||
| 				netdev_sae_external_auth_frame_event, | ||||
| 				false, netdev_sae_external_auth_frame_event, | ||||
| 				netdev, NULL); | ||||
| } | ||||
| 
 | ||||
| @ -6497,6 +6708,16 @@ struct netdev *netdev_create_from_genl(struct l_genl_msg *msg, | ||||
| 
 | ||||
| 	netdev_get_link(netdev); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Call the netdev-specific variant to flush only this devices PMKSA | ||||
| 	 * cache in the kernel. This will make IWD's cache and the kernel's | ||||
| 	 * cache consistent, i.e. no entries | ||||
| 	 * | ||||
| 	 * TODO: If we ever are storing PMKSA's on disk we would first need to | ||||
| 	 *       flush, then add all the PMKSA entries at this time. | ||||
| 	 */ | ||||
| 	netdev_flush_pmksa(netdev); | ||||
| 
 | ||||
| 	return netdev; | ||||
| } | ||||
| 
 | ||||
| @ -6612,6 +6833,10 @@ static int netdev_init(void) | ||||
| 
 | ||||
| 	__ft_set_tx_frame_func(netdev_tx_ft_frame); | ||||
| 
 | ||||
| 	__pmksa_set_driver_callbacks(netdev_pmksa_driver_add, | ||||
| 					netdev_pmksa_driver_remove, | ||||
| 					netdev_pmksa_driver_flush); | ||||
| 
 | ||||
| 	unicast_watch = l_genl_add_unicast_watch(genl, NL80211_GENL_NAME, | ||||
| 						netdev_unicast_notify, | ||||
| 						NULL, NULL); | ||||
|  | ||||
| @ -158,6 +158,7 @@ const char *netdev_get_name(struct netdev *netdev); | ||||
| bool netdev_get_is_up(struct netdev *netdev); | ||||
| const char *netdev_get_path(struct netdev *netdev); | ||||
| uint8_t netdev_get_rssi_level_idx(struct netdev *netdev); | ||||
| int netdev_get_low_signal_threshold(uint32_t frequency); | ||||
| 
 | ||||
| struct handshake_state *netdev_handshake_state_new(struct netdev *netdev); | ||||
| struct handshake_state *netdev_get_handshake(struct netdev *netdev); | ||||
|  | ||||
| @ -56,6 +56,7 @@ | ||||
| #include "src/erp.h" | ||||
| #include "src/handshake.h" | ||||
| #include "src/band.h" | ||||
| #include "src/util.h" | ||||
| 
 | ||||
| #define SAE_PT_SETTING "SAE-PT-Group%u" | ||||
| 
 | ||||
| @ -167,6 +168,11 @@ static bool network_secret_check_cacheable(void *data, void *user_data) | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| void network_clear_blacklist(struct network *network) | ||||
| { | ||||
| 	l_queue_clear(network->blacklist, NULL); | ||||
| } | ||||
| 
 | ||||
| void network_connected(struct network *network) | ||||
| { | ||||
| 	enum security security = network_get_security(network); | ||||
| @ -197,8 +203,6 @@ void network_connected(struct network *network) | ||||
| 	l_queue_foreach_remove(network->secrets, | ||||
| 				network_secret_check_cacheable, network); | ||||
| 
 | ||||
| 	l_queue_clear(network->blacklist, NULL); | ||||
| 
 | ||||
| 	network->provisioning_hidden = false; | ||||
| } | ||||
| 
 | ||||
| @ -206,37 +210,16 @@ void network_disconnected(struct network *network) | ||||
| { | ||||
| 	network_settings_close(network); | ||||
| 
 | ||||
| 	l_queue_clear(network->blacklist, NULL); | ||||
| 	network_clear_blacklist(network); | ||||
| 
 | ||||
| 	if (network->provisioning_hidden) | ||||
| 		station_hide_network(network->station, network); | ||||
| } | ||||
| 
 | ||||
| /* First 64 entries calculated by 1 / pow(n, 0.3) for n >= 1 */ | ||||
| static const double rankmod_table[] = { | ||||
| 	1.0000000000, 0.8122523964, 0.7192230933, 0.6597539554, | ||||
| 	0.6170338627, 0.5841906811, 0.5577898253, 0.5358867313, | ||||
| 	0.5172818580, 0.5011872336, 0.4870596972, 0.4745102806, | ||||
| 	0.4632516708, 0.4530661223, 0.4437850034, 0.4352752816, | ||||
| 	0.4274303178, 0.4201634287, 0.4134032816, 0.4070905315, | ||||
| 	0.4011753236, 0.3956154062, 0.3903746872, 0.3854221125, | ||||
| 	0.3807307877, 0.3762772797, 0.3720410580, 0.3680040435, | ||||
| 	0.3641502401, 0.3604654325, 0.3569369365, 0.3535533906, | ||||
| 	0.3503045821, 0.3471812999, 0.3441752105, 0.3412787518, | ||||
| 	0.3384850430, 0.3357878061, 0.3331812996, 0.3306602598, | ||||
| 	0.3282198502, 0.3258556179, 0.3235634544, 0.3213395618, | ||||
| 	0.3191804229, 0.3170827751, 0.3150435863, 0.3130600345, | ||||
| 	0.3111294892, 0.3092494947, 0.3074177553, 0.3056321221, | ||||
| 	0.3038905808, 0.3021912409, 0.3005323264, 0.2989121662, | ||||
| 	0.2973291870, 0.2957819051, 0.2942689208, 0.2927889114, | ||||
| 	0.2913406263, 0.2899228820, 0.2885345572, 0.2871745887, | ||||
| }; | ||||
| 
 | ||||
| bool network_rankmod(const struct network *network, double *rankmod) | ||||
| { | ||||
| 	struct network_info *info = network->info; | ||||
| 	int n; | ||||
| 	int nmax; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Current policy is that only networks successfully connected | ||||
| @ -250,12 +233,7 @@ bool network_rankmod(const struct network *network, double *rankmod) | ||||
| 	if (n < 0) | ||||
| 		return false; | ||||
| 
 | ||||
| 	nmax = L_ARRAY_SIZE(rankmod_table); | ||||
| 
 | ||||
| 	if (n >= nmax) | ||||
| 		n = nmax - 1; | ||||
| 
 | ||||
| 	*rankmod = rankmod_table[n]; | ||||
| 	*rankmod = util_exponential_decay(n); | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| @ -911,6 +889,9 @@ int network_can_connect_bss(struct network *network, const struct scan_bss *bss) | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	if (IE_AKM_IS_OWE(rsn.akm_suites) && wiphy_owe_disabled(wiphy)) | ||||
| 		return -EPERM; | ||||
| 
 | ||||
| 	if (!config || !config->have_transition_disable) { | ||||
| 		if (band == BAND_FREQ_6_GHZ) | ||||
| 			goto mfp_no_tkip; | ||||
| @ -1278,6 +1259,7 @@ struct scan_bss *network_bss_select(struct network *network, | ||||
| 	struct l_queue *bss_list = network->bss_list; | ||||
| 	const struct l_queue_entry *bss_entry; | ||||
| 	struct scan_bss *candidate = NULL; | ||||
| 	bool skipped_open = false; | ||||
| 
 | ||||
| 	for (bss_entry = l_queue_get_entries(bss_list); bss_entry; | ||||
| 			bss_entry = bss_entry->next) { | ||||
| @ -1297,30 +1279,35 @@ struct scan_bss *network_bss_select(struct network *network, | ||||
| 		if (!candidate) | ||||
| 			candidate = bss; | ||||
| 
 | ||||
| 		/* check if temporarily blacklisted */ | ||||
| 		if (l_queue_find(network->blacklist, match_bss, bss)) | ||||
| 			continue; | ||||
| 
 | ||||
| 		if (blacklist_contains_bss(bss->addr, | ||||
| 					BLACKLIST_REASON_CONNECT_FAILED)) | ||||
| 			continue; | ||||
| 
 | ||||
| 		/* OWE Transition BSS */ | ||||
| 		if (bss->owe_trans) { | ||||
| 			/* Don't want to connect to the Open BSS if possible */ | ||||
| 			if (!bss->rsne) | ||||
| 			if (!bss->rsne) { | ||||
| 				skipped_open = true; | ||||
| 				continue; | ||||
| 			} | ||||
| 
 | ||||
| 			/* Candidate is not OWE, set this as new candidate */ | ||||
| 			if (!(candidate->owe_trans && candidate->rsne)) | ||||
| 				candidate = bss; | ||||
| 		} | ||||
| 
 | ||||
| 		/* check if temporarily blacklisted */ | ||||
| 		if (l_queue_find(network->blacklist, match_bss, bss)) | ||||
| 			continue; | ||||
| 
 | ||||
| 		if (!blacklist_contains_bss(bss->addr)) | ||||
| 			return bss; | ||||
| 		return bss; | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * No BSS was found, but if we are falling back to blacklisted BSS's we | ||||
| 	 * can just use the first connectable candidate found above. | ||||
| 	 */ | ||||
| 	if (fallback_to_blacklist) | ||||
| 	if (fallback_to_blacklist || skipped_open) | ||||
| 		return candidate; | ||||
| 
 | ||||
| 	return NULL; | ||||
| @ -2010,10 +1997,8 @@ void network_rank_update(struct network *network, bool connected) | ||||
| 
 | ||||
| 		L_WARN_ON(n < 0); | ||||
| 
 | ||||
| 		if (n >= (int) L_ARRAY_SIZE(rankmod_table)) | ||||
| 			n = L_ARRAY_SIZE(rankmod_table) - 1; | ||||
| 
 | ||||
| 		network->rank = rankmod_table[n] * best_bss->rank + USHRT_MAX; | ||||
| 		network->rank = | ||||
| 			util_exponential_decay(n) * best_bss->rank + USHRT_MAX; | ||||
| 	} else | ||||
| 		network->rank = best_bss->rank; | ||||
| 
 | ||||
|  | ||||
| @ -74,6 +74,7 @@ bool network_bss_update(struct network *network, struct scan_bss *bss); | ||||
| const char *network_bss_get_path(const struct network *network, | ||||
| 						const struct scan_bss *bss); | ||||
| bool network_bss_list_isempty(struct network *network); | ||||
| void network_clear_blacklist(struct network *network); | ||||
| 
 | ||||
| const char *__network_path_append_bss(const char *network_path, | ||||
| 					const struct scan_bss *bss); | ||||
|  | ||||
| @ -177,6 +177,7 @@ static const struct { | ||||
| 	{ NL80211_CMD_UNPROT_BEACON,		"Unprotected Beacon"	}, | ||||
| 	{ NL80211_CMD_CONTROL_PORT_FRAME_TX_STATUS, | ||||
| 						"Control Port TX Status" }, | ||||
| 	{ NL80211_CMD_ASSOC_COMEBACK,		"Association comeback delay" }, | ||||
| 	{ } | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -190,6 +190,7 @@ static attr_handler handler_for_nl80211(int type) | ||||
| 	case NL80211_ATTR_CENTER_FREQ2: | ||||
| 	case NL80211_ATTR_AKM_SUITES: | ||||
| 	case NL80211_ATTR_EXTERNAL_AUTH_ACTION: | ||||
| 	case NL80211_ATTR_TIMEOUT: | ||||
| 		return extract_uint32; | ||||
| 	case NL80211_ATTR_FRAME: | ||||
| 		return extract_iovec; | ||||
| @ -697,8 +698,7 @@ int nl80211_parse_chandef(struct l_genl_msg *msg, struct band_chandef *out) | ||||
| 
 | ||||
| int nl80211_parse_supported_frequencies(struct l_genl_attr *band_freqs, | ||||
| 					struct scan_freq_set *supported_list, | ||||
| 					struct band_freq_attrs *list, | ||||
| 					size_t num_channels) | ||||
| 					struct band *band) | ||||
| { | ||||
| 	uint16_t type, len; | ||||
| 	const void *data; | ||||
| @ -712,6 +712,7 @@ int nl80211_parse_supported_frequencies(struct l_genl_attr *band_freqs, | ||||
| 	while (l_genl_attr_next(&nested, NULL, NULL, NULL)) { | ||||
| 		uint32_t freq = 0; | ||||
| 		struct band_freq_attrs freq_attr = { 0 }; | ||||
| 		enum band_freq out_band; | ||||
| 
 | ||||
| 		if (!l_genl_attr_recurse(&nested, &attr)) | ||||
| 			continue; | ||||
| @ -752,17 +753,20 @@ int nl80211_parse_supported_frequencies(struct l_genl_attr *band_freqs, | ||||
| 		if (!freq) | ||||
| 			continue; | ||||
| 
 | ||||
| 		channel = band_freq_to_channel(freq, NULL); | ||||
| 		channel = band_freq_to_channel(freq, &out_band); | ||||
| 		if (!channel) | ||||
| 			continue; | ||||
| 
 | ||||
| 		if (L_WARN_ON(channel > num_channels)) | ||||
| 		if (L_WARN_ON(out_band != band->freq)) | ||||
| 			continue; | ||||
| 
 | ||||
| 		if (L_WARN_ON(channel > band->freqs_len)) | ||||
| 			continue; | ||||
| 
 | ||||
| 		if (supported_list) | ||||
| 			scan_freq_set_add(supported_list, freq); | ||||
| 
 | ||||
| 		list[channel] = freq_attr; | ||||
| 		band->freq_attrs[channel] = freq_attr; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
|  | ||||
| @ -26,6 +26,7 @@ struct band_chandef; | ||||
| struct scan_freq_set; | ||||
| struct band_freq_attrs; | ||||
| struct handshake_state; | ||||
| struct band; | ||||
| 
 | ||||
| int nl80211_parse_attrs(struct l_genl_msg *msg, int tag, ...); | ||||
| int nl80211_parse_nested(struct l_genl_attr *attr, int type, int tag, ...); | ||||
| @ -95,8 +96,7 @@ struct l_genl_msg *nl80211_build_external_auth(uint32_t ifindex, | ||||
| int nl80211_parse_chandef(struct l_genl_msg *msg, struct band_chandef *out); | ||||
| int nl80211_parse_supported_frequencies(struct l_genl_attr *band_freqs, | ||||
| 					struct scan_freq_set *supported_list, | ||||
| 					struct band_freq_attrs *list, | ||||
| 					size_t num_channels); | ||||
| 					struct band *band); | ||||
| 
 | ||||
| void nl80211_append_rsn_attributes(struct l_genl_msg *msg, | ||||
| 						struct handshake_state *hs); | ||||
|  | ||||
| @ -293,7 +293,7 @@ static void offchannel_mlme_notify(struct l_genl_msg *msg, void *user_data) | ||||
| 		 *  - an event coming from an external ROC request (we just | ||||
| 		 *    happened to have also sent an ROC request). | ||||
| 		 * | ||||
| 		 * We can't tell where the event originated until we recieve our | ||||
| 		 * We can't tell where the event originated until we receive our | ||||
| 		 * ACK so set early_cookie to track it. | ||||
| 		 */ | ||||
| 		if (i->roc_cmd_id != 0 && l_genl_family_request_sent(nl80211, | ||||
|  | ||||
							
								
								
									
										13
									
								
								src/p2p.c
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								src/p2p.c
									
									
									
									
									
								
							| @ -1497,7 +1497,7 @@ static void p2p_handshake_event(struct handshake_state *hs, | ||||
| static void p2p_try_connect_group(struct p2p_device *dev) | ||||
| { | ||||
| 	struct scan_bss *bss = dev->conn_wsc_bss; | ||||
| 	_auto_(handshake_state_free) struct handshake_state *hs = NULL; | ||||
| 	_auto_(handshake_state_unref) struct handshake_state *hs = NULL; | ||||
| 	struct iovec ie_iov[16]; | ||||
| 	int ie_num = 0; | ||||
| 	int r; | ||||
| @ -2217,7 +2217,7 @@ static bool p2p_go_negotiation_confirm_cb(const struct mmpdu_header *mpdu, | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * Start setting the group up right away and we'll add the | ||||
| 		 * client's Configuation Timeout to the WSC start timeout's | ||||
| 		 * client's Configuration Timeout to the WSC start timeout's | ||||
| 		 * value. | ||||
| 		 */ | ||||
| 		p2p_device_interface_create(dev); | ||||
| @ -2549,7 +2549,7 @@ static void p2p_go_negotiation_confirm_done(int error, void *user_data) | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Frame was ACKed.  On the GO start setting the group up right | ||||
| 	 * away and we'll add the client's Configuation Timeout to the | ||||
| 	 * away and we'll add the client's Configuration Timeout to the | ||||
| 	 * WSC start timeout's value.  On the client wait idly the | ||||
| 	 * maximum amount of time indicated by the peer in the GO | ||||
| 	 * Negotiation Response's Configuration Timeout attribute and | ||||
| @ -2951,7 +2951,7 @@ static bool p2p_provision_disc_resp_cb(const struct mmpdu_header *mpdu, | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Indended P2P Interface address is optional, we don't have the | ||||
| 	 * Intended P2P Interface address is optional, we don't have the | ||||
| 	 * BSSID of the group here. | ||||
| 	 * | ||||
| 	 * We might want to make sure that Group Formation is false but the | ||||
| @ -4163,10 +4163,11 @@ static void p2p_device_discovery_start(struct p2p_device *dev) | ||||
| 						L_ARRAY_SIZE(channels_social)]; | ||||
| 
 | ||||
| 	frame_watch_add(dev->wdev_id, FRAME_GROUP_P2P_LISTEN, 0x0040, | ||||
| 			(uint8_t *) "", 0, p2p_device_probe_cb, dev, NULL); | ||||
| 			(uint8_t *) "", 0, false, | ||||
| 			p2p_device_probe_cb, dev, NULL); | ||||
| 	frame_watch_add(dev->wdev_id, FRAME_GROUP_P2P_LISTEN, 0x00d0, | ||||
| 			p2p_frame_go_neg_req.data, p2p_frame_go_neg_req.len, | ||||
| 			p2p_device_go_negotiation_req_cb, dev, NULL); | ||||
| 			false, p2p_device_go_negotiation_req_cb, dev, NULL); | ||||
| 
 | ||||
| 	p2p_device_scan_start(dev); | ||||
| } | ||||
|  | ||||
							
								
								
									
										269
									
								
								src/pmksa.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										269
									
								
								src/pmksa.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,269 @@ | ||||
| /*
 | ||||
|  * | ||||
|  *  Wireless daemon for Linux | ||||
|  * | ||||
|  *  Copyright (C) 2023  Cruise LLC. 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 | ||||
| 
 | ||||
| #define _GNU_SOURCE | ||||
| #include <stdint.h> | ||||
| #include <errno.h> | ||||
| 
 | ||||
| #include <ell/ell.h> | ||||
| #include "ell/useful.h" | ||||
| 
 | ||||
| #include "src/module.h" | ||||
| #include "src/pmksa.h" | ||||
| 
 | ||||
| #define PMKID "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" | ||||
| #define PMKID_STR(x) x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], \ | ||||
| 			x[8], x[9], x[10], x[11], x[12], x[13], x[14], x[15] | ||||
| 
 | ||||
| static uint64_t dot11RSNAConfigPMKLifetime = 43200ULL * L_USEC_PER_SEC; | ||||
| static uint32_t pmksa_cache_capacity = 255; | ||||
| static pmksa_cache_add_func_t driver_add; | ||||
| static pmksa_cache_remove_func_t driver_remove; | ||||
| static pmksa_cache_flush_func_t driver_flush; | ||||
| 
 | ||||
| struct min_heap { | ||||
| 	struct pmksa **data; | ||||
| 	uint32_t capacity; | ||||
| 	uint32_t used; | ||||
| }; | ||||
| 
 | ||||
| static struct min_heap cache; | ||||
| 
 | ||||
| static __always_inline void swap_ptr(void *l, void *r) | ||||
| { | ||||
| 	struct pmksa **lp = l; | ||||
| 	struct pmksa **rp = r; | ||||
| 
 | ||||
| 	SWAP(*lp, *rp); | ||||
| } | ||||
| 
 | ||||
| static __always_inline | ||||
| bool pmksa_compare_expiration(const void *l, const void *r) | ||||
| { | ||||
| 	const struct pmksa * const *lp = l; | ||||
| 	const struct pmksa * const *rp = r; | ||||
| 
 | ||||
| 	return (*lp)->expiration < (*rp)->expiration; | ||||
| } | ||||
| 
 | ||||
| static struct l_minheap_ops ops = { | ||||
| 	.elem_size = sizeof(struct pmksa *), | ||||
| 	.swap = swap_ptr, | ||||
| 	.less = pmksa_compare_expiration, | ||||
| }; | ||||
| 
 | ||||
| static int pmksa_cache_find(const uint8_t spa[static 6], | ||||
| 					const uint8_t aa[static 6], | ||||
| 					const uint8_t *ssid, size_t ssid_len, | ||||
| 					uint32_t akm) | ||||
| { | ||||
| 	unsigned int i; | ||||
| 
 | ||||
| 	for (i = 0; i < cache.used; i++) { | ||||
| 		struct pmksa *pmksa = cache.data[i]; | ||||
| 
 | ||||
| 		if (memcmp(pmksa->spa, spa, sizeof(pmksa->spa))) | ||||
| 			continue; | ||||
| 
 | ||||
| 		if (memcmp(pmksa->aa, aa, sizeof(pmksa->aa))) | ||||
| 			continue; | ||||
| 
 | ||||
| 		if (ssid_len != pmksa->ssid_len) | ||||
| 			continue; | ||||
| 
 | ||||
| 		if (memcmp(pmksa->ssid, ssid, ssid_len)) | ||||
| 			continue; | ||||
| 
 | ||||
| 		if (akm & pmksa->akm) | ||||
| 			return i; | ||||
| 	} | ||||
| 
 | ||||
| 	return -ENOENT; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Try to obtain a PMKSA entry from the cache.  If the the entry matching | ||||
|  * the parameters is found, it is removed from the cache and returned to the | ||||
|  * caller.  The caller is responsible for managing the returned pmksa | ||||
|  * structure | ||||
|  */ | ||||
| struct pmksa *pmksa_cache_get(const uint8_t spa[static 6], | ||||
| 				const uint8_t aa[static 6], | ||||
| 				const uint8_t *ssid, size_t ssid_len, | ||||
| 				uint32_t akm) | ||||
| { | ||||
| 	struct pmksa *pmksa; | ||||
| 	int r = pmksa_cache_find(spa, aa, ssid, ssid_len, akm); | ||||
| 
 | ||||
| 	if (r < 0) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	cache.used -= 1; | ||||
| 	if ((uint32_t) r == cache.used) | ||||
| 		goto done; | ||||
| 
 | ||||
| 	SWAP(cache.data[r], cache.data[cache.used]); | ||||
| 	__minheap_sift_down(cache.data, cache.used, r, &ops); | ||||
| 
 | ||||
| done: | ||||
| 	pmksa = cache.data[cache.used]; | ||||
| 
 | ||||
| 	l_debug("Returning entry with PMKID: "PMKID, PMKID_STR(pmksa->pmkid)); | ||||
| 
 | ||||
| 	return pmksa; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Put a PMKSA into the cache.  It will be sorted in soonest-to-expire order. | ||||
|  * If the cache is full, then soonest-to-expire entry is removed first. | ||||
|  */ | ||||
| int pmksa_cache_put(struct pmksa *pmksa) | ||||
| { | ||||
| 	l_debug("Adding entry with PMKID: "PMKID, PMKID_STR(pmksa->pmkid)); | ||||
| 
 | ||||
| 	if (cache.used == cache.capacity) { | ||||
| 		pmksa_cache_free(cache.data[0]); | ||||
| 		cache.data[0] = pmksa; | ||||
| 		__minheap_sift_down(cache.data, cache.used, 0, &ops); | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
| 	cache.data[cache.used] = pmksa; | ||||
| 	__minheap_sift_up(cache.data, cache.used, &ops); | ||||
| 	cache.used += 1; | ||||
| 
 | ||||
| 	if (driver_add) | ||||
| 		driver_add(pmksa); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Expire all PMKSA entries with expiration time smaller or equal to the cutoff | ||||
|  * time. | ||||
|  */ | ||||
| int pmksa_cache_expire(uint64_t cutoff) | ||||
| { | ||||
| 	int i; | ||||
| 	int used = cache.used; | ||||
| 	int remaining = 0; | ||||
| 
 | ||||
| 	for (i = 0; i < used; i++) { | ||||
| 		if (cache.data[i]->expiration <= cutoff) { | ||||
| 			pmksa_cache_free(cache.data[i]); | ||||
| 			continue; | ||||
| 		} | ||||
| 
 | ||||
| 		cache.data[remaining] = cache.data[i]; | ||||
| 		remaining += 1; | ||||
| 	} | ||||
| 
 | ||||
| 	cache.used = remaining; | ||||
| 
 | ||||
| 	for (i = cache.used >> 1; i >= 0; i--) | ||||
| 		__minheap_sift_down(cache.data, cache.used, i, &ops); | ||||
| 
 | ||||
| 	return used - remaining; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Flush all PMKSA entries from the cache, regardless of expiration time. | ||||
|  */ | ||||
| int pmksa_cache_flush(void) | ||||
| { | ||||
| 	uint32_t i; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * The driver flush operation is done via a single kernel API call which | ||||
| 	 * is why below we use l_free instead of pmksa_cache_free as to not | ||||
| 	 * induce a DEL_PMKSA kernel call for each entry. | ||||
| 	 */ | ||||
| 	if (driver_flush) | ||||
| 		driver_flush(); | ||||
| 
 | ||||
| 	for (i = 0; i < cache.used; i++) | ||||
| 		l_free(cache.data[i]); | ||||
| 
 | ||||
| 	memset(cache.data, 0, cache.capacity * sizeof(struct pmksa *)); | ||||
| 	cache.used = 0; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| int pmksa_cache_free(struct pmksa *pmksa) | ||||
| { | ||||
| 	if (driver_remove) | ||||
| 		driver_remove(pmksa); | ||||
| 
 | ||||
| 	l_free(pmksa); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| struct pmksa **__pmksa_cache_get_all(uint32_t *out_n_entries) | ||||
| { | ||||
| 	if (out_n_entries) | ||||
| 		*out_n_entries = cache.used; | ||||
| 
 | ||||
| 	return cache.data; | ||||
| } | ||||
| 
 | ||||
| uint64_t pmksa_lifetime(void) | ||||
| { | ||||
| 	return dot11RSNAConfigPMKLifetime; | ||||
| } | ||||
| 
 | ||||
| void __pmksa_set_config(const struct l_settings *config) | ||||
| { | ||||
| 	l_settings_get_uint(config, "PMKSA", "Capacity", | ||||
| 					&pmksa_cache_capacity); | ||||
| } | ||||
| 
 | ||||
| void __pmksa_set_driver_callbacks(pmksa_cache_add_func_t add, | ||||
| 					pmksa_cache_remove_func_t remove, | ||||
| 					pmksa_cache_flush_func_t flush) | ||||
| { | ||||
| 	driver_add = add; | ||||
| 	driver_remove = remove; | ||||
| 	driver_flush = flush; | ||||
| } | ||||
| 
 | ||||
| static int pmksa_init(void) | ||||
| { | ||||
| 	cache.capacity = pmksa_cache_capacity; | ||||
| 	cache.used = 0; | ||||
| 	cache.data = l_new(struct pmksa *, cache.capacity); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void pmksa_exit(void) | ||||
| { | ||||
| 	pmksa_cache_flush(); | ||||
| 	l_free(cache.data); | ||||
| } | ||||
| 
 | ||||
| IWD_MODULE(pmksa, pmksa_init, pmksa_exit); | ||||
							
								
								
									
										55
									
								
								src/pmksa.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/pmksa.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | ||||
| /*
 | ||||
|  * | ||||
|  *  Wireless daemon for Linux | ||||
|  * | ||||
|  *  Copyright (C) 2023  Cruise, LLC. 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 | ||||
|  * | ||||
|  */ | ||||
| 
 | ||||
| struct pmksa { | ||||
| 	uint64_t expiration; | ||||
| 	uint8_t spa[6]; | ||||
| 	uint8_t aa[6]; | ||||
| 	uint8_t ssid[32]; | ||||
| 	size_t ssid_len; | ||||
| 	uint32_t akm; | ||||
| 	uint8_t pmkid[16]; | ||||
| 	uint8_t pmk[64]; | ||||
| 	size_t pmk_len; | ||||
| }; | ||||
| 
 | ||||
| typedef void (*pmksa_cache_add_func_t)(const struct pmksa *pmksa); | ||||
| typedef void (*pmksa_cache_remove_func_t)(const struct pmksa *pmksa); | ||||
| typedef void (*pmksa_cache_flush_func_t)(void); | ||||
| 
 | ||||
| struct pmksa **__pmksa_cache_get_all(uint32_t *out_n_entries); | ||||
| 
 | ||||
| struct pmksa *pmksa_cache_get(const uint8_t spa[static 6], | ||||
| 				const uint8_t aa[static 6], | ||||
| 				const uint8_t *ssid, size_t ssid_len, | ||||
| 				uint32_t akm); | ||||
| int pmksa_cache_put(struct pmksa *pmksa); | ||||
| int pmksa_cache_expire(uint64_t cutoff); | ||||
| int pmksa_cache_flush(void); | ||||
| int pmksa_cache_free(struct pmksa *pmksa); | ||||
| 
 | ||||
| uint64_t pmksa_lifetime(void); | ||||
| void __pmksa_set_config(const struct l_settings *config); | ||||
| 
 | ||||
| void __pmksa_set_driver_callbacks(pmksa_cache_add_func_t add, | ||||
| 					pmksa_cache_remove_func_t remove, | ||||
| 					pmksa_cache_flush_func_t flush); | ||||
| @ -798,7 +798,7 @@ static void rrm_add_frame_watches(struct rrm_state *rrm) | ||||
| 	l_debug(""); | ||||
| 
 | ||||
| 	frame_watch_add(rrm->wdev_id, 0, frame_type, prefix, sizeof(prefix), | ||||
| 					rrm_frame_watch_cb, rrm, NULL); | ||||
| 					false, rrm_frame_watch_cb, rrm, NULL); | ||||
| } | ||||
| 
 | ||||
| static struct rrm_state *rrm_new_state(struct netdev *netdev) | ||||
|  | ||||
							
								
								
									
										24
									
								
								src/sae.c
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								src/sae.c
									
									
									
									
									
								
							| @ -169,6 +169,14 @@ static int sae_choose_next_group(struct sae_sm *sm) | ||||
| 				!sm->handshake->ecc_sae_pts[sm->group_retry]) | ||||
| 			continue; | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * TODO: Groups for P192, P224 and P521 are currently | ||||
| 		 * non-functional with SAE. Until this is fixed we need to | ||||
| 		 * avoid these groups from being used. | ||||
| 		 */ | ||||
| 		if (group == 21 || group == 25 || group == 26) | ||||
| 			continue; | ||||
| 
 | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| @ -258,7 +266,7 @@ static struct l_ecc_scalar *sae_pwd_value(const struct l_ecc_curve *curve, | ||||
| 	is_in_range = util_secure_fill_with_msb(is_in_range); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * libell has public Legendre symbol only for l_ecc_scalar, but they | ||||
| 	 * ELL has public Legendre symbol only for l_ecc_scalar, but they | ||||
| 	 * cannot be created if the coordinate is greater than the p. Hence, | ||||
| 	 * to avoid control flow dependencies, we replace pwd_value by a dummy | ||||
| 	 * quadratic non residue if we generate a value >= prime. | ||||
| @ -994,7 +1002,8 @@ static int sae_process_anti_clogging(struct sae_sm *sm, const uint8_t *ptr, | ||||
| 	sm->token_len = len; | ||||
| 	sm->sync = 0; | ||||
| 
 | ||||
| 	sae_send_commit(sm, true); | ||||
| 	if (L_WARN_ON(!sae_send_commit(sm, true))) | ||||
| 		return -EPROTO; | ||||
| 
 | ||||
| 	return -EAGAIN; | ||||
| } | ||||
| @ -1074,7 +1083,9 @@ static int sae_verify_committed(struct sae_sm *sm, uint16_t transaction, | ||||
| 			return -ETIMEDOUT; | ||||
| 
 | ||||
| 		sm->sync++; | ||||
| 		sae_send_commit(sm, true); | ||||
| 
 | ||||
| 		if (L_WARN_ON(!sae_send_commit(sm, true))) | ||||
| 			return -EPROTO; | ||||
| 
 | ||||
| 		return -EAGAIN; | ||||
| 	} | ||||
| @ -1129,7 +1140,9 @@ static int sae_verify_committed(struct sae_sm *sm, uint16_t transaction, | ||||
| 				sm->group); | ||||
| 
 | ||||
| 		sm->sync = 0; | ||||
| 		sae_send_commit(sm, false); | ||||
| 
 | ||||
| 		if (L_WARN_ON(!sae_send_commit(sm, false))) | ||||
| 			return -EPROTO; | ||||
| 
 | ||||
| 		return -EAGAIN; | ||||
| 	} | ||||
| @ -1294,7 +1307,8 @@ static int sae_verify_confirmed(struct sae_sm *sm, uint16_t trans, | ||||
| 	sm->sync++; | ||||
| 	sm->sc++; | ||||
| 
 | ||||
| 	sae_send_commit(sm, true); | ||||
| 	if (L_WARN_ON(!sae_send_commit(sm, true))) | ||||
| 		return -EPROTO; | ||||
| 
 | ||||
| 	if (!sae_send_confirm(sm)) | ||||
| 		return -EPROTO; | ||||
|  | ||||
							
								
								
									
										112
									
								
								src/scan.c
									
									
									
									
									
								
							
							
						
						
									
										112
									
								
								src/scan.c
									
									
									
									
									
								
							| @ -51,11 +51,14 @@ | ||||
| #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; | ||||
| static double RANK_5G_FACTOR; | ||||
| static double RANK_6G_FACTOR; | ||||
| static uint32_t RANK_HIGH_UTILIZATION; | ||||
| static uint32_t RANK_HIGH_STATION_COUNT; | ||||
| static uint32_t SCAN_MAX_INTERVAL; | ||||
| static uint32_t SCAN_INIT_INTERVAL; | ||||
| 
 | ||||
| @ -141,9 +144,9 @@ struct scan_survey { | ||||
| }; | ||||
| 
 | ||||
| struct scan_survey_results { | ||||
| 	struct scan_survey survey_2_4[14]; | ||||
| 	struct scan_survey survey_5[196]; | ||||
| 	struct scan_survey survey_6[233]; | ||||
| 	struct scan_survey survey_2_4[15]; | ||||
| 	struct scan_survey survey_5[197]; | ||||
| 	struct scan_survey survey_6[234]; | ||||
| }; | ||||
| 
 | ||||
| struct scan_results { | ||||
| @ -412,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); | ||||
| @ -1218,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; | ||||
| @ -1343,8 +1352,8 @@ static bool scan_parse_bss_information_elements(struct scan_bss *bss, | ||||
| 								iter.len + 2); | ||||
| 			break; | ||||
| 		case IE_TYPE_BSS_LOAD: | ||||
| 			if (ie_parse_bss_load(&iter, NULL, &bss->utilization, | ||||
| 						NULL) < 0) | ||||
| 			if (ie_parse_bss_load(&iter, &bss->sta_count, | ||||
| 						&bss->utilization, NULL) < 0) | ||||
| 				l_warn("Unable to parse BSS Load IE for " | ||||
| 					MAC, MAC_STR(bss->addr)); | ||||
| 			else | ||||
| @ -1681,10 +1690,77 @@ static struct scan_bss *scan_parse_result(struct l_genl_msg *msg, | ||||
| 	return bss; | ||||
| } | ||||
| 
 | ||||
| static void scan_bss_compute_rank(struct scan_bss *bss) | ||||
| static double scan_legacy_utilization_factor(uint8_t utilization) | ||||
| { | ||||
| 	static const double RANK_HIGH_UTILIZATION_FACTOR = 0.8; | ||||
| 	static const double RANK_LOW_UTILIZATION_FACTOR = 1.2; | ||||
| 
 | ||||
| 	if (utilization >= 192) | ||||
| 		return RANK_HIGH_UTILIZATION_FACTOR; | ||||
| 	else if (utilization <= 63) | ||||
| 		return RANK_LOW_UTILIZATION_FACTOR; | ||||
| 
 | ||||
| 	return 1.0; | ||||
| } | ||||
| 
 | ||||
| static double scan_get_load_factors(uint8_t utilization, uint16_t sta_count) | ||||
| { | ||||
| 	double n; | ||||
| 	double factor = 1.0; | ||||
| 	/*
 | ||||
| 	 * The exponential decay table has 64 entries (0 <= n <= 63) which range | ||||
| 	 * from 1.0 to 0.28. For the utilization and station count factors we | ||||
| 	 * likely don't want to adjust the rank so drastically (potentially a | ||||
| 	 * 78% reduction in the worse case) so cap the index at 30 which equates | ||||
| 	 * to ~64% at the worst case. | ||||
| 	 */ | ||||
| 	static const uint8_t LOAD_DECAY_OFFSET = 30; | ||||
| 
 | ||||
| 	if (!RANK_HIGH_UTILIZATION) { | ||||
| 		/*
 | ||||
| 		 * To maintain existing behavior, if the utilization factor is | ||||
| 		 * unset (default) fall back to the static thresholds and | ||||
| 		 * factor weights. | ||||
| 		 */ | ||||
| 		factor = scan_legacy_utilization_factor(utilization); | ||||
| 		goto sta_count; | ||||
| 	} else if (utilization < RANK_HIGH_UTILIZATION) | ||||
| 		goto sta_count; | ||||
| 
 | ||||
| 	/* Map the utilization threshold -> 255 to rankmod_table indexes */ | ||||
| 	if (L_WARN_ON(!util_linear_map(utilization, RANK_HIGH_UTILIZATION, | ||||
| 					255, 0, LOAD_DECAY_OFFSET, &n))) | ||||
| 		goto sta_count; | ||||
| 
 | ||||
| 	factor = util_exponential_decay(n); | ||||
| 
 | ||||
| sta_count: | ||||
| 	if (!RANK_HIGH_STATION_COUNT || sta_count < RANK_HIGH_STATION_COUNT) | ||||
| 		return factor; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * The station count is a uint16 so in theory it could be excessively | ||||
| 	 * large. In practice APs generally can't handle anywhere near this so | ||||
| 	 * put a cap at 255. If at some time in the future APs begin to handle | ||||
| 	 * this level of capacity we could increase this. | ||||
| 	 * | ||||
| 	 * TODO: A warning is used here to make this visible. If we see cases | ||||
| 	 * where this is happening we may need to give this another look. | ||||
| 	 */ | ||||
| 	if (L_WARN_ON(sta_count > 255)) | ||||
| 		sta_count = 255; | ||||
| 
 | ||||
| 	if (L_WARN_ON(!util_linear_map(sta_count, RANK_HIGH_STATION_COUNT, | ||||
| 					255, 0, LOAD_DECAY_OFFSET, &n))) | ||||
| 		return factor; | ||||
| 
 | ||||
| 	factor *= util_exponential_decay(n); | ||||
| 
 | ||||
| 	return factor; | ||||
| } | ||||
| 
 | ||||
| static void scan_bss_compute_rank(struct scan_bss *bss) | ||||
| { | ||||
| 	static const double RANK_HIGH_SNR_FACTOR = 1.2; | ||||
| 	static const double RANK_LOW_SNR_FACTOR = 0.8; | ||||
| 	double rank; | ||||
| @ -1708,12 +1784,8 @@ static void scan_bss_compute_rank(struct scan_bss *bss) | ||||
| 		rank *= RANK_6G_FACTOR; | ||||
| 
 | ||||
| 	/* Rank loaded APs lower and lightly loaded APs higher */ | ||||
| 	if (bss->have_utilization) { | ||||
| 		if (bss->utilization >= 192) | ||||
| 			rank *= RANK_HIGH_UTILIZATION_FACTOR; | ||||
| 		else if (bss->utilization <= 63) | ||||
| 			rank *= RANK_LOW_UTILIZATION_FACTOR; | ||||
| 	} | ||||
| 	if (bss->have_utilization) | ||||
| 		rank *= scan_get_load_factors(bss->utilization, bss->sta_count); | ||||
| 
 | ||||
| 	if (bss->have_snr) { | ||||
| 		if (bss->snr <= 15) | ||||
| @ -2599,6 +2671,20 @@ static int scan_init(void) | ||||
| 	if (SCAN_MAX_INTERVAL > UINT16_MAX) | ||||
| 		SCAN_MAX_INTERVAL = UINT16_MAX; | ||||
| 
 | ||||
| 	if (!l_settings_get_uint(config, "Rank", "HighUtilizationThreshold", | ||||
| 					&RANK_HIGH_UTILIZATION)) | ||||
| 		RANK_HIGH_UTILIZATION = 0; | ||||
| 
 | ||||
| 	if (L_WARN_ON(RANK_HIGH_UTILIZATION > 255)) | ||||
| 		RANK_HIGH_UTILIZATION = 255; | ||||
| 
 | ||||
| 	if (!l_settings_get_uint(config, "Rank", "HighStationCountThreshold", | ||||
| 					&RANK_HIGH_STATION_COUNT)) | ||||
| 		RANK_HIGH_STATION_COUNT = 0; | ||||
| 
 | ||||
| 	if (L_WARN_ON(RANK_HIGH_STATION_COUNT > 255)) | ||||
| 		RANK_HIGH_STATION_COUNT = 255; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -21,6 +21,7 @@ | ||||
|  */ | ||||
| 
 | ||||
| #include "src/defs.h" | ||||
| #include "src/vendor_quirks.h" | ||||
| 
 | ||||
| struct scan_freq_set; | ||||
| struct ie_rsn_info; | ||||
| @ -67,6 +68,7 @@ struct scan_bss { | ||||
| 	uint8_t ssid[SSID_MAX_SIZE]; | ||||
| 	uint8_t ssid_len; | ||||
| 	uint8_t utilization; | ||||
| 	uint16_t sta_count; | ||||
| 	uint8_t cc[3]; | ||||
| 	uint16_t rank; | ||||
| 	uint64_t time_stamp; | ||||
| @ -78,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; | ||||
|  | ||||
							
								
								
									
										451
									
								
								src/station.c
									
									
									
									
									
								
							
							
						
						
									
										451
									
								
								src/station.c
									
									
									
									
									
								
							| @ -63,6 +63,8 @@ | ||||
| #include "src/eap.h" | ||||
| #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 | ||||
| @ -74,6 +76,11 @@ static uint32_t roam_retry_interval; | ||||
| static bool anqp_disabled; | ||||
| static bool supports_arp_evict_nocarrier; | ||||
| static bool supports_ndisc_evict_nocarrier; | ||||
| static bool supports_drop_gratuitous_arp; | ||||
| static bool supports_drop_unsolicited_na; | ||||
| static bool supports_ipv4_drop_unicast_in_l2_multicast; | ||||
| static bool supports_ipv6_drop_unicast_in_l2_multicast; | ||||
| static bool pmksa_disabled; | ||||
| static struct watchlist event_watches; | ||||
| static uint32_t known_networks_watch; | ||||
| static uint32_t allowed_bands; | ||||
| @ -131,6 +138,8 @@ struct station { | ||||
| 	unsigned int affinity_watch; | ||||
| 	char *affinity_client; | ||||
| 
 | ||||
| 	struct handshake_state *hs; | ||||
| 
 | ||||
| 	bool preparing_roam : 1; | ||||
| 	bool roam_scan_full : 1; | ||||
| 	bool signal_low : 1; | ||||
| @ -147,6 +156,54 @@ struct anqp_entry { | ||||
| 	uint32_t pending; | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * Rather than sorting BSS's purely based on ranking a higher level grouping | ||||
|  * is used. The purpose of this higher order grouping is the consider the BSS's | ||||
|  * roam blacklist status. The roam blacklist is a "soft" blacklist in that we | ||||
|  * still should connect to these BSS's if they are the only "good" option. | ||||
|  * The question here is: what makes a BSS "good" vs "bad". | ||||
|  * | ||||
|  * For an initial (probably naive) approach here we can use the | ||||
|  * RoamThreshold[5G] which indicates the signal level that would not | ||||
|  * be of an acceptable connection quality. BSS can then be sorted either | ||||
|  * above or below this threshold. Within each of these groups a BSS may be | ||||
|  * blacklisted, meaning it should get sorted lower on the list compared to | ||||
|  * others within the same group. | ||||
|  * | ||||
|  * This sorting is achieved by extending rank to a uint32_t where the first 16 | ||||
|  * bits are the standard rank calculated by scan.c. Above that bits can be | ||||
|  * reserved for this higher level grouping: | ||||
|  * | ||||
|  * Bit 16 indicates the BSS is not blacklisted | ||||
|  * Bit 17 indicates the BSS is above the critical signal threshold | ||||
|  */ | ||||
| 
 | ||||
| #define ABOVE_THRESHOLD_BIT 17 | ||||
| #define NOT_BLACKLISTED_BIT 16 | ||||
| 
 | ||||
| static uint32_t evaluate_bss_group_rank(const uint8_t *addr, uint32_t freq, | ||||
| 					int16_t signal_strength, uint16_t rank) | ||||
| { | ||||
| 	int signal = signal_strength / 100; | ||||
| 	bool roam_blacklist; | ||||
| 	bool good_signal; | ||||
| 	uint32_t rank_out = (uint32_t) rank; | ||||
| 
 | ||||
| 	if (blacklist_contains_bss(addr, BLACKLIST_REASON_CONNECT_FAILED)) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	roam_blacklist = blacklist_contains_bss(addr, BLACKLIST_REASON_AP_BUSY); | ||||
| 	good_signal = signal >= netdev_get_low_signal_threshold(freq); | ||||
| 
 | ||||
| 	if (good_signal) | ||||
| 		set_bit(&rank_out, ABOVE_THRESHOLD_BIT); | ||||
| 
 | ||||
| 	if (!roam_blacklist) | ||||
| 		set_bit(&rank_out, NOT_BLACKLISTED_BIT); | ||||
| 
 | ||||
| 	return rank_out; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Used as entries for the roam list since holding scan_bss pointers directly | ||||
|  * from station->bss_list is not 100% safe due to the possibility of the | ||||
| @ -154,13 +211,13 @@ struct anqp_entry { | ||||
|  */ | ||||
| struct roam_bss { | ||||
| 	uint8_t addr[6]; | ||||
| 	uint16_t rank; | ||||
| 	uint32_t rank; | ||||
| 	int32_t signal_strength; | ||||
| 	bool ft_failed: 1; | ||||
| }; | ||||
| 
 | ||||
| static struct roam_bss *roam_bss_from_scan_bss(const struct scan_bss *bss, | ||||
| 						uint16_t rank) | ||||
| 						uint32_t rank) | ||||
| { | ||||
| 	struct roam_bss *rbss = l_new(struct roam_bss, 1); | ||||
| 
 | ||||
| @ -421,7 +478,8 @@ static void station_print_scan_bss(const struct scan_bss *bss) | ||||
| 		ptr += sprintf(ptr, ", snr: %d", bss->snr); | ||||
| 
 | ||||
| 	if (bss->have_utilization) | ||||
| 		ptr += sprintf(ptr, ", load: %u/255", bss->utilization); | ||||
| 		ptr += sprintf(ptr, ", load: %u/255, clients: %u", | ||||
| 				bss->utilization, bss->sta_count); | ||||
| 
 | ||||
| 	l_debug("Processing BSS '%s' with SSID: %s, freq: %u, rank: %u, " | ||||
| 			"strength: %i, data_rate: %u.%u%s", | ||||
| @ -1150,6 +1208,7 @@ static int station_build_handshake_rsn(struct handshake_state *hs, | ||||
| 					struct network *network, | ||||
| 					struct scan_bss *bss) | ||||
| { | ||||
| 	struct netdev *netdev = netdev_find(hs->ifindex); | ||||
| 	const struct l_settings *settings = iwd_get_config(); | ||||
| 	enum security security = network_get_security(network); | ||||
| 	bool add_mde = false; | ||||
| @ -1160,6 +1219,7 @@ static int station_build_handshake_rsn(struct handshake_state *hs, | ||||
| 	uint8_t *ap_ie; | ||||
| 	bool disable_ocv; | ||||
| 	enum band_freq band; | ||||
| 	struct pmksa *pmksa; | ||||
| 
 | ||||
| 	memset(&info, 0, sizeof(info)); | ||||
| 
 | ||||
| @ -1293,6 +1353,17 @@ build_ie: | ||||
| 			IE_CIPHER_IS_GCMP_CCMP(info.pairwise_ciphers)) | ||||
| 		info.extended_key_id = true; | ||||
| 
 | ||||
| 	if (IE_AKM_IS_SAE(info.akm_suites) && !pmksa_disabled) { | ||||
| 		pmksa = pmksa_cache_get(netdev_get_address(netdev), bss->addr, | ||||
| 					bss->ssid, bss->ssid_len, | ||||
| 					info.akm_suites); | ||||
| 		if (pmksa) { | ||||
| 			handshake_state_set_pmksa(hs, pmksa); | ||||
| 			info.num_pmkids = 1; | ||||
| 			info.pmkids = hs->pmksa->pmkid; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/* RSN takes priority */ | ||||
| 	if (bss->rsne) { | ||||
| 		ap_ie = bss->rsne; | ||||
| @ -1376,8 +1447,10 @@ 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 Assigment independent of | ||||
| 	 * It can't hurt to try the FILS IP Address Assignment independent of | ||||
| 	 * which auth-proto is actually used. | ||||
| 	 */ | ||||
| 	if (station->netconfig && netconfig_get_fils_ip_req(station->netconfig, | ||||
| @ -1389,7 +1462,7 @@ static struct handshake_state *station_handshake_setup(struct station *station, | ||||
| 	return hs; | ||||
| 
 | ||||
| not_supported: | ||||
| 	handshake_state_free(hs); | ||||
| 	handshake_state_unref(hs); | ||||
| 	return NULL; | ||||
| } | ||||
| 
 | ||||
| @ -1641,10 +1714,13 @@ static void station_set_drop_neighbor_discovery(struct station *station, | ||||
| { | ||||
| 	char *v = value ? "1" : "0"; | ||||
| 
 | ||||
| 	sysfs_write_ipv4_setting(netdev_get_name(station->netdev), | ||||
| 				"drop_gratuitous_arp", v); | ||||
| 	sysfs_write_ipv6_setting(netdev_get_name(station->netdev), | ||||
| 				"drop_unsolicited_na", v); | ||||
| 	if (supports_drop_gratuitous_arp) | ||||
| 		sysfs_write_ipv4_setting(netdev_get_name(station->netdev), | ||||
| 					"drop_gratuitous_arp", v); | ||||
| 
 | ||||
| 	if (supports_drop_unsolicited_na) | ||||
| 		sysfs_write_ipv6_setting(netdev_get_name(station->netdev), | ||||
| 					"drop_unsolicited_na", v); | ||||
| } | ||||
| 
 | ||||
| static void station_set_drop_unicast_l2_multicast(struct station *station, | ||||
| @ -1652,10 +1728,13 @@ static void station_set_drop_unicast_l2_multicast(struct station *station, | ||||
| { | ||||
| 	char *v = value ? "1" : "0"; | ||||
| 
 | ||||
| 	sysfs_write_ipv4_setting(netdev_get_name(station->netdev), | ||||
| 				"drop_unicast_in_l2_multicast", v); | ||||
| 	sysfs_write_ipv6_setting(netdev_get_name(station->netdev), | ||||
| 				"drop_unicast_in_l2_multicast", v); | ||||
| 	if (supports_ipv4_drop_unicast_in_l2_multicast) | ||||
| 		sysfs_write_ipv4_setting(netdev_get_name(station->netdev), | ||||
| 					"drop_unicast_in_l2_multicast", v); | ||||
| 
 | ||||
| 	if (supports_ipv6_drop_unicast_in_l2_multicast) | ||||
| 		sysfs_write_ipv6_setting(netdev_get_name(station->netdev), | ||||
| 					"drop_unicast_in_l2_multicast", v); | ||||
| } | ||||
| 
 | ||||
| static void station_signal_agent_notify(struct station *station); | ||||
| @ -1719,6 +1798,15 @@ static void station_enter_state(struct station *station, | ||||
| 		periodic_scan_stop(station); | ||||
| 		break; | ||||
| 	case STATION_STATE_CONNECTED: | ||||
| 		network_clear_blacklist(station->connected_network); | ||||
| 
 | ||||
| 		if (station->connect_pending) { | ||||
| 			struct l_dbus_message *reply = | ||||
| 				l_dbus_message_new_method_return( | ||||
| 						station->connect_pending); | ||||
| 			dbus_pending_reply(&station->connect_pending, reply); | ||||
| 		} | ||||
| 
 | ||||
| 		l_dbus_object_add_interface(dbus, | ||||
| 					netdev_get_path(station->netdev), | ||||
| 					IWD_STATION_DIAGNOSTIC_INTERFACE, | ||||
| @ -1760,6 +1848,11 @@ static void station_enter_state(struct station *station, | ||||
| 			station->affinity_watch = 0; | ||||
| 		} | ||||
| 
 | ||||
| 		if (station->hs) { | ||||
| 			handshake_state_unref(station->hs); | ||||
| 			station->hs = NULL; | ||||
| 		} | ||||
| 
 | ||||
| 		break; | ||||
| 	case STATION_STATE_DISCONNECTING: | ||||
| 	case STATION_STATE_NETCONFIG: | ||||
| @ -1905,7 +1998,7 @@ static void station_reset_connection_state(struct station *station) | ||||
| 	/*
 | ||||
| 	 * Perform this step last since calling network_disconnected() might | ||||
| 	 * result in the removal of the network (for example if provisioning | ||||
| 	 * a new hidden network fails with an incorrect pasword). | ||||
| 	 * a new hidden network fails with an incorrect password). | ||||
| 	 */ | ||||
| 	if (station->state == STATION_STATE_CONNECTED || | ||||
| 			station->state == STATION_STATE_CONNECTING || | ||||
| @ -2133,6 +2226,26 @@ static void station_early_neighbor_report_cb(struct netdev *netdev, int err, | ||||
| 				&station->roam_freqs); | ||||
| } | ||||
| 
 | ||||
| static bool station_try_next_bss(struct station *station) | ||||
| { | ||||
| 	struct scan_bss *next; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	next = network_bss_select(station->connected_network, false); | ||||
| 
 | ||||
| 	if (!next) | ||||
| 		return false; | ||||
| 
 | ||||
| 	ret = __station_connect_network(station, station->connected_network, | ||||
| 						next, station->state); | ||||
| 	if (ret < 0) | ||||
| 		return false; | ||||
| 
 | ||||
| 	l_debug("Attempting to connect to next BSS "MAC, MAC_STR(next->addr)); | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| static bool station_can_fast_transition(struct station *station, | ||||
| 					struct handshake_state *hs, | ||||
| 					struct scan_bss *bss) | ||||
| @ -2175,28 +2288,26 @@ static bool station_can_fast_transition(struct station *station, | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| static void station_disconnect_on_error_cb(struct netdev *netdev, bool success, | ||||
| 					void *user_data) | ||||
| static void station_disconnect_on_netconfig_failed(struct netdev *netdev, | ||||
| 							bool success, | ||||
| 							void *user_data) | ||||
| { | ||||
| 	struct station *station = user_data; | ||||
| 	bool continue_autoconnect; | ||||
| 
 | ||||
| 	station_enter_state(station, STATION_STATE_DISCONNECTED); | ||||
| 
 | ||||
| 	continue_autoconnect = station->state == STATION_STATE_CONNECTING_AUTO; | ||||
| 
 | ||||
| 	if (continue_autoconnect) { | ||||
| 		if (station_autoconnect_next(station) < 0) { | ||||
| 			l_debug("Nothing left on autoconnect list"); | ||||
| 			station_enter_state(station, | ||||
| 					STATION_STATE_AUTOCONNECT_FULL); | ||||
| 		} | ||||
| 
 | ||||
| 	if (station_try_next_bss(station)) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (station->connect_pending) { | ||||
| 		struct l_dbus_message *reply = dbus_error_failed( | ||||
| 						station->connect_pending); | ||||
| 
 | ||||
| 		dbus_pending_reply(&station->connect_pending, reply); | ||||
| 	} | ||||
| 
 | ||||
| 	if (station->autoconnect) | ||||
| 		station_enter_state(station, STATION_STATE_AUTOCONNECT_QUICK); | ||||
| 	station_reset_connection_state(station); | ||||
| 
 | ||||
| 	station_enter_state(station, STATION_STATE_DISCONNECTED); | ||||
| 	station_enter_state(station, STATION_STATE_AUTOCONNECT_FULL); | ||||
| } | ||||
| 
 | ||||
| static void station_netconfig_event_handler(enum netconfig_event event, | ||||
| @ -2209,23 +2320,20 @@ static void station_netconfig_event_handler(enum netconfig_event event, | ||||
| 		station_enter_state(station, STATION_STATE_CONNECTED); | ||||
| 		break; | ||||
| 	case NETCONFIG_EVENT_FAILED: | ||||
| 		if (station->connect_pending) { | ||||
| 			struct l_dbus_message *reply = dbus_error_failed( | ||||
| 						station->connect_pending); | ||||
| 		station_debug_event(station, "netconfig-failed"); | ||||
| 
 | ||||
| 			dbus_pending_reply(&station->connect_pending, reply); | ||||
| 		} | ||||
| 		netconfig_reset(station->netconfig); | ||||
| 
 | ||||
| 		if (station->state == STATION_STATE_NETCONFIG) | ||||
| 			network_connect_failed(station->connected_network, | ||||
| 						false); | ||||
| 
 | ||||
| 		netdev_disconnect(station->netdev, | ||||
| 					station_disconnect_on_error_cb, | ||||
| 					station); | ||||
| 		station_reset_connection_state(station); | ||||
| 		network_blacklist_add(station->connected_network, | ||||
| 						station->connected_bss); | ||||
| 
 | ||||
| 		station_enter_state(station, STATION_STATE_DISCONNECTING); | ||||
| 		netdev_disconnect(station->netdev, | ||||
| 					station_disconnect_on_netconfig_failed, | ||||
| 					station); | ||||
| 		break; | ||||
| 	default: | ||||
| 		l_error("station: Unsupported netconfig event: %d.", event); | ||||
| @ -2299,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); | ||||
| } | ||||
| @ -2328,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, | ||||
| @ -2341,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); | ||||
| 
 | ||||
| @ -2473,9 +2595,12 @@ static void station_preauthenticate_cb(struct netdev *netdev, | ||||
| 	} | ||||
| 
 | ||||
| 	if (station_transition_reassociate(station, bss, new_hs) < 0) { | ||||
| 		handshake_state_free(new_hs); | ||||
| 		handshake_state_unref(new_hs); | ||||
| 		station_roam_failed(station); | ||||
| 	} | ||||
| 
 | ||||
| 	handshake_state_unref(station->hs); | ||||
| 	station->hs = handshake_state_ref(new_hs); | ||||
| } | ||||
| 
 | ||||
| static void station_transition_start(struct station *station); | ||||
| @ -2625,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; | ||||
| @ -2676,10 +2805,13 @@ static bool station_try_next_transition(struct station *station, | ||||
| 	} | ||||
| 
 | ||||
| 	if (station_transition_reassociate(station, bss, new_hs) < 0) { | ||||
| 		handshake_state_free(new_hs); | ||||
| 		handshake_state_unref(new_hs); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	handshake_state_unref(station->hs); | ||||
| 	station->hs = handshake_state_ref(new_hs); | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| @ -2766,7 +2898,7 @@ static bool station_roam_scan_notify(int err, struct l_queue *bss_list, | ||||
| 	struct handshake_state *hs = netdev_get_handshake(station->netdev); | ||||
| 	struct scan_bss *current_bss = station->connected_bss; | ||||
| 	struct scan_bss *bss; | ||||
| 	double cur_bss_rank = 0.0; | ||||
| 	uint32_t cur_bss_group_rank = 0; | ||||
| 	static const double RANK_FT_FACTOR = 1.3; | ||||
| 	uint16_t mdid; | ||||
| 	enum security orig_security, security; | ||||
| @ -2795,10 +2927,15 @@ static bool station_roam_scan_notify(int err, struct l_queue *bss_list, | ||||
| 	 */ | ||||
| 	bss = l_queue_find(bss_list, bss_match_bssid, current_bss->addr); | ||||
| 	if (bss && !station->ap_directed_roaming) { | ||||
| 		cur_bss_rank = bss->rank; | ||||
| 		double cur_bss_rank = bss->rank; | ||||
| 
 | ||||
| 		if (hs->mde && bss->mde_present && l_get_le16(bss->mde) == mdid) | ||||
| 			cur_bss_rank *= RANK_FT_FACTOR; | ||||
| 
 | ||||
| 		cur_bss_group_rank = evaluate_bss_group_rank(bss->addr, | ||||
| 						bss->frequency, | ||||
| 						bss->signal_strength, | ||||
| 						(uint16_t) cur_bss_rank); | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| @ -2820,6 +2957,7 @@ static bool station_roam_scan_notify(int err, struct l_queue *bss_list, | ||||
| 	while ((bss = l_queue_pop_head(bss_list))) { | ||||
| 		double rank; | ||||
| 		struct roam_bss *rbss; | ||||
| 		uint32_t group_rank; | ||||
| 
 | ||||
| 		station_print_scan_bss(bss); | ||||
| 
 | ||||
| @ -2841,7 +2979,8 @@ static bool station_roam_scan_notify(int err, struct l_queue *bss_list, | ||||
| 		if (network_can_connect_bss(network, bss) < 0) | ||||
| 			goto next; | ||||
| 
 | ||||
| 		if (blacklist_contains_bss(bss->addr)) | ||||
| 		if (blacklist_contains_bss(bss->addr, | ||||
| 					BLACKLIST_REASON_CONNECT_FAILED)) | ||||
| 			goto next; | ||||
| 
 | ||||
| 		rank = bss->rank; | ||||
| @ -2849,7 +2988,11 @@ static bool station_roam_scan_notify(int err, struct l_queue *bss_list, | ||||
| 		if (hs->mde && bss->mde_present && l_get_le16(bss->mde) == mdid) | ||||
| 			rank *= RANK_FT_FACTOR; | ||||
| 
 | ||||
| 		if (rank <= cur_bss_rank) | ||||
| 		group_rank = evaluate_bss_group_rank(bss->addr, bss->frequency, | ||||
| 						bss->signal_strength, | ||||
| 						(uint16_t) rank); | ||||
| 
 | ||||
| 		if (group_rank <= cur_bss_group_rank) | ||||
| 			goto next; | ||||
| 
 | ||||
| 		/*
 | ||||
| @ -2858,7 +3001,7 @@ static bool station_roam_scan_notify(int err, struct l_queue *bss_list, | ||||
| 		 */ | ||||
| 		station_update_roam_bss(station, bss); | ||||
| 
 | ||||
| 		rbss = roam_bss_from_scan_bss(bss, rank); | ||||
| 		rbss = roam_bss_from_scan_bss(bss, group_rank); | ||||
| 
 | ||||
| 		l_queue_insert(station->roam_bss_list, rbss, | ||||
| 				roam_bss_rank_compare, NULL); | ||||
| @ -2916,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); | ||||
| 
 | ||||
| @ -3085,7 +3229,7 @@ static void station_roam_timeout_rearm(struct station *station, int seconds) | ||||
| 	if (!station->roam_trigger_timeout) | ||||
| 		goto new_timeout; | ||||
| 
 | ||||
| 	/* If we cant get the remaining time just create a new timer */ | ||||
| 	/* If we can't get the remaining time just create a new timer */ | ||||
| 	if (L_WARN_ON(!l_timeout_remaining(station->roam_trigger_timeout, | ||||
| 						&remaining))) { | ||||
| 		l_timeout_remove(station->roam_trigger_timeout); | ||||
| @ -3126,12 +3270,12 @@ static void station_ap_directed_roam(struct station *station, | ||||
| 	uint8_t req_mode; | ||||
| 	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)); | ||||
| 
 | ||||
| 	if (station_cannot_roam(station)) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (station->state != STATION_STATE_CONNECTED) { | ||||
| 		l_debug("roam: unexpected AP directed roam -- ignore"); | ||||
| 		return; | ||||
| @ -3195,8 +3339,13 @@ static void station_ap_directed_roam(struct station *station, | ||||
| 	 * disassociating us. If either of these bits are set, set the | ||||
| 	 * ap_directed_roaming flag. Otherwise still try roaming but don't | ||||
| 	 * treat it any different than a normal roam. | ||||
| 	 * | ||||
| 	 * The only exception here is if we are in the middle of roaming | ||||
| 	 * (can_roam == false) since we cannot reliably know if the roam scan | ||||
| 	 * included frequencies from potential candidates in this request, | ||||
| 	 * forcing a roam in this case might result in unintended behavior. | ||||
| 	 */ | ||||
| 	if (req_mode & (WNM_REQUEST_MODE_DISASSOCIATION_IMMINENT | | ||||
| 	if (can_roam && req_mode & (WNM_REQUEST_MODE_DISASSOCIATION_IMMINENT | | ||||
| 			WNM_REQUEST_MODE_TERMINATION_IMMINENT | | ||||
| 			WNM_REQUEST_MODE_ESS_DISASSOCIATION_IMMINENT)) | ||||
| 		station->ap_directed_roaming = true; | ||||
| @ -3223,17 +3372,39 @@ static void station_ap_directed_roam(struct station *station, | ||||
| 		pos += url_len; | ||||
| 	} | ||||
| 
 | ||||
| 	blacklist_add_bss(station->connected_bss->addr, | ||||
| 				BLACKLIST_REASON_AP_BUSY); | ||||
| 	station_debug_event(station, "ap-roam-blacklist-added"); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Validating the frame and blacklisting should still be done even if | ||||
| 	 * we are mid-roam. Its important to track the BSS requesting the | ||||
| 	 * transition so when the current roam completes IWD will be less likely | ||||
| 	 * to roam back to the current BSS. | ||||
| 	 */ | ||||
| 	if (!can_roam) | ||||
| 		return; | ||||
| 
 | ||||
| 	station->preparing_roam = true; | ||||
| 
 | ||||
| 	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); | ||||
| 	} | ||||
| @ -3295,26 +3466,6 @@ static void station_event_channel_switched(struct station *station, | ||||
| 	network_bss_update(network, station->connected_bss); | ||||
| } | ||||
| 
 | ||||
| static bool station_try_next_bss(struct station *station) | ||||
| { | ||||
| 	struct scan_bss *next; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	next = network_bss_select(station->connected_network, false); | ||||
| 
 | ||||
| 	if (!next) | ||||
| 		return false; | ||||
| 
 | ||||
| 	ret = __station_connect_network(station, station->connected_network, | ||||
| 						next, station->state); | ||||
| 	if (ret < 0) | ||||
| 		return false; | ||||
| 
 | ||||
| 	l_debug("Attempting to connect to next BSS "MAC, MAC_STR(next->addr)); | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| static bool station_retry_owe_default_group(struct station *station) | ||||
| { | ||||
| 	/*
 | ||||
| @ -3361,38 +3512,101 @@ static bool station_retry_with_reason(struct station *station, | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	blacklist_add_bss(station->connected_bss->addr); | ||||
| 	blacklist_add_bss(station->connected_bss->addr, | ||||
| 				BLACKLIST_REASON_CONNECT_FAILED); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Network blacklist the BSS as well, since the timeout blacklist could | ||||
| 	 * be disabled | ||||
| 	 */ | ||||
| 	network_blacklist_add(station->connected_network, | ||||
| 				station->connected_bss); | ||||
| 
 | ||||
| try_next: | ||||
| 	return station_try_next_bss(station); | ||||
| } | ||||
| 
 | ||||
| /* A bit more concise for trying to fit these into 80 characters */ | ||||
| #define IS_TEMPORARY_STATUS(code) \ | ||||
| 	((code) == MMPDU_STATUS_CODE_DENIED_UNSUFFICIENT_BANDWIDTH || \ | ||||
| 	(code) == MMPDU_STATUS_CODE_DENIED_POOR_CHAN_CONDITIONS || \ | ||||
| 	(code) == MMPDU_STATUS_CODE_REJECTED_WITH_SUGG_BSS_TRANS || \ | ||||
| 	(code) == MMPDU_STATUS_CODE_DENIED_NO_MORE_STAS) | ||||
| static bool station_pmksa_fallback(struct station *station, uint16_t status) | ||||
| { | ||||
| 	/*
 | ||||
| 	 * IEEE 802.11-2020 12.6.10.3 Cached PMKSAs and RSNA key management | ||||
| 	 * | ||||
| 	 * "If the Authenticator does not have a PMKSA for the PMKIDs in the | ||||
| 	 * (re)association request or the AKM does not match, its behavior | ||||
| 	 * depends on how the PMKSA was established. If SAE authentication was | ||||
| 	 * used to establish the PMKSA, then the AP shall reject (re)association | ||||
| 	 * by sending a (Re)Association Response frame with status code | ||||
| 	 * STATUS_INVALID_PMKID. Note that this allows the non-AP STA to fall | ||||
| 	 * back to full SAE authentication to establish another PMKSA" | ||||
| 	 */ | ||||
| 	if (status != MMPDU_STATUS_CODE_INVALID_PMKID) | ||||
| 		return false; | ||||
| 
 | ||||
| 	if (L_WARN_ON(!station->hs)) | ||||
| 		return false; | ||||
| 
 | ||||
| 	if (!IE_AKM_IS_SAE(station->hs->akm_suite) || !station->hs->have_pmksa) | ||||
| 		return false; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Remove the PMKSA from the handshake and return true to re-try the | ||||
| 	 * same BSS without PMKSA. | ||||
| 	 */ | ||||
| 	handshake_state_remove_pmksa(station->hs); | ||||
| 
 | ||||
| 	station_debug_event(station, "pmksa-invalid-pmkid"); | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| static bool station_retry_with_status(struct station *station, | ||||
| 					uint16_t status_code) | ||||
| { | ||||
| 	/* If PMKSA failed don't blacklist so we can retry this BSS */ | ||||
| 	if (station_pmksa_fallback(station, status_code)) | ||||
| 		goto try_next; | ||||
| 
 | ||||
| 	switch (status_code) { | ||||
| 	/*
 | ||||
| 	 * Certain Auth/Assoc failures should not cause a timeout blacklist. | ||||
| 	 * In these cases we want to only temporarily blacklist the BSS until | ||||
| 	 * the connection is complete. | ||||
| 	 * | ||||
| 	 */ | ||||
| 	case MMPDU_STATUS_CODE_DENIED_UNSUFFICIENT_BANDWIDTH: | ||||
| 	case MMPDU_STATUS_CODE_DENIED_POOR_CHAN_CONDITIONS: | ||||
| 	/*
 | ||||
| 	 * TODO: The WITH_SUGG_BSS_TRANS case should also include a neighbor | ||||
| 	 *       report IE in the frame. This would allow us to target a | ||||
| 	 *       specific BSS on our next attempt. There is currently no way to | ||||
| 	 *       obtain that IE, but this should be done in the future. | ||||
| 	*/ | ||||
| 	case MMPDU_STATUS_CODE_REJECTED_WITH_SUGG_BSS_TRANS: | ||||
| 		break; | ||||
| 	/*
 | ||||
| 	 * If a BSS is indicating its unable to handle more connections we will | ||||
| 	 * blacklist this the same way we do for BSS's issuing BSS-TM requests | ||||
| 	 * thereby avoiding roams to this BSS for the configured timeout. | ||||
| 	 */ | ||||
| 	if (IS_TEMPORARY_STATUS(status_code)) | ||||
| 		network_blacklist_add(station->connected_network, | ||||
| 						station->connected_bss); | ||||
| 	else | ||||
| 		blacklist_add_bss(station->connected_bss->addr); | ||||
| 	case MMPDU_STATUS_CODE_DENIED_NO_MORE_STAS: | ||||
| 		blacklist_add_bss(station->connected_bss->addr, | ||||
| 					BLACKLIST_REASON_AP_BUSY); | ||||
| 		break; | ||||
| 	/* For any other status codes, blacklist the BSS */ | ||||
| 	default: | ||||
| 		blacklist_add_bss(station->connected_bss->addr, | ||||
| 					BLACKLIST_REASON_CONNECT_FAILED); | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Unconditionally network blacklist the BSS if we are retrying. This | ||||
| 	 * will allow network_bss_select to traverse the BSS list and ignore | ||||
| 	 * BSS's which have previously failed | ||||
| 	 */ | ||||
| 	network_blacklist_add(station->connected_network, | ||||
| 				station->connected_bss); | ||||
| 
 | ||||
| try_next: | ||||
| 	iwd_notice(IWD_NOTICE_CONNECT_FAILED, "status: %u", status_code); | ||||
| 
 | ||||
| 	return station_try_next_bss(station); | ||||
| @ -3404,13 +3618,6 @@ static void station_connect_ok(struct station *station) | ||||
| 
 | ||||
| 	l_debug(""); | ||||
| 
 | ||||
| 	if (station->connect_pending) { | ||||
| 		struct l_dbus_message *reply = | ||||
| 			l_dbus_message_new_method_return( | ||||
| 						station->connect_pending); | ||||
| 		dbus_pending_reply(&station->connect_pending, reply); | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Get a neighbor report now so future roams can avoid waiting for | ||||
| 	 * a report at that time | ||||
| @ -3477,7 +3684,8 @@ static void station_connect_cb(struct netdev *netdev, enum netdev_result result, | ||||
| 
 | ||||
| 	switch (result) { | ||||
| 	case NETDEV_RESULT_OK: | ||||
| 		blacklist_remove_bss(station->connected_bss->addr); | ||||
| 		blacklist_remove_bss(station->connected_bss->addr, | ||||
| 					BLACKLIST_REASON_CONNECT_FAILED); | ||||
| 		station_connect_ok(station); | ||||
| 		return; | ||||
| 	case NETDEV_RESULT_DISCONNECTED: | ||||
| @ -3709,6 +3917,16 @@ 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, | ||||
| 	 * unref that now | ||||
| 	 */ | ||||
| 	if (station->hs) { | ||||
| 		handshake_state_unref(station->hs); | ||||
| 		station->hs = NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	if (station->netconfig && !netconfig_load_settings( | ||||
| 					station->netconfig, | ||||
| @ -3723,7 +3941,7 @@ int __station_connect_network(struct station *station, struct network *network, | ||||
| 				station_netdev_event, | ||||
| 				station_connect_cb, station); | ||||
| 	if (r < 0) { | ||||
| 		handshake_state_free(hs); | ||||
| 		handshake_state_unref(hs); | ||||
| 		return r; | ||||
| 	} | ||||
| 
 | ||||
| @ -3734,8 +3952,13 @@ 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); | ||||
| 
 | ||||
| 	if (station->state != state) | ||||
| 		station_enter_state(station, state); | ||||
| @ -4637,7 +4860,7 @@ static struct l_dbus_message *station_property_set_affinities( | ||||
| 	struct l_dbus_message_iter array; | ||||
| 	const char *sender = l_dbus_message_get_sender(message); | ||||
| 	char *old_path = l_queue_peek_head(station->affinities); | ||||
| 	const char *new_path = NULL; | ||||
| 	const char *new_path; | ||||
| 	struct scan_bss *new_bss = NULL; | ||||
| 	struct scan_bss *old_bss = NULL; | ||||
| 	bool lower_threshold = false; | ||||
| @ -4657,10 +4880,15 @@ static struct l_dbus_message *station_property_set_affinities( | ||||
| 	if (!l_dbus_message_iter_get_variant(new_value, "ao", &array)) | ||||
| 		return dbus_error_invalid_args(message); | ||||
| 
 | ||||
| 	/* Get first entry, there should be only one */ | ||||
| 	l_dbus_message_iter_next_entry(&array, &new_path); | ||||
| 	/* Get first entry, or if an empty array set the path to NULL */ | ||||
| 	if (!l_dbus_message_iter_next_entry(&array, &new_path)) | ||||
| 		new_path = NULL; | ||||
| 
 | ||||
| 	if (l_dbus_message_iter_next_entry(&array, &new_path)) | ||||
| 	/*
 | ||||
| 	 * Only allowing single values for now. If there is more than a single | ||||
| 	 * value, fail | ||||
| 	 */ | ||||
| 	if (new_path && l_dbus_message_iter_next_entry(&array, &new_path)) | ||||
| 		return dbus_error_invalid_args(message); | ||||
| 
 | ||||
| 	old_path = l_queue_peek_head(station->affinities); | ||||
| @ -5028,6 +5256,11 @@ static void station_free(struct station *station) | ||||
| 		l_queue_destroy(station->owe_hidden_scan_ids, NULL); | ||||
| 	} | ||||
| 
 | ||||
| 	if (station->hs) { | ||||
| 		handshake_state_unref(station->hs); | ||||
| 		station->hs = NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	station_roam_state_clear(station); | ||||
| 
 | ||||
| 	l_queue_destroy(station->networks_sorted, NULL); | ||||
| @ -5683,7 +5916,7 @@ static void add_frame_watches(struct netdev *netdev) | ||||
| 	 */ | ||||
| 	frame_watch_add(netdev_get_wdev_id(netdev), 0, 0x00d0, | ||||
| 			action_ap_roam_prefix, sizeof(action_ap_roam_prefix), | ||||
| 			ap_roam_frame_event, | ||||
| 			false, ap_roam_frame_event, | ||||
| 			L_UINT_TO_PTR(netdev_get_ifindex(netdev)), NULL); | ||||
| } | ||||
| 
 | ||||
| @ -5791,6 +6024,10 @@ static int station_init(void) | ||||
| 				&anqp_disabled)) | ||||
| 		anqp_disabled = true; | ||||
| 
 | ||||
| 	if (!l_settings_get_bool(iwd_get_config(), "General", "DisablePMKSA", | ||||
| 				&pmksa_disabled)) | ||||
| 		pmksa_disabled = false; | ||||
| 
 | ||||
| 	if (!netconfig_enabled()) | ||||
| 		l_info("station: Network configuration is disabled."); | ||||
| 
 | ||||
| @ -5798,6 +6035,16 @@ static int station_init(void) | ||||
| 						"arp_evict_nocarrier"); | ||||
| 	supports_ndisc_evict_nocarrier = sysfs_supports_ipv6_setting("all", | ||||
| 						"ndisc_evict_nocarrier"); | ||||
| 	supports_drop_gratuitous_arp = sysfs_supports_ipv4_setting("all", | ||||
| 						"drop_gratuitous_arp"); | ||||
| 	supports_drop_unsolicited_na = sysfs_supports_ipv6_setting("all", | ||||
| 						"drop_unsolicited_na"); | ||||
| 	supports_ipv4_drop_unicast_in_l2_multicast = | ||||
| 					sysfs_supports_ipv4_setting("all", | ||||
| 					"drop_unicast_in_l2_multicast"); | ||||
| 	supports_ipv6_drop_unicast_in_l2_multicast = | ||||
| 					sysfs_supports_ipv6_setting("all", | ||||
| 					"drop_unicast_in_l2_multicast"); | ||||
| 
 | ||||
| 	watchlist_init(&event_watches, NULL); | ||||
| 
 | ||||
|  | ||||
| @ -500,6 +500,13 @@ int __storage_decrypt(struct l_settings *settings, const char *ssid, | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * It should likely be far larger than this, but that will get caught | ||||
| 	 * later when reloading the decrypted data. | ||||
| 	 */ | ||||
| 	if (elen < 16) | ||||
| 		return -EBADMSG; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * AES-SIV automatically verifies the IV (16 bytes) and returns only | ||||
| 	 * the decrypted data portion. We add one here for the NULL terminator | ||||
| @ -535,7 +542,7 @@ int __storage_decrypt(struct l_settings *settings, const char *ssid, | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Load decrypted data into existing settings. This is not how the API | ||||
| 	 * is indended to be used (since this could result in duplicate groups) | ||||
| 	 * is intended to be used (since this could result in duplicate groups) | ||||
| 	 * but since the Security group was just removed and EncryptedSecurity | ||||
| 	 * should only contain a Security group its safe to use it this way. | ||||
| 	 */ | ||||
| @ -596,7 +603,7 @@ struct l_settings *storage_network_open(enum security type, const char *ssid) | ||||
| 	struct l_settings *settings; | ||||
| 	_auto_(l_free) char *path = NULL; | ||||
| 
 | ||||
| 	if (ssid == NULL) | ||||
| 	if (!ssid) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	path = storage_get_network_file_path(type, ssid); | ||||
| @ -623,7 +630,7 @@ int storage_network_touch(enum security type, const char *ssid) | ||||
| 	char *path; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	if (ssid == NULL) | ||||
| 	if (!ssid) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	path = storage_get_network_file_path(type, ssid); | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user