Commit cc5d8a37 authored by Luis R. Rodriguez's avatar Luis R. Rodriguez Committed by John W. Linville

cfg80211: Fix country IE parsing for single channel triplets

This enhances the way we parse country IEs to minimize
the number of regulatory rules that we create. It also fixes
our current implementation which treated country IE triplets
with only one channel as one independed regulatory rule even
though adjecent rules were also being provided.

Without this patch APs which send country IE information with
a channel triplet for each individual channel will force cfg80211
to deny HT40 operation as a regulatory rule would have been created
independently for each channel and as such configured only for
20 MHz operation.

Although 802.11n APs which send country IEs triplets in this fassion
are likely rare Benoit reports this against the Ubiquity NanoStation M5,
with Country "FR" and HT40 enabled.

Since we now have a helper which parses the triplets in intermediate
steps we now take care extra care to process padding.
Reported-by: default avatarBenoit PAPILLAULT <benoit.papillault@free.fr>
Signed-off-by: default avatarLuis R. Rodriguez <lrodriguez@atheros.com>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 08030db6
...@@ -484,6 +484,178 @@ static bool freq_in_rule_band(const struct ieee80211_freq_range *freq_range, ...@@ -484,6 +484,178 @@ static bool freq_in_rule_band(const struct ieee80211_freq_range *freq_range,
#undef ONE_GHZ_IN_KHZ #undef ONE_GHZ_IN_KHZ
} }
/*
* Some APs may send a country IE triplet for each channel they
* support and while this is completely overkill and silly we still
* need to support it. We avoid making a single rule for each channel
* though and to help us with this we use this helper to find the
* actual subband end channel. These type of country IE triplet
* scenerios are handled then, all yielding two regulaotry rules from
* parsing a country IE:
*
* [1]
* [2]
* [36]
* [40]
*
* [1]
* [2-4]
* [5-12]
* [36]
* [40-44]
*
* [1-4]
* [5-7]
* [36-44]
* [48-64]
*
* [36-36]
* [40-40]
* [44-44]
* [48-48]
* [52-52]
* [56-56]
* [60-60]
* [64-64]
* [100-100]
* [104-104]
* [108-108]
* [112-112]
* [116-116]
* [120-120]
* [124-124]
* [128-128]
* [132-132]
* [136-136]
* [140-140]
*
* Returns 0 if the IE has been found to be invalid in the middle
* somewhere.
*/
static int max_subband_chan(int orig_cur_chan,
int orig_end_channel,
s8 orig_max_power,
u8 **country_ie,
u8 *country_ie_len)
{
u8 *triplets_start = *country_ie;
u8 len_at_triplet = *country_ie_len;
int end_subband_chan = orig_end_channel;
enum ieee80211_band band;
/*
* We'll deal with padding for the caller unless
* its not immediate and we don't process any channels
*/
if (*country_ie_len == 1) {
*country_ie += 1;
*country_ie_len -= 1;
return orig_end_channel;
}
/* Move to the next triplet and then start search */
*country_ie += 3;
*country_ie_len -= 3;
if (orig_cur_chan <= 14)
band = IEEE80211_BAND_2GHZ;
else
band = IEEE80211_BAND_5GHZ;
while (*country_ie_len >= 3) {
int end_channel = 0;
struct ieee80211_country_ie_triplet *triplet =
(struct ieee80211_country_ie_triplet *) *country_ie;
int cur_channel = 0, next_expected_chan;
enum ieee80211_band next_band = IEEE80211_BAND_2GHZ;
/* means last triplet is completely unrelated to this one */
if (triplet->ext.reg_extension_id >=
IEEE80211_COUNTRY_EXTENSION_ID) {
*country_ie -= 3;
*country_ie_len += 3;
break;
}
if (triplet->chans.first_channel == 0) {
*country_ie += 1;
*country_ie_len -= 1;
if (*country_ie_len != 0)
return 0;
break;
}
/* Monitonically increasing channel order */
if (triplet->chans.first_channel <= end_subband_chan)
return 0;
/* 2 GHz */
if (triplet->chans.first_channel <= 14) {
end_channel = triplet->chans.first_channel +
triplet->chans.num_channels - 1;
}
else {
end_channel = triplet->chans.first_channel +
(4 * (triplet->chans.num_channels - 1));
next_band = IEEE80211_BAND_5GHZ;
}
if (band != next_band) {
*country_ie -= 3;
*country_ie_len += 3;
break;
}
if (orig_max_power != triplet->chans.max_power) {
*country_ie -= 3;
*country_ie_len += 3;
break;
}
cur_channel = triplet->chans.first_channel;
/* The key is finding the right next expected channel */
if (band == IEEE80211_BAND_2GHZ)
next_expected_chan = end_subband_chan + 1;
else
next_expected_chan = end_subband_chan + 4;
if (cur_channel != next_expected_chan) {
*country_ie -= 3;
*country_ie_len += 3;
break;
}
end_subband_chan = end_channel;
/* Move to the next one */
*country_ie += 3;
*country_ie_len -= 3;
/*
* Padding needs to be dealt with if we processed
* some channels.
*/
if (*country_ie_len == 1) {
*country_ie += 1;
*country_ie_len -= 1;
break;
}
/* If seen, the IE is invalid */
if (*country_ie_len == 2)
return 0;
}
if (end_subband_chan == orig_end_channel) {
*country_ie = triplets_start;
*country_ie_len = len_at_triplet;
return orig_end_channel;
}
return end_subband_chan;
}
/* /*
* Converts a country IE to a regulatory domain. A regulatory domain * Converts a country IE to a regulatory domain. A regulatory domain
* structure has a lot of information which the IE doesn't yet have, * structure has a lot of information which the IE doesn't yet have,
...@@ -552,6 +724,19 @@ static struct ieee80211_regdomain *country_ie_2_rd( ...@@ -552,6 +724,19 @@ static struct ieee80211_regdomain *country_ie_2_rd(
continue; continue;
} }
/*
* APs can add padding to make length divisible
* by two, required by the spec.
*/
if (triplet->chans.first_channel == 0) {
country_ie++;
country_ie_len--;
/* This is expected to be at the very end only */
if (country_ie_len != 0)
return NULL;
break;
}
/* 2 GHz */ /* 2 GHz */
if (triplet->chans.first_channel <= 14) if (triplet->chans.first_channel <= 14)
end_channel = triplet->chans.first_channel + end_channel = triplet->chans.first_channel +
...@@ -570,6 +755,20 @@ static struct ieee80211_regdomain *country_ie_2_rd( ...@@ -570,6 +755,20 @@ static struct ieee80211_regdomain *country_ie_2_rd(
(4 * (triplet->chans.num_channels - 1)); (4 * (triplet->chans.num_channels - 1));
cur_channel = triplet->chans.first_channel; cur_channel = triplet->chans.first_channel;
/*
* Enhancement for APs that send a triplet for every channel
* or for whatever reason sends triplets with multiple channels
* separated when in fact they should be together.
*/
end_channel = max_subband_chan(cur_channel,
end_channel,
triplet->chans.max_power,
&country_ie,
&country_ie_len);
if (!end_channel)
return NULL;
cur_sub_max_channel = end_channel; cur_sub_max_channel = end_channel;
/* Basic sanity check */ /* Basic sanity check */
...@@ -600,9 +799,12 @@ static struct ieee80211_regdomain *country_ie_2_rd( ...@@ -600,9 +799,12 @@ static struct ieee80211_regdomain *country_ie_2_rd(
last_sub_max_channel = cur_sub_max_channel; last_sub_max_channel = cur_sub_max_channel;
num_rules++;
if (country_ie_len >= 3) {
country_ie += 3; country_ie += 3;
country_ie_len -= 3; country_ie_len -= 3;
num_rules++; }
/* /*
* Note: this is not a IEEE requirement but * Note: this is not a IEEE requirement but
...@@ -646,6 +848,12 @@ static struct ieee80211_regdomain *country_ie_2_rd( ...@@ -646,6 +848,12 @@ static struct ieee80211_regdomain *country_ie_2_rd(
continue; continue;
} }
if (triplet->chans.first_channel == 0) {
country_ie++;
country_ie_len--;
break;
}
reg_rule = &rd->reg_rules[i]; reg_rule = &rd->reg_rules[i];
freq_range = &reg_rule->freq_range; freq_range = &reg_rule->freq_range;
power_rule = &reg_rule->power_rule; power_rule = &reg_rule->power_rule;
...@@ -660,6 +868,12 @@ static struct ieee80211_regdomain *country_ie_2_rd( ...@@ -660,6 +868,12 @@ static struct ieee80211_regdomain *country_ie_2_rd(
end_channel = triplet->chans.first_channel + end_channel = triplet->chans.first_channel +
(4 * (triplet->chans.num_channels - 1)); (4 * (triplet->chans.num_channels - 1));
end_channel = max_subband_chan(triplet->chans.first_channel,
end_channel,
triplet->chans.max_power,
&country_ie,
&country_ie_len);
/* /*
* The +10 is since the regulatory domain expects * The +10 is since the regulatory domain expects
* the actual band edge, not the center of freq for * the actual band edge, not the center of freq for
...@@ -682,9 +896,12 @@ static struct ieee80211_regdomain *country_ie_2_rd( ...@@ -682,9 +896,12 @@ static struct ieee80211_regdomain *country_ie_2_rd(
power_rule->max_antenna_gain = DBI_TO_MBI(100); power_rule->max_antenna_gain = DBI_TO_MBI(100);
power_rule->max_eirp = DBM_TO_MBM(triplet->chans.max_power); power_rule->max_eirp = DBM_TO_MBM(triplet->chans.max_power);
i++;
if (country_ie_len >= 3) {
country_ie += 3; country_ie += 3;
country_ie_len -= 3; country_ie_len -= 3;
i++; }
BUG_ON(i > NL80211_MAX_SUPP_REG_RULES); BUG_ON(i > NL80211_MAX_SUPP_REG_RULES);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment