You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
495 lines
12 KiB
495 lines
12 KiB
extern "C" {
|
|
#include <netlink/genl/ctrl.h>
|
|
#include <netlink/genl/genl.h>
|
|
#include <netlink/genl/family.h>
|
|
#include <pthread.h>
|
|
#include <linux/nl80211.h>
|
|
#include <dirent.h>
|
|
#include "ieee80211.h"
|
|
}
|
|
|
|
#include "wireless.h"
|
|
#include "utils.h"
|
|
#include <iostream>
|
|
#include <QStringList>
|
|
#include <QRegularExpression>
|
|
|
|
#define BIT(x) (1ULL<<(x))
|
|
|
|
int IEEE80211Freq[][2] = {
|
|
{1, 2412},
|
|
{2, 2417},
|
|
{3, 2422},
|
|
{4, 2427},
|
|
{5, 2432},
|
|
{6, 2437},
|
|
{7, 2442},
|
|
{8, 2447},
|
|
{9, 2452},
|
|
{10, 2457},
|
|
{11, 2462},
|
|
{12, 2467},
|
|
{13, 2472},
|
|
{14, 2484},
|
|
// We could do the math here, but what about 4ghz nonsense?
|
|
// We'll do table lookups for now.
|
|
{36, 5180},
|
|
{37, 5185},
|
|
{38, 5190},
|
|
{39, 5195},
|
|
{40, 5200},
|
|
{41, 5205},
|
|
{42, 5210},
|
|
{43, 5215},
|
|
{44, 5220},
|
|
{45, 5225},
|
|
{46, 5230},
|
|
{47, 5235},
|
|
{48, 5240},
|
|
{52, 5260},
|
|
{53, 5265},
|
|
{54, 5270},
|
|
{55, 5275},
|
|
{56, 5280},
|
|
{57, 5285},
|
|
{58, 5290},
|
|
{59, 5295},
|
|
{60, 5300},
|
|
{64, 5320},
|
|
{100, 5500},
|
|
{104, 5520},
|
|
{108, 5540},
|
|
{112, 5560},
|
|
{116, 5580},
|
|
{120, 5600},
|
|
{124, 5620},
|
|
{128, 5640},
|
|
{132, 5660},
|
|
{136, 5680},
|
|
{140, 5700},
|
|
{149, 5745},
|
|
{150, 5750},
|
|
{152, 5760},
|
|
{153, 5765},
|
|
{157, 5785},
|
|
{160, 5800},
|
|
{161, 5805},
|
|
{165, 5825},
|
|
{0, 0}
|
|
};
|
|
|
|
struct nl_callback {
|
|
char *phyname;
|
|
void *bands;
|
|
};
|
|
|
|
Wireless::Wireless(QString interface) :
|
|
QObject(0),
|
|
m_interface(interface),
|
|
m_bands(),
|
|
m_isValid(false),
|
|
m_isNL80211(false)
|
|
{
|
|
void *handle = NULL;
|
|
struct genl_family *family = NULL;
|
|
struct nl_cache *cache = NULL;
|
|
struct nl_msg *msg = nlmsg_alloc();
|
|
struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT);
|
|
QByteArray interfaceArray = m_interface.toLatin1();
|
|
const char *interfaceStr = interfaceArray.constData();
|
|
int err = 1;
|
|
|
|
char *phyname = nl80211_find_parent(interfaceStr);
|
|
|
|
if(phyname == NULL) {
|
|
if(!(Utils::interfaceIndex(interfaceStr))) {
|
|
fprintf(stderr, "Interface %s doesn't exist\n", interfaceStr);
|
|
}else{
|
|
fprintf(stderr, "could not find a parent phy device for interface %s, it isn't nl80211?\n", interfaceStr);
|
|
}
|
|
|
|
return;
|
|
}else{
|
|
m_isNL80211 = true;
|
|
}
|
|
|
|
if(nl80211_connect(interfaceStr, &handle, &cache, &family) < 0) {
|
|
std::cerr << "could not connect to nl80211" << std::endl;
|
|
return;
|
|
}
|
|
|
|
struct nl_callback callback = { .phyname = phyname, .bands = &m_bands };
|
|
|
|
nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, nl80211_freqlist_cb, &callback);
|
|
nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, nl80211_finish_cb, &err);
|
|
nl_cb_err(cb, NL_CB_CUSTOM, nl80211_error_cb, &err);
|
|
|
|
genlmsg_put(msg, 0, 0, genl_family_get_id((struct genl_family *)family), 0, NLM_F_DUMP, NL80211_CMD_GET_WIPHY, 0);
|
|
|
|
if(nl_send_auto_complete((struct nl_sock *)handle, msg) < 0) {
|
|
fprintf(stderr, "%s: Failed to write nl80211 message\n",
|
|
__FUNCTION__);
|
|
nl80211_disconnect(handle, cache, family);
|
|
return;
|
|
}
|
|
|
|
while(err)
|
|
nl_recvmsgs((struct nl_sock *)handle, cb);
|
|
|
|
nl80211_disconnect(handle, cache, family);
|
|
|
|
m_isValid = true;
|
|
}
|
|
|
|
Wireless::~Wireless() {
|
|
std::cerr << "del wireless" << std::endl;
|
|
|
|
m_bands.clear();
|
|
}
|
|
|
|
QList<int> Wireless::allChannels() const {
|
|
QList<int> channels;
|
|
|
|
foreach(BandInfo info, m_bands) {
|
|
foreach(ChannelInfo chan, info.channels) {
|
|
channels.append(chan.chan);
|
|
}
|
|
}
|
|
|
|
return channels;
|
|
}
|
|
|
|
QList<int> Wireless::allowedChannels(Direction dir) const {
|
|
QList<int> channels;
|
|
|
|
foreach(BandInfo info, m_bands) {
|
|
foreach(ChannelInfo chan, info.channels) {
|
|
if(!chan.disabled) {
|
|
if(((dir & Direction_Both) == Direction_Both) && (!chan.passive)) {
|
|
channels.append(chan.chan);
|
|
}else if(((dir & Direction_RX) == Direction_RX)) {
|
|
channels.append(chan.chan);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return channels;
|
|
}
|
|
|
|
QList<int> Wireless::allFrequencies() const {
|
|
QList<int> freqs;
|
|
|
|
foreach(BandInfo band, m_bands) {
|
|
foreach(ChannelInfo info, band.channels) {
|
|
freqs.append(info.freq);
|
|
}
|
|
}
|
|
|
|
return freqs;
|
|
}
|
|
|
|
QList<int> Wireless::allowedFrequencies(Direction dir) const {
|
|
QList<int> freqs;
|
|
|
|
foreach(BandInfo band, m_bands) {
|
|
foreach(ChannelInfo info, band.channels) {
|
|
if(!info.disabled) {
|
|
if(((dir & Direction_Both) == Direction_Both) && (!info.passive)) {
|
|
freqs.append(info.freq);
|
|
}else if(((dir & Direction_RX) == Direction_RX)) {
|
|
freqs.append(info.freq);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return freqs;
|
|
}
|
|
|
|
bool Wireless::channelSupported(int chan) const {
|
|
bool found = false;
|
|
|
|
foreach(BandInfo band, m_bands) {
|
|
foreach(ChannelInfo info, band.channels) {
|
|
if(info.chan == chan) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
bool Wireless::frequencySupported(int freq) const {
|
|
bool found = false;
|
|
|
|
foreach(BandInfo band, m_bands) {
|
|
foreach(ChannelInfo info, band.channels) {
|
|
if(info.freq == freq) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
bool Wireless::isValid() const {
|
|
return m_isValid;
|
|
}
|
|
|
|
bool Wireless::isNL80211() const {
|
|
return m_isNL80211;
|
|
}
|
|
|
|
const QString& Wireless::name() const {
|
|
return m_interface;
|
|
}
|
|
|
|
int Wireless::ChanToFreq(int in_chan) {
|
|
int x = 0;
|
|
|
|
while(IEEE80211Freq[x][0] != 0) {
|
|
if(IEEE80211Freq[x][0] == in_chan) {
|
|
return IEEE80211Freq[x][1];
|
|
}
|
|
|
|
++x;
|
|
}
|
|
|
|
return in_chan;
|
|
}
|
|
|
|
QString Wireless::ChanToFreq(QString in_chan, QString outputFormat) {
|
|
QString freq;
|
|
QRegularExpression re("[^\\d]");
|
|
in_chan = in_chan.replace(re, "");
|
|
|
|
if(outputFormat.isEmpty()) {
|
|
bool ok = false;
|
|
int chanInt = in_chan.toInt(&ok);
|
|
|
|
if(ok) {
|
|
freq = QString::number(ChanToFreq(chanInt));
|
|
}
|
|
}
|
|
|
|
return freq;
|
|
}
|
|
|
|
int Wireless::FreqToChan(int in_freq) {
|
|
int x = 0;
|
|
|
|
while(IEEE80211Freq[x][1] != 0) {
|
|
if(IEEE80211Freq[x][1] == in_freq) {
|
|
return IEEE80211Freq[x][0];
|
|
}
|
|
|
|
++x;
|
|
}
|
|
|
|
return in_freq;
|
|
}
|
|
|
|
QString Wireless::FreqToChan(QString in_freq, QString outputFormat) {
|
|
QString chan;
|
|
QRegularExpression re("[^\\d]");
|
|
in_freq = in_freq.replace(re, "");
|
|
|
|
if(in_freq.length() < 4) {
|
|
for(int i = in_freq.length() + 1; i <= 4; ++i) {
|
|
in_freq.append("0");
|
|
}
|
|
}
|
|
|
|
if(outputFormat.isEmpty()) {
|
|
bool ok = false;
|
|
int freqInt = in_freq.toInt(&ok);
|
|
|
|
if(ok) {
|
|
chan = QString::number(FreqToChan(freqInt));
|
|
}
|
|
}
|
|
|
|
return chan;
|
|
}
|
|
|
|
int Wireless::nl80211_connect(const char * /*interface*/, void **handle, struct nl_cache **cache, struct genl_family **family) const {
|
|
struct nl_sock *nl_handle;
|
|
struct nl_cache *nl_cache;
|
|
struct genl_family *nl80211;
|
|
|
|
if((nl_handle = nl_socket_alloc()) == NULL) {
|
|
fprintf(stderr, "%s failed to allocate nlhandle\n",
|
|
__FUNCTION__);
|
|
return -1;
|
|
}
|
|
|
|
if(genl_connect(nl_handle)) {
|
|
fprintf(stderr, "%s failed to connect to generic netlink\n",
|
|
__FUNCTION__);
|
|
nl_socket_free(nl_handle);
|
|
return -1;
|
|
}
|
|
|
|
if(genl_ctrl_alloc_cache(nl_handle, &nl_cache) != 0) {
|
|
fprintf(stderr, "%s failed to allocate "
|
|
"generic netlink cache\n", __FUNCTION__);
|
|
nl_socket_free(nl_handle);
|
|
return -1;
|
|
}
|
|
|
|
if ((nl80211 = genl_ctrl_search_by_name(nl_cache, "nl80211")) == NULL) {
|
|
fprintf(stderr, "%s failed to find "
|
|
"nl80211 controls, kernel may be too old\n", __FUNCTION__);
|
|
nl_socket_free(nl_handle);
|
|
return -1;
|
|
}
|
|
|
|
(*handle) = (void *) nl_handle;
|
|
(*cache) = nl_cache;
|
|
(*family) = nl80211;
|
|
|
|
return 1;
|
|
}
|
|
|
|
void Wireless::nl80211_disconnect(void *handle, struct nl_cache *cache, struct genl_family *family) const {
|
|
genl_family_put(family);
|
|
nl_cache_free(cache);
|
|
nl_socket_free((struct nl_sock *)handle);
|
|
}
|
|
|
|
int Wireless::nl80211_freqlist_cb(struct nl_msg *msg, void *arg) {
|
|
struct nlattr *tb_msg[NL80211_ATTR_MAX + 1];
|
|
struct genlmsghdr *gnlh = (struct genlmsghdr *) nlmsg_data(nlmsg_hdr(msg));
|
|
struct nlattr *tb_band[NL80211_BAND_ATTR_MAX + 1];
|
|
struct nlattr *tb_freq[NL80211_FREQUENCY_ATTR_MAX + 1];
|
|
struct nlattr *nl_band, *nl_freq;
|
|
int rem_band, rem_freq = 0;
|
|
struct nl_callback *callback = (struct nl_callback*)arg;
|
|
|
|
nla_parse(tb_msg, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
|
|
genlmsg_attrlen(gnlh, 0), NULL);
|
|
|
|
if (!tb_msg[NL80211_ATTR_WIPHY_BANDS]) {
|
|
return NL_SKIP;
|
|
}
|
|
|
|
if (tb_msg[NL80211_ATTR_WIPHY_NAME]) {
|
|
if (strcmp(nla_get_string(tb_msg[NL80211_ATTR_WIPHY_NAME]),
|
|
callback->phyname) != 0) {
|
|
return NL_SKIP;
|
|
}
|
|
}
|
|
|
|
// Count the number of channels
|
|
for (nl_band = (struct nlattr *) nla_data(tb_msg[NL80211_ATTR_WIPHY_BANDS]),
|
|
rem_band = nla_len(tb_msg[NL80211_ATTR_WIPHY_BANDS]);
|
|
nla_ok(nl_band, rem_band);
|
|
nl_band = (struct nlattr *) nla_next(nl_band, &rem_band)) {
|
|
|
|
nla_parse(tb_band, NL80211_BAND_ATTR_MAX, (struct nlattr *) nla_data(nl_band),
|
|
nla_len(nl_band), NULL);
|
|
|
|
QList<BandInfo> *bands = static_cast<QList<BandInfo>*>(callback->bands);
|
|
BandInfo band;
|
|
|
|
if(tb_band[NL80211_BAND_ATTR_HT_CAPA]) {
|
|
__u16 cap = nla_get_u16(tb_band[NL80211_BAND_ATTR_HT_CAPA]);
|
|
band.protocols = static_cast<Protocols>(band.protocols | PROTOCOL_80211_N);
|
|
band.width |= WIDTH_20MHZ;
|
|
|
|
if((cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40)) { // 40-mhz 802.11n
|
|
band.width |= WIDTH_40MHZ;
|
|
}
|
|
}
|
|
|
|
for (nl_freq = (struct nlattr *) nla_data(tb_band[NL80211_BAND_ATTR_FREQS]),
|
|
rem_freq = nla_len(tb_band[NL80211_BAND_ATTR_FREQS]);
|
|
nla_ok(nl_freq, rem_freq);
|
|
nl_freq = (struct nlattr *) nla_next(nl_freq, &rem_freq)) {
|
|
|
|
nla_parse(tb_freq, NL80211_FREQUENCY_ATTR_MAX,
|
|
(struct nlattr *) nla_data(nl_freq),
|
|
nla_len(nl_freq), NULL);
|
|
|
|
if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ])
|
|
continue;
|
|
|
|
uint32_t freq = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_FREQ]);
|
|
|
|
ChannelInfo info;
|
|
info.chan = FreqToChan(freq);
|
|
info.freq = freq;
|
|
info.disabled = (tb_freq[NL80211_FREQUENCY_ATTR_DISABLED]) ? 1 : 0;
|
|
info.passive = (tb_freq[NL80211_FREQUENCY_ATTR_PASSIVE_SCAN]) ? 1 : 0;
|
|
info.radar = (tb_freq[NL80211_FREQUENCY_ATTR_RADAR]) ? 1 : 0;
|
|
info.max_txpower = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_MAX_TX_POWER]);
|
|
|
|
// check supported channels to see what bands we support (only makes sense for A and B)
|
|
// is there a better way to do this?
|
|
if(!((band.protocols & PROTOCOL_80211_A) == PROTOCOL_80211_A)) {
|
|
if(info.chan >= 36) { // first US 5GHz channel
|
|
band.protocols = static_cast<Protocols>(band.protocols | PROTOCOL_80211_A);
|
|
band.band = static_cast<Bands>(band.band | BAND_5GHZ);
|
|
}
|
|
}
|
|
|
|
if(!((band.protocols & PROTOCOL_80211_B) == PROTOCOL_80211_B)) {
|
|
if(info.chan >= 1) { // first 2.4GHz channel
|
|
band.protocols = static_cast<Protocols>(band.protocols | PROTOCOL_80211_B);
|
|
band.band = static_cast<Bands>(band.band | BAND_2GHZ);
|
|
}
|
|
}
|
|
|
|
band.channels.append(info);
|
|
}
|
|
|
|
bands->append(band);
|
|
}
|
|
|
|
return NL_SKIP;
|
|
}
|
|
|
|
int Wireless::nl80211_error_cb(struct sockaddr_nl * /*nla*/, struct nlmsgerr *err, void *arg) {
|
|
int *ret = (int *) arg;
|
|
*ret = err->error;
|
|
return NL_STOP;
|
|
}
|
|
|
|
int Wireless::nl80211_finish_cb(struct nl_msg * /*msg*/, void *arg) {
|
|
int *ret = (int *) arg;
|
|
*ret = 0;
|
|
return NL_SKIP;
|
|
}
|
|
|
|
char* Wireless::nl80211_find_parent(const char *interface) const {
|
|
DIR *devdir;
|
|
struct dirent *devfile;
|
|
char dirpath[1024];
|
|
char *ret;
|
|
|
|
snprintf(dirpath, 1024, "/sys/class/net/%s/phy80211/device/ieee80211", interface);
|
|
|
|
if ((devdir = opendir(dirpath)) == NULL)
|
|
return NULL;
|
|
|
|
while ((devfile = readdir(devdir)) != NULL) {
|
|
if(strncmp("phy", devfile->d_name, 3) == 0) {
|
|
ret = strdup(devfile->d_name);
|
|
closedir(devdir);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
closedir(devdir);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
const QList<BandInfo>& Wireless::bandMap() const {
|
|
return m_bands;
|
|
}
|
|
|