Skip to content

Commit

Permalink
Allow the client to approve or deny connection requests from the guest (
Browse files Browse the repository at this point in the history
#19)

* Allow the client to approve or deny guest connection requests
* Add unit tests
  • Loading branch information
guhetier authored May 26, 2022
1 parent 0b25c59 commit d74cbee
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 57 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,10 @@ You can additionnaly setup a logger to chose where logs will be output.

class MyObserver: public ProxyWifiObserver
{
void OnGuestConnectionRequest(OperationType type, const DOT11_SSID& ssid) noexcept override
Authorization AuthorizeGuestConnectionRequest(OperationType type, const ConnectRequestArgs& connectionInfo) noexcept override
{
std::cout << "The guest requested a connection" << std::endl;
return Authorization::Approve;
}
};

Expand Down
67 changes: 30 additions & 37 deletions include/ProxyWifi/ProxyWifiService.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ class ProxyWifiService
/// @brief Indicate the status of a guest initiated operation
enum class OperationStatus
{
Succeeded,
Failed
Succeeded, ///< The operation was completed successfully and success will be indicated to the guest
Failed, ///< The operation failed and failure will be indicated to the guest
Denied ///< The operation was denied by the client and failure will be indicated to the guest
};

/// @brief List basic information about a Wi-Fi network
Expand All @@ -66,26 +67,6 @@ struct WifiNetworkInfo
DOT11_MAC_ADDRESS bssid = {};
};

/// @brief Package the argugments for the `OnConnection` callback
struct OnConnectionArgs {
/// The host interface that connected
GUID interfaceGuid{};
/// The ssid of the connected network
DOT11_SSID connectedNetwork{};
/// The authentication algorithm of the connected network, for host connections
/// Remarks: This is the auth algo that is reported to the guest in scan results,
/// it might differ from the actual auth algo used by the host
DOT11_AUTH_ALGORITHM authAlgo{DOT11_AUTH_ALGO_80211_OPEN};
};

/// @brief Package the argugments for the `OnDisconnection` callback
struct OnDisconnectionArgs {
/// The host interface that disconnected
GUID interfaceGuid{};
/// The ssid of the disconnected network
DOT11_SSID disconnectedNetwork{};
};

/// @brief Indicate the impact a guest requested operation will have on the host
enum class OperationType
{
Expand All @@ -100,25 +81,36 @@ class ProxyWifiObserver
public:
virtual ~ProxyWifiObserver() = default;

struct ConnectRequestArgs {
struct ConnectRequestArgs
{
DOT11_SSID ssid;
};

struct ConnectCompleteArgs {
struct ConnectCompleteArgs
{
GUID interfaceGuid;
DOT11_SSID ssid;
DOT11_AUTH_ALGORITHM authAlgo;
};

struct DisconnectRequestArgs {
struct DisconnectRequestArgs
{
DOT11_SSID ssid;
};

struct DisconnectCompleteArgs {
struct DisconnectCompleteArgs
{
GUID interfaceGuid;
DOT11_SSID ssid;
};

/// @brief Indicate whether proxy_wifi should proceed with a guest request or answer immediately with a failure
enum class Authorization
{
Approve,
Deny
};

/// @brief An host WiFi interface connected to a network
virtual void OnHostConnection(const ConnectCompleteArgs& /* connectionInfo */) noexcept
{
Expand All @@ -130,30 +122,31 @@ class ProxyWifiObserver
}

/// @brief The guest requested a connection to a network
/// If `type == OperationType::HostMirroring`, an host inteface is already connected to the network, otherwise, one will be
/// connected The connection won't proceed until the callback returns
virtual void OnGuestConnectionRequest(OperationType /* type */, const ConnectRequestArgs& /* connectionInfo */) noexcept
/// @return Return `Authorization::Approve` to let the connection proceed, return `Authorization::Deny` to answer to the guest
/// with a failure If `type == OperationType::HostMirroring`, an host inteface is already connected to the network, otherwise,
/// one will be connected. The connection won't proceed until the callback returns
virtual Authorization AuthorizeGuestConnectionRequest(OperationType /* type */, const ConnectRequestArgs& /* connectionInfo */) noexcept
{
return Authorization::Approve;
}

/// @brief A guest connection request was processed
/// If `type == OperationType::HostMirroring`, an host inteface was already connected to the network, otherwise, one has been be connected
/// The response won't be sent to the guest until this callback returns
virtual void OnGuestConnectionCompletion(
OperationType /* type */, OperationStatus /* status */, const ConnectCompleteArgs& /* connectionInfo */) noexcept
/// If `type == OperationType::HostMirroring`, an host inteface was already connected to the network, otherwise, one has been
/// be connected The response won't be sent to the guest until this callback returns
virtual void OnGuestConnectionCompletion(OperationType /* type */, OperationStatus /* status */, const ConnectCompleteArgs& /* connectionInfo */) noexcept
{
}

/// @brief The guest requested a disconnection from the connected network
/// If `type == OperationType::HostMirroring`, the host won't be impacted, otherwise, a matching host interface will be disconnected
/// The disconnection won't proceed until the callback returns
/// If `type == OperationType::HostMirroring`, the host won't be impacted, otherwise, a matching host interface will be
/// disconnected The disconnection won't proceed until the callback returns
virtual void OnGuestDisconnectionRequest(OperationType /* type */, const DisconnectRequestArgs& /* connectionInfo */) noexcept
{
}

/// @brief A guest disconnection request was processed
/// If `type == OperationType::HostMirroring`, this was a no-op for the host, otherwise, a matching host interface has been disconnected
/// The response won't be sent to the guest until this callback returns
/// If `type == OperationType::HostMirroring`, this was a no-op for the host, otherwise, a matching host interface has been
/// disconnected The response won't be sent to the guest until this callback returns
virtual void OnGuestDisconnectionCompletion(OperationType /* type */, OperationStatus /* status */, const DisconnectCompleteArgs& /* disconnectionInfo */) noexcept
{
}
Expand Down
53 changes: 37 additions & 16 deletions lib/OperationHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,26 +62,30 @@ void OperationHandler::SendGuestNotification(std::variant<DisconnectNotif, Signa
}
}

void OperationHandler::OnGuestConnectionRequest(OperationType type, const Ssid& ssid) noexcept
ProxyWifiObserver::Authorization OperationHandler::AuthorizeGuestConnectionRequest(OperationType type, const Ssid& ssid) noexcept
{
m_clientNotificationQueue.RunAndWait([&] {
return m_clientNotificationQueue.RunAndWait([&] {
Log::Info(
L"Notifying the client of a guest connection request. Type: %ws, Ssid: %ws",
ToString(type),
SsidToLogString(ssid.value()).c_str());

if (m_pClientObserver)
{
m_pClientObserver->OnGuestConnectionRequest(type, {ssid});
return m_pClientObserver->AuthorizeGuestConnectionRequest(type, {ssid});
}

return ProxyWifiObserver::Authorization::Approve;
});
}

void OperationHandler::OnGuestConnectionCompletion(OperationType type, OperationStatus status, const GUID& interfaceGuid, const Ssid& ssid, DOT11_AUTH_ALGORITHM authAlgo) noexcept
void OperationHandler::OnGuestConnectionCompletion(
OperationType type, OperationStatus status, const GUID& interfaceGuid, const Ssid& ssid, DOT11_AUTH_ALGORITHM authAlgo) noexcept
{
m_clientNotificationQueue.RunAndWait([&] {
Log::Info(
L"Notifying the client of a guest connection completion. Type: %ws, Status: %ws, Interface: %ws, Ssid: %ws, AuthAlgo: %ws",
L"Notifying the client of a guest connection completion. Type: %ws, Status: %ws, Interface: %ws, Ssid: %ws, "
L"AuthAlgo: %ws",
ToString(type),
ToString(status),
GuidToString(interfaceGuid).c_str(),
Expand Down Expand Up @@ -249,7 +253,7 @@ ConnectResponse OperationHandler::HandleConnectRequestSerialized(const ConnectRe
{
// No interface to connect with: fails directly
Log::Trace(L"No interfaces are present");
return ConnectResponse{WlanStatus::UnspecifiedFailure, connectRequest->bssid, m_sessionId};
return ConnectResponse{WlanStatus::UnspecifiedFailure, connectRequest->bssid, ++m_sessionId};
}

for (const auto& wlanIntf : m_wlanInterfaces)
Expand All @@ -262,16 +266,31 @@ ConnectResponse OperationHandler::HandleConnectRequestSerialized(const ConnectRe
const auto connectedIntfGuid = wlanIntf->GetGuid();
m_guestConnection = {ConnectionType::Mirrored, connectedIntfGuid, ssid};

Log::Info(L"Successfully mirrored the connection on interface %ws. Notifying clients.", GuidToString(connectedIntfGuid).c_str());
OnGuestConnectionRequest(OperationType::HostMirroring, ssid);
OnGuestConnectionCompletion(OperationType::HostMirroring, OperationStatus::Succeeded, connectedIntfGuid, ssid, networkInfo->auth);
Log::Info(
L"Successfully mirrored the connection on interface %ws. Notifying clients.", GuidToString(connectedIntfGuid).c_str());

auto connectionStatus = WlanStatus::Success;
auto operationStatus = OperationStatus::Succeeded;

// Notify the client of the connection request and ask for permission, fail the request if they deny it
if (AuthorizeGuestConnectionRequest(OperationType::HostMirroring, ssid) == ProxyWifiObserver::Authorization::Deny)
{
connectionStatus = WlanStatus::UnspecifiedFailure;
operationStatus = OperationStatus::Denied;
}

return ConnectResponse{WlanStatus::Success, networkInfo->bssid, ++m_sessionId};
OnGuestConnectionCompletion(OperationType::HostMirroring, operationStatus, connectedIntfGuid, ssid, networkInfo->auth);
return ConnectResponse{connectionStatus, networkInfo->bssid, ++m_sessionId};
}
}

// At this point, an host interface will be connected. Notify the client of a guest direct connection
OnGuestConnectionRequest(OperationType::GuestDirected, ssid);
// No host interface is connected to the request network: if the request go through, we will connect one on the host
// Notify the client of the connection request and ask for permission, fail the request if they deny it
if (AuthorizeGuestConnectionRequest(OperationType::GuestDirected, ssid) == ProxyWifiObserver::Authorization::Deny)
{
OnGuestConnectionCompletion(OperationType::HostMirroring, OperationStatus::Denied, {}, ssid, {});
return ConnectResponse{WlanStatus::UnspecifiedFailure, Bssid{}, ++m_sessionId};
}

try
{
Expand Down Expand Up @@ -326,7 +345,8 @@ ConnectResponse OperationHandler::HandleConnectRequestSerialized(const ConnectRe
m_guestConnection = {ConnectionType::GuestDirected, connectedIntfGuid, ssid};

Log::Info(L"Successfully connected on interface %ws. Notifying clients.", GuidToString(connectedIntfGuid).c_str());
OnGuestConnectionCompletion(OperationType::GuestDirected, OperationStatus::Succeeded, connectedIntfGuid, ssid, networkInfo.auth);
OnGuestConnectionCompletion(
OperationType::GuestDirected, OperationStatus::Succeeded, connectedIntfGuid, ssid, networkInfo.auth);

return ConnectResponse{connectionResult, networkInfo.bssid, ++m_sessionId};
}
Expand Down Expand Up @@ -375,19 +395,20 @@ DisconnectResponse OperationHandler::HandleDisconnectRequestSerialized(const Dis
const auto guestConnectInfo = m_guestConnection;
m_guestConnection.reset();

OnGuestDisconnectionCompletion(OperationType::HostMirroring, OperationStatus::Succeeded, guestConnectInfo->interfaceGuid, guestConnectInfo->ssid);
OnGuestDisconnectionCompletion(
OperationType::HostMirroring, OperationStatus::Succeeded, guestConnectInfo->interfaceGuid, guestConnectInfo->ssid);
return DisconnectResponse{};
}


// At this point, an host interface will be disconnected. Notify the client (make sure it is notified of the completion on each path)
OnGuestDisconnectionRequest(OperationType::GuestDirected, m_guestConnection->ssid);

auto completeDisconnection = [&] {
const auto guestConnectInfo = m_guestConnection;
m_guestConnection.reset();

OnGuestDisconnectionCompletion(OperationType::GuestDirected, OperationStatus::Succeeded, guestConnectInfo->interfaceGuid, guestConnectInfo->ssid);
OnGuestDisconnectionCompletion(
OperationType::GuestDirected, OperationStatus::Succeeded, guestConnectInfo->interfaceGuid, guestConnectInfo->ssid);
return DisconnectResponse{};
};

Expand Down
2 changes: 1 addition & 1 deletion lib/OperationHandler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class OperationHandler: private INotificationHandler
void SendGuestNotification(std::variant<DisconnectNotif, SignalQualityNotif> notif);

/// @brief Notify the guest of guest operation request and completion
void OnGuestConnectionRequest(OperationType type, const Ssid& ssid) noexcept;
ProxyWifiObserver::Authorization AuthorizeGuestConnectionRequest(OperationType type, const Ssid& ssid) noexcept;
void OnGuestConnectionCompletion(OperationType type, OperationStatus status, const GUID& interfaceGuid, const Ssid& ssid, DOT11_AUTH_ALGORITHM authAlgo) noexcept;
void OnGuestDisconnectionRequest(OperationType type, const Ssid& ssid) noexcept;
void OnGuestDisconnectionCompletion(OperationType type, OperationStatus status, const GUID& interfaceGuid, const Ssid& ssid) noexcept;
Expand Down
83 changes: 81 additions & 2 deletions test/TestOpHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -424,25 +424,31 @@ TEST_CASE("Notify the client on connection and disconnection", "[wlansvcOpHandle
{
notifs.emplace_back(Notif::HostConnect, Type::None);
}

void OnHostDisconnection(const DisconnectCompleteArgs&) noexcept override
{
notifs.emplace_back(Notif::HostDisconnect, Type::None);
}
void OnGuestConnectionRequest(OperationType t, const ConnectRequestArgs&) noexcept override

Authorization AuthorizeGuestConnectionRequest(OperationType t, const ConnectRequestArgs&) noexcept override
{
auto type = t == OperationType::GuestDirected ? Type::GuestDirected : Type::HostMirroring;
notifs.emplace_back(Notif::GuestConnectRequest, type);
return Authorization::Approve;
}

void OnGuestConnectionCompletion(OperationType t, OperationStatus, const ConnectCompleteArgs&) noexcept override
{
auto type = t == OperationType::GuestDirected ? Type::GuestDirected : Type::HostMirroring;
notifs.emplace_back(Notif::GuestConnectComplete, type);
}

void OnGuestDisconnectionRequest(OperationType t, const DisconnectRequestArgs&) noexcept override
{
auto type = t == OperationType::GuestDirected ? Type::GuestDirected : Type::HostMirroring;
notifs.emplace_back(Notif::GuestDisconnectRequest, type);
}

void OnGuestDisconnectionCompletion(OperationType t, OperationStatus, const DisconnectCompleteArgs&) noexcept override
{
auto type = t == OperationType::GuestDirected ? Type::GuestDirected : Type::HostMirroring;
Expand All @@ -457,7 +463,7 @@ TEST_CASE("Notify the client on connection and disconnection", "[wlansvcOpHandle
auto connectRequest = MakeOpenConnectRequest(Mock::c_openNetwork.bss.ssid);
auto disconnectRequest = MakeDisconnectRequest(1);

SECTION("Notifications on guest initiated operations")
SECTION("Notifications on guest directed operations")
{
auto connectResponse = opHandler->HandleConnectRequest(connectRequest);
opHandler->DrainClientNotifications();
Expand Down Expand Up @@ -531,6 +537,79 @@ TEST_CASE("Notify the client on connection and disconnection", "[wlansvcOpHandle
}
}

TEST_CASE("The client can approve or deny guest connection requests", "[wlansvcOpHandler][clientNotification]")
{
struct TestObserver : public ProxyWifiObserver
{
Authorization AuthorizeGuestConnectionRequest(OperationType, const ConnectRequestArgs&) noexcept override
{
return authorizeToConnect;
}

void OnGuestConnectionCompletion(OperationType, OperationStatus status, const ConnectCompleteArgs&) noexcept override
{
lastOperationStatus = status;
}

Authorization authorizeToConnect;
OperationStatus lastOperationStatus = OperationStatus::Succeeded;
};

auto fakeWlansvc =
std::make_shared<Mock::WlanSvcFake>(std::vector{Mock::c_intf1}, std::vector{Mock::c_wpa2PskNetwork, Mock::c_openNetwork});

auto pObserver = std::make_unique<TestObserver>();
auto opHandler = MakeUnitTestOperationHandler(fakeWlansvc, {}, pObserver.get());
auto connectRequest = MakeOpenConnectRequest(Mock::c_openNetwork.bss.ssid);
auto disconnectRequest = MakeDisconnectRequest(1);

SECTION("The client can approve a guest directed connection request")
{
pObserver->authorizeToConnect = ProxyWifiObserver::Authorization::Approve;
auto connectResponse = opHandler->HandleConnectRequest(connectRequest);
opHandler->DrainClientNotifications();
CHECK(connectResponse->result_code == WI_EnumValue(WlanStatus::Success));
CHECK(pObserver->lastOperationStatus == OperationStatus::Succeeded);
}

SECTION("The client can deny a guest directed connection request")
{
pObserver->authorizeToConnect = ProxyWifiObserver::Authorization::Deny;
auto connectResponse = opHandler->HandleConnectRequest(connectRequest);
opHandler->DrainClientNotifications();
CHECK(connectResponse->result_code == WI_EnumValue(WlanStatus::UnspecifiedFailure));
CHECK(pObserver->lastOperationStatus == OperationStatus::Denied);
}

SECTION("The client can approve a host mirroring connection request")
{
pObserver->authorizeToConnect = ProxyWifiObserver::Authorization::Approve;

fakeWlansvc->ConnectHost(Mock::c_intf1, Mock::c_openNetwork.bss.ssid);
fakeWlansvc->WaitForNotifComplete();
opHandler->DrainClientNotifications();

auto connectResponse = opHandler->HandleConnectRequest(connectRequest);
opHandler->DrainClientNotifications();
CHECK(connectResponse->result_code == WI_EnumValue(WlanStatus::Success));
CHECK(pObserver->lastOperationStatus == OperationStatus::Succeeded);
}

SECTION("The client can deny a host mirroring connection request")
{
pObserver->authorizeToConnect = ProxyWifiObserver::Authorization::Deny;

fakeWlansvc->ConnectHost(Mock::c_intf1, Mock::c_openNetwork.bss.ssid);
fakeWlansvc->WaitForNotifComplete();
opHandler->DrainClientNotifications();

auto connectResponse = opHandler->HandleConnectRequest(connectRequest);
opHandler->DrainClientNotifications();
CHECK(connectResponse->result_code == WI_EnumValue(WlanStatus::UnspecifiedFailure));
CHECK(pObserver->lastOperationStatus == OperationStatus::Denied);
}
}

TEST_CASE("Notify client for guest scans", "[wlansvcOpHandler][clientNotification]")
{
const auto fakeWlansvc = std::make_shared<Mock::WlanSvcFake>(std::vector{Mock::c_intf1}, std::vector{Mock::c_wpa2PskNetwork});
Expand Down

0 comments on commit d74cbee

Please sign in to comment.