#include <netlink/attr.h>
#include <netlink/genl/genl.h>
#include <netlink/msg.h>
+#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
+#include <sys/queue.h>
#include <network/libnetwork.h>
#include <network/logging.h>
#include <network/phy.h>
#include "libnetwork-private.h"
+struct network_phy_channel {
+ unsigned int number;
+ unsigned int frequency;
+ bool dfs;
+ double max_tx_power; // dBm
+
+ TAILQ_ENTRY(network_phy_channel) channels;
+};
+
struct network_phy {
struct network_ctx* ctx;
int refcount;
int index;
char* name;
+ TAILQ_HEAD(head, network_phy_channel) channels;
+
+ ssize_t max_mpdu_length;
+ unsigned int vht_caps;
unsigned int ht_caps;
};
return atoi(index);
}
+static void phy_parse_vht_capabilities(struct network_phy* phy, __u32 caps) {
+ // Max MPDU length
+ switch (caps & 0x3) {
+ case 0:
+ phy->max_mpdu_length = 3895;
+ break;
+
+ case 1:
+ phy->max_mpdu_length = 7991;
+ break;
+
+ case 2:
+ phy->max_mpdu_length = 11454;
+ break;
+
+ case 3:
+ phy->max_mpdu_length = -1;
+ }
+
+ // Supported channel widths
+ switch ((caps >> 2) & 0x3) {
+ case 0:
+ break;
+
+ // Supports 160 MHz
+ case 1:
+ phy->vht_caps |= NETWORK_PHY_VHT_CAP_VHT160;
+ break;
+
+ // Supports 160 MHz and 80+80 MHz
+ case 2:
+ phy->vht_caps |= NETWORK_PHY_VHT_CAP_VHT160;
+ phy->vht_caps |= NETWORK_PHY_VHT_CAP_VHT80PLUS80;
+ break;
+ }
+
+ // RX LDPC
+ if (caps & BIT(4))
+ phy->vht_caps |= NETWORK_PHY_VHT_CAP_RX_LDPC;
+
+ // RX Short GI 80 MHz
+ if (caps & BIT(5))
+ phy->vht_caps |= NETWORK_PHY_VHT_CAP_RX_SHORT_GI_80;
+
+ // RX Short GI 160 MHz and 80+80 MHz
+ if (caps & BIT(6))
+ phy->vht_caps |= NETWORK_PHY_VHT_CAP_RX_SHORT_GI_160;
+
+ // TX STBC
+ if (caps & BIT(7))
+ phy->vht_caps |= NETWORK_PHY_VHT_CAP_TX_STBC;
+
+ // Single User Beamformer
+ if (caps & BIT(11))
+ phy->vht_caps |= NETWORK_PHY_VHT_CAP_SU_BEAMFORMER;
+
+ // Single User Beamformee
+ if (caps & BIT(12))
+ phy->vht_caps |= NETWORK_PHY_VHT_CAP_SU_BEAMFORMEE;
+
+ // Multi User Beamformer
+ if (caps & BIT(19))
+ phy->vht_caps |= NETWORK_PHY_VHT_CAP_MU_BEAMFORMER;
+
+ // Multi User Beamformee
+ if (caps & BIT(20))
+ phy->vht_caps |= NETWORK_PHY_VHT_CAP_MU_BEAMFORMEE;
+
+ // TX-OP-PS
+ if (caps & BIT(21))
+ phy->vht_caps |= NETWORK_PHY_VHT_CAP_TXOP_PS;
+
+ // HTC-VHT
+ if (caps & BIT(22))
+ phy->vht_caps |= NETWORK_PHY_VHT_CAP_HTC_VHT;
+
+ // RX Antenna Pattern Consistency
+ if (caps & BIT(28))
+ phy->vht_caps |= NETWORK_PHY_VHT_CAP_RX_ANTENNA_PATTERN;
+
+ // TX Antenna Pattern Consistency
+ if (caps & BIT(29))
+ phy->vht_caps |= NETWORK_PHY_VHT_CAP_TX_ANTENNA_PATTERN;
+}
+
static void phy_parse_ht_capabilities(struct network_phy* phy, __u16 caps) {
- // RX LDCP
+ // RX LDPC
if (caps & BIT(0))
- phy->ht_caps |= NETWORK_PHY_HT_CAP_RX_LDCP;
+ phy->ht_caps |= NETWORK_PHY_HT_CAP_RX_LDPC;
// HT40
if (caps & BIT(1))
phy->ht_caps |= NETWORK_PHY_HT_CAP_LSIG_TXOP_PROT;
}
+static struct nla_policy phy_frequency_policy[NL80211_FREQUENCY_ATTR_MAX + 1] = {
+ [NL80211_FREQUENCY_ATTR_FREQ] = { .type = NLA_U32 },
+ [NL80211_FREQUENCY_ATTR_DISABLED] = { .type = NLA_FLAG },
+ [NL80211_FREQUENCY_ATTR_NO_IR] = { .type = NLA_FLAG },
+ [__NL80211_FREQUENCY_ATTR_NO_IBSS] = { .type = NLA_FLAG },
+ [NL80211_FREQUENCY_ATTR_RADAR] = { .type = NLA_FLAG },
+ [NL80211_FREQUENCY_ATTR_MAX_TX_POWER] = { .type = NLA_U32 },
+};
+
+static unsigned int phy_frequency_to_channel(unsigned int freq) {
+ if (freq == 2484)
+ return 14;
+
+ else if (freq < 2484)
+ return (freq - 2407) / 5;
+
+ else if (freq >= 4910 && freq <= 4980)
+ return (freq - 4000) / 5;
+
+ else if (freq <= 45000)
+ return (freq - 5000) / 5;
+
+ else if (freq >= 58320 && freq <= 64800)
+ return (freq - 56160) / 2160;
+
+ return 0;
+};
+
+static int phy_parse_channels(struct network_phy* phy, struct nlattr* nl_freqs) {
+ struct nlattr* nl_freq;
+ int rem_freq;
+
+ struct nlattr* tb_freq[NL80211_FREQUENCY_ATTR_MAX + 1];
+ nla_for_each_nested(nl_freq, nl_freqs, rem_freq) {
+ // Get data
+ nla_parse(tb_freq, NL80211_FREQUENCY_ATTR_MAX, nla_data(nl_freq),
+ nla_len(nl_freq), phy_frequency_policy);
+
+ if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ])
+ continue;
+
+ // Skip any disabled channels
+ if (tb_freq[NL80211_FREQUENCY_ATTR_DISABLED])
+ continue;
+
+ struct network_phy_channel* channel = calloc(1, sizeof(*channel));
+ if (!channel)
+ return -1;
+
+ // Append object to list of channels
+ TAILQ_INSERT_TAIL(&phy->channels, channel, channels);
+
+ // Get frequency
+ channel->frequency = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_FREQ]);
+
+ // Convert frequency to channel
+ channel->number = phy_frequency_to_channel(channel->frequency);
+
+ // Radar detection
+ if (tb_freq[NL80211_FREQUENCY_ATTR_RADAR])
+ channel->dfs = true;
+
+ // Maximum TX power
+ if (tb_freq[NL80211_FREQUENCY_ATTR_MAX_TX_POWER])
+ channel->max_tx_power = \
+ nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_MAX_TX_POWER]) * 0.01;
+ }
+
+ return 0;
+}
+
static int phy_parse_info(struct nl_msg* msg, void* data) {
struct network_phy* phy = data;
// HT Capabilities
if (band_attrs[NL80211_BAND_ATTR_HT_CAPA]) {
- __u16 caps = nla_get_u16(band_attrs[NL80211_BAND_ATTR_HT_CAPA]);
- phy_parse_ht_capabilities(phy, caps);
+ __u16 ht_caps = nla_get_u16(band_attrs[NL80211_BAND_ATTR_HT_CAPA]);
+ phy_parse_ht_capabilities(phy, ht_caps);
+ }
+
+ // VHT Capabilities
+ if (band_attrs[NL80211_BAND_ATTR_VHT_CAPA]) {
+ __u32 vht_caps = nla_get_u32(band_attrs[NL80211_BAND_ATTR_VHT_CAPA]);
+
+ phy_parse_vht_capabilities(phy, vht_caps);
+ }
+
+ // Frequencies
+ if (band_attrs[NL80211_BAND_ATTR_FREQS]) {
+ phy_parse_channels(phy, band_attrs[NL80211_BAND_ATTR_FREQS]);
}
}
}
static void network_phy_free(struct network_phy* phy) {
DEBUG(phy->ctx, "Releasing phy at %p\n", phy);
+ // Destroy all channels
+ while (!TAILQ_EMPTY(&phy->channels)) {
+ struct network_phy_channel* channel = TAILQ_FIRST(&phy->channels);
+ TAILQ_REMOVE(&phy->channels, channel, channels);
+ free(channel);
+ }
+
if (phy->name)
free(phy->name);
p->refcount = 1;
p->name = strdup(name);
+ p->index = index;
+
+ TAILQ_INIT(&p->channels);
// Load information from kernel
int r = phy_get_info(p);
return r;
}
- DEBUG(p->ctx, "Allocated phy at %p\n", p);
+ DEBUG(p->ctx, "Allocated phy at %p (index = %d)\n", p, p->index);
*phy = p;
return 0;
}
return NULL;
}
+NETWORK_EXPORT int network_phy_has_vht_capability(struct network_phy* phy, const enum network_phy_vht_caps cap) {
+ return phy->vht_caps & cap;
+}
+
NETWORK_EXPORT int network_phy_has_ht_capability(struct network_phy* phy, const enum network_phy_ht_caps cap) {
return phy->ht_caps & cap;
}
+static const char* network_phy_get_vht_capability_string(const enum network_phy_vht_caps cap) {
+ switch (cap) {
+ case NETWORK_PHY_VHT_CAP_VHT160:
+ return "[VHT-160]";
+
+ case NETWORK_PHY_VHT_CAP_VHT80PLUS80:
+ return "[VHT-160-80PLUS80]";
+
+ case NETWORK_PHY_VHT_CAP_RX_LDPC:
+ return "[RXLDPC]";
+
+ case NETWORK_PHY_VHT_CAP_RX_SHORT_GI_80:
+ return "[SHORT-GI-80]";
+
+ case NETWORK_PHY_VHT_CAP_RX_SHORT_GI_160:
+ return "[SHORT-GI-160]";
+
+ case NETWORK_PHY_VHT_CAP_TX_STBC:
+ return "[TX-STBC-2BY1]";
+
+ case NETWORK_PHY_VHT_CAP_SU_BEAMFORMER:
+ return "[SU-BEAMFORMER]";
+
+ case NETWORK_PHY_VHT_CAP_SU_BEAMFORMEE:
+ return "[SU-BEAMFORMEE]";
+
+ case NETWORK_PHY_VHT_CAP_MU_BEAMFORMER:
+ return "[MU-BEAMFORMER]";
+
+ case NETWORK_PHY_VHT_CAP_MU_BEAMFORMEE:
+ return "[MU-BEAMFORMEE]";
+
+ case NETWORK_PHY_VHT_CAP_TXOP_PS:
+ return "[VHT-TXOP-PS]";
+
+ case NETWORK_PHY_VHT_CAP_HTC_VHT:
+ return "[HTC-VHT]";
+
+ case NETWORK_PHY_VHT_CAP_RX_ANTENNA_PATTERN:
+ return "[RX-ANTENNA-PATTERN]";
+
+ case NETWORK_PHY_VHT_CAP_TX_ANTENNA_PATTERN:
+ return "[TX-ANTENNA-PATTERN]";
+ }
+
+ return NULL;
+}
+
static const char* network_phy_get_ht_capability_string(const enum network_phy_ht_caps cap) {
switch (cap) {
- case NETWORK_PHY_HT_CAP_RX_LDCP:
+ case NETWORK_PHY_HT_CAP_RX_LDPC:
return "[LDPC]";
case NETWORK_PHY_HT_CAP_HT40:
return NULL;
}
+NETWORK_EXPORT char* network_phy_list_vht_capabilities(struct network_phy* phy) {
+ char* buffer = malloc(1024);
+ *buffer = '\0';
+
+ char* p = buffer;
+
+ switch (phy->max_mpdu_length) {
+ case 7991:
+ case 11454:
+ snprintf(p, 1024 - 1, "[MAX-MPDU-%zu]", phy->max_mpdu_length);
+ break;
+
+ }
+
+ foreach_vht_cap(cap) {
+ if (network_phy_has_vht_capability(phy, cap)) {
+ const char* cap_str = network_phy_get_vht_capability_string(cap);
+
+ if (cap_str)
+ p = strncat(p, cap_str, 1024 - 1);
+ }
+ }
+
+ return buffer;
+}
+
NETWORK_EXPORT char* network_phy_list_ht_capabilities(struct network_phy* phy) {
char* buffer = malloc(1024);
*buffer = '\0';
return buffer;
}
+
+NETWORK_EXPORT char* network_phy_list_channels(struct network_phy* phy) {
+ char string[10240] = "CHAN FREQ DFS TXPWR\n";
+ char* p = string + strlen(string);
+
+ struct network_phy_channel* channel;
+ TAILQ_FOREACH(channel, &phy->channels, channels) {
+ p += sprintf(p, "%-4u %-5u %-3s %-4.1f\n",
+ channel->number,
+ channel->frequency,
+ (channel->dfs) ? "Y" : "N",
+ channel->max_tx_power
+ );
+ }
+
+ return strdup(string);
+}