Sunday, January 8, 2012

Using ioctl to gather Wifi information

For part of a project that I am working on, I needed a way to get status information about a wifi device on a linux machine. My first instinct was that I would just parse the output of the iwconfig command. However, iwconfig does not provide every piece of information that I wanted to gather, and one of my cohorts urged me not to resort to the ugliness that is parsing. Instead, he suggested that I look in to ioctl to gather the information that I needed.

I am constantly learning new things about operating systems, and I had never heard of ioctl before. It only took a quick Google search to realize how immensely powerful it is, and how well it suited my needs. Ioctl is a means of interacting with device drivers. You pass it a request code along with a pointer to memory.

Since I don't consider the automated gathering of Wifi signal strength to be that obscure of a task, I've decided to post my solution here on my blog. I'm surprised that I was unable to find it documented elsewhere.

There are a few libraries that you need to be sure to include. I believe I have read that wireless.h may exist elsewhere on other distributions, but every machine that my code is running on uses Ubuntu so I didn't look any further in to that.

//libraries necessary for wifi ioctl communication
#include <linux/wireless.h>
#include <sys/ioctl.h>

//struct to hold collected information
struct signalInfo {
    char mac[18];
    char ssid[33];
    int bitrate;
    int level;
};

For clarity, I also have a struct that I'm using to hold all of the data I collect.
Here is the function in which the actual collection occurs. The iwname parameter is the name of the wireless network interface.

int getSignalInfo(signalInfo *sigInfo, char *iwname){
    iwreq req;
    strcpy(req.ifr_name, iwname);

    iw_statistics *stats;

    //have to use a socket for ioctl
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    //make room for the iw_statistics object
    req.u.data.pointer = (iw_statistics *)malloc(sizeof(iw_statistics));
    req.u.data.length = sizeof(iw_statistics);

    //this will gather the signal strength
    if(ioctl(sockfd, SIOCGIWSTATS, &req) == -1){
        //die with error, invalid interface
        fprintf(stderr, "Invalid interface.\n");
        return(-1);
    }
    else if(((iw_statistics *)req.u.data.pointer)->qual.updated & IW_QUAL_DBM){
        //signal is measured in dBm and is valid for us to use
        sigInfo->level=((iw_statistics *)req.u.data.pointer)->qual.level - 256;
    }

    //SIOCGIWESSID for ssid
    char buffer[32];
    memset(buffer, 0, 32);
    req.u.essid.pointer = buffer;
    req.u.essid.length = 32;
    //this will gather the SSID of the connected network
    if(ioctl(sockfd, SIOCGIWESSID, &req) == -1){
        //die with error, invalid interface
        return(-1);
    }
    else{
        memcpy(&sigInfo->ssid, req.u.essid.pointer, req.u.essid.length);
        memset(&sigInfo->ssid[req.u.essid.length],0,1);
    }

    //SIOCGIWRATE for bits/sec (convert to mbit)
    int bitrate=-1;
    //this will get the bitrate of the link
    if(ioctl(sockfd, SIOCGIWRATE, &req) == -1){
        fprintf(stderr, "bitratefail");
        return(-1);
    }else{
        memcpy(&bitrate, &req.u.bitrate, sizeof(int));
        sigInfo->bitrate=bitrate/1000000;
    }


    //SIOCGIFHWADDR for mac addr
    ifreq req2;
    strcpy(req2.ifr_name, iwname);
    //this will get the mac address of the interface
    if(ioctl(sockfd, SIOCGIFHWADDR, &req2) == -1){
        fprintf(stderr, "mac error");
        return(-1);
    }
    else{
        sprintf(sigInfo->mac, "%.2X", (unsigned char)req2.ifr_hwaddr.sa_data[0]);
        for(int s=1; s<6; s++){
            sprintf(sigInfo->mac+strlen(sigInfo->mac), ":%.2X", (unsigned char)req2.ifr_hwaddr.sa_data[s]);
        }
    }
    close(sockfd);
}

This seems to work very reliably for me. Hopefully it will save some poor soul from having to parse iwconfig in the future. Happy programming!

5 comments:

  1. Sir I m totally new to this field
    kindly guide me how can I get Received Signal Strength from the wifi interface every time it receives a packett/frame

    I'll be really grateful
    thank you n waiting for your rply

    ReplyDelete
    Replies
    1. Haroon,

      Sorry for the late reply. Unfortunately, I am not sure how to do that. The only way I would know how to do that is by polling ioctl (like I did above) after a socket recv(). I do not think that is an efficient way, and I am sure there is a better way to do it. Unfortunately, I am not that familiar with capturing system events from UNIX. Sorry that I could not be of more help!

      Adam

      Delete
  2. Line 5 should be : struct iw_statistics *stats;

    ReplyDelete
  3. line 11~12 should be:
    req.u.data.pointer = (struct iw_statistics *) malloc(sizeof(* stats));
    req.u.data.length = sizeof(* stats);
    req.u.data.flags = 1;

    ReplyDelete
  4. invalid interface i am getting.

    ReplyDelete