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.
 
 
 

698 lines
20 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 "interface.h"
#include "wireless.h"
#include "utils.h"
#include <iostream>
#include <QStringList>
#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 *extra;
};
struct nl_interfaceCallback {
char *ifname;
nl80211_iftype *iftype;
QString *iftypeString;
};
/*!
\class Wireless
\brief The Wireless class is used to manage 802.11 wireless information associated with an interface. Currently this class makes the assumption that it will only be used within the United States FCC regulatory domain. Only drivers supporting the Linux kernel nl80211 interface are supported.
*/
static QRegularExpression s_noDigitRegex("[^\\d]");
/*!
Constructs a Wireless object. All operations will be limited to the specified \a interface.
*/
Wireless::Wireless(QString interface) :
QObject(0),
m_interface(interface),
m_interfaceType(),
m_bands(),
m_isValid(false),
m_isNL80211(false),
m_iftype(NL80211_IFTYPE_UNSPECIFIED)
{
if(parseBands() && parseInterface()) {
m_isValid = true;
}
}
Wireless::~Wireless() {
//std::cerr << "del wireless" << std::endl;
m_bands.clear();
}
QStringList Wireless::interfaceList() {
QStringList interfaces = Interface::list();
foreach(QString interface, interfaces) {
Wireless wir(interface);
if(!wir.isValid()) {
interfaces.removeAll(interface);
}
}
interfaces.sort();
return interfaces;
}
bool Wireless::parseBands() {
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);
}
nlmsg_free(msg);
nl_cb_put(cb);
return false;
}else{
m_isNL80211 = true;
}
if(nl80211_connect(interfaceStr, &handle, &cache, &family) < 0) {
std::cerr << "could not connect to nl80211" << std::endl;
nlmsg_free(msg);
nl_cb_put(cb);
return false;
}
struct nl_callback callback = { .phyname = phyname, .extra = &m_bands };
// freqlist
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);
nlmsg_free(msg);
nl_cb_put(cb);
return false;
}
while(err)
nl_recvmsgs((struct nl_sock *)handle, cb);
nl80211_disconnect(handle, cache, family);
nlmsg_free(msg);
nl_cb_put(cb);
return true;
}
bool Wireless::parseInterface() {
void *handle = NULL;
struct genl_family *family = NULL;
struct nl_cache *cache = NULL;
struct nl_msg *msgInterface = nlmsg_alloc();
struct nl_cb *cbInterface = nl_cb_alloc(NL_CB_DEFAULT);
QByteArray interfaceArray = m_interface.toLatin1();
const char *interfaceStr = interfaceArray.constData();
char *interfaceData = interfaceArray.data();
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);
}
nlmsg_free(msgInterface);
nl_cb_put(cbInterface);
return false;
}else{
m_isNL80211 = true;
}
if(nl80211_connect(interfaceStr, &handle, &cache, &family) < 0) {
std::cerr << "could not connect to nl80211" << std::endl;
nlmsg_free(msgInterface);
nl_cb_put(cbInterface);
return false;
}
struct nl_interfaceCallback callback = { .ifname = interfaceData, .iftype = &m_iftype, .iftypeString = &m_interfaceType};
nl_cb_set(cbInterface, NL_CB_VALID, NL_CB_CUSTOM, nl80211_interface_cb, &callback);
nl_cb_set(cbInterface, NL_CB_FINISH, NL_CB_CUSTOM, nl80211_finish_cb, &err);
nl_cb_err(cbInterface, NL_CB_CUSTOM, nl80211_error_cb, &err);
genlmsg_put(msgInterface, 0, 0, genl_family_get_id((struct genl_family *)family), 0, NLM_F_DUMP, NL80211_CMD_GET_INTERFACE, 0);
if(nl_send_auto_complete((struct nl_sock *)handle, msgInterface) < 0) {
fprintf(stderr, "%s: Failed to write nl80211 message\n",
__FUNCTION__);
nl80211_disconnect(handle, cache, family);
nlmsg_free(msgInterface);
nl_cb_put(cbInterface);
return false;
}
while(err)
nl_recvmsgs((struct nl_sock *)handle, cbInterface);
nl80211_disconnect(handle, cache, family);
nlmsg_free(msgInterface);
nl_cb_put(cbInterface);
return true;
}
/*!
Returns a list of integers representing all physically supported channels in all supported bands, regardless if TX/RX is allowed or not in the current regulatory domain.
*/
QList<int> Wireless::allChannels() const {
QList<int> channels;
foreach(BandInfo info, m_bands) {
foreach(ChannelInfo chan, info.channels) {
channels.append(chan.chan);
}
}
return channels;
}
/*!
Returns a list of integers representing all channels (regardless of band) that are allowed to be used in the specified \a direction. Some channels are only available in the receive direction because of regulatory rules, or require that DFS/TPC be used in order to transmit. If \a direction is specified as \a Both, then all non-disabled channels regardless of TX/RX regulation will be returned.
*/
QList<int> Wireless::allowedChannels(Direction direction) const {
QList<int> channels;
foreach(BandInfo info, m_bands) {
foreach(ChannelInfo chan, info.channels) {
if(!chan.disabled) {
if(((direction & Direction_Both) == Direction_Both) && (!chan.passive)) {
channels.append(chan.chan);
}else if(((direction & Direction_RX) == Direction_RX)) {
channels.append(chan.chan);
}
}
}
}
return channels;
}
/*!
Returns a list of integers representing all physically supported frequencies (in MHz) in all supported bands, regardless if TX/RX is allowed or not in the current regulatory domain.
*/
QList<int> Wireless::allFrequencies() const {
QList<int> freqs;
foreach(BandInfo band, m_bands) {
foreach(ChannelInfo info, band.channels) {
freqs.append(info.freq);
}
}
return freqs;
}
/*!
Returns a list of integers representing all frequencies in MHz (regardless of band) that are allowed to be used in the specified \a direction. Some frequencies are only available in the receive direction because of regulatory rules, or require that DFS/TPC be used in order to transmit. If \a direction is specified as \a Both, then all non-disabled frequencies regardless of TX/RX regulation will be returned.
*/
QList<int> Wireless::allowedFrequencies(Direction direction) const {
QList<int> freqs;
foreach(BandInfo band, m_bands) {
foreach(ChannelInfo info, band.channels) {
if(!info.disabled) {
if(((direction & Direction_Both) == Direction_Both) && (!info.passive)) {
freqs.append(info.freq);
}else if(((direction & Direction_RX) == Direction_RX)) {
freqs.append(info.freq);
}
}
}
}
return freqs;
}
/*!
Returns true if the specified channel is physically supported by the hardware, otherwise false. No checks are made in the regulatory domain as to the current TX/RX rules for the channel.
*/
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;
}
/*!
Returns true if the specified frequency (in MHz) is physically supported by the hardware, otherwise false. No checks are made in the regulatory domain as to the current TX/RX rules for the frequency.
*/
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;
}
/*!
Returns true if the interface exists and is supported by nl80211. If the interfaces does not exist, is unsupported, or an error occurred reading interface information, false is returned.
*/
bool Wireless::isValid() const {
return m_isValid;
}
/*!
Returns true if the interface is supported by nl80211, otherwise false.
*/
bool Wireless::isNL80211() const {
return m_isNL80211;
}
/*!
Returns the name of the interface used when the Wireless object was constructed.
*/
const QString& Wireless::name() const {
return m_interface;
}
/*!
Returns the specified \a channel converted to its corresponding frequency, in MHz. If the conversion failed, 0 is returned.
*/
int Wireless::ChanToFreq(int channel) {
int x = 0;
while(IEEE80211Freq[x][0] != 0) {
if(IEEE80211Freq[x][0] == channel) {
return IEEE80211Freq[x][1];
}
++x;
}
return 0;
}
/*!
Returns the specified \a channel converted to its corresponding frequency, in MHz. If the conversion failed, an empty string is returned.
*/
QString Wireless::ChanToFreq(QString channel, QString outputFormat) {
QString freq;
channel = channel.replace(s_noDigitRegex, "");
if(outputFormat.isEmpty()) {
bool ok = false;
int chanInt = channel.toInt(&ok);
if(ok) {
freq = QString::number(ChanToFreq(chanInt));
}
}
return freq;
}
/*!
Returns the specified \a frequency converted to its corresponding channel. If the conversion failed, 0 is returned.
*/
int Wireless::FreqToChan(int frequency) {
int x = 0;
while(IEEE80211Freq[x][1] != 0) {
if(IEEE80211Freq[x][1] == frequency) {
return IEEE80211Freq[x][0];
}
++x;
}
return 0;
}
/*!
Returns the specified \a frequency converted to its corresponding channel. If the conversion failed, an empty string is returned.
*/
QString Wireless::FreqToChan(QString frequency, QString outputFormat) {
QString chan;
frequency = frequency.replace(s_noDigitRegex, "");
if(frequency.length() < 4) {
for(int i = frequency.length() + 1; i <= 4; ++i) {
frequency.append("0");
}
}
if(outputFormat.isEmpty()) {
bool ok = false;
int freqInt = frequency.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);
}
nl80211_iftype Wireless::getInterfaceTypeRaw() {
return m_iftype;
}
const QString& Wireless::getInterfaceType() const {
return m_interfaceType;
}
int Wireless::nl80211_interface_cb(struct nl_msg *msg, void *arg) {
struct nlattr *tb_msg[NL80211_ATTR_MAX + 1] = {NULL};
struct genlmsghdr *gnlh = (struct genlmsghdr *) nlmsg_data(nlmsg_hdr(msg));
//struct nlattr *tb_interface[NL80211_IFTYPE_MAX + 1];
//struct nlattr *nl_interface = NULL;
//int rem_interface = 0;
struct nl_interfaceCallback *callback = (struct nl_interfaceCallback*)arg;
nla_parse(tb_msg, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);
if (!tb_msg[NL80211_ATTR_IFTYPE]) {
return NL_SKIP;
}
if (tb_msg[NL80211_ATTR_IFNAME]) {
if (strcmp(nla_get_string(tb_msg[NL80211_ATTR_IFNAME]),
callback->ifname) != 0) {
return NL_SKIP;
}
}
*(callback->iftype) = static_cast<nl80211_iftype>(nla_get_u32(tb_msg[NL80211_ATTR_IFTYPE]));
switch(*(callback->iftype)) {
case NL80211_IFTYPE_UNSPECIFIED: { *(callback->iftypeString) = "Unspecified"; break; }
case NL80211_IFTYPE_ADHOC: { *(callback->iftypeString) = "Ad-hoc"; break; }
case NL80211_IFTYPE_STATION: { *(callback->iftypeString) = "Client"; break; }
case NL80211_IFTYPE_AP: { *(callback->iftypeString) = "AP"; break; }
case NL80211_IFTYPE_AP_VLAN: { *(callback->iftypeString) = "AP-VLAN"; break; }
case NL80211_IFTYPE_WDS: { *(callback->iftypeString) = "WDS"; break; }
case NL80211_IFTYPE_MONITOR: { *(callback->iftypeString) = "Monitor"; break; }
case NL80211_IFTYPE_MESH_POINT: { *(callback->iftypeString) = "Mesh Point"; break; }
case NL80211_IFTYPE_P2P_CLIENT: { *(callback->iftypeString) = "P2P Client"; break; }
case NL80211_IFTYPE_P2P_GO: { *(callback->iftypeString) = "P2P Go"; break; }
#if defined(NL80211_IFTYPE_P2P_DEVICE)
// ubuntu 12.04 doesn't define this type
case NL80211_IFTYPE_P2P_DEVICE: { *(callback->iftypeString) = "P2P Device"; break; }
#endif
default: { *(callback->iftypeString) = "Unspecified"; break; }
}
return NL_SKIP;
}
int Wireless::nl80211_freqlist_cb(struct nl_msg *msg, void *arg) {
struct nlattr *tb_msg[NL80211_ATTR_MAX + 1] = {NULL};
struct genlmsghdr *gnlh = (struct genlmsghdr *) nlmsg_data(nlmsg_hdr(msg));
struct nlattr *tb_band[NL80211_BAND_ATTR_MAX + 1] = {NULL};
struct nlattr *tb_freq[NL80211_FREQUENCY_ATTR_MAX + 1] = {NULL};
struct nlattr *nl_band, *nl_freq = NULL;
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->extra);
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 <= 14) { // last 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;
}
/*!
Returns a list of BandInfo structs containing all information about the supported bands and frequencies of the interface.
*/
const QList<Wireless::BandInfo>& Wireless::bandMap() const {
return m_bands;
}