Bandwidth meter

Querying libiptc HOWTO
Prev |  | Next

# 13. Bandwidth meter

In this chapter I am going to develop a simple bandwidth meter using the following functions from libiptc:

- To initialize the system: iptc_handle_t iptc_init(const char *tablename).

- To catch from errors: const char *iptc_strerror(int err).

- To iterate through the chains of the table: const char *iptc_first_chain(iptc_handle_t *handle) and const char *iptc_next_chain(iptc_handle_t *handle).

- To read packet and byte counters for a specific rule: struct ipt_counters *iptc_read_counter(const ipt_chainlabel chain, unsigned int rulenum, iptc_handle_t *handle).

Also the function gettimeofday will be used to measure elapsed time and the function getopt to get options from the command line.

I don't know really if the term bandwidth meter is well used here. I interpret bandwidth as a reference to a flow capacity; perhaps a better term could be flow meter.

Here is the bandwidth meter `bw.c`. It's well commented to be easy followed by everyone:

    /*
     * How to use libiptc- program #4
     * /usr/local/src/bw.c
     * By Leonardo Balliache - 04.09.2002
     * e-mail: leonardo@opalsoft.net
     * --WELL COMMENTED-- to be easy followed by everyone.
     */

    /* include files required */
    #include <getopt.h>
    #include <sys/errno.h>
    #include <sys/time.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <string.h>
    #include <dlfcn.h>
    #include <time.h>
    #include <unistd.h>
    #include "libiptc/libiptc.h"
    #include "iptables.h"

    /* colors to differentiate chains measures */
    #define RED     "\033[41m\033[37m"
    #define GREEN   "\033[42m\033[30m"
    #define ORANGE  "\033[43m\033[30m"
    #define BLUE    "\033[44m\033[37m"
    #define MAGENTA "\033[45m\033[37m"
    #define CYAN    "\033[46m\033[30m"
    #define WHITE   "\033[47m\033[30m"
    #define BLACK   "\033[40m\033[37m"
    #define RESET   "\033[00m"

    /* maximum number of chains to be processed */
    #define MAXUSERCHAINS 7

    /* time between measures in seconds; adjust as you like */
    #define SLEEPTIME 1

    /* structure to count bytes per chain */
    struct bwcnt  {
      int start;           /* the chain was initialized */
      u_int64_t icnt;      /* bytes through; previous measure */
      u_int64_t ocnt;      /* bytes through; current measure */
      double bw;           /* bandwitdh (flow) on this chain */
    };

    /* function to calculate differences of time in seconds.
     * micro-seconds precision.
     */
    double delta(struct timeval a, struct timeval b)
    {
      if (a.tv_usec & b.tv_usec)  {
        a.tv_sec--;
        a.tv_usec += 1000000;
      }
      return a.tv_sec-b.tv_sec + (a.tv_usec-b.tv_usec)/1000000.0;
    }

    /* main function */
    int main(int argc, char *argv[])
    {
      int i, j, ok;
      double totbw;
      iptc_handle_t h;
      int c, act_bw = 0;
      const char *chain = NULL;
      const char *tablename = "filter";
      struct timeval ti, to;
      struct bwcnt bw[MAXUSERCHAINS];
      struct ipt_counters *counters;
      char *col[9] = { RED,GREEN,ORANGE,BLUE,MAGENTA,CYAN,WHITE,BLACK,RESET };

      program_name = "bw";
      program_version = NETFILTER_VERSION;

     /* check options
      * we have 2 options:
      *        -c = display current flow (each SLEEPTIME).
      *        -a = display average flow (from start); default option.
      */
      while ((c = getopt (argc, argv, "ac")) != -1)
      switch (c)  {
      case 'a':
        act_bw = 0;
        break;
      case 'c':
        act_bw = 1;
        break;
      case '?':
        if (isprint(optopt))
          fprintf (stderr, "Unknown option `-%c'.\n", optopt);
        else
          fprintf (stderr,"Unknown option character `\\x%x'.\n",optopt);
        exit(1);
      default:
        abort();
      }

      /* initialize array of chains */
      memset(&bw, 0, MAXUSERCHAINS * sizeof(struct bwcnt));

      /* get time to start meter on variable ti */
      gettimeofday(&ti, NULL);

      /* fire meter loop */
      if ( act_bw )
        printf("Displaying current flow values ...\n");
      else
        printf("Displaying average flow values ...\n");

      /* forever loop; abort the program with ^C */
      while ( 1 )  {
        /* you have to initialize for each measure to be done */
        if ( !(h = iptc_init(tablename)) )  {
          printf("Error initializing: %s\n", iptc_strerror(errno));
          exit(errno);
        }
        ok = 0;    /* we start a new loop */
        gettimeofday(&to, NULL);  /* have a time shoot */

        /* iterate through each chain of the table */
        for (chain = iptc_first_chain(&h), i = 0;
             chain;
             chain = iptc_next_chain(&h))  {
          if ( iptc_builtin(chain, h) )
            continue;    /* if it is a built-in chain, ignore it */

          /* ok, read the counters of this chain */
          if ( !(counters = iptc_read_counter(chain, 0, &h)) )  {
             printf("Error reading %s: %s\n", chain, iptc_strerror(errno));
             exit(errno);
          }

          /* check that we do not have more chains than we can process */
          if ( i >= MAXUSERCHAINS )  {
             printf("Maximum of %d user chains exceeded!!\n", MAXUSERCHAINS);
             exit(1);
          }

          /* this chain counter has not been initialized; initialize it */
          if ( bw[i].start == 0 )  {
            bw[i].icnt = counters->bcnt;
            bw[i].start = 1;
          }

          /* this chain has a previous measure; take the current one */
          else  {
            bw[i].ocnt = counters->bcnt;
            if ( bw[i].ocnt == bw[i].icnt )    /* no new bytes flowing? */
              bw[i].bw = 0;                    /* flow is zero */
            else
             /* flow in this chain is:
              *   current bytes count (bw[i].octn)    *minus*
              *   previous bytes count (bw[i].icnt)   *divided by*
              *   128.0 to convert bytes to kbits     *and divided by*
              *   difference in times in seconds      *to get*
              *   flow in kbits/sec that is what we want.
              */
              bw[i].bw = (bw[i].ocnt - bw[i].icnt) / (128.0 * delta(to, ti));

           /* do you want current flow of this chain? initialize previous
            * bytes count to current bytes count; we get the flow in last
            * SLEEPTIME elapsed time.
            */
            if ( act_bw )
              bw[i].icnt = bw[i].ocnt;
            ok = 1;    /* ok, we have some measure to show */
          }
          ++i;  /* next chain, please */
        }

        /* we iterate and i == 0; we do not have user chains at all */
        if ( i == 0 )  {
           printf("No user chains to meter!!\n");
           exit(1);
        }

       /* do you want to measure current flow? initialize previous time
        * to actual time; we get the time elapsed in last SLEEPTIME.
        */
        if ( act_bw )
          ti = to;

        /* do we have something to show? ok, display it */
        if ( ok )  {
          totbw = 0;
          for ( j = 0; j < i; ++j )  {
            totbw = totbw + bw[j].bw;   /* calculate total flow */
          }
          printf("%s%6.1fk:%s ", col[7], totbw, col[8]);  /* display total */
          for ( j = 0; j < i; ++j )  {  /* display flow of each chain in color */
            printf("%s%6.1fk%s ", col[j], bw[j].bw, col[8]);
          }
          printf("\n");
        }
        sleep(SLEEPTIME);  /* rest a little; you go too fast */
      }             /* give us enough time in order to let some bytes flow */

      exit(0);  /* bye, we have our measures!! */

    } /* main */

Write your program and compile as before:

    bash# ./ipt-cc bw

Before using the meter we need to set our environment.

First, we have to have at least 2 PCs connected in a network. This is our diagram configuration:

    +--------+ eth0       eth0 +--------+
    | PC #1  +-----------------+ PC #2  |
    +--------+                 +--------+
    eth0=192.168.1.1           eth0=192.168.1.2

Second, we need to install a very nice and useful package called netcat written by Hobbit. This excellent package will help us to inject and receive a flow of bytes between 2 NICs. If you don't have the package in your system, download it from http://rr.sans.org/audit/netcat.php.

The version that I use is 1.10-277. To install it follow these instructions:

    bash# cp netcat-1.10.tar.gz /usr/local/src
    bash# tar xzvf netcat-1.10.tar.gz
    bash# cd netcat-1.10

My version requires to make a patch first; check yours if you have a file with a .dif extension and apply it too:

    bash# patch -p0 -i netcat-1.10.dif

Next compile the package using make:

    bash# make linux

Copy the binary nc to your user bin directory:

    bash# cp nc /usr/bin

And also to the second PC in your network:

    bash# scp nc 192.168.1.2:/usr/bin

We are going to use netcat to "listen" to a flow of bytes from PC #2 and to "talk" from PC #1. Using tty1 to tty4 consoles on PC #2 let's start netcat to listen from this PC. Go to PC #2 and in tty1 type:

    bash# nc -n -v -l -s 192.168.1.2 -p 1001 >/dev/null

netcat must respond with:

    listening on [192.168.1.2] 1001 ...

This command started netcat to listen from address 192.168.1.2 using port number 1001. Arguments are: -n = use numeric address identification; -v = verbose; -l = listen. All the flow that netcat receives in 192.168.1.2:1001 will be redirected to the "black hole" in `/dev/null`.

Repeat the command in tty2, tty3 and tty4; change to tty2 using ALT-F2 and after logging in write:

    bash# nc -n -v -l -s 192.168.1.2 -p 1002 >/dev/null

Now we are "listening" to the same address but port number 1002.

Go on now with tty3:

    bash# nc -n -v -l -s 192.168.1.2 -p 1003 >/dev/null

And tty4:

    bash# nc -n -v -l -s 192.168.1.2 -p 1004 >/dev/null

Now we are listening in PC #2, address 192.168.1.2 in ports 1001, 1002, 1003 and 1004.

Come back to PC #1 and let's set the environment to allow iptables to help us to complete our tests:

On PC #1, type the into tty1 as follows:

    bash# iptables -F
    bash# iptables -X
    bash# iptables -N chn_1
    bash# iptables -N chn_2
    bash# iptables -N chn_3
    bash# iptables -N chn_4
    bash# iptables -A chn_1 -j ACCEPT
    bash# iptables -A chn_2 -j ACCEPT
    bash# iptables -A chn_3 -j ACCEPT
    bash# iptables -A chn_4 -j ACCEPT
    bash# iptables -A OUTPUT -o eth0 -p tcp --dport 1001 -j chn_1
    bash# iptables -A OUTPUT -o eth0 -p tcp --dport 1002 -j chn_2
    bash# iptables -A OUTPUT -o eth0 -p tcp --dport 1003 -j chn_3
    bash# iptables -A OUTPUT -o eth0 -p tcp --dport 1004 -j chn_4

These commands will:

- Flush all chains in table filter.

- Delete all user chains in table filter.

- Create user chains chn_1, chn_2, chn_3 and chn_4.

- Establish a target ACCEPT in each user chain.

- Create 4 rules in chain OUTPUT that matches port numbers 1001 to 1004 and target it to user chains chn_1 to chn_4.

Now start the bw meter using current values:

    bash# ./bw -c

It must respond with:

    Displaying current flow values ...
       0.0k:    0.0k    0.0k    0.0k    0.0k
       0.0k:    0.0k    0.0k    0.0k    0.0k
       0.0k:    0.0k    0.0k    0.0k    0.0k
       0.0k:    0.0k    0.0k    0.0k    0.0k

It informs that measures are current flows. Every line is a measure taken each SLEEPTIME lapse (1 second in our program). First column (in black) are total flow, next columns (in red, green, orange and blue) are flows in chains chn_1, chn_2, chn_3 and chn_4 respectively. Of course we do not have any flow now. However let bw run and continue reading.

Let's start now one of our byte flows; go to tty2 in PC #1 with ALT-F2 and after logging in, type:

    bash# yes 000000000000000000 | nc -n -v -s 192.168.1.1 -p 2001 192.168.1.2 1001

netcat responds with:

    (UNKNOWN) [192.168.1.2] 1000 (?) open

Now we have a flow of bytes from PC #1 to PC #2. yes generates a constant flow of zeroes; this flow is piped to netcat through address 192.168.1.1, port 2001 and sends it to PC #2, address 192.168.1.2, port 1001 (where PC #2 is listening).

Check now the display of bw in tty1:

    7653.2k: 7653.2k    0.0k    0.0k    0.0k
    7829.5k: 7829.5k    0.0k    0.0k    0.0k
    7786.7k: 7786.7k    0.0k    0.0k    0.0k
    7892.1k: 7982.1k    0.0k    0.0k    0.0k

Your mileage can vary depending of the physical characteristics of your system. In mine I have a flow of aproximately 7700 kbits/sec in the first chain chn_1 which corresponds to port number 1001 in PC #2.

Let's start now the second bytes flow; go to tty3 in PC #1 with ALT-F3 and after logging in, type:

    bash# yes 000000000000000000 | nc -n -v -s 192.168.1.1 -p 2002 192.168.1.2 1002

netcat responds with:

    (UNKNOWN) [192.168.1.2] 1002 (?) open

Now we have 2 flows of bytes from PC #1 to PC #2; one from 192.168.1.1:2001 to 192.168.1.2:1001 and another from 192.168.1.1:2002 to 192.168.1.2:1002.

Now check the display of bw in tty1:

    7819.6k: 4144.2k 3675.4k    0.0k    0.0k
    8090.5k: 3923.9k 4166.6k    0.0k    0.0k
    7794.7k: 3920.8k 3873.9k    0.0k    0.0k
    7988.3k: 3754.6k 4233.7k    0.0k    0.0k

Now we have 2 flows; each of them is more or less 50% of the total flow going out of the computer. The Linux kernel tries to balance the bandwidth available between the 2 channels of output.

To continue, start the 2 aditional flows through channels 192.168.1.1:2003-192.168.1.2:1003 and 192.168.1.1:2004-192.168.1.2:1004.

In tty4 type:

    bash# yes 000000000000000000 | nc -n -v -s 192.168.1.1 -p 2003 192.168.1.2 1003

In tty5 type:

    bash# yes 000000000000000000 | nc -n -v -s 192.168.1.1 -p 2004 192.168.1.2 1004

The display of bw in tty1 will be something like:

    8120.6k: 1705.3k 2354.9k 1898.6k 2161.8k
    7765.3k: 1634.2k 2560.2k 2011.4k 1559.5k
    7911.9k: 1699.8k 2090.3k 1768.0k 2353.8k
    8309.4k: 1734.5k 1999.7k 1999.9k 2575.3k

Total bandwidth is distributed between the 4 channels of flow.

Prev | Home | Next
Functions to modify firewalling rules and statistics |  | Controlling flows