wifiscan.cpp
Go to the documentation of this file.
00001 /*
00002  The WifiScan class allows WiFi scans using iwlib.h.
00003  Copyright (C) 2013  Rafael Berkvens rafael.berkvens@ua.ac.be
00004 
00005  This program is free software: you can redistribute it and/or modify
00006  it under the terms of the GNU General Public License as published by
00007  the Free Software Foundation, either version 3 of the License, or
00008  (at your option) any later version.
00009 
00010  This program is distributed in the hope that it will be useful,
00011  but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  GNU General Public License for more details.
00014 
00015  You should have received a copy of the GNU General Public License
00016  along with this program.  If not, see <http://www.gnu.org/licenses/>.
00017  */
00018 
00019 #include "wifiscan.h"
00020 
00021 struct wireless_scan * WifiScan::iw_process_scanning_token(
00022     struct iw_event * event, struct wireless_scan * wscan)
00023 {
00024   struct wireless_scan * oldwscan;
00025 
00026   /* Now, let's decode the event */
00027   switch (event->cmd)
00028   {
00029     case SIOCGIWAP:
00030       /* New cell description. Allocate new cell descriptor, zero it. */
00031       oldwscan = wscan;
00032       wscan = (struct wireless_scan *)malloc(sizeof(struct wireless_scan));
00033       if (wscan == NULL)
00034         return (wscan);
00035       /* Link at the end of the list */
00036       if (oldwscan != NULL)
00037         oldwscan->next = wscan;
00038 
00039       /* Reset it */
00040       bzero(wscan, sizeof(struct wireless_scan));
00041 
00042       /* Save cell identifier */
00043       wscan->has_ap_addr = 1;
00044       memcpy(&(wscan->ap_addr), &(event->u.ap_addr), sizeof(sockaddr));
00045       break;
00046     case SIOCGIWNWID:
00047       wscan->b.has_nwid = 1;
00048       memcpy(&(wscan->b.nwid), &(event->u.nwid), sizeof(iwparam));
00049       break;
00050     case SIOCGIWFREQ:
00051       wscan->b.has_freq = 1;
00052       wscan->b.freq = iw_freq2float(&(event->u.freq));
00053       wscan->b.freq_flags = event->u.freq.flags;
00054       break;
00055     case SIOCGIWMODE:
00056       wscan->b.mode = event->u.mode;
00057       if ((wscan->b.mode < IW_NUM_OPER_MODE) && (wscan->b.mode >= 0))
00058         wscan->b.has_mode = 1;
00059       break;
00060     case SIOCGIWESSID:
00061       wscan->b.has_essid = 1;
00062       wscan->b.essid_on = event->u.data.flags;
00063       memset(wscan->b.essid, '\0', IW_ESSID_MAX_SIZE + 1);
00064       if ((event->u.essid.pointer) && (event->u.essid.length))
00065         memcpy(wscan->b.essid, event->u.essid.pointer, event->u.essid.length);
00066       break;
00067     case SIOCGIWENCODE:
00068       wscan->b.has_key = 1;
00069       wscan->b.key_size = event->u.data.length;
00070       wscan->b.key_flags = event->u.data.flags;
00071       if (event->u.data.pointer)
00072         memcpy(wscan->b.key, event->u.essid.pointer, event->u.data.length);
00073       else
00074         wscan->b.key_flags |= IW_ENCODE_NOKEY;
00075       break;
00076     case IWEVQUAL:
00077       /* We don't get complete stats, only qual */
00078       wscan->has_stats = 1;
00079       memcpy(&wscan->stats.qual, &event->u.qual, sizeof(struct iw_quality));
00080       break;
00081     case SIOCGIWRATE:
00082       /* Scan may return a list of bitrates. As we have space for only
00083        * a single bitrate, we only keep the largest one. */
00084       if ((!wscan->has_maxbitrate) ||
00085           (event->u.bitrate.value > wscan->maxbitrate.value))
00086       {
00087         wscan->has_maxbitrate = 1;
00088         memcpy(&(wscan->maxbitrate), &(event->u.bitrate), sizeof(iwparam));
00089       }
00090     case IWEVCUSTOM:
00091       /* How can we deal with those sanely ? Jean II */
00092     default:
00093       break;
00094   } /* switch(event->cmd) */
00095 
00096   return (wscan);
00097 }
00098 
00099 int WifiScan::scan_channels(wireless_scan_head * context)
00100 {
00101   struct iwreq wrq;
00102   struct iw_scan_req scanopt; // Options for 'set'.
00103   int scanflags = 0; // Flags for scan.
00104   unsigned char * buffer = NULL; // Results.
00105   int buflen = IW_SCAN_MAX_DATA; // Min for compat WE < 17.
00106   struct iw_range range;
00107   int has_range;
00108   struct timeval tv; // Select timeout.
00109   int timeout = 15000000; // 15 s.
00110 
00111   /* Get and check range stuff.
00112    * I would like to update this function to a class method, so that this range
00113    * info can be a private variable and must not be constantly checked.
00114    * Also, the function should be void and use throws instead of returns to
00115    * indicate errors.
00116    */
00117   has_range = (iw_get_range_info(socket_, interface_, &range) >= 0);
00118   if ((!has_range) || (range.we_version_compiled < 14))
00119   {
00120     std::cerr << interface_ << "does not support scanning." << std::endl;
00121     return SCAN_CHANNELS_NO_SCAN_SUPPORT;
00122   }
00123 
00124   /* Initialize timeout value -> 250 ms between set and first get.
00125    * This can potentially be increased in speed.
00126    */
00127   tv.tv_sec = 0;
00128   tv.tv_usec = 250000;
00129 
00130   /* Clean up set arguments. */
00131   memset(&scanopt, 0, sizeof(scanopt));
00132 
00133   /* Set options. */
00134   double channel_to_freq;
00135   iw_freq freq;
00136   for (int i = 0; i < channels_.size(); i++)
00137   {
00138     iw_channel_to_freq(channels_[i], &channel_to_freq, &range);
00139     iw_float2freq(channel_to_freq, &freq);
00140     scanopt.channel_list[i] = freq;
00141   }
00142   scanopt.num_channels = channels_.size();
00143 
00144   wrq.u.data.pointer = (caddr_t)&scanopt;
00145   wrq.u.data.length = sizeof(scanopt);
00146   wrq.u.data.flags = scanflags;
00147 
00148   /* Initiate scanning. */
00149   if (iw_set_ext(socket_, interface_, SIOCSIWSCAN, &wrq) < 0)
00150   {
00151     if ((errno != EPERM) || (scanflags != 0))
00152     {
00153       std::cerr << interface_ << "does not support scanning: "
00154           << strerror(errno) << std::endl;
00155       return SCAN_CHANNELS_NO_SCAN_SUPPORT;
00156     }
00157     tv.tv_usec = 0;
00158   }
00159   usleep(tv.tv_usec);
00160   timeout -= tv.tv_usec;
00161 
00162   while (true)
00163   {
00164     fd_set rfds;
00165     int last_fd;
00166     int ret;
00167 
00168     FD_ZERO(&rfds);
00169     last_fd = -1;
00170 
00171     ret = select(last_fd + 1, &rfds, NULL, NULL, &tv);
00172     if (ret < 0)
00173     {
00174       if (errno == EAGAIN || errno == EINTR)
00175         continue;
00176       std::cerr << "Unhandled signal, exiting..." << std::endl;
00177       return SCAN_CHANNELS_UNHANDLED_SIGNAL;
00178     }
00179 
00180     if (ret == 0)
00181     {
00182       unsigned char * newbuf;
00183 
00184       realloc:
00185       /* Allocate or reallocate the buffer. */
00186       newbuf = (unsigned char *)realloc(buffer, buflen);
00187       if (newbuf == NULL)
00188       {
00189         if (buffer)
00190           free(buffer);
00191         std::cerr << "Allocation failed." << std::endl;
00192         return SCAN_CHANNELS_ALLOCATION_FAILED;
00193       }
00194       buffer = newbuf;
00195 
00196       /* Try to read the results. */
00197       wrq.u.data.pointer = buffer;
00198       wrq.u.data.flags = 0;
00199       wrq.u.data.length = buflen;
00200       if (iw_get_ext(socket_, interface_, SIOCGIWSCAN, &wrq) < 0)
00201       {
00202         /* Check if buffer was too small (WE-17 only) */
00203         if ((errno == E2BIG) && (range.we_version_compiled > 16)
00204             && (buflen < 0xFFFF))
00205         {
00206           /* Some driver may return very large scan results, either
00207            * because there are many cells, or because they have many
00208            * large elements in cells (like IWEVCUSTOM). Most will
00209            * only need the regular sized buffer. We now use a dynamic
00210            * allocation of the buffer to satisfy everybody. Of course,
00211            * as we don't know in advance the size of the array, we try
00212            * various increasing sizes. Jean II */
00213 
00214           /* Check if the driver gave us any hints. */
00215           if (wrq.u.data.length > buflen)
00216             buflen = wrq.u.data.length;
00217           else
00218             buflen *= 2;
00219 
00220           /* wrq.u.data.length is 16 bits so max size is 65535 */
00221           if (buflen > 0xFFFF)
00222             buflen = 0xFFFF;
00223 
00224           /* Try again */
00225           goto realloc;
00226         }
00227 
00228         /* Check if results not available yet */
00229         if (errno == EAGAIN)
00230         {
00231           /* Restart timer for only 100ms*/
00232           tv.tv_sec = 0;
00233           tv.tv_usec = 100000;
00234           timeout -= tv.tv_usec;
00235           if (timeout > 0)
00236           {
00237             usleep(tv.tv_usec);
00238             continue; /* Try again later */
00239           }
00240         }
00241 
00242         /* Bad error. */
00243         free(buffer);
00244         std::cerr << interface_ << ": failed to read scan data: "
00245             << strerror(errno) << std::endl;
00246         return SCAN_CHANNELS_FAILED_TO_READ;
00247       }
00248       else
00249         break;
00250     }
00251   }
00252 
00253   if (wrq.u.data.length)
00254   {
00255     struct iw_event iwe;
00256     struct stream_descr stream;
00257     struct wireless_scan * wscan = NULL;
00258     struct iwscan_state state = { /*.ap_num =*/1, /*.val_index =*/0};
00259     int ret;
00260 
00261     iw_init_event_stream(&stream, (char *)buffer, wrq.u.data.length);
00262     /* This is dangerous, we may leak user data... */
00263     context->result = NULL;
00264 
00265     /* Look every token */
00266     do
00267     {
00268       /* Extract an event and print it. */
00269       ret = iw_extract_event_stream(&stream, &iwe, range.we_version_compiled);
00270       if (ret > 0)
00271       {
00272         /* Convert to wireless_scan struct */
00273         wscan = iw_process_scanning_token(&iwe, wscan);
00274         /* Check problems. */
00275         if (wscan == NULL)
00276         {
00277           free(buffer);
00278           errno = ENOMEM;
00279           return SCAN_CHANNELS_PROBLEMS_PROCESSING;
00280         }
00281         /* Save head of list */
00282         if (context->result == NULL)
00283           context->result = wscan;
00284       }
00285     } while (ret > 0);
00286 
00287     std::cout << interface_ << ": scan completed" << std::endl;
00288   }
00289   else
00290     std::cout << interface_ << ": no scan results" << std::endl;
00291 
00292   free(buffer);
00293   return 0;
00294 }
00295 
00296 bool WifiScan::DeviceAddressCompare::operator ()(const std::string &lhs,
00297                                                  const std::string &rhs) const
00298                                                  {
00299   return lhs.substr(0, 10) < rhs.substr(0, 10);
00300 }
00301 
00302 double WifiScan::MeanRSSI(const std::vector<double> &RSSIValues)
00303 {
00304   double total = 0.0;
00305   for (std::vector<double>::const_iterator it = RSSIValues.begin();
00306       it != RSSIValues.end(); it++)
00307   {
00308     total += *it;
00309   }
00310   return total / RSSIValues.size();
00311 }
00312 
00313 WifiScan::WifiScan(std::vector<int> channels, std::string interface)
00314 {
00315   interface_ = new char[interface.length() + 1];
00316   std::strcpy(interface_, interface.c_str());
00317   channels_ = channels;
00318 
00319   if ((socket_ = iw_sockets_open()) < 0)
00320     throw WIFISCAN_ERROR_OPENING_IOCTL_SOCKET;
00321   kernel_version_ = iw_get_kernel_we_version();
00322   if (iw_get_range_info(socket_, interface_, &range_) < 0)
00323     throw WIFISCAN_ERROR_GETTING_RANGE_INFO;
00324 }
00325 
00326 WifiScan::~WifiScan()
00327 {
00328   iw_sockets_close(socket_);
00329 }
00330 
00331 void WifiScan::createFingerprint(ros::Publisher *pub)
00332 {
00333   wireless_scan_head scan_context;
00334 
00335   if (geteuid() != 0)
00336     ROS_FATAL_STREAM("uid: " << geteuid());
00337 
00338   if (scan_channels(&scan_context) < 0)
00339     throw WIFISCAN_ERROR_IN_IW_SCAN;
00340   if (scan_context.result != 0)
00341     ROS_DEBUG_STREAM("Fingerprint collected, publishing...");
00342 
00343   /* Loop over result, build fingerprint. */
00344   std::map<std::string, double, DeviceAddressCompare> fingerprint;
00345   std::map<std::string, std::vector<double> > devicesWithMoreSSIDs;
00346   for (wireless_scan *i = scan_context.result; i != 0; i = i->next)
00347   {
00348     /* Retrieve device address. */
00349     char address[128];
00350     snprintf(address, 128, "%02X%02X%02X%02X%02X%02X",
00351              (unsigned char)i->ap_addr.sa_data[0],
00352              (unsigned char)i->ap_addr.sa_data[1],
00353              (unsigned char)i->ap_addr.sa_data[2],
00354              (unsigned char)i->ap_addr.sa_data[3],
00355              (unsigned char)i->ap_addr.sa_data[4],
00356              (unsigned char)i->ap_addr.sa_data[5]);
00357 
00358     /* Retrieve RSSI */
00359     double dBm;
00360     if (i->stats.qual.updated & IW_QUAL_DBM)
00361     {
00362       dBm = i->stats.qual.level;
00363       if (i->stats.qual.level >= 64)
00364         dBm -= 0x100;
00365     }
00366     else if (i->stats.qual.updated & IW_QUAL_RCPI)
00367     {
00368       dBm = (i->stats.qual.level / 2.0) - 110.0;
00369     }
00370 
00371     /* Put address and RSSI in fingerprint if address is unique*/
00372     std::pair<std::map<std::string, double>::iterator, bool> ret;
00373     ret = fingerprint.insert(
00374         std::pair<std::string, double>(std::string(address), dBm));
00375     if (ret.second == false)
00376     {
00377       std::vector<double> RSSIValues;
00378       std::string newSSID = std::string(address);
00379       std::map<std::string, std::vector<double> >::iterator deviceWithMoreSSIDs;
00380       deviceWithMoreSSIDs = devicesWithMoreSSIDs.find(newSSID.substr(0, 10));
00381 
00382       if (deviceWithMoreSSIDs == devicesWithMoreSSIDs.end())
00383       {
00384         if (ret.first->first.substr(11, 2) == newSSID.substr(11, 2))
00385         {
00386           ROS_WARN_STREAM("Address not unique.");
00387         }
00388         else
00389         {
00390           RSSIValues.push_back(dBm);
00391           RSSIValues.push_back(ret.first->second);
00392           newSSID = newSSID.substr(0, 10);
00393           devicesWithMoreSSIDs.insert(
00394               std::pair<std::string, std::vector<double> >(newSSID,
00395                                                            RSSIValues));
00396           dBm = MeanRSSI(RSSIValues);
00397           fingerprint.erase(ret.first);
00398           fingerprint.insert(
00399               std::pair<std::string, double>(newSSID + "**", dBm));
00400         }
00401       }
00402       else
00403       {
00404         RSSIValues = deviceWithMoreSSIDs->second;
00405         RSSIValues.push_back(dBm);
00406         deviceWithMoreSSIDs->second = RSSIValues;
00407         dBm = MeanRSSI(RSSIValues);
00408         ret.first->second = dBm;
00409       }
00410     }
00411   }
00412 
00413   /* Create fingerprint message, publish message. */
00414   wifi_scan::Fingerprint fingerprint_message;
00415   fingerprint_message.list.clear();
00416   for (std::map<std::string, double>::iterator it = fingerprint.begin();
00417       it != fingerprint.end();
00418       it++)
00419   {
00420     wifi_scan::AddressRSSI address_rssi;
00421     address_rssi.address = it->first;
00422     address_rssi.rssi = it->second;
00423     ROS_DEBUG_STREAM(it->first << ", " << it->second);
00424     fingerprint_message.list.push_back(address_rssi);
00425   }
00426   fingerprint_message.header.stamp = ros::Time::now();
00427   pub->publish(fingerprint_message);
00428 }
00429 // kate: indent-mode cstyle; indent-width 2; replace-tabs on; 


wifi_scan
Author(s): Rafael Berkvens
autogenerated on Fri Aug 28 2015 13:41:23