Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add helpers to parse nl80211 PHY information #108

Merged
merged 12 commits into from
Jan 16, 2024
7 changes: 7 additions & 0 deletions src/linux/external/hostap/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ set(HOSTAPD_BIN
"hostapd daemon binary"
)

set(HOSTAPD_CLI_BIN
${HOSTAP_INSTALL_BIN_DIR}/hostapd_cli
CACHE FILEPATH
"hostapd_cli binary"
)

install(
FILES ${LIBWPA_CLIENT}
TYPE LIB
Expand All @@ -91,6 +97,7 @@ install(
install(
PROGRAMS
${HOSTAPD_BIN}
${HOSTAPD_CLI_BIN}
DESTINATION
${CMAKE_INSTALL_SBINDIR}
)
Expand Down
4 changes: 4 additions & 0 deletions src/linux/libnl-helpers/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ target_sources(libnl-helpers
Netlink80211.cxx
Netlink80211Interface.cxx
Netlink80211ProtocolState.cxx
Netlink80211Wiphy.cxx
NetlinkMessage.cxx
NetlinkSocket.cxx
Netlink80211WiphyBand.cxx
Netlink80211WiphyBandFrequency.cxx
PUBLIC
FILE_SET HEADERS
BASE_DIRS ${LIBNL_HELPERS_PUBLIC_INCLUDE}
Expand All @@ -21,6 +24,7 @@ target_sources(libnl-helpers
${LIBNL_HELPERS_PUBLIC_INCLUDE_PREFIX}/nl80211/Netlink80211.hxx
${LIBNL_HELPERS_PUBLIC_INCLUDE_PREFIX}/nl80211/Netlink80211Interface.hxx
${LIBNL_HELPERS_PUBLIC_INCLUDE_PREFIX}/nl80211/Netlink80211ProtocolState.hxx
${LIBNL_HELPERS_PUBLIC_INCLUDE_PREFIX}/nl80211/Netlink80211Wiphy.hxx
)

target_link_libraries(libnl-helpers
Expand Down
46 changes: 46 additions & 0 deletions src/linux/libnl-helpers/Netlink80211.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,52 @@ Nl80211InterfaceTypeToString(nl80211_iftype interfaceType) noexcept
}
}

std::string_view
Nl80211CipherSuiteToString(uint32_t cipherSuite) noexcept
{
static constexpr uint32_t CipherTypeWep40 = 0x000fac01;
static constexpr uint32_t CipherTypeWep104 = 0x000fac05;
static constexpr uint32_t CipherTypeTkip = 0x000fac02;
static constexpr uint32_t CipherTypeCcmp128 = 0x000fac04;
static constexpr uint32_t CipherTypeCmac = 0x000fac06;
static constexpr uint32_t CipherTypeGcmp128 = 0x000fac08;
static constexpr uint32_t CipherTypeGcmp256 = 0x000fac09;
static constexpr uint32_t CipherTypeCcmp256 = 0x000fac0a;
static constexpr uint32_t CipherTypeGmac128 = 0x000fac0b;
static constexpr uint32_t CipherTypeGmac256 = 0x000fac0c;
static constexpr uint32_t CipherTypeCmac256 = 0x000fac0d;
static constexpr uint32_t CipherTypeWpiSms4 = 0x00147201;

switch (cipherSuite) {
case CipherTypeWep40:
return "WEP40";
case CipherTypeWep104:
return "WEP104";
case CipherTypeTkip:
return "TKIP";
case CipherTypeCcmp128:
return "CCMP-128";
case CipherTypeCmac:
return "CMAC";
case CipherTypeGcmp128:
return "GCMP-128";
case CipherTypeGcmp256:
return "GCMP-256";
case CipherTypeCcmp256:
return "CCMP-256";
case CipherTypeGmac128:
return "GMAC-128";
case CipherTypeGmac256:
return "GMAC-256";
case CipherTypeCmac256:
return "CMAC-256";
case CipherTypeWpiSms4:
return "WPI-SMS4";
default:
return "Uknown";
}
}

using Microsoft::Net::Netlink::NetlinkSocket;

std::optional<NetlinkSocket>
Expand Down
22 changes: 15 additions & 7 deletions src/linux/libnl-helpers/Netlink80211Interface.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,23 @@ using namespace Microsoft::Net::Netlink::Nl80211;
using Microsoft::Net::Netlink::NetlinkMessage;
using Microsoft::Net::Netlink::NetlinkSocket;

Nl80211Interface::Nl80211Interface(std::string_view name, nl80211_iftype type, uint32_t index) noexcept :
Nl80211Interface::Nl80211Interface(std::string_view name, nl80211_iftype type, uint32_t index, uint32_t wiphyIndex) noexcept :
Name(name),
Type(type),
Index(index)
Index(index),
WiphyIndex(wiphyIndex)
{
}

std::string
Nl80211Interface::ToString() const
{
return std::format("[{}] {} {}", Index, Name, magic_enum::enum_name(Type));
return std::format("[{}/{}] {} {}", Index, WiphyIndex, Name, magic_enum::enum_name(Type));
}

/* static */
std::optional<Nl80211Interface>
Nl80211Interface::Parse(struct nl_msg* nl80211Message) noexcept
Nl80211Interface::Parse(struct nl_msg *nl80211Message) noexcept
{
// Ensure the message is valid.
if (nl80211Message == nullptr) {
Expand Down Expand Up @@ -62,18 +63,19 @@ Nl80211Interface::Parse(struct nl_msg* nl80211Message) noexcept
auto *interfaceName = static_cast<const char *>(nla_data(newInterfaceMessageAttributes[NL80211_ATTR_IFNAME]));
auto interfaceType = static_cast<nl80211_iftype>(nla_get_u32(newInterfaceMessageAttributes[NL80211_ATTR_IFTYPE]));
auto interfaceIndex = static_cast<uint32_t>(nla_get_u32(newInterfaceMessageAttributes[NL80211_ATTR_IFINDEX]));
auto wiphyIndex = static_cast<uint32_t>(nla_get_u32(newInterfaceMessageAttributes[NL80211_ATTR_WIPHY]));

return Nl80211Interface(interfaceName, interfaceType, interfaceIndex);
return Nl80211Interface(interfaceName, interfaceType, interfaceIndex, wiphyIndex);
}

namespace detail
{
/**
* @brief Handler function for NL80211_CMD_GET_INTERFACE responses.
*
*
* @param nl80211Message The response message to a NL80211_CMD_GET_INTERFACE dump request.
* @param context The context pointer provided to nl_socket_modify_cb. This must be a std::vector<Nl80211Interface>*.
* @return int
* @return int
*/
int
HandleNl80211InterfaceDumpResponse(struct nl_msg *nl80211Message, void *context)
Expand Down Expand Up @@ -152,3 +154,9 @@ Nl80211Interface::Enumerate()

return nl80211Interfaces;
}

std::optional<Nl80211Wiphy>
Nl80211Interface::GetWiphy() const
{
return Nl80211Wiphy::FromIndex(WiphyIndex);
}
222 changes: 222 additions & 0 deletions src/linux/libnl-helpers/Netlink80211Wiphy.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@

#include <array>
#include <format>
#include <optional>
#include <sstream>

#include <magic_enum.hpp>
#include <microsoft/net/netlink/NetlinkMessage.hxx>
#include <microsoft/net/netlink/NetlinkSocket.hxx>
#include <microsoft/net/netlink/nl80211/Netlink80211.hxx>
#include <microsoft/net/netlink/nl80211/Netlink80211ProtocolState.hxx>
#include <microsoft/net/netlink/nl80211/Netlink80211Wiphy.hxx>
#include <netlink/attr.h>
#include <netlink/genl/genl.h>
#include <plog/Log.h>

using namespace Microsoft::Net::Netlink::Nl80211;

Nl80211Wiphy::Nl80211Wiphy(uint32_t index, std::string_view name, std::vector<uint32_t> cipherSuites, std::unordered_map<nl80211_band, Nl80211WiphyBand> bands, std::vector<nl80211_iftype> supportedInterfaceTypes, bool supportsRoaming) noexcept :
Index(index),
Name(name),
CipherSuites(std::move(cipherSuites)),
Bands(std::move(bands)),
SupportedInterfaceTypes(std::move(supportedInterfaceTypes)),
SupportsRoaming(supportsRoaming)
{
}

namespace detail
{
int
HandleNl80211GetWiphyResponse(struct nl_msg *nl80211Message, void *context) noexcept
{
if (context == nullptr) {
LOGE << "Received nl80211 get wiphy response with null context";
return NL_SKIP;
}

// Extract the std::optional<Nl80211Wiphy> from the context.
auto &nl80211WiphyResult = *static_cast<std::optional<Nl80211Wiphy> *>(context);

// Attempt to parse the message.
nl80211WiphyResult = Nl80211Wiphy::Parse(nl80211Message);
if (!nl80211WiphyResult.has_value()) {
LOGE << "Failed to parse nl80211 wiphy message";
return NL_SKIP;
}

LOGD << std::format("Successfully parsed an nl80211 wiphy:\n{}", nl80211WiphyResult->ToString());

return NL_OK;
}
} // namespace detail

/* static */
std::optional<Nl80211Wiphy>
Nl80211Wiphy::FromIndex(uint32_t wiphyIndex)
{
// Allocate a new netlink socket.
auto nl80211SocketOpt{ CreateNl80211Socket() };
if (!nl80211SocketOpt.has_value()) {
LOGE << "Failed to create nl80211 socket";
return std::nullopt;
}

// Allocate a new nl80211 message for sending the dump request for all interfaces.
auto nl80211Socket{ std::move(nl80211SocketOpt.value()) };
auto nl80211MessageGetWiphy{ NetlinkMessage::Allocate() };
if (nl80211MessageGetWiphy == nullptr) {
LOGE << "Failed to allocate nl80211 message for wiphy request";
return std::nullopt;
}

// Populate the genl message for the wiphy request.
const int nl80211DriverId = Nl80211ProtocolState::Instance().DriverId;
const auto *genlMessageGetInterfaces = genlmsg_put(nl80211MessageGetWiphy, NL_AUTO_PID, NL_AUTO_SEQ, nl80211DriverId, 0, 0, NL80211_CMD_GET_WIPHY, 0);
if (genlMessageGetInterfaces == nullptr) {
LOGE << "Failed to populate genl message for interface dump request";
return std::nullopt;
}

// Add the wiphy index attribute so nl80211 knows what to lookup.
nla_put_u32(nl80211MessageGetWiphy, NL80211_ATTR_WIPHY, wiphyIndex);
std::optional<Nl80211Wiphy> nl80211Wiphy{};
int ret = nl_socket_modify_cb(nl80211Socket, NL_CB_VALID, NL_CB_CUSTOM, detail::HandleNl80211GetWiphyResponse, &nl80211Wiphy);

if (ret < 0) {
LOGE << std::format("Failed to modify socket callback with error {} ({})", ret, nl_geterror(ret));
return std::nullopt;
}

// Send the request.
ret = nl_send_auto(nl80211Socket, nl80211MessageGetWiphy);
if (ret < 0) {
LOGE << std::format("Failed to send get wiphy request with error {} ({})", ret, nl_geterror(ret));
return std::nullopt;
}

// Receive the response, which will invoke the configured callback.
ret = nl_recvmsgs_default(nl80211Socket);
if (ret < 0) {
LOGE << std::format("Failed to receive get wiphy response with error {} ({})", ret, nl_geterror(ret));
return std::nullopt;
}

return nl80211Wiphy;
}

/* static */
std::optional<Nl80211Wiphy>
Nl80211Wiphy::Parse(struct nl_msg *nl80211Message) noexcept
{
// Ensure the message is valid.
if (nl80211Message == nullptr) {
LOGE << "Received null nl80211 message";
return std::nullopt;
}

// Ensure the message has a valid genl header.
auto *nl80211MessageHeader{ static_cast<struct nlmsghdr *>(nlmsg_hdr(nl80211Message)) };
if (genlmsg_valid_hdr(nl80211MessageHeader, 1) < 0) {
LOGE << "Received invalid nl80211 message header";
return std::nullopt;
}

// Extract the nl80211 (genl) message header.
const auto *genl80211MessageHeader{ static_cast<struct genlmsghdr *>(nlmsg_data(nl80211MessageHeader)) };

// Parse the message.
std::array<struct nlattr *, NL80211_ATTR_MAX + 1> wiphyAttributes{};
int ret = nla_parse(std::data(wiphyAttributes), std::size(wiphyAttributes), genlmsg_attrdata(genl80211MessageHeader, 0), genlmsg_attrlen(genl80211MessageHeader, 0), nullptr);
if (ret < 0) {
LOG_ERROR << std::format("Failed to parse netlink message attributes with error {} ({})", ret, strerror(-ret));
return std::nullopt;
}

// Process top-level identifiers.
if (wiphyAttributes[NL80211_ATTR_WIPHY] == nullptr || wiphyAttributes[NL80211_ATTR_WIPHY_NAME] == nullptr) {
LOGE << "Received nl80211 message with missing wiphy index or name";
return std::nullopt;
}

auto wiphyIndex = static_cast<uint32_t>(nla_get_u32(wiphyAttributes[NL80211_ATTR_WIPHY]));
auto wiphyName = static_cast<const char *>(nla_data(wiphyAttributes[NL80211_ATTR_WIPHY_NAME]));

// Process bands.
auto wiphyBands = wiphyAttributes[NL80211_ATTR_WIPHY_BANDS];
std::unordered_map<nl80211_band, Nl80211WiphyBand> wiphyBandMap{};
if (wiphyBands != nullptr) {
int remainingBands;
struct nlattr *wiphyBand;

nla_for_each_nested(wiphyBand, wiphyBands, remainingBands)
{
auto nl80211BandType = static_cast<nl80211_band>(wiphyBand->nla_type);
if (nl80211BandType == nl80211_band::NUM_NL80211_BANDS) {
continue;
}

auto nl80211Band = Nl80211WiphyBand::Parse(wiphyBand);
if (nl80211Band.has_value()) {
wiphyBandMap.emplace(nl80211BandType, std::move(nl80211Band.value()));
}
}
}

// Process cipher suites.
uint32_t *wiphyCipherSuites;
auto wiphyNumCipherSuites = static_cast<std::size_t>(nla_len(wiphyAttributes[NL80211_ATTR_CIPHER_SUITES])) / sizeof(*wiphyCipherSuites);
wiphyCipherSuites = static_cast<uint32_t *>(nla_data(wiphyAttributes[NL80211_ATTR_CIPHER_SUITES]));
std::vector<uint32_t> cipherSuites(wiphyCipherSuites, wiphyCipherSuites + wiphyNumCipherSuites);

// Process supported interface types.
std::vector<nl80211_iftype> supportedInterfaceTypes{};
if (wiphyAttributes[NL80211_ATTR_SUPPORTED_IFTYPES] != nullptr) {
int remainingSupportedInterfaceTypes;
struct nlattr *supportedInterfaceType;
nla_for_each_nested(supportedInterfaceType, wiphyAttributes[NL80211_ATTR_SUPPORTED_IFTYPES], remainingSupportedInterfaceTypes)
{
auto interfaceType = static_cast<nl80211_iftype>(supportedInterfaceType->nla_type);
supportedInterfaceTypes.emplace_back(interfaceType);
}
}

// Process roaming support.
auto wiphySupportsRoaming = wiphyAttributes[NL80211_ATTR_ROAM_SUPPORT] != nullptr;

Nl80211Wiphy nl80211Wiphy{ wiphyIndex, wiphyName, std::move(cipherSuites), std::move(wiphyBandMap), std::move(supportedInterfaceTypes), wiphySupportsRoaming };
return nl80211Wiphy;
}

std::string
Nl80211Wiphy::ToString() const
{
std::ostringstream ss;

ss << std::format("Wiphy {} [{}]\n", Name, Index);
ss << std::format(" Supports roaming: {}\n", SupportsRoaming);

ss << " Cipher Suites:\n ";
for (const auto &cipherSuite : CipherSuites) {
ss << std::format("{} ", Nl80211CipherSuiteToString(cipherSuite));
}

constexpr auto IfTypePrefixLength = std::size(std::string_view("NL80211_IFTYPE_"));
ss << "\n Supported Interface Types:\n";
for (const auto &interfaceType : SupportedInterfaceTypes) {
std::string_view interfaceTypeName{ magic_enum::enum_name(interfaceType) };
interfaceTypeName.remove_prefix(IfTypePrefixLength);
ss << std::format(" {}\n", interfaceTypeName);
}

constexpr auto BandPrefixLength = std::size(std::string_view("NL80211_BAND_"));
ss << "\n Bands:\n";
for (const auto &[band, wiphyBand] : Bands) {
std::string_view bandName{ magic_enum::enum_name(band) };
bandName.remove_prefix(BandPrefixLength);
ss << std::format(" [Band {}]\n{}\n", bandName, wiphyBand.ToString());
}

return ss.str();
}
Loading
Loading