Skip to content

Commit

Permalink
Merge pull request #29 from microsoft/wpactrlsockinit
Browse files Browse the repository at this point in the history
Add ability to send wpa control socket messages
  • Loading branch information
abeltrano authored Nov 21, 2023
2 parents c37d6b5 + 47f2585 commit 478a498
Show file tree
Hide file tree
Showing 25 changed files with 1,052 additions and 16 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ set(protobuf_MODULE_COMPATIBLE TRUE)
find_package(gRPC CONFIG REQUIRED)
find_package(Protobuf CONFIG REQUIRED)
find_package(Threads REQUIRED)
find_package(magic_enum CONFIG REQUIRED)

# Enable POSITION_INDEPENDENT_CODE variable to control passing PIE flags to the linker.
if (POLICY CMP0083)
Expand Down
3 changes: 1 addition & 2 deletions external/hostap/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@ ExternalProject_Add(hostap
COMMAND Q=1 DESTDIR=${HOSTAP_INSTALL_DIR} ${MAKE} -C ${HOSTAPD_DIR_NAME} install
)

# Define an interface library for libwpa_client.so that other cmake targets can
# use in target_link_libraries to pull it in as a dependency.
# Define an interface library for libwpa_client.so that other cmake targets can use in target_link_libraries to pull it in as a dependency.
add_library(libwpa-client INTERFACE)
target_include_directories(libwpa-client INTERFACE ${HOSTAP_INSTALL_INCLUDE_DIR})
target_link_libraries(libwpa-client INTERFACE ${HOSTAP_INSTALL_LIB_DIR}/libwpa_client.so)
Expand Down
9 changes: 9 additions & 0 deletions linux/wpa-controller/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ set(WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX ${WPA_CONTROLLER_PUBLIC_INCLUDE}/Wpa)

target_sources(wpa-controller
PRIVATE
${CMAKE_CURRENT_LIST_DIR}/WpaCommand.cxx
${CMAKE_CURRENT_LIST_DIR}/WpaCore.cxx
${CMAKE_CURRENT_LIST_DIR}/WpaController.cxx
${CMAKE_CURRENT_LIST_DIR}/WpaResponse.cxx
PUBLIC
${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCommand.hxx
${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaController.hxx
${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCore.hxx
${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaResponse.hxx
)

target_include_directories(wpa-controller
Expand All @@ -17,6 +25,7 @@ target_include_directories(wpa-controller
target_link_libraries(wpa-controller
PUBLIC
libwpa-client
magic_enum::magic_enum
)

list(APPEND WPA_CONTROLLER_PUBLIC_HEADERS
Expand Down
9 changes: 9 additions & 0 deletions linux/wpa-controller/WpaCommand.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

#include <Wpa/WpaCommand.hxx>

using namespace Wpa;

WpaCommand::WpaCommand(std::string_view data) :
Data(data)
{
}
69 changes: 69 additions & 0 deletions linux/wpa-controller/WpaController.cxx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@

#include <array>
#include <cstddef>
#include <format>
#include <iostream>
#include <mutex>

#include <magic_enum.hpp>
#include <wpa_ctrl.h>
#include <Wpa/WpaController.hxx>

Expand Down Expand Up @@ -32,3 +37,67 @@ std::filesystem::path WpaController::ControlSocketPath() const noexcept
{
return m_controlSocketPath;
}

struct wpa_ctrl*
WpaController::GetCommandControlSocket()
{
// Check if a socket connection is already established.
{
std::shared_lock lockShared{m_controlSocketCommandGate};
if (m_controlSocketCommand != nullptr)
{
return m_controlSocketCommand;
}
}

// Check if socket was updated between releasing the shared lock and acquiring the exclusive lock.
std::scoped_lock lockExclusive{ m_controlSocketCommandGate };
if (m_controlSocketCommand != nullptr)
{
return m_controlSocketCommand;
}

// Establish a new socket connection. Continue holding the exclusive lock
// until the connection is established to prevent multiple connections.
const auto controlSocketPath = m_controlSocketPath / m_interfaceName;
struct wpa_ctrl* controlSocket = wpa_ctrl_open(controlSocketPath.c_str());
if (controlSocket == nullptr)
{
std::cerr << std::format("Failed to open control socket for {} interface at {}.", m_interfaceName, controlSocketPath.c_str()) << std::endl;
return nullptr;
}

// Update the member and return it.
m_controlSocketCommand = controlSocket;
return controlSocket;
}

std::shared_ptr<WpaResponse>
WpaController::SendCommand(const WpaCommand& command)
{
// Obtain a control socket connection to send the command over.
struct wpa_ctrl* controlSocket = GetCommandControlSocket();
if (controlSocket == nullptr)
{
std::cerr << std::format("Failed to get control socket for {}.", m_interfaceName) << std::endl;
return nullptr;
}

// Send the command and receive the response.
std::array<char, WpaControlSocket::MessageSizeMax> responseBuffer;
std::size_t responseSize = std::size(responseBuffer);
int ret = wpa_ctrl_request(controlSocket, std::data(command.Data), std::size(command.Data), std::data(responseBuffer), &responseSize, nullptr);
switch (ret)
{
case 0:
break;
case -1:
std::cerr << std::format("Failed to send or receive command to {} interface.", m_interfaceName) << std::endl;
return nullptr;
case -2:
std::cerr << std::format("Sending command to {} interface timed out.", m_interfaceName) << std::endl;
return nullptr;
}

return std::make_shared<WpaResponse>(std::string_view{std::data(responseBuffer), responseSize});
}
15 changes: 15 additions & 0 deletions linux/wpa-controller/WpaCore.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

#include <Wpa/WpaCore.hxx>

std::string_view Wpa::GetWpaTypeDaemonBinaryName(WpaType type) noexcept
{
switch (type)
{
case WpaType::Hostapd:
return "hostapd";
case WpaType::WpaSupplicant:
return "wpa_supplicant";
default:
return "unknown";
}
}
9 changes: 9 additions & 0 deletions linux/wpa-controller/WpaResponse.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

#include <Wpa/WpaResponse.hxx>

using namespace Wpa;

WpaResponse::WpaResponse(std::string_view payload) :
Payload(payload)
{
}
23 changes: 23 additions & 0 deletions linux/wpa-controller/include/Wpa/WpaCommand.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

#ifndef WPA_COMMAND_HXX
#define WPA_COMMAND_HXX

#include <string_view>
#include <string>

namespace Wpa
{
/**
* @brief Object to hold generic command data for a wpa_supplicant or hostapd
* request.
*/
struct WpaCommand
{
WpaCommand() = default;
WpaCommand(std::string_view data);

std::string Data;
};
} // namespace Wpa

#endif // WPA_COMMAND_HXX
37 changes: 37 additions & 0 deletions linux/wpa-controller/include/Wpa/WpaController.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
#define WPA_CONTROLLER_HXX

#include <filesystem>
#include <memory>
#include <shared_mutex>
#include <stdexcept>
#include <string>
#include <string_view>

#include <Wpa/WpaCore.hxx>
#include <Wpa/WpaCommand.hxx>
#include <Wpa/WpaResponse.hxx>

namespace Wpa
{
Expand Down Expand Up @@ -59,13 +64,32 @@ struct WpaController
*/
std::filesystem::path ControlSocketPath() const noexcept;

/**
* @brief Send a command over the control socket and return the response
* synchronously.
*
* This will not receive any unsolicited event messages.
*
* @param command The command to send.
* @return std::shared_ptr<WpaResponse>
*/
std::shared_ptr<WpaResponse>
SendCommand(const WpaCommand& command);

/**
* @brief Helper class for working with the wpa control socket.
*/
struct WpaControlSocket
{
WpaControlSocket() = delete;

/**
* @brief Maximum WPA control interface message size, im bytes. The
* official wpa_cli tool uses this as an upper bound, so is used
* similarly here.
*/
static constexpr auto MessageSizeMax = 4096;

static constexpr auto DefaultPathHostapd = "/var/run/hostapd";
static constexpr auto DefaultPathWpaSupplicant = "/var/run/wpa_supplicant";

Expand All @@ -89,10 +113,23 @@ struct WpaController
}
};

private:
/**
* @brief Get the control socket object. If a socket connection does not
* exist, one will be established.
*
* @return struct wpa_ctrl*
*/
struct wpa_ctrl*
GetCommandControlSocket();

private:
const WpaType m_type;
const std::string m_interfaceName;
std::filesystem::path m_controlSocketPath;
// Protects m_controlSocketCommand.
std::shared_mutex m_controlSocketCommandGate;
struct wpa_ctrl* m_controlSocketCommand{ nullptr };
};
} // namespace Wpa

Expand Down
9 changes: 9 additions & 0 deletions linux/wpa-controller/include/Wpa/WpaCore.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ enum class WpaType
Hostapd,
WpaSupplicant
};

/**
* @brief Get the WpaType associated daemon binary name.
*
* @param type The WpaType to obtain the daemon binary name for.
* @return std::string_view
*/
std::string_view GetWpaTypeDaemonBinaryName(WpaType type) noexcept;

} // namespace Wpa

#endif // WPA_CORE_HXX
25 changes: 25 additions & 0 deletions linux/wpa-controller/include/Wpa/WpaResponse.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

#ifndef WPA_RESPONSE_HXX
#define WPA_RESPONSE_HXX

#include <string_view>
#include <string>

namespace Wpa
{
/**
* @brief Object to hold generic, unparsed data from a wpa_supplicant or hostapd
* command response.
*/
struct WpaResponse
{
virtual ~WpaResponse() = default;

WpaResponse() = default;
WpaResponse(std::string_view payload);

std::string Payload;
};
} // namespace Wpa

#endif // WPA_RESPONSE_HXX
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ add_subdirectory(client)
add_subdirectory(dotnet)
add_subdirectory(server)
add_subdirectory(service)
add_subdirectory(shared)
2 changes: 2 additions & 0 deletions src/shared/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

add_subdirectory(strings)
19 changes: 19 additions & 0 deletions src/shared/strings/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

add_library(strings INTERFACE)

set(STRINGS_PUBLIC_INCLUDE ${CMAKE_CURRENT_LIST_DIR}/include)
set(STRINGS_PUBLIC_INCLUDE_PREFIX ${STRINGS_PUBLIC_INCLUDE}/strings)

target_sources(strings
PUBLIC
${STRINGS_PUBLIC_INCLUDE_PREFIX}/StringHelpers.hxx
)

list(APPEND STRINGS_PUBLIC_HEADERS
${STRINGS_PUBLIC_INCLUDE_PREFIX}/StringHelpers.hxx
)

set_target_properties(strings PROPERTIES
PUBLIC_HEADER "${STRINGS_PUBLIC_HEADERS}"
INTERFACE_INCLUDE_DIRECTORIES "${STRINGS_PUBLIC_INCLUDE}"
)
Loading

0 comments on commit 478a498

Please sign in to comment.