/* horst - Highly Optimized Radio Scanning Tool
*
* Copyright (C) 2005-2016 Bruno Randolf (br1@einfach.org)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <stdlib.h>
#include "main.h"
#include "util.h"
#include "ifctrl.h"
#include "channel.h"
#include "ieee80211_util.h"
#include "wlan_util.h"
static struct timespec last_channelchange;
static struct channel_list channels;
uint32_t channel_get_remaining_dwell_time(void)
{
if (!conf.do_change_channel)
return UINT32_MAX;
int64_t ret = (int64_t)conf.channel_time
- (time_mono.tv_sec - last_channelchange.tv_sec) * 1000000
- (time_mono.tv_nsec - last_channelchange.tv_nsec) / 1000;
if (ret < 0)
return 0;
else if (ret > UINT32_MAX)
return UINT32_MAX;
else
return ret;
}
static struct band_info channel_get_band_from_idx(int idx)
{
int b = idx - channels.band[0].num_channels < 0 ? 0 : 1;
return channels.band[b];
}
static int get_center_freq_ht40(unsigned int freq, bool upper)
{
unsigned int center = 0;
/*
* For HT40 we have a channel offset of 20 MHz, and the
* center frequency is in the middle: +/- 10 MHz, depending
* on HT40+ or HT40- and whether the channel exists
*/
if (upper && channel_find_index_from_freq(freq + 20) != -1)
center = freq + 10;
else if (!upper && channel_find_index_from_freq(freq - 20) != -1)
center = freq - 10;
return center;
}
static int get_center_freq_vht(unsigned int freq, enum chan_width width)
{
unsigned int center1 = 0;
switch(width) {
case CHAN_WIDTH_80:
/*
* VHT80 channels are non-overlapping and the primary
* channel can be on any HT20/40 channel in the range
*/
if (freq >= 5180 && freq <= 5240)
center1 = 5210;
else if (freq >= 5260 && freq <= 5320)
center1 = 5290;
else if (freq >= 5500 && freq <= 5560)
center1 = 5530;
else if (freq >= 5580 && freq <= 5640)
center1 = 5610;
else if (freq >= 5660 && freq <= 5720)
center1 = 5690;
else if (freq >= 5745 && freq <= 5805)
center1 = 5775;
break;
case CHAN_WIDTH_160:
/*
* There are only two possible VHT160 channels
*/
if (freq >= 5180 && freq <= 5320)
center1 = 5250;
else if (freq >= 5180 && freq <= 5320)
center1 = 5570;
break;
case CHAN_WIDTH_8080:
printlog("VHT80+80 not supported");
break;
default:
printlog("%s is not VHT", channel_width_string(width, -1));
}
return center1;
}
const char* channel_width_string(enum chan_width w, int ht40p)
{
switch (w) {
case CHAN_WIDTH_UNSPEC: return "?";
case CHAN_WIDTH_20_NOHT: return "20 (no HT)";
case CHAN_WIDTH_20: return "HT20";
case CHAN_WIDTH_40: return ht40p < 0 ? "HT40" : ht40p ? "HT40+" : "HT40-";
case CHAN_WIDTH_80: return "VHT80";
case CHAN_WIDTH_160: return "VHT160";
case CHAN_WIDTH_8080: return "VHT80+80";
}
return "";
}
const char* channel_width_string_short(enum chan_width w, int ht40p)
{
switch (w) {
case CHAN_WIDTH_UNSPEC: return "?";
case CHAN_WIDTH_20_NOHT: return "20g";
case CHAN_WIDTH_20: return "20";
case CHAN_WIDTH_40: return ht40p < 0 ? "40" : ht40p ? "40+" : "40-";
case CHAN_WIDTH_80: return "80";
case CHAN_WIDTH_160: return "160";
case CHAN_WIDTH_8080: return "80+80";
}
return "";
}
/* Note: ht40plus is only used for HT40 channel width, to distinguish between
* HT40+ and HT40- */
bool channel_change(int idx, enum chan_width width, bool ht40plus)
{
unsigned int center1 = 0;
if (width == CHAN_WIDTH_UNSPEC)
width = channel_get_band_from_idx(idx).max_chan_width;
switch (width) {
case CHAN_WIDTH_20_NOHT:
case CHAN_WIDTH_20:
break;
case CHAN_WIDTH_40:
center1 = get_center_freq_ht40(channels.chan[idx].freq, ht40plus);
break;
case CHAN_WIDTH_80:
case CHAN_WIDTH_160:
center1 = get_center_freq_vht(channels.chan[idx].freq, width);
break;
default:
printlog("%s not implemented", channel_width_string(width, -1));
break;
}
/* only 20 MHz channels don't need additional center freq, otherwise we fail here
* quietly because the scanning code sometimes tries invalid HT40+/- channels */
if (center1 == 0 && !(width == CHAN_WIDTH_20_NOHT || width == CHAN_WIDTH_20))
return false;
if (!ifctrl_iwset_freq(conf.ifname, channels.chan[idx].freq, width, center1)) {
printlog("ERROR: Failed to set CH %d (%d MHz) %s center %d",
channels.chan[idx].chan, channels.chan[idx].freq,
channel_width_string(width, ht40plus),
center1);
return false;
}
#if 0
printlog("Set CH %d (%d MHz) %s center %d after %ldms",
channels.chan[idx].chan, channels.chan[idx].freq,
channel_width_string(width, ht40plus),
center1, (the_time.tv_sec - last_channelchange.tv_sec) * 1000
+ (the_time.tv_nsec - last_channelchange.tv_nsec) / 1000000);
#endif
conf.channel_idx = idx;
conf.channel_width = width;
conf.channel_ht40plus = ht40plus;
conf.max_phy_rate = get_phy_thruput(width, channel_get_band_from_idx(idx).streams_rx);
last_channelchange = time_mono;
return true;
}
bool channel_auto_change(void)
{
int new_idx;
bool ret = true;
int start_idx;
if (conf.channel_idx == -1)
return false; /* The current channel is still unknown for some
* reason (mac80211 bug, busy physical interface,
* etc.), it will be fixed when the first packet
* arrives, see fixup_packet_channel().
*
* Without this return, horst would busy-loop forever
* (making the ui totally unresponsive) in the channel
* changing code below because start_idx would be -1
* as well. Short-circuit exiting here is quite
* logical though: it does not make any sense to scan
* channels as long as the channel module is not
* initialized properly. */
if (channel_get_remaining_dwell_time() > 0)
return false; /* too early */
if (conf.do_change_channel) {
start_idx = new_idx = conf.channel_idx;
do {
enum chan_width max_width = channel_get_band_from_idx(new_idx).max_chan_width;
/*
* For HT40 we visit the same channel twice, once with HT40+
* and once with HT40-. This is necessary to see the HT40+/-
* data packets
*/
if (max_width == CHAN_WIDTH_40) {
if (conf.channel_ht40plus)
new_idx++;
conf.channel_ht40plus = !conf.channel_ht40plus; // toggle
} else {
new_idx++;
}
if (new_idx >= channels.num_channels ||
new_idx >= MAX_CHANNELS ||
(conf.channel_max &&
channel_get_chan(new_idx) > conf.channel_max)) {
new_idx = 0;
max_width = channel_get_band_from_idx(new_idx).max_chan_width;
conf.channel_ht40plus = true;
}
ret = channel_change(new_idx, max_width, conf.channel_ht40plus);
/* try setting different channels in case we get errors only
* on some channels (e.g. ipw2200 reports channel 14 but cannot
* be set to use it). stop if we tried all channels */
} while (ret != 1 && new_idx != start_idx);
}
return ret;
}
char* channel_get_string(int idx)
{
static char buf[32];
struct chan_freq* c = &channels.chan[idx];
snprintf(buf, sizeof(buf), "%-3d: %d HT40%s%s", c->chan, c->freq,
get_center_freq_ht40(c->freq, true) ? "+" : "",
get_center_freq_ht40(c->freq, false) ? "-" : "");
return buf;
}
bool channel_init(void)
{
/* get available channels */
ifctrl_iwget_freqlist(conf.if_phy, &channels);
conf.channel_initialized = 1;
printf("Got %d Bands, %d Channels:\n", channels.num_bands, channels.num_channels);
for (int i = 0; i < channels.num_channels && i < MAX_CHANNELS; i++)
printf("%s\n", channel_get_string(i));
if (channels.num_bands <= 0 || channels.num_channels <= 0)
return false;
if (conf.channel_set_num > 0) {
/* configured values */
printf("Setting configured channel %d\n", conf.channel_set_num);
int ini_idx = channel_find_index_from_chan(conf.channel_set_num);
if (!channel_change(ini_idx, conf.channel_set_width, conf.channel_set_ht40plus))
return false;
} else {
if (conf.if_freq <= 0) {
/* this happens when we have not been able to change
* the original interface to monitor mode and we added
* an additional monitor (horstX) interface */
printf("Could not get current channel of interface\n");
conf.max_phy_rate = get_phy_thruput(channels.band[0].max_chan_width,
channels.band[0].streams_rx);
return true; // not failure
}
conf.channel_idx = channel_find_index_from_freq(conf.if_freq);
conf.channel_set_num = channel_get_chan(conf.channel_idx);
/* try to set max width */
struct band_info b = channel_get_band_from_idx(conf.channel_idx);
if (conf.channel_width != b.max_chan_width) {
printlog("Try to set max channel width %s",
channel_width_string(b.max_chan_width, -1));
// try both HT40+ and HT40- if necessary
if (!channel_change(conf.channel_idx, b.max_chan_width, true) &&
!channel_change(conf.channel_idx, b.max_chan_width, false))
return false;
} else {
conf.channel_set_width = conf.channel_width;
conf.channel_set_ht40plus = conf.channel_ht40plus;
conf.max_phy_rate = get_phy_thruput(conf.channel_width, b.streams_rx);
}
}
return true;
}
int channel_find_index_from_chan(int c)
{
int i = -1;
for (i = 0; i < channels.num_channels && i < MAX_CHANNELS; i++)
if (channels.chan[i].chan == c)
return i;
return -1;
}
int channel_find_index_from_freq(unsigned int f)
{
int i = -1;
for (i = 0; i < channels.num_channels && i < MAX_CHANNELS; i++)
if (channels.chan[i].freq == f)
return i;
return -1;
}
int channel_get_chan(int i)
{
if (i >= 0 && i < channels.num_channels && i < MAX_CHANNELS)
return channels.chan[i].chan;
else
return -1;
}
int channel_get_freq(int idx)
{
if (idx >= 0 && idx < channels.num_channels && idx < MAX_CHANNELS)
return channels.chan[idx].freq;
else
return -1;
}
bool channel_list_add(int freq)
{
if (channels.num_channels >= MAX_CHANNELS)
return false;
channels.chan[channels.num_channels].chan = ieee80211_freq2channel(freq);
channels.chan[channels.num_channels].freq = freq;
channels.num_channels++;
return true;
}
int channel_get_num_channels(void)
{
return channels.num_channels;
}
int channel_get_num_bands(void)
{
return channels.num_bands;
}
int channel_get_idx_from_band_idx(int band, int idx)
{
if (band < 0 || band >= channels.num_bands)
return -1;
if (idx < 0 || idx >= channels.band[band].num_channels)
return -1;
if (band > 0)
idx = idx + channels.band[0].num_channels;
return idx;
}
const struct band_info* channel_get_band(int b)
{
if (b < 0 || b > channels.num_bands)
return NULL;
return &channels.band[b];
}
bool channel_band_add(int num_channels, enum chan_width max_chan_width,
unsigned char streams_rx, unsigned char streams_tx)
{
if (channels.num_bands >= MAX_BANDS)
return false;
channels.band[channels.num_bands].num_channels = num_channels;
channels.band[channels.num_bands].max_chan_width = max_chan_width;
channels.band[channels.num_bands].streams_rx = streams_rx;
channels.band[channels.num_bands].streams_tx = streams_tx;
channels.num_bands++;
return true;
}