| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <lib/timekeeper/clock.h> |
| #include <wlan/common/buffer_writer.h> |
| #include <wlan/mlme/client/client_mlme.h> |
| #include <wlan/mlme/mac_frame.h> |
| #include <wlan/mlme/packet.h> |
| #include <wlan/mlme/service.h> |
| #include <wlan/mlme/timer.h> |
| |
| #include <fbl/unique_ptr.h> |
| #include <fuchsia/wlan/mlme/c/fidl.h> |
| #include <fuchsia/wlan/mlme/cpp/fidl.h> |
| |
| #include "mock_device.h" |
| #include "test_bss.h" |
| #include "test_utils.h" |
| |
| #include <gtest/gtest.h> |
| |
| namespace wlan { |
| |
| namespace { |
| |
| namespace wlan_mlme = ::fuchsia::wlan::mlme; |
| |
| static constexpr uint8_t kTestPayload[] = "Hello Fuchsia"; |
| |
| struct ClientTest : public ::testing::Test { |
| ClientTest() : device(), client(&device) {} |
| |
| void SetUp() override { |
| device.SetTime(zx::time(0)); |
| client.Init(); |
| TriggerTimeout(ObjectTarget::kChannelScheduler); |
| } |
| |
| zx_status_t SendDataFrame() { |
| auto frame = CreateDataFrame(kTestPayload, sizeof(kTestPayload)); |
| if (frame.IsEmpty()) { return ZX_ERR_NO_RESOURCES; } |
| client.HandleFramePacket(frame.Take()); |
| return ZX_OK; |
| } |
| |
| zx_status_t SendEmptyDataFrame() { |
| auto frame = CreateDataFrame(nullptr, 0); |
| if (frame.IsEmpty()) { return ZX_ERR_NO_RESOURCES; } |
| client.HandleFramePacket(frame.Take()); |
| return ZX_OK; |
| } |
| |
| zx_status_t SendNullDataFrame() { |
| auto frame = CreateNullDataFrame(); |
| if (frame.IsEmpty()) { return ZX_ERR_NO_RESOURCES; } |
| client.HandleFramePacket(frame.Take()); |
| return ZX_OK; |
| } |
| |
| zx_status_t SendEthFrame() { |
| auto eth_frame = CreateEthFrame(kTestPayload, sizeof(kTestPayload)); |
| if (eth_frame.IsEmpty()) { return ZX_ERR_NO_RESOURCES; } |
| client.HandleFramePacket(eth_frame.Take()); |
| return ZX_OK; |
| } |
| |
| void SendBeaconFrame(common::MacAddr bssid = common::MacAddr(kBssid1)) { |
| client.HandleFramePacket(CreateBeaconFrame(bssid)); |
| } |
| |
| void TriggerTimeout(ObjectTarget target) { |
| ObjectId timer_id; |
| timer_id.set_subtype(to_enum_type(ObjectSubtype::kTimer)); |
| timer_id.set_target(to_enum_type(target)); |
| client.HandleTimeout(timer_id); |
| } |
| |
| void Join() { |
| ASSERT_EQ(ZX_OK, client.HandleMlmeMsg(CreateJoinRequest())); |
| device.svc_queue.clear(); |
| } |
| |
| void Authenticate() { |
| client.HandleMlmeMsg(CreateAuthRequest()); |
| client.HandleFramePacket(CreateAuthRespFrame(AuthAlgorithm::kOpenSystem)); |
| device.svc_queue.clear(); |
| device.wlan_queue.clear(); |
| TriggerTimeout(ObjectTarget::kStation); |
| TriggerTimeout(ObjectTarget::kChannelScheduler); |
| } |
| |
| void Associate() { |
| client.HandleMlmeMsg(CreateAssocRequest(false)); |
| client.HandleFramePacket(CreateAssocRespFrame()); |
| device.svc_queue.clear(); |
| device.wlan_queue.clear(); |
| TriggerTimeout(ObjectTarget::kStation); |
| TriggerTimeout(ObjectTarget::kChannelScheduler); |
| } |
| |
| void Connect() { |
| Join(); |
| Authenticate(); |
| Associate(); |
| TriggerTimeout(ObjectTarget::kStation); |
| } |
| |
| zx::duration BeaconPeriodsToDuration(size_t periods) { |
| return zx::usec(1024) * (periods * kBeaconPeriodTu); |
| } |
| |
| void SetTimeInBeaconPeriods(size_t periods) { |
| device.SetTime(zx::time(0) + BeaconPeriodsToDuration(periods)); |
| } |
| |
| void IncreaseTimeByBeaconPeriods(size_t periods) { |
| device.SetTime(device.GetTime() + BeaconPeriodsToDuration(periods)); |
| } |
| |
| void GoOffChannel() { |
| // For our test, scan duration doesn't matter for now since we explicit force station |
| // to go back on channel by calling `HandleTimeout` |
| ASSERT_EQ(ZX_OK, client.HandleMlmeMsg(CreateScanRequest(kBeaconPeriodTu))); |
| device.wlan_queue.erase(device.wlan_queue.begin()); // dequeue the power-saving frame |
| ASSERT_FALSE(client.OnChannel()); // sanity check |
| } |
| |
| // Forces station to go back on channel by issuing a timeout to channel scheduler. This assumes |
| // that a scan message was previously issue to cause station to go off channel. |
| void GoOnChannel() { |
| TriggerTimeout(ObjectTarget::kChannelScheduler); |
| device.wlan_queue.erase(device.wlan_queue.begin()); // dequeue the power-saving frame |
| ASSERT_TRUE(client.OnChannel()); // sanity check |
| } |
| |
| void AssertAuthConfirm(MlmeMsg<wlan_mlme::AuthenticateConfirm> msg, |
| wlan_mlme::AuthenticateResultCodes result_code) { |
| ASSERT_EQ(msg.body()->result_code, result_code); |
| } |
| |
| void AssertAssocConfirm(MlmeMsg<wlan_mlme::AssociateConfirm> msg, uint16_t aid, |
| wlan_mlme::AssociateResultCodes result_code) { |
| ASSERT_EQ(msg.body()->association_id, aid); |
| ASSERT_EQ(msg.body()->result_code, result_code); |
| } |
| |
| void AssertDeauthFrame(fbl::unique_ptr<Packet> pkt, wlan_mlme::ReasonCode reason_code) { |
| ASSERT_EQ(pkt->peer(), Packet::Peer::kWlan); |
| auto type_checked_frame = MgmtFrameView<Deauthentication>::CheckType(pkt.get()); |
| ASSERT_TRUE(type_checked_frame); |
| auto frame = type_checked_frame.CheckLength(); |
| ASSERT_TRUE(frame); |
| ASSERT_EQ(std::memcmp(frame.hdr()->addr1.byte, kBssid1, 6), 0); |
| ASSERT_EQ(std::memcmp(frame.hdr()->addr2.byte, kClientAddress, 6), 0); |
| ASSERT_EQ(std::memcmp(frame.hdr()->addr3.byte, kBssid1, 6), 0); |
| ASSERT_EQ(frame.body()->reason_code, static_cast<uint16_t>(reason_code)); |
| } |
| |
| MockDevice device; |
| ClientMlme client; |
| }; |
| |
| TEST_F(ClientTest, Join) { |
| ASSERT_EQ(ZX_OK, client.HandleMlmeMsg(CreateJoinRequest())); |
| |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| auto joins = |
| device.GetServiceMsgs<wlan_mlme::JoinConfirm>(fuchsia_wlan_mlme_MLMEJoinConfOrdinal); |
| ASSERT_EQ(joins.size(), 1ULL); |
| ASSERT_EQ(joins[0].body()->result_code, wlan_mlme::JoinResultCodes::SUCCESS); |
| } |
| |
| TEST_F(ClientTest, Authenticate) { |
| Join(); |
| |
| // Send AUTHENTICATION.request. Verify that no confirmation was sent yet. |
| ASSERT_EQ(ZX_OK, client.HandleMlmeMsg(CreateAuthRequest())); |
| ASSERT_TRUE(device.svc_queue.empty()); |
| |
| // Verify wlan frame is correct. |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| auto pkt = std::move(*device.wlan_queue.begin()); |
| ASSERT_EQ(pkt->peer(), Packet::Peer::kWlan); |
| auto type_checked_frame = MgmtFrameView<Authentication>::CheckType(pkt.get()); |
| ASSERT_TRUE(type_checked_frame); |
| auto frame = type_checked_frame.CheckLength(); |
| ASSERT_TRUE(frame); |
| ASSERT_EQ(std::memcmp(frame.hdr()->addr1.byte, kBssid1, 6), 0); |
| ASSERT_EQ(std::memcmp(frame.hdr()->addr2.byte, kClientAddress, 6), 0); |
| ASSERT_EQ(std::memcmp(frame.hdr()->addr3.byte, kBssid1, 6), 0); |
| ASSERT_EQ(frame.body()->auth_algorithm_number, AuthAlgorithm::kOpenSystem); |
| ASSERT_EQ(frame.body()->auth_txn_seq_number, 1); |
| ASSERT_EQ(frame.body()->status_code, 0); |
| |
| // Respond with a Authentication frame and verify a AUTHENTICATION.confirm message was sent. |
| ASSERT_EQ(ZX_OK, client.HandleFramePacket(CreateAuthRespFrame(AuthAlgorithm::kOpenSystem))); |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| auto auths = device.GetServiceMsgs<wlan_mlme::AuthenticateConfirm>( |
| fuchsia_wlan_mlme_MLMEAuthenticateConfOrdinal); |
| ASSERT_EQ(auths.size(), 1ULL); |
| AssertAuthConfirm(std::move(auths[0]), wlan_mlme::AuthenticateResultCodes::SUCCESS); |
| |
| // Verify a delayed timeout won't cause another confirmation. |
| device.svc_queue.clear(); |
| SetTimeInBeaconPeriods(100); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_TRUE(device.svc_queue.empty()); |
| } |
| |
| TEST_F(ClientTest, Associate) { |
| Join(); |
| Authenticate(); |
| |
| // Send ASSOCIATE.request. Verify that no confirmation was sent yet. |
| ASSERT_EQ(ZX_OK, client.HandleMlmeMsg(CreateAssocRequest(false))); |
| ASSERT_TRUE(device.svc_queue.empty()); |
| |
| // Verify wlan frame is correct. |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| auto pkt = std::move(*device.wlan_queue.begin()); |
| ASSERT_EQ(pkt->peer(), Packet::Peer::kWlan); |
| auto type_checked_frame = MgmtFrameView<AssociationRequest>::CheckType(pkt.get()); |
| ASSERT_TRUE(type_checked_frame); |
| auto frame = type_checked_frame.CheckLength(); |
| ASSERT_TRUE(frame); |
| ASSERT_EQ(std::memcmp(frame.hdr()->addr1.byte, kBssid1, 6), 0); |
| ASSERT_EQ(std::memcmp(frame.hdr()->addr2.byte, kClientAddress, 6), 0); |
| ASSERT_EQ(std::memcmp(frame.hdr()->addr3.byte, kBssid1, 6), 0); |
| auto assoc_req_frame = frame.NextFrame(); |
| Span<const uint8_t> ie_chain{assoc_req_frame.body()->data, assoc_req_frame.body_len()}; |
| ASSERT_TRUE(frame.body()->Validate(ie_chain)); |
| |
| // Respond with a Association Response frame and verify a ASSOCIATE.confirm message was sent. |
| ASSERT_EQ(ZX_OK, client.HandleFramePacket(CreateAssocRespFrame())); |
| ASSERT_FALSE(device.svc_queue.empty()); |
| auto assocs = device.GetServiceMsgs<wlan_mlme::AssociateConfirm>( |
| fuchsia_wlan_mlme_MLMEAssociateConfOrdinal); |
| ASSERT_EQ(assocs.size(), 1ULL); |
| AssertAssocConfirm(std::move(assocs[0]), kAid, wlan_mlme::AssociateResultCodes::SUCCESS); |
| |
| // Verify a delayed timeout won't cause another confirmation. |
| device.svc_queue.clear(); |
| SetTimeInBeaconPeriods(100); |
| TriggerTimeout(ObjectTarget::kStation); |
| assocs = device.GetServiceMsgs<wlan_mlme::AssociateConfirm>( |
| fuchsia_wlan_mlme_MLMEAssociateConfOrdinal); |
| ASSERT_EQ(assocs.size(), 0ULL); |
| } |
| |
| TEST_F(ClientTest, AuthTimeout) { |
| Join(); |
| |
| // Send AUTHENTICATE.request. Verify that no confirmation was sent yet. |
| ASSERT_EQ(ZX_OK, client.HandleMlmeMsg(CreateAuthRequest())); |
| ASSERT_TRUE(device.svc_queue.empty()); |
| |
| // Timeout not yet hit. |
| SetTimeInBeaconPeriods(kAuthTimeout - 1); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_TRUE(device.svc_queue.empty()); |
| |
| // Timeout hit, verify a AUTHENTICATION.confirm message was sent. |
| SetTimeInBeaconPeriods(kAuthTimeout); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| auto auths = device.GetServiceMsgs<wlan_mlme::AuthenticateConfirm>( |
| fuchsia_wlan_mlme_MLMEAuthenticateConfOrdinal); |
| ASSERT_EQ(auths.size(), 1ULL); |
| AssertAuthConfirm(std::move(auths[0]), |
| wlan_mlme::AuthenticateResultCodes::AUTH_FAILURE_TIMEOUT); |
| } |
| |
| TEST_F(ClientTest, AssocTimeout) { |
| Join(); |
| Authenticate(); |
| |
| // Send ASSOCIATE.request. Verify that no confirmation was sent yet. |
| ASSERT_EQ(ZX_OK, client.HandleMlmeMsg(CreateAssocRequest(false))); |
| ASSERT_TRUE(device.svc_queue.empty()); |
| |
| // Timeout not yet hit. |
| SetTimeInBeaconPeriods(10); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_TRUE(device.svc_queue.empty()); |
| |
| // Timeout hit, verify a ASSOCIATE.confirm message was sent. |
| SetTimeInBeaconPeriods(40); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| auto assocs = device.GetServiceMsgs<wlan_mlme::AssociateConfirm>( |
| fuchsia_wlan_mlme_MLMEAssociateConfOrdinal); |
| ASSERT_EQ(assocs.size(), 1ULL); |
| AssertAssocConfirm(std::move(assocs[0]), 0, |
| wlan_mlme::AssociateResultCodes::REFUSED_TEMPORARILY); |
| } |
| |
| TEST_F(ClientTest, ExchangeDataAfterAssociation) { |
| // Verify no data frame is exchanged before being associated. |
| Join(); |
| device.eth_queue.clear(); |
| SendDataFrame(); |
| SendNullDataFrame(); |
| ASSERT_TRUE(device.eth_queue.empty()); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| ASSERT_TRUE(device.svc_queue.empty()); |
| |
| Authenticate(); |
| SendDataFrame(); |
| SendNullDataFrame(); |
| ASSERT_TRUE(device.eth_queue.empty()); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| ASSERT_TRUE(device.svc_queue.empty()); |
| |
| // Associate and send a data frame. |
| Associate(); |
| SendDataFrame(); |
| auto eth_frames = device.GetEthPackets(); |
| ASSERT_EQ(eth_frames.size(), static_cast<size_t>(1)); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| ASSERT_TRUE(device.svc_queue.empty()); |
| |
| // Verify queued up ethernet frame is correct. |
| auto eth_frame = std::move(*eth_frames.begin()); |
| ASSERT_EQ(sizeof(EthernetII) + sizeof(kTestPayload), eth_frame.size()); |
| auto eth_header = reinterpret_cast<const EthernetII*>(eth_frame.data()); |
| ASSERT_EQ(std::memcmp(eth_header->src.byte, kClientAddress, 6), 0); |
| ASSERT_EQ(std::memcmp(eth_header->dest.byte, kBssid1, 6), 0); |
| ASSERT_EQ(eth_header->ether_type, 42); |
| auto eth_payload = Span<const uint8_t>(eth_frame).subspan(sizeof(EthernetII)); |
| ASSERT_RANGES_EQ(eth_payload, kTestPayload); |
| |
| // Send null data frame which shouldn't queue up any Ethernet frames but instead a "Keep Alive" |
| // one. |
| SendNullDataFrame(); |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| ASSERT_TRUE(device.svc_queue.empty()); |
| |
| // Verify queued up "Keep Alive" frame is correct. |
| auto pkt = std::move(*device.wlan_queue.begin()); |
| ASSERT_EQ(pkt->peer(), Packet::Peer::kWlan); |
| auto partially_checked = DataFrameView<>::CheckType(pkt.get()); |
| ASSERT_TRUE(partially_checked); |
| auto data_frame = partially_checked.CheckLength(); |
| ASSERT_EQ(data_frame.hdr()->fc.to_ds(), 1); |
| ASSERT_EQ(data_frame.hdr()->fc.from_ds(), 0); |
| ASSERT_EQ(std::memcmp(data_frame.hdr()->addr1.byte, kBssid1, 6), 0); |
| ASSERT_EQ(std::memcmp(data_frame.hdr()->addr2.byte, kClientAddress, 6), 0); |
| ASSERT_EQ(std::memcmp(data_frame.hdr()->addr3.byte, kBssid1, 6), 0); |
| ASSERT_EQ(data_frame.body_len(), static_cast<size_t>(0)); |
| } |
| |
| TEST_F(ClientTest, ProcessEmptyDataFrames) { |
| Connect(); |
| |
| // Send a data frame which carries an LLC frame with no payload. |
| // Verify no ethernet frame was queued. |
| SendEmptyDataFrame(); |
| ASSERT_TRUE(device.eth_queue.empty()); |
| } |
| |
| TEST_F(ClientTest, DropManagementFrames) { |
| Connect(); |
| |
| // Construct and send deauthentication frame from another BSS. |
| constexpr size_t max_frame_len = MgmtFrameHeader::max_len() + Deauthentication::max_len(); |
| auto packet = GetWlanPacket(max_frame_len); |
| ASSERT_NE(packet, nullptr); |
| |
| BufferWriter w(*packet); |
| auto mgmt_hdr = w.Write<MgmtFrameHeader>(); |
| mgmt_hdr->fc.set_type(FrameType::kManagement); |
| mgmt_hdr->fc.set_subtype(ManagementSubtype::kDeauthentication); |
| mgmt_hdr->addr1 = common::MacAddr(kBssid2); |
| mgmt_hdr->addr2 = common::MacAddr(kClientAddress); |
| mgmt_hdr->addr3 = common::MacAddr(kBssid2); |
| w.Write<Deauthentication>()->reason_code = 42; |
| client.HandleFramePacket(std::move(packet)); |
| |
| // Verify neither a management frame nor service message were sent. |
| ASSERT_TRUE(device.svc_queue.empty()); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| ASSERT_TRUE(device.eth_queue.empty()); |
| |
| // Verify data frames can still be send and the clientis presumably associated. |
| SendDataFrame(); |
| ASSERT_EQ(device.eth_queue.size(), static_cast<size_t>(1)); |
| } |
| |
| TEST_F(ClientTest, AutoDeauth_NoBeaconReceived) { |
| Connect(); |
| |
| // Timeout not yet hit. |
| IncreaseTimeByBeaconPeriods(kAutoDeauthTimeout - 1); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| auto deauth_inds = device.GetServiceMsgs<wlan_mlme::DeauthenticateIndication>( |
| fuchsia_wlan_mlme_MLMEDeauthenticateIndOrdinal); |
| ASSERT_EQ(deauth_inds.size(), 0ULL); |
| |
| // Auto-deauth timeout, client should be deauthenticated. |
| IncreaseTimeByBeaconPeriods(1); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| AssertDeauthFrame(std::move(*device.wlan_queue.begin()), |
| wlan_mlme::ReasonCode::LEAVING_NETWORK_DEAUTH); |
| deauth_inds = device.GetServiceMsgs<wlan_mlme::DeauthenticateIndication>( |
| fuchsia_wlan_mlme_MLMEDeauthenticateIndOrdinal); |
| ASSERT_EQ(deauth_inds.size(), 1ULL); |
| } |
| |
| TEST_F(ClientTest, AutoDeauth_NoBeaconsShortlyAfterConnecting) { |
| Connect(); |
| |
| IncreaseTimeByBeaconPeriods(1); |
| SendBeaconFrame(); |
| |
| // Not enough time has passed yet since beacon frame was sent, so no deauth. |
| IncreaseTimeByBeaconPeriods(kAutoDeauthTimeout - 1); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| |
| // Auto-deauth triggers now. |
| IncreaseTimeByBeaconPeriods(1); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| AssertDeauthFrame(std::move(*device.wlan_queue.begin()), |
| wlan_mlme::ReasonCode::LEAVING_NETWORK_DEAUTH); |
| auto deauth_inds = device.GetServiceMsgs<wlan_mlme::DeauthenticateIndication>( |
| fuchsia_wlan_mlme_MLMEDeauthenticateIndOrdinal); |
| ASSERT_EQ(deauth_inds.size(), 1ULL); |
| } |
| |
| TEST_F(ClientTest, AutoDeauth_DoNotDeauthWhileSwitchingChannel) { |
| Connect(); |
| |
| IncreaseTimeByBeaconPeriods(kAutoDeauthTimeout - 1); |
| GoOffChannel(); |
| |
| // For next two timeouts, still off channel, so should not deauth. |
| IncreaseTimeByBeaconPeriods(1); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| |
| IncreaseTimeByBeaconPeriods(kAutoDeauthTimeout); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| |
| // Have not been back on main channel for long enough, so should not deauth. |
| IncreaseTimeByBeaconPeriods(kAutoDeauthTimeout); |
| GoOnChannel(); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| |
| // Before going off channel, we did not receive beacon for `kAutoDeauthTimeout - 1` period. |
| // Now one more beacon period has passed after going back on channel, so should auto deauth. |
| IncreaseTimeByBeaconPeriods(1); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| AssertDeauthFrame(std::move(*device.wlan_queue.begin()), |
| wlan_mlme::ReasonCode::LEAVING_NETWORK_DEAUTH); |
| auto deauth_inds = device.GetServiceMsgs<wlan_mlme::DeauthenticateIndication>( |
| fuchsia_wlan_mlme_MLMEDeauthenticateIndOrdinal); |
| ASSERT_EQ(deauth_inds.size(), 1ULL); |
| } |
| |
| TEST_F(ClientTest, AutoDeauth_InterleavingBeaconsAndChannelSwitches) { |
| Connect(); |
| |
| // Going off channel. |
| IncreaseTimeByBeaconPeriods(kAutoDeauthTimeout - 5); // -- On-channel time without beacon -- // |
| GoOffChannel(); |
| |
| // No deauth since off channel. |
| IncreaseTimeByBeaconPeriods(5); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| |
| IncreaseTimeByBeaconPeriods(1); |
| GoOnChannel(); |
| |
| // Got beacon frame, which should reset the timeout. |
| IncreaseTimeByBeaconPeriods(3); // -- On-channel time without beacon -- // |
| SendBeaconFrame(); // -- Beacon timeout refresh -- /// |
| |
| // No deauth since beacon was received not too long ago. |
| IncreaseTimeByBeaconPeriods(2); // -- On-channel time without beacon -- // |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| |
| // Going off channel and back on channel |
| // Total on-channel time without beacons so far: 2 beacon intervals |
| GoOffChannel(); |
| IncreaseTimeByBeaconPeriods(kAutoDeauthTimeout); |
| GoOnChannel(); |
| |
| IncreaseTimeByBeaconPeriods(kAutoDeauthTimeout - 3); // -- On-channel time without beacon -- // |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| |
| // Going off channel and back on channel again |
| // Total on-channel time without beacons so far: 2 + kAutoDeauthTimeout - 3 |
| GoOffChannel(); |
| IncreaseTimeByBeaconPeriods(kAutoDeauthTimeout); |
| GoOnChannel(); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| |
| // One more beacon period and auto-deauth triggers |
| IncreaseTimeByBeaconPeriods(1); // -- On-channel time without beacon -- // |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| AssertDeauthFrame(std::move(*device.wlan_queue.begin()), |
| wlan_mlme::ReasonCode::LEAVING_NETWORK_DEAUTH); |
| auto deauth_inds = device.GetServiceMsgs<wlan_mlme::DeauthenticateIndication>( |
| fuchsia_wlan_mlme_MLMEDeauthenticateIndOrdinal); |
| ASSERT_EQ(deauth_inds.size(), 1ULL); |
| } |
| |
| // This test explores what happens if the whole auto-deauth timeout duration is exhausted, but |
| // the client switches channel before auto-deauth can trigger. For the current implementation |
| // where we cancel timer when going off channel and reschedule when going back on channel, |
| // this test is intended to be a safeguard against making the mistake of scheduling or exactly |
| // in the present when going back on channel. |
| TEST_F(ClientTest, AutoDeauth_SwitchingChannelBeforeDeauthTimeoutCouldTrigger) { |
| Connect(); |
| |
| // No deauth since off channel. |
| IncreaseTimeByBeaconPeriods(kAutoDeauthTimeout); |
| GoOffChannel(); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| |
| IncreaseTimeByBeaconPeriods(1); |
| GoOnChannel(); |
| |
| // Auto-deauth timeout shouldn't trigger yet. This is because after going back on channel, |
| // the client should always schedule timeout sufficiently far enough in the future |
| // (at least one beacon interval) |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| |
| // Auto-deauth now |
| IncreaseTimeByBeaconPeriods(1); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| AssertDeauthFrame(std::move(*device.wlan_queue.begin()), |
| wlan_mlme::ReasonCode::LEAVING_NETWORK_DEAUTH); |
| auto deauth_inds = device.GetServiceMsgs<wlan_mlme::DeauthenticateIndication>( |
| fuchsia_wlan_mlme_MLMEDeauthenticateIndOrdinal); |
| ASSERT_EQ(deauth_inds.size(), 1ULL); |
| } |
| |
| TEST_F(ClientTest, AutoDeauth_ForeignBeaconShouldNotPreventDeauth) { |
| Connect(); |
| |
| IncreaseTimeByBeaconPeriods(kAutoDeauthTimeout - 1); |
| SendBeaconFrame(common::MacAddr(kBssid2)); // beacon frame from another AP |
| |
| IncreaseTimeByBeaconPeriods(1); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| AssertDeauthFrame(std::move(*device.wlan_queue.begin()), |
| wlan_mlme::ReasonCode::LEAVING_NETWORK_DEAUTH); |
| auto deauth_inds = device.GetServiceMsgs<wlan_mlme::DeauthenticateIndication>( |
| fuchsia_wlan_mlme_MLMEDeauthenticateIndOrdinal); |
| ASSERT_EQ(deauth_inds.size(), 1ULL); |
| } |
| |
| TEST_F(ClientTest, BufferFramesWhileOffChannelAndSendWhenOnChannel) { |
| Connect(); |
| |
| GoOffChannel(); |
| SendEthFrame(); |
| ASSERT_TRUE(device.wlan_queue.empty()); |
| |
| GoOnChannel(); |
| ASSERT_EQ(device.wlan_queue.size(), static_cast<size_t>(1)); |
| |
| auto pkt = std::move(*device.wlan_queue.begin()); |
| ASSERT_EQ(pkt->peer(), Packet::Peer::kWlan); |
| auto type_checked_frame = DataFrameView<LlcHeader>::CheckType(pkt.get()); |
| ASSERT_TRUE(type_checked_frame); |
| auto frame = type_checked_frame.CheckLength(); |
| ASSERT_TRUE(frame); |
| ASSERT_EQ(std::memcmp(frame.hdr()->addr1.byte, kBssid1, 6), 0); |
| ASSERT_EQ(std::memcmp(frame.hdr()->addr2.byte, kClientAddress, 6), 0); |
| ASSERT_EQ(std::memcmp(frame.hdr()->addr3.byte, kBssid1, 6), 0); |
| |
| auto llc_hdr = frame.body(); |
| ASSERT_EQ(frame.body_len() - llc_hdr->len(), sizeof(kTestPayload)); |
| ASSERT_EQ(std::memcmp(llc_hdr->payload, kTestPayload, sizeof(kTestPayload)), 0); |
| } |
| |
| TEST_F(ClientTest, InvalidAuthenticationResponse) { |
| Join(); |
| |
| // Send AUTHENTICATION.request. Verify that no confirmation was sent yet. |
| ASSERT_EQ(ZX_OK, client.HandleMlmeMsg(CreateAuthRequest())); |
| ASSERT_TRUE(device.svc_queue.empty()); |
| |
| // Send authentication frame with wrong algorithm. |
| ASSERT_EQ(ZX_OK, client.HandleFramePacket(CreateAuthRespFrame(AuthAlgorithm::kSae))); |
| |
| // Verify that AUTHENTICATION.confirm was received. |
| ASSERT_EQ(device.svc_queue.size(), static_cast<size_t>(1)); |
| auto auths = device.GetServiceMsgs<wlan_mlme::AuthenticateConfirm>( |
| fuchsia_wlan_mlme_MLMEAuthenticateConfOrdinal); |
| ASSERT_EQ(auths.size(), 1ULL); |
| AssertAuthConfirm(std::move(auths[0]), |
| wlan_mlme::AuthenticateResultCodes::AUTHENTICATION_REJECTED); |
| |
| // Fast forward in time would have caused a timeout. |
| // The timeout however should have been canceled and we should not receive |
| // and additional confirmation. |
| SetTimeInBeaconPeriods(kAuthTimeout); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_TRUE(device.svc_queue.empty()); |
| |
| // Send a second, now valid authentication frame. |
| // This frame should be ignored as the client reset. |
| ASSERT_EQ(ZX_OK, client.HandleFramePacket(CreateAuthRespFrame(AuthAlgorithm::kOpenSystem))); |
| |
| // Fast forward in time far beyond an authentication timeout. |
| // There should not be any AUTHENTICATION.confirm sent as the client |
| // is expected to have been reset into |idle| state after failing |
| // to authenticate. |
| SetTimeInBeaconPeriods(1000); |
| TriggerTimeout(ObjectTarget::kStation); |
| ASSERT_TRUE(device.svc_queue.empty()); |
| } |
| |
| // Add additional tests for (tracked in NET-801): |
| // AP refuses Authentication/Association |
| // Regression tests for: |
| // - NET-898: PS-POLL after TIM indication. |
| // Deauthenticate in any state issued by AP/SME. |
| // Disassociation in any state issued by AP/SME. |
| // Handle Action frames and setup Block-Ack session. |
| // Drop data frames from unknown BSS. |
| // Handle AMSDUs. |
| // Connect to a: |
| // - protected network, exchange keys and send protected frames. |
| // - HT/VHT capable network |
| // - 5GHz network |
| // - different network than currently associated to |
| // Notify driver about association |
| // Ensure Deauthentication Indicaiton and notification is sent whenever deauthenticating. |
| // Enter/Leave power management when going off/on channel. |
| // Verify timeouts don't hit after resetting the station. |
| |
| } // namespace |
| } // namespace wlan |