]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
hostapd: Add AP DFS support
authorJanusz Dziedzic <janusz.dziedzic@tieto.com>
Tue, 15 Oct 2013 17:27:25 +0000 (20:27 +0300)
committerJouni Malinen <j@w1.fi>
Thu, 17 Oct 2013 18:05:15 +0000 (21:05 +0300)
Add DFS structures/events handlers, CAC handling, and radar detection.
By default, after radar is detected or the channel became unavailable, a
random channel will be chosen.

This patches are based on the original work by Boris Presman and
Victor Goldenshtein. Most of the DFS code is moved to a new dfs.c/dfs.h
files.

Cc: Boris Presman <boris.presman@ti.com>
Cc: Victor Goldenshtein <victorg@ti.com>
Signed-hostap: Simon Wunderlich <siwu@hrz.tu-chemnitz.de>
Signed-hostap: Janusz Dziedzic <janusz.dziedzic@tieto.com>

hostapd/Makefile
src/ap/ap_drv_ops.c
src/ap/ap_drv_ops.h
src/ap/dfs.c [new file with mode: 0644]
src/ap/dfs.h [new file with mode: 0644]
src/ap/drv_callbacks.c
src/ap/hostapd.c
src/ap/hw_features.c
src/ap/hw_features.h
wpa_supplicant/Makefile

index fda4e4e35db21260a5744c4ad44cbbf75a18549b..c56d3687628fe4ce9ebad3cce0b029843f7b4511 100644 (file)
@@ -782,6 +782,7 @@ OBJS += ../src/ap/wmm.o
 OBJS += ../src/ap/ap_list.o
 OBJS += ../src/ap/ieee802_11.o
 OBJS += ../src/ap/hw_features.o
+OBJS += ../src/ap/dfs.o
 CFLAGS += -DNEED_AP_MLME
 endif
 ifdef CONFIG_IEEE80211N
index 3072562e34d8ae0fd81cfd809930d23891340076..cedfe8e13ed0c4b42193db57d635764cef8fe269 100644 (file)
@@ -699,3 +699,30 @@ int hostapd_drv_send_action(struct hostapd_data *hapd, unsigned int freq,
                                         hapd->own_addr, hapd->own_addr, data,
                                         len, 0);
 }
+
+
+int hostapd_start_dfs_cac(struct hostapd_data *hapd, int freq, int flags)
+{
+       if (!hapd->driver || !hapd->driver->start_dfs_cac)
+               return 0;
+
+       if (!(flags & HOSTAPD_CHAN_RADAR)) {
+               wpa_printf(MSG_ERROR, "Can't start DFS_CAC, the channel %u is "
+                          "not DFS channel", hapd->iconf->channel);
+               return -1;
+       }
+
+       if (!hapd->iface->conf->ieee80211h) {
+               wpa_printf(MSG_ERROR, "Can't start DFS CAC, DFS functionality "
+                          "is not enabled");
+               return -1;
+       }
+
+       if (hapd->iface->conf->secondary_channel) {
+               wpa_printf(MSG_ERROR, "Can't start DFS CAC, DFS functionality "
+                          "on HT40 is not supported");
+               return -1;
+       }
+
+       return hapd->driver->start_dfs_cac(hapd->drv_priv, freq);
+}
index 23277b6b73d272ae69a0433fd5b735b3b2e143fd..d56aa91064e33bf31e7b02422b093712c97fd2e5 100644 (file)
@@ -101,6 +101,7 @@ int hostapd_sta_assoc(struct hostapd_data *hapd, const u8 *addr,
                      int reassoc, u16 status, const u8 *ie, size_t len);
 int hostapd_add_tspec(struct hostapd_data *hapd, const u8 *addr,
                      u8 *tspec_ie, size_t tspec_ielen);
+int hostapd_start_dfs_cac(struct hostapd_data *hapd, int freq, int flags);
 
 
 #include "drivers/driver.h"
diff --git a/src/ap/dfs.c b/src/ap/dfs.c
new file mode 100644 (file)
index 0000000..387b625
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ * DFS - Dynamic Frequency Selection
+ * Copyright (c) 2002-2013, Jouni Malinen <j@w1.fi>
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "common/ieee802_11_defs.h"
+#include "hostapd.h"
+#include "hw_features.h"
+#include "ap_drv_ops.h"
+#include "drivers/driver.h"
+#include "dfs.h"
+
+
+static int hostapd_dfs_find_channel(struct hostapd_data *hapd,
+                                   struct hostapd_channel_data **ret_chan,
+                                   int idx)
+{
+       struct hostapd_hw_modes *mode;
+       struct hostapd_channel_data *chan;
+       int i, channel_idx = 0;
+
+       mode = hapd->iface->current_mode;
+
+       for (i = 0; i < mode->num_channels; i++) {
+               chan = &mode->channels[i];
+
+               if (chan->flag & HOSTAPD_CHAN_DISABLED)
+                       continue;
+
+               if (chan->flag & HOSTAPD_CHAN_RADAR &&
+                   chan->flag & HOSTAPD_CHAN_DFS_UNAVAILABLE)
+                       continue;
+
+               if (ret_chan && idx == channel_idx) {
+                       wpa_printf(MSG_DEBUG, "Selected ch. #%d", chan->chan);
+                       *ret_chan = chan;
+                       return idx;
+               }
+               channel_idx++;
+       }
+       return channel_idx;
+}
+
+
+struct hostapd_channel_data * hostapd_dfs_get_valid_channel(
+       struct hostapd_data *hapd)
+{
+       struct hostapd_hw_modes *mode;
+       struct hostapd_channel_data *chan = NULL;
+       int channel_idx, new_channel_idx;
+       u32 _rand;
+
+       wpa_printf(MSG_DEBUG, "DFS: Selecting random channel");
+
+       if (hapd->iface->current_mode == NULL)
+               return NULL;
+
+       mode = hapd->iface->current_mode;
+       if (mode->mode != HOSTAPD_MODE_IEEE80211A)
+               return NULL;
+
+       /* get random available channel */
+       channel_idx = hostapd_dfs_find_channel(hapd, NULL, 0);
+       if (channel_idx > 0) {
+               os_get_random((u8 *) &_rand, sizeof(_rand));
+               new_channel_idx = _rand % channel_idx;
+               hostapd_dfs_find_channel(hapd, &chan, new_channel_idx);
+       }
+
+       return chan;
+}
+
+
+int ieee802_11_set_dfs_state(struct hostapd_data *hapd, int freq, u32 state)
+{
+       struct hostapd_hw_modes *mode;
+       struct hostapd_channel_data *chan = NULL;
+       int i;
+
+       mode = hapd->iface->current_mode;
+       if (mode == NULL)
+               return 0;
+
+       if (mode->mode != HOSTAPD_MODE_IEEE80211A) {
+               wpa_printf(MSG_WARNING, "current_mode != IEEE80211A");
+               return 0;
+       }
+
+       for (i = 0; i < hapd->iface->current_mode->num_channels; i++) {
+               chan = &hapd->iface->current_mode->channels[i];
+               if (chan->freq == freq) {
+                       if (chan->flag & HOSTAPD_CHAN_RADAR) {
+                               chan->flag &= ~HOSTAPD_CHAN_DFS_MASK;
+                               chan->flag |= state;
+                               return 1; /* Channel found */
+                       }
+               }
+       }
+       wpa_printf(MSG_WARNING, "Can't set DFS state for freq %d MHz", freq);
+       return 0;
+}
+
+
+/*
+ * Main DFS handler
+ * 1 - continue channel/ap setup
+ * 0 - channel/ap setup will be continued after CAC
+ * -1 - hit critical error
+ */
+int hostapd_handle_dfs(struct hostapd_data *hapd)
+{
+       int flags;
+       struct hostapd_channel_data *channel;
+
+       /* Handle DFS channel */
+check_dfs_chan_again:
+       flags = hostapd_hw_get_channel_flag(hapd, hapd->iconf->channel);
+       if (flags & HOSTAPD_CHAN_RADAR) {
+               switch (flags & HOSTAPD_CHAN_DFS_MASK) {
+               case HOSTAPD_CHAN_DFS_USABLE:
+                       wpa_printf(MSG_DEBUG, "DFS start CAC on %d MHz",
+                                  hapd->iface->freq);
+                       if (hostapd_start_dfs_cac(hapd,
+                                                 hapd->iface->freq,
+                                                 flags)) {
+                               wpa_printf(MSG_DEBUG, "DFS start_dfs_cac() failed");
+                               return -1;
+                       }
+                       /* Continue initialisation after CAC */
+                       return 0;
+               case HOSTAPD_CHAN_DFS_UNAVAILABLE:
+                       wpa_printf(MSG_DEBUG, "HOSTAPD_CHAN_DFS_UNAVAILABLE, get new chan");
+                       /* find other channel here */
+                       channel = hostapd_dfs_get_valid_channel(hapd);
+                       if (!channel) {
+                               wpa_printf(MSG_ERROR, "could not get valid channel");
+                               return -1;
+                       }
+                       hapd->iconf->channel = channel->chan;
+                       hapd->iface->freq = channel->freq;
+                       goto check_dfs_chan_again;
+               case HOSTAPD_CHAN_DFS_AVAILABLE:
+                       /* We don't need CAC here */
+                       wpa_printf(MSG_DEBUG, "HOSTAPD_CHAN_DFS_AVAILABLE, skip CAC");
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       return 1;
+}
+
+
+int ieee802_11_complete_cac(struct hostapd_data *hapd, int success, int freq)
+{
+       struct hostapd_channel_data *channel;
+       int err = 1;
+
+       if (success) {
+               /* Complete iface/ap configuration */
+               ieee802_11_set_dfs_state(hapd, freq,
+                                        HOSTAPD_CHAN_DFS_AVAILABLE);
+               hostapd_setup_interface_complete(hapd->iface, 0);
+       } else {
+               /* Switch to new channel */
+               ieee802_11_set_dfs_state(hapd, freq,
+                                        HOSTAPD_CHAN_DFS_UNAVAILABLE);
+               channel = hostapd_dfs_get_valid_channel(hapd);
+               if (channel) {
+                       hapd->iconf->channel = channel->chan;
+                       hapd->iface->freq = channel->freq;
+                       err = 0;
+               } else
+                       wpa_printf(MSG_ERROR, "No valid channel available");
+
+               hostapd_setup_interface_complete(hapd->iface, err);
+       }
+
+       return 0;
+}
+
+
+int ieee802_11_start_channel_switch(struct hostapd_data *hapd)
+{
+       struct hostapd_channel_data *channel;
+       int err = 1;
+
+       wpa_printf(MSG_DEBUG, "%s called", __func__);
+       channel = hostapd_dfs_get_valid_channel(hapd);
+       if (channel) {
+               hapd->iconf->channel = channel->chan;
+               hapd->iface->freq = channel->freq;
+               err = 0;
+       }
+
+       hapd->driver->stop_ap(hapd->drv_priv);
+
+       hostapd_setup_interface_complete(hapd->iface, err);
+       return 0;
+}
diff --git a/src/ap/dfs.h b/src/ap/dfs.h
new file mode 100644 (file)
index 0000000..3a4e875
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * DFS - Dynamic Frequency Selection
+ * Copyright (c) 2002-2013, Jouni Malinen <j@w1.fi>
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+#ifndef DFS_H
+#define DFS_H
+
+struct hostapd_channel_data * hostapd_dfs_get_valid_channel(
+       struct hostapd_data *hapd);
+int ieee802_11_complete_cac(struct hostapd_data *hapd, int success, int freq);
+int ieee802_11_set_dfs_state(struct hostapd_data *hapd, int freq, u32 state);
+int ieee802_11_start_channel_switch(struct hostapd_data *hapd);
+int hostapd_handle_dfs(struct hostapd_data *hapd);
+
+#endif /* DFS_H */
index d6bc98d0791ed7ccb7a6faecfb99a8c4f992b9f7..6fa83c927a542873a000cb7cffaba0bcb21c7e7c 100644 (file)
@@ -29,6 +29,7 @@
 #include "ap_drv_ops.h"
 #include "ap_config.h"
 #include "hw_features.h"
+#include "dfs.h"
 
 
 int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr,
@@ -785,6 +786,61 @@ static void hostapd_event_get_survey(struct hostapd_data *hapd,
 }
 
 
+#ifdef NEED_AP_MLME
+
+static void hostapd_event_dfs_radar_detected(struct hostapd_data *hapd,
+                                            struct dfs_event *radar)
+{
+       int res;
+
+       wpa_printf(MSG_DEBUG, "DFS radar detected on %d MHz", radar->freq);
+
+       if (!hapd->iconf->ieee80211h)
+               return;
+
+       /* mark radar frequency as invalid */
+       res = ieee802_11_set_dfs_state(hapd, radar->freq,
+                                      HOSTAPD_CHAN_DFS_UNAVAILABLE);
+
+       /* other frequency, just mark it and return. */
+       if (hapd->iface->freq != radar->freq)
+               return;
+
+       /* we are working on non-DFS channel - skip event */
+       if (res == 0)
+               return;
+
+       /* radar detected while operating, switch the channel. */
+       ieee802_11_start_channel_switch(hapd);
+}
+
+
+static void hostapd_event_dfs_cac_finished(struct hostapd_data *hapd,
+                                          struct dfs_event *radar)
+{
+       wpa_printf(MSG_DEBUG, "DFS CAC finished on %d MHz", radar->freq);
+       ieee802_11_complete_cac(hapd, 1, radar->freq);
+}
+
+
+static void hostapd_event_dfs_cac_aborted(struct hostapd_data *hapd,
+                                         struct dfs_event *radar)
+{
+       wpa_printf(MSG_DEBUG, "DFS CAC aborted on %d MHz", radar->freq);
+       ieee802_11_complete_cac(hapd, 0, radar->freq);
+}
+
+
+static void hostapd_event_dfs_nop_finished(struct hostapd_data *hapd,
+                                          struct dfs_event *radar)
+{
+       wpa_printf(MSG_DEBUG, "DFS NOP finished on %d MHz", radar->freq);
+       ieee802_11_set_dfs_state(hapd, radar->freq, HOSTAPD_CHAN_DFS_USABLE);
+}
+
+#endif /* NEED_AP_MLME */
+
+
 void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
                          union wpa_event_data *data)
 {
@@ -929,6 +985,34 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
        case EVENT_SURVEY:
                hostapd_event_get_survey(hapd, &data->survey_results);
                break;
+#ifdef NEED_AP_MLME
+       case EVENT_DFS_RADAR_DETECTED:
+               if (!data)
+                       break;
+               hostapd_event_dfs_radar_detected(hapd, &data->dfs_event);
+               break;
+       case EVENT_DFS_CAC_FINISHED:
+               if (!data)
+                       break;
+               hostapd_event_dfs_cac_finished(hapd, &data->dfs_event);
+               break;
+       case EVENT_DFS_CAC_ABORTED:
+               if (!data)
+                       break;
+               hostapd_event_dfs_cac_aborted(hapd, &data->dfs_event);
+               break;
+       case EVENT_DFS_NOP_FINISHED:
+               if (!data)
+                       break;
+               hostapd_event_dfs_nop_finished(hapd, &data->dfs_event);
+               break;
+       case EVENT_CHANNEL_LIST_CHANGED:
+               /* channel list changed (regulatory?), update channel list */
+               /* TODO: check this. hostapd_get_hw_features() initializes
+                * too much stuff. */
+               /* hostapd_get_hw_features(hapd->iface); */
+               break;
+#endif /* NEED_AP_MLME */
        default:
                wpa_printf(MSG_DEBUG, "Unknown event %d", event);
                break;
index 575ef2ad27eab95e5a42a9da349daf8bcd717c3a..3bca385ae7ba642fbe995a461f6145964b35fd7e 100644 (file)
@@ -32,6 +32,7 @@
 #include "ap_config.h"
 #include "p2p_hostapd.h"
 #include "gas_serv.h"
+#include "dfs.h"
 
 
 static int hostapd_flush_old_stations(struct hostapd_data *hapd, u16 reason);
@@ -931,6 +932,9 @@ static int setup_interface(struct hostapd_iface *iface)
                                   "be completed in a callback");
                        return 0;
                }
+
+               if (iface->conf->ieee80211h)
+                       wpa_printf(MSG_DEBUG, "DFS support is enabled");
        }
        return hostapd_setup_interface_complete(iface, 0);
 }
@@ -950,12 +954,23 @@ int hostapd_setup_interface_complete(struct hostapd_iface *iface, int err)
 
        wpa_printf(MSG_DEBUG, "Completing interface initialization");
        if (hapd->iconf->channel) {
+#ifdef NEED_AP_MLME
+               int res;
+#endif /* NEED_AP_MLME */
+
                iface->freq = hostapd_hw_get_freq(hapd, hapd->iconf->channel);
                wpa_printf(MSG_DEBUG, "Mode: %s  Channel: %d  "
                           "Frequency: %d MHz",
                           hostapd_hw_mode_txt(hapd->iconf->hw_mode),
                           hapd->iconf->channel, iface->freq);
 
+#ifdef NEED_AP_MLME
+               /* Check DFS */
+               res = hostapd_handle_dfs(hapd);
+               if (res <= 0)
+                       return res;
+#endif /* NEED_AP_MLME */
+
                if (hostapd_set_freq(hapd, hapd->iconf->hw_mode, iface->freq,
                                     hapd->iconf->channel,
                                     hapd->iconf->ieee80211n,
index 8a239f49f3df583abce93566ebc4c5f6a90ad35e..7e351d784757c45810637b04803ddefae87034e1 100644 (file)
@@ -45,6 +45,36 @@ void hostapd_free_hw_features(struct hostapd_hw_modes *hw_features,
 }
 
 
+#ifndef CONFIG_NO_STDOUT_DEBUG
+static char * dfs_info(struct hostapd_channel_data *chan)
+{
+       static char info[256];
+       char *state;
+
+       switch (chan->flag & HOSTAPD_CHAN_DFS_MASK) {
+       case HOSTAPD_CHAN_DFS_UNKNOWN:
+               state = "unknown";
+               break;
+       case HOSTAPD_CHAN_DFS_USABLE:
+               state = "usable";
+               break;
+       case HOSTAPD_CHAN_DFS_UNAVAILABLE:
+               state = "unavailable";
+               break;
+       case HOSTAPD_CHAN_DFS_AVAILABLE:
+               state = "available";
+               break;
+       default:
+               return "";
+       }
+       os_snprintf(info, sizeof(info), " (DFS state = %s)", state);
+       info[sizeof(info) - 1] = '\0';
+
+       return info;
+}
+#endif /* CONFIG_NO_STDOUT_DEBUG */
+
+
 int hostapd_get_hw_features(struct hostapd_iface *iface)
 {
        struct hostapd_data *hapd = iface->bss[0];
@@ -71,30 +101,40 @@ int hostapd_get_hw_features(struct hostapd_iface *iface)
 
        for (i = 0; i < num_modes; i++) {
                struct hostapd_hw_modes *feature = &modes[i];
+               int dfs_enabled = hapd->iconf->ieee80211h &&
+                       (iface->drv_flags & WPA_DRIVER_FLAGS_RADAR);
+
                /* set flag for channels we can use in current regulatory
                 * domain */
                for (j = 0; j < feature->num_channels; j++) {
+                       int dfs = 0;
+
                        /*
                         * Disable all channels that are marked not to allow
-                        * IBSS operation or active scanning. In addition,
-                        * disable all channels that require radar detection,
-                        * since that (in addition to full DFS) is not yet
-                        * supported.
+                        * IBSS operation or active scanning.
+                        * Use radar channels only if the driver supports DFS.
                         */
-                       if (feature->channels[j].flag &
-                           (HOSTAPD_CHAN_NO_IBSS |
-                            HOSTAPD_CHAN_PASSIVE_SCAN |
-                            HOSTAPD_CHAN_RADAR))
+                       if ((feature->channels[j].flag &
+                            HOSTAPD_CHAN_RADAR) && dfs_enabled) {
+                               dfs = 1;
+                       } else if (feature->channels[j].flag &
+                                  (HOSTAPD_CHAN_NO_IBSS |
+                                   HOSTAPD_CHAN_PASSIVE_SCAN |
+                                   HOSTAPD_CHAN_RADAR)) {
                                feature->channels[j].flag |=
                                        HOSTAPD_CHAN_DISABLED;
+                       }
+
                        if (feature->channels[j].flag & HOSTAPD_CHAN_DISABLED)
                                continue;
+
                        wpa_printf(MSG_MSGDUMP, "Allowed channel: mode=%d "
-                                  "chan=%d freq=%d MHz max_tx_power=%d dBm",
+                                  "chan=%d freq=%d MHz max_tx_power=%d dBm%s",
                                   feature->mode,
                                   feature->channels[j].chan,
                                   feature->channels[j].freq,
-                                  feature->channels[j].max_tx_power);
+                                  feature->channels[j].max_tx_power,
+                                  dfs ? dfs_info(&feature->channels[j]) : "");
                }
        }
 
@@ -849,3 +889,21 @@ int hostapd_hw_get_channel(struct hostapd_data *hapd, int freq)
 
        return 0;
 }
+
+
+int hostapd_hw_get_channel_flag(struct hostapd_data *hapd, int chan)
+{
+       int i;
+
+       if (!hapd->iface->current_mode)
+               return 0;
+
+       for (i = 0; i < hapd->iface->current_mode->num_channels; i++) {
+               struct hostapd_channel_data *ch =
+                       &hapd->iface->current_mode->channels[i];
+               if (ch->chan == chan)
+                       return ch->flag;
+       }
+
+       return 0;
+}
index abadcd137db1e8b66b1bf9ff8be4177f8ffff0e7..b8e287b824b89df5e6db72a7046fdedbd89fc56e 100644 (file)
@@ -28,6 +28,7 @@ int hostapd_hw_get_channel(struct hostapd_data *hapd, int freq);
 int hostapd_check_ht_capab(struct hostapd_iface *iface);
 int hostapd_prepare_rates(struct hostapd_iface *iface,
                          struct hostapd_hw_modes *mode);
+int hostapd_hw_get_channel_flag(struct hostapd_data *hapd, int chan);
 #else /* NEED_AP_MLME */
 static inline void
 hostapd_free_hw_features(struct hostapd_hw_modes *hw_features,
index 569861962b4c8f0ad78c695a9ff908d16db8bd69..7c442413517432bc3669437317adf3ef7b43bd76 100644 (file)
@@ -772,6 +772,7 @@ OBJS += ../src/ap/wmm.o
 OBJS += ../src/ap/ap_list.o
 OBJS += ../src/ap/ieee802_11.o
 OBJS += ../src/ap/hw_features.o
+OBJS += ../src/ap/dfs.o
 CFLAGS += -DNEED_AP_MLME
 endif
 ifdef CONFIG_WPS