Permalink
Browse files

[Linux] Improved scanning for access points to include MAC address, c…

…hannel and signal strength.

Includes general improvements and readability fixups in NetworkLinux.cpp.
  • Loading branch information...
1 parent 036162c commit 23edb86de8bf2f4dfb7554f99e5022c0f91b2180 @garbear committed Jul 25, 2011
Showing with 148 additions and 37 deletions.
  1. +49 −0 xbmc/network/Network.cpp
  2. +20 −8 xbmc/network/Network.h
  3. +79 −29 xbmc/network/linux/NetworkLinux.cpp
View
49 xbmc/network/Network.cpp
@@ -91,6 +91,55 @@ bool in_ether (const char *bufp, unsigned char *addr)
return true;
}
+/*!
+ \brief Gets the quality, normalized as a percentage, of the network access point
+ \return The quality as an integer between 0 and 100
+ */
+int NetworkAccessPoint::getQuality() const
+{
+ // Cisco dBm lookup table (partially nonlinear)
+ // Source: "Converting Signal Strength Percentage to dBm Values, 2002"
+ int quality;
+ if (m_dBm >= -10) quality = 100;
+ else if (m_dBm >= -20) quality = 85 + (m_dBm + 20);
+ else if (m_dBm >= -30) quality = 77 + (m_dBm + 30);
+ else if (m_dBm >= -60) quality = 48 + (m_dBm + 60);
+ else if (m_dBm >= -98) quality = 13 + (m_dBm + 98);
+ else if (m_dBm >= -112) quality = 1 + (m_dBm + 112);
+ else quality = 0;
+ return quality;
+}
+
+/*!
+ \brief Translates a 802.11a+g frequency into the corresponding channel
+ \param frequency The frequency of the channel in units of Hertz
+ \return The channel as an integer between 1 and 14 (802.11b) or between 36 and 165 (802.11a)
+ */
+int NetworkAccessPoint::FreqToChannel(float frequency)
+{
+ int IEEE80211Freq[] = {2412, 2417, 2422, 2427, 2432,
+ 2437, 2442, 2447, 2452, 2457,
+ 2462, 2467, 2472, 2484,
+ 5180, 5200, 5210, 5220, 5240, 5250,
+ 5260, 5280, 5290, 5300, 5320,
+ 5745, 5760, 5765, 5785, 5800, 5805, 5825};
+ int IEEE80211Ch[] = { 1, 2, 3, 4, 5,
+ 6, 7, 8, 9, 10,
+ 11, 12, 13, 14,
+ 36, 40, 42, 44, 48, 50,
+ 52, 56, 58, 60, 64,
+ 149, 152, 153, 157, 160, 161, 165};
+ // Round frequency to the nearest MHz
+ int mod_chan = (int)(frequency / 1000000 + 0.5f);
+ for (unsigned int i = 0; i < sizeof(IEEE80211Freq) / sizeof(int); ++i)
+ {
+ if (IEEE80211Freq[i] == mod_chan)
+ return IEEE80211Ch[i];
+ }
+ return 0; // unknown
+}
+
+
CNetwork::CNetwork()
{
g_application.getApplicationMessenger().NetworkMessage(SERVICES_UP, 0);
View
28 xbmc/network/Network.h
@@ -31,21 +31,33 @@ enum NetworkAssignment { NETWORK_DASH = 0, NETWORK_DHCP = 1, NETWORK_STATIC = 2,
class NetworkAccessPoint
{
public:
- NetworkAccessPoint(CStdString& essId, int quality, EncMode encryption)
+ NetworkAccessPoint(CStdString& essId, CStdString& macAddress, int signalStrength, EncMode encryption, int channel = 0)
@jmarshallnz
jmarshallnz Mar 30, 2012

These should be const CStdString &

{
m_essId = essId;
- m_quality = quality;
+ m_macAddress = macAddress;
+ m_dBm = signalStrength;
m_encryptionMode = encryption;
+ m_channel = channel;
}
- CStdString getEssId() { return m_essId; }
- int getQuality() { return m_quality; }
- EncMode getEncryptionMode() { return m_encryptionMode; }
+ const CStdString &getEssId() const { return m_essId; }
+ const CStdString &getMacAddress() const { return m_macAddress; }
+ int getSignalStrength() const { return m_dBm; }
+ EncMode getEncryptionMode() const { return m_encryptionMode; }
+ int getChannel() const { return m_channel; }
+
+ /* Returns the quality, normalized as a percentage, of the network access point */
+ int getQuality() const;
+
+ /* Translates a 802.11a+g frequency into the corresponding channel */
+ static int FreqToChannel(float frequency);
private:
- CStdString m_essId;
- int m_quality;
- EncMode m_encryptionMode;
+ CStdString m_essId;
+ CStdString m_macAddress;
+ int m_dBm;
+ EncMode m_encryptionMode;
+ int m_channel;
};
class CNetworkInterface
View
108 xbmc/network/linux/NetworkLinux.cpp
@@ -467,7 +467,7 @@ std::vector<NetworkAccessPoint> CNetworkInterfaceLinux::GetAccessPoints(void)
return result;
#if defined(TARGET_LINUX)
- // Query the wireless extentsions version number. It will help us when we
+ // Query the wireless extension's version number. It will help us when we
// parse the resulting events
struct iwreq iwr;
char rangebuffer[sizeof(iw_range) * 2]; /* Large enough */
@@ -490,14 +490,18 @@ std::vector<NetworkAccessPoint> CNetworkInterfaceLinux::GetAccessPoints(void)
strncpy(iwr.ifr_name, GetName().c_str(), IFNAMSIZ);
if (ioctl(m_network->GetSocket(), SIOCSIWSCAN, &iwr) < 0)
{
- CLog::Log(LOGWARNING, "Cannot initiate wireless scan: ioctl[SIOCSIWSCAN]: %s", strerror(errno));
+ // Triggering scanning is a privileged operation (root only)
+ if (errno == EPERM)
+ CLog::Log(LOGWARNING, "Cannot initiate wireless scan: ioctl[SIOCSIWSCAN]: %s. Try running with sudo.", strerror(errno));
+ else
+ CLog::Log(LOGWARNING, "Cannot initiate wireless scan: ioctl[SIOCSIWSCAN]: %s", strerror(errno));
return result;
}
// Get the results of the scanning. Three scenarios:
// 1. There's not enough room in the result buffer (E2BIG)
// 2. The scanning is not complete (EAGAIN) and we need to try again. We cap this with 15 seconds.
- // 3. Were'e good.
+ // 3. We're good.
int duration = 0; // ms
unsigned char* res_buf = NULL;
int res_buf_len = IW_SCAN_MAX_DATA;
@@ -541,53 +545,78 @@ std::vector<NetworkAccessPoint> CNetworkInterfaceLinux::GetAccessPoints(void)
}
}
- size_t len = iwr.u.data.length;
- char* pos = (char *) res_buf;
- char* end = (char *) res_buf + len;
- char* custom;
- struct iw_event iwe_buf, *iwe = &iwe_buf;
+ size_t len = iwr.u.data.length; // total length of the wireless events from the scan results
+ unsigned char* pos = res_buf; // pointer to the current event (about 10 per wireless network)
+ unsigned char* end = res_buf + len; // marks the end of the scan results
+ unsigned char* custom; // pointer to the event payload
+ struct iw_event iwe_buf, *iwe = &iwe_buf; // buffer to hold individual events
CStdString essId;
- int quality = 0;
+ CStdString macAddress;
+ int signalLevel = 0;
EncMode encryption = ENC_NONE;
- bool first = true;
+ int channel = 0;
while (pos + IW_EV_LCP_LEN <= end)
{
/* Event data may be unaligned, so make a local, aligned copy
* before processing. */
+
+ // copy event prefix (size of event minus IOCTL fixed payload)
memcpy(&iwe_buf, pos, IW_EV_LCP_LEN);
if (iwe->len <= IW_EV_LCP_LEN)
break;
+ // if the payload is nontrivial (i.e. > 16 octets) assume it comes after a pointer
custom = pos + IW_EV_POINT_LEN;
if (range->we_version_compiled > 18 &&
(iwe->cmd == SIOCGIWESSID ||
iwe->cmd == SIOCGIWENCODE ||
iwe->cmd == IWEVGENIE ||
iwe->cmd == IWEVCUSTOM))
{
- /* Wireless extentsions v19 removed the pointer from struct iw_point */
- char *dpos = (char *) &iwe_buf.u.data.length;
- int dlen = dpos - (char *) &iwe_buf;
- memcpy(dpos, pos + IW_EV_LCP_LEN, sizeof(struct iw_event) - dlen);
+ /* Wireless extensions v19 removed the pointer from struct iw_point */
+ char *data_pos = (char *) &iwe_buf.u.data.length;
+ int data_len = data_pos - (char *) &iwe_buf;
+ memcpy(data_pos, pos + IW_EV_LCP_LEN, sizeof(struct iw_event) - data_len);
}
else
{
+ // copy the rest of the event and point custom toward the payload offset
memcpy(&iwe_buf, pos, sizeof(struct iw_event));
custom += IW_EV_POINT_OFF;
}
+ // Interpret the payload based on event type. Each access point generates ~12 different events
switch (iwe->cmd)
{
+ // Get access point MAC addresses
case SIOCGIWAP:
- if (first)
- first = false;
- else
- result.push_back(NetworkAccessPoint(essId, quality, encryption));
- encryption = ENC_NONE;
+ {
+ // This even marks a new access point, so push back the old information
+ if (!macAddress.IsEmpty())
+ result.push_back(NetworkAccessPoint(essId, macAddress, signalLevel, encryption, channel));
+ unsigned char* mac = (unsigned char*)iwe->u.ap_addr.sa_data;
+ // macAddress is big-endian, write in byte chunks
+ macAddress.Format("%02x-%02x-%02x-%02x-%02x-%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+ // Reset the remaining fields
+ essId = "";
+ encryption = ENC_NONE;
+ signalLevel = 0;
+ channel = 0;
break;
+ }
+ // Get operation mode
+ case SIOCGIWMODE:
+ {
+ // Ignore Ad-Hoc networks (1 is the magic number for this)
+ if (iwe->u.mode == 1)
+ macAddress = "";
+ break;
+ }
+
+ // Get ESSID
case SIOCGIWESSID:
{
char essid[IW_ESSID_MAX_SIZE+1];
@@ -600,21 +629,42 @@ std::vector<NetworkAccessPoint> CNetworkInterfaceLinux::GetAccessPoints(void)
break;
}
+ // Quality part of statistics
case IWEVQUAL:
- quality = iwe->u.qual.qual;
- break;
+ {
+ // u.qual.qual is scaled to a vendor-specific RSSI_Max, so use u.qual.level
+ signalLevel = iwe->u.qual.level - 0x100; // and remember we use 8-bit arithmetic
+ break;
+ }
+
+ // Get channel/frequency (Hz)
+ // This gets called twice per network, what's the difference between the two?
+ case SIOCGIWFREQ:
+ {
+ float freq = ((float)iwe->u.freq.m) * pow(10, iwe->u.freq.e);
+ if (freq > 1000)
+ channel = NetworkAccessPoint::FreqToChannel(freq);
+ else
+ channel = (int)freq; // Some drivers report channel instead of frequency
+ break;
+ }
+ // Get encoding token & mode
case SIOCGIWENCODE:
- if (!(iwe->u.data.flags & IW_ENCODE_DISABLED) && encryption == ENC_NONE)
- encryption = ENC_WEP;
- break;
+ {
+ if (!(iwe->u.data.flags & IW_ENCODE_DISABLED) && encryption == ENC_NONE)
+ encryption = ENC_WEP;
+ break;
+ }
+ // Generic IEEE 802.11 information element (IE) for WPA, RSN, WMM, ...
case IWEVGENIE:
{
int offset = 0;
- while (offset <= iwe_buf.u.data.length)
+ // Loop on each IE, each IE is minimum 2 bytes
+ while (offset <= (iwe_buf.u.data.length - 2))
{
- switch ((unsigned char)custom[offset])
+ switch (custom[offset])
{
case 0xdd: /* WPA1 */
if (encryption != ENC_WPA2)
@@ -623,7 +673,7 @@ std::vector<NetworkAccessPoint> CNetworkInterfaceLinux::GetAccessPoints(void)
case 0x30: /* WPA2 */
encryption = ENC_WPA2;
}
-
+ // Skip over this IE to the next one in the list
offset += custom[offset+1] + 2;
}
}
@@ -632,8 +682,8 @@ std::vector<NetworkAccessPoint> CNetworkInterfaceLinux::GetAccessPoints(void)
pos += iwe->len;
}
- if (!first)
- result.push_back(NetworkAccessPoint(essId, quality, encryption));
+ if (!essId.IsEmpty())
+ result.push_back(NetworkAccessPoint(essId, macAddress, signalLevel, encryption, channel));
free(res_buf);
res_buf = NULL;

0 comments on commit 23edb86

Please sign in to comment.