Just Another Security/Programming Blog: Monitoring Network I/O w/ Download & Upload Speeds on FreeBSD, in C

Saturday, 23 April 2016

Monitoring Network I/O w/ Download & Upload Speeds on FreeBSD, in C

While creating my custom i3bar-equivelant program, I decided one of the features I wanted was the ability to display my network's I/O so I can watch it whenever I want.

Ends up, in FreeBSD, this isn't exactly clear as to how it's done(and a bug may exist?)
After a little while I was able to find the correct manpage to read the documentation -- which I then just later read the header file since it was much clearer than the manpage(you must read the manpage for `if_data(9)' too)

Ends up, we have to use `ifmib(4)' with sysctl and if_data, to grab our network data.


From ifmib(4):

           The generic interface information, common to all interfaces, can be
     accessed via the following procedure:

           int
           get_ifmib_general(int row, struct ifmibdata *ifmd)
           {
                   int name[6];
                   size_t len;

                   name[0] = CTL_NET;
                   name[1] = PF_LINK;
                   name[2] = NETLINK_GENERIC;
                   name[3] = IFMIB_IFDATA;
                   name[4] = row;
                   name[5] = IFDATA_GENERAL;

                   len = sizeof(*ifmd);

                   return sysctl(name, 6, ifmd, &len, (void *)0, 0);
           }

     The fields in struct ifmibdata are as follows:

     ifmd_name       (char []) the name of the interface, including the unit
                     number

     ifmd_pcount     (int) the number of promiscuous listeners

     ifmd_flags      (int) the interface's flags (defined in <net/if.h>)

     ifmd_snd_len    (int) the current instantaneous length of the send queue

     ifmd_snd_drops  (int) the number of packets dropped at this interface
                     because the send queue was full

     ifmd_data       (struct if_data) more information from a structure
                     defined in <net/if.h> (see if_data(9))


And from if_data(9):
           ifi_ibytes          Total traffic received, in bytes.

           ifi_obytes          Total traffic transmitted, in bytes.



So, here is a quick version of printing the uploaded/downloaded bytes of an interface since it's been up.

#include <unistd.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_mib.h>
#include <stdio.h>
#include <string.h>
/*
 Grabs the total amount of bytes that we have sent("upload") and received("download") and prints it.
 Returns 1 on error.
*/
int main(void) {
 struct ifmibdata ifmd;
 size_t a_size = sizeof ifmd;
 int n_ntwk;
 size_t int_size = sizeof n_ntwk;
 int name[6];
 const char* ifname = "wlan0";


 /* Prepare for sysctl to grab IFMIB_IFDATA */
 name[0] = CTL_NET;
 name[1] = PF_LINK;
 name[2] = NETLINK_GENERIC;
 name[3] = IFMIB_IFDATA;
 name[5] = IFDATA_GENERAL;

 /* Check how many network interfaces are up */
 if(sysctlbyname("net.link.generic.system.ifcount", &n_ntwk, &int_size, (void*)0, 0) == -1) {
  fprintf(stderr, "Error when getting number of networks\n");
  /* sysctlbyname failed. Something is extremely wrong. */
  return 1;
 }


 /*
 From the manpage:
  The index of the last row in the
  table is given by ``net.link.generic.system.ifcount''

  ....


   A management application searching for a particular
  interface should start with row 1 and continue through the table row-by-
  row until the desired interface is found.

 Thus, start at i=1, and continued until <= n_ntwk
 */
 /* Loop through all the interfaces, grabbing data, and looking for the one named 'wlan0' */
 for(int i=1; i <= n_ntwk; i++) {
  name[4] = i;
  /* Grab the actual data. */
  if(sysctl(name, 6, &ifmd, &a_size, (void*)0, 0) == -1) {
   fprintf(stderr, "Error with sysctl\n");
   /* Sysctl failed.. Not good. */
   continue;
  }

  /* Is this interface named 'wlan0'? */
  if(strcmp(ifmd.ifmd_name, ifname) == 0) {
   /* This is the interface we're looking for. */
   printf("Downloaded bytes: %lu\n", ifmd.ifmd_data.ifi_ibytes);
   printf("Uploaded bytes: %lu\n", ifmd.ifmd_data.ifi_obytes);
   return 0;
  }
 }

 return 1;

}


In my case, 'wlan0' is the interface I want to monitor. So for you, replace it with what you need.


However, since we want the average upload/download in kb/s, we have to edit the code a bit.
In this code, we look at the inbytes, outbytes, and time, to work out an average kb/s. We save those, and then calculate the change from now to when it was saved(calculated by the displacement in time).
 
This is done fairly simply by doing (now_outbytes - before_outbytes) / (now_time - before_time) where the 4 variables are intuitively named. now_time-before should be 2, since we're sleeping for 2 seconds. now_outbytes-before_outbytes is the amount of bytes that we have uploaded in the past 2 seconds(or, in the past now_time - before_time seconds.)


Here is the code:


#include <unistd.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_mib.h>
#include <stdio.h>
#include <string.h>

/*
 ifdata[0] holds the old upload byte count.
 ifdata[1] holds the old download byte count.
 ifdata[2] holds the old time when the info was retreived.
*/
unsigned long ifdata[3];

/*
 Grabs the total amount of bytes that we have sent("upload") and received("download") and prints it.
 Returns 1 on error.
*/
int printit(void) {
 struct ifmibdata ifmd;
 size_t a_size = sizeof ifmd;
 int n_ntwk;
 size_t int_size = sizeof n_ntwk;
 int name[6];
 const char* ifname = "wlan0";
 float avg[2]; /* [0] contains upload, [1] contains download */


 /* Prepare for sysctl to grab IFMIB_IFDATA */
 name[0] = CTL_NET;
 name[1] = PF_LINK;
 name[2] = NETLINK_GENERIC;
 name[3] = IFMIB_IFDATA;
 name[5] = IFDATA_GENERAL;

 /* Check how many network interfaces are up */
 if(sysctlbyname("net.link.generic.system.ifcount", &n_ntwk, &int_size, (void*)0, 0) == -1) {
  fprintf(stderr, "Error when getting number of networks\n");
  /* sysctlbyname failed. Something is extremely wrong. */
  return 1;
 }


 /*
 From the manpage:
  The index of the last row in the
  table is given by ``net.link.generic.system.ifcount''

  ....


   A management application searching for a particular
  interface should start with row 1 and continue through the table row-by-
  row until the desired interface is found.

 Thus, start at i=1, and continued until <= n_ntwk
 */
 /* Loop through all the interfaces, grabbing data, and looking for the one named 'wlan0' */
 for(int i=1; i <= n_ntwk; i++) {
  name[4] = i;
  /* Grab the actual data. */
  if(sysctl(name, 6, &ifmd, &a_size, (void*)0, 0) == -1) {
   fprintf(stderr, "Error with sysctl\n");
   /* Sysctl failed.. Not good. */
   continue;
  }

   /* Is this interface named 'wlan0'? */
  if(strcmp(ifmd.ifmd_name, ifname) == 0) {
   /* This is the interface we're looking for. */

   if(ifdata[0] > 0 && ifdata[1] > 0 && ifdata[2] > 0 && ifmd.ifmd_data.ifi_obytes >= ifdata[0] && ifmd.ifmd_data.ifi_ibytes >= ifdata[1] && time(NULL) > ifdata[2]) {
    avg[0] = (ifmd.ifmd_data.ifi_obytes - ifdata[0]) / (time(NULL) - ifdata[2]);
    avg[1] = (ifmd.ifmd_data.ifi_ibytes - ifdata[1]) / (time(NULL) - ifdata[2]);
    printf("Average upload speed over %d seconds: %fkb/s\n", time(NULL) - ifdata[2], avg[0]/1000);
    printf("Average download speed over %d seconds: %fkb/s\n", time(NULL) - ifdata[2], avg[1]/1000);
    printf("\n\n");
   }

   ifdata[0] = ifmd.ifmd_data.ifi_obytes;
   ifdata[1] = ifmd.ifmd_data.ifi_ibytes;
   ifdata[2] = (unsigned long)time(NULL);
   return 0;
  }
 }

 return 1;

}
int main(void) {
 ifdata[0] = ifdata[1] = ifdata[2] = 0;

 for(;;) {
  printit(); /* Don't really care about the return */
  fflush(stdout);
  sleep(2);
 }
}








There are also some caveats when it comes to this code, which are all noted in the manpage. I've noted them in the code too.

Also, before I 'upgraded' to 11-CURRENT, I was using 10-RELEASE, which when running this program, seemed to always have the upload speed at 0, and the download speed was a combination of the upload and download speed.


There's not much more to say. I just wanted to litter Google with this blog post in case somebody else was going through the trouble I was.

1 comment:

Note: only a member of this blog may post a comment.