SNMP and MRTG in the Home

Chris Moore · March 4, 2022

MRTG - The Multi Router Traffic Grapher is a network bandwidth monitoring tool that can be easily ran as a service off a lightweight machine, that emits static HTML files for a lightweight webserver.

You may have seen similar graphs before, here is an example:

An example of a rendered MRTG page, showing 5 and 30 minute averages of bandwidth usage

I initially explored MRTG while taking a Scripting for Network Management class at DePaul University. One of our assignments (in a lab environment) was to setup a cron job to poll three switches with MRTG (which uses SNMP internally), and update a set of exposed HTML files/images that show bandwidth usage over 5m/30m/2hr/1d periods.

This post serves as both an installation guide for my future-self, and an exploration of my thoughts as I set it up on my home network.

In the home

Having network metrics is useful for even small, home networks. It allows one to easily see how much of a purchased Internet package is being used. Many commercial routers will support the SNMP configuration necessary (not sure about ISP-provided ones), and my OpenWRT router just runs Linux - SNMP should be no problem for it.

MRTG and an nginx webserver will be running off of a Raspberry Pi 4 Model B, running Raspbian 10.

The router will be an Espressobin running OpenWRT.


Installation of MRTG

Here’s a brief shell script documenting my installation journey.

Much of this is distilled from the HTML version of the MRTG Unix Guide.

Some commands are explicitly commented out (using two hashes) so this can be copy/pasted as it’s own shell script.

#!/bin/bash

INSTALL_PATH="/usr/local/mrtg-2"

# Just in case it's not already installed... (We'll need GCC specifically)
sudo apt install build-essential

# Create a hosting folder for MRTG
mkdir ~/mrtg
cd ~/mrtg

# download the latest version
wget https://oss.oetiker.ch/mrtg/pub/mrtg.tar.gz

# extract it, and rename the folder so it doesn't have a version number
tar xvf mrtg.tar.gz
mv mrtg-* src

cd src

# Read the unix guide...
## less doc/mrtg-unix-guide.txt

# Try to configure it!
## ./configure --prefix="$INSTALL_PATH"

# Oh... I needs libgd... Install that!
# -> libgd is a library meant for easier dynamic image creation.
# -> Like those made by MRTG!
sudo apt install libgd-dev --no-install-recommends

# Actually configure it this time...
./configure --prefix="$INSTALL_PATH"

# Time to build it!
make

# Great, it's built - ready to install
sudo make install

# Just to double check...
ls "$INSTALL_PATH/bin"

Now a global install of MRTG itself is setup and ready to be used by any user of the system.

At this point, I started a brief dive into how MRTG’s binary was built - see the ‘Aside’ section at the bottom of this post.


OpenWRT Configuration for SNMP

While not the focus of this post, here are the basic steps I took:

  1. Enable SSHing into the router
  2. Update opkg, and install the snmpd package
  3. Edit the /etc/config/snmpd file for your own contact details & customizations
  4. Restart the SNMPD service: /etc/init.d/snmpd restart

MRTG Configuration

Ideally, this would be placed within it’s own user profile, with limited permissions. For simplicity, I’m going to run it off of my own user profile.

mkdir -p ~/mrtg/static # dir where our HTML assets will be placed
mkdir -p ~/mrtg/cfg # where we will keep MRTG files
cd ~/mrtg/cfg

# Add our installed MRTG to our path
PATH="$PATH:/usr/local/mrtg-2/bin"

# Setup MRTG with our directories
cfgmaker --global "WorkDir: $HOME/mrtg/static"  \
    --global "Options[_]: bits,growright" \
    --output "$HOME/mrtg/cfg/mrtg.cfg"    \
    "public@192.168.0.1"

# Initially run it - this may error the first few times.
LANG=C mrtg "$HOME/mrtg/cfg/mrtg.cfg"
LANG=C indexmaker --output="$HOME/mrtg/static/index_192.168.0.1.html" "$HOME/mrtg.cfg/mrtg.cfg"

# Setup a log file
sudo touch /var/log/mrtg.log
sudo chmod 600 /var/log/mrtg.log
sudo chown "$USER" /var/log/mrtg.log

# Create an index.html for nginx to point to. If multiple hosts are being watched, this could be something pointing to each host's index file.
cd ~/mrtg/static/
ln -s index_192.168.0.1.html index.html

If that seems to run successfully, add the following to your user crontab (crontab -e)

*/5 * * * * env LANG=C /usr/local/mrtg-2/bin/mrtg $HOME/mrtg/cfg/mrtg.cfg --logging /var/log/mrtg.log && env LANG=C /usr/local/mrtg-2/bin/indexmaker --output="$HOME/mrtg/static/index_192.168.0.1.html" "$HOME/mrtg.cfg/mrtg.cfg"

And that should be it! Here is a summary of what we have:

Path Description
$HOME/mrtg/static/ Static HTML and image files, suitable to be presented from a web server such as Nginx or Apache. These files should update around every 5 minutes or so.
$HOME/mrtg/cfg/ Configuration and historical data files for MRTG use, to keep stock of current and past bandwidth data and device configurations
/var/log/mrtg.log A log file we can use to ensure MRTG ran, and examine possible issues. (/var/log/syslog can also be useful here for investigating cron issues!)
/usr/local/mrtg-2 A global install folder for MRTG binaries, libraries, and other assets

Webserver Configuration

MRTG emits files with relative paths, so any server capable of serving static files should work. I’m personally using Nginx, over a subdomain only accessible on my local network. The webserver should have minimal caching, since the files should be updated every five minutes.


Aside - How is MRTG built?

A reverse-engineering Desire path

Especially when installing from source, like we did with MRTG just now, I always like to see what programs have been installed. Lets take a look at the binary folder for our installation directory:

# What kinds of files did MRTG install?
$ file /usr/local/mrtg-2/bin/*
/usr/local/mrtg-2/bin/cfgmaker:         Perl script text executable
/usr/local/mrtg-2/bin/indexmaker:       Perl script text executable
/usr/local/mrtg-2/bin/mrtg:             Perl script text executable
/usr/local/mrtg-2/bin/mrtg-traffic-sum: Perl script text executable
/usr/local/mrtg-2/bin/rateup:           ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=53b1955674edf0030abefb6f563bc018660d22db, with debug_info, not stripped

Huh - there is actually only one binary application. Most of it is written in Perl!

One thing that may be interesting is the number of libraries the main rateup binary links against. In other words - what common libraries does MRTG use? Lets take a look:

$ ldd /usr/local/mrtg-2/bin/rateup
        linux-vdso.so.1 (0xbefb4000)
        /usr/lib/arm-linux-gnueabihf/libarmmem-${PLATFORM}.so => /usr/lib/arm-linux-gnueabihf/libarmmem-v7l.so (0xb6f60000)
        libgd.so.3 => /lib/arm-linux-gnueabihf/libgd.so.3 (0xb6ef9000)
        libpng16.so.16 => /lib/arm-linux-gnueabihf/libpng16.so.16 (0xb6ebd000)
        libz.so.1 => /lib/arm-linux-gnueabihf/libz.so.1 (0xb6e92000)
        libm.so.6 => /lib/arm-linux-gnueabihf/libm.so.6 (0xb6e10000)
        libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0xb6cc2000)
        libfontconfig.so.1 => /lib/arm-linux-gnueabihf/libfontconfig.so.1 (0xb6c7a000)
        libfreetype.so.6 => /lib/arm-linux-gnueabihf/libfreetype.so.6 (0xb6bd7000)
        libjpeg.so.62 => /lib/arm-linux-gnueabihf/libjpeg.so.62 (0xb6b91000)
        libXpm.so.4 => /lib/arm-linux-gnueabihf/libXpm.so.4 (0xb6b72000)
        libtiff.so.5 => /lib/arm-linux-gnueabihf/libtiff.so.5 (0xb6af2000)
        libwebp.so.6 => /lib/arm-linux-gnueabihf/libwebp.so.6 (0xb6a8e000)
        /lib/ld-linux-armhf.so.3 (0xb6f75000)
        libexpat.so.1 => /lib/arm-linux-gnueabihf/libexpat.so.1 (0xb6a4d000)
        libuuid.so.1 => /lib/arm-linux-gnueabihf/libuuid.so.1 (0xb6a36000)
        libpthread.so.0 => /lib/arm-linux-gnueabihf/libpthread.so.0 (0xb6a0c000)
        libX11.so.6 => /lib/arm-linux-gnueabihf/libX11.so.6 (0xb68ea000)
        libzstd.so.1 => /lib/arm-linux-gnueabihf/libzstd.so.1 (0xb6851000)
        liblzma.so.5 => /lib/arm-linux-gnueabihf/liblzma.so.5 (0xb6820000)
        libjbig.so.0 => /lib/arm-linux-gnueabihf/libjbig.so.0 (0xb6803000)
        libgcc_s.so.1 => /lib/arm-linux-gnueabihf/libgcc_s.so.1 (0xb67d6000)
        libxcb.so.1 => /lib/arm-linux-gnueabihf/libxcb.so.1 (0xb67a7000)
        libdl.so.2 => /lib/arm-linux-gnueabihf/libdl.so.2 (0xb6794000)
        libXau.so.6 => /lib/arm-linux-gnueabihf/libXau.so.6 (0xb6781000)
        libXdmcp.so.6 => /lib/arm-linux-gnueabihf/libXdmcp.so.6 (0xb676c000)
        libbsd.so.0 => /lib/arm-linux-gnueabihf/libbsd.so.0 (0xb6744000)
        librt.so.1 => /lib/arm-linux-gnueabihf/librt.so.1 (0xb672d000)

That’s a lot of libraries! Especially notice that we’re linking to libgd - Don’t uninstall it!

Many of these are common Linux/libc/gcc libraries that many programs use: libc, linux-vdso, libgcc_s, librt. Many of them though, are plain image or graphics libraries: libpng16, libjpeg, libtiff, libX11, etc.

It’s hard to believe that MRTG uses that many libraries directly. What if we ask rateup, our main MRTG binary, what external functions it uses?

# Get the 'relocations', and filter out any common C standard library functions
$ objdump -R /usr/local/mrtg-2/bin/rateup  | grep -v GLIBC

/usr/local/mrtg-2/bin/rateup:     file format elf32-littlearm

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE
0002a0e4 R_ARM_GLOB_DAT    __gmon_start__
0002a120 R_ARM_COPY        gdFontSmall
0002a014 R_ARM_JUMP_SLOT   gdImageDestroy
0002a020 R_ARM_JUMP_SLOT   __gmon_start__
0002a04c R_ARM_JUMP_SLOT   gdImageStringUp
0002a05c R_ARM_JUMP_SLOT   gdImageLine
0002a068 R_ARM_JUMP_SLOT   gdImageInterlace
0002a06c R_ARM_JUMP_SLOT   gdImageColorTransparent
0002a080 R_ARM_JUMP_SLOT   gdImageSetStyle
0002a098 R_ARM_JUMP_SLOT   gdImageRectangle
0002a0b0 R_ARM_JUMP_SLOT   gdImageString
0002a0b4 R_ARM_JUMP_SLOT   gdImagePng
0002a0c0 R_ARM_JUMP_SLOT   gdImageCreate
0002a0dc R_ARM_JUMP_SLOT   gdImageColorAllocate
0002a0e0 R_ARM_JUMP_SLOT   gdImageSetBrush

The C convention, which carries over to these ‘symbols’ (functions, variables, etc, that a program needs to access at runtime), typically includes putting the library name in front of the function. Since there isn’t sort of namespacing or heiarchy, everything is just out in the open. Prefixing the library name helps to contain the chaos.

That explains all these functions starting with gd - they must be from libgd. What happened to all the other libraries we’re using?

Let’s take a look at libgd itself:

$ ldd /lib/arm-linux-gnueabihf/libgd.so.3
        linux-vdso.so.1 (0xbef51000)
        /usr/lib/arm-linux-gnueabihf/libarmmem-${PLATFORM}.so => /usr/lib/arm-linux-gnueabihf/libarmmem-v7l.so (0xb6ee5000)
        libm.so.6 => /lib/arm-linux-gnueabihf/libm.so.6 (0xb6e63000)
        libpng16.so.16 => /lib/arm-linux-gnueabihf/libpng16.so.16 (0xb6e27000)
        libz.so.1 => /lib/arm-linux-gnueabihf/libz.so.1 (0xb6dfc000)
        libfontconfig.so.1 => /lib/arm-linux-gnueabihf/libfontconfig.so.1 (0xb6db4000)
        libfreetype.so.6 => /lib/arm-linux-gnueabihf/libfreetype.so.6 (0xb6d11000)
        libjpeg.so.62 => /lib/arm-linux-gnueabihf/libjpeg.so.62 (0xb6ccb000)
        libXpm.so.4 => /lib/arm-linux-gnueabihf/libXpm.so.4 (0xb6cac000)
        libtiff.so.5 => /lib/arm-linux-gnueabihf/libtiff.so.5 (0xb6c2c000)
        libwebp.so.6 => /lib/arm-linux-gnueabihf/libwebp.so.6 (0xb6bc8000)
        libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0xb6a7a000)
        /lib/ld-linux-armhf.so.3 (0xb6f61000)
        libexpat.so.1 => /lib/arm-linux-gnueabihf/libexpat.so.1 (0xb6a39000)
        libuuid.so.1 => /lib/arm-linux-gnueabihf/libuuid.so.1 (0xb6a22000)
        libpthread.so.0 => /lib/arm-linux-gnueabihf/libpthread.so.0 (0xb69f8000)
        libX11.so.6 => /lib/arm-linux-gnueabihf/libX11.so.6 (0xb68d6000)
        libzstd.so.1 => /lib/arm-linux-gnueabihf/libzstd.so.1 (0xb683d000)
        liblzma.so.5 => /lib/arm-linux-gnueabihf/liblzma.so.5 (0xb680c000)
        libjbig.so.0 => /lib/arm-linux-gnueabihf/libjbig.so.0 (0xb67ef000)
        libgcc_s.so.1 => /lib/arm-linux-gnueabihf/libgcc_s.so.1 (0xb67c2000)
        libxcb.so.1 => /lib/arm-linux-gnueabihf/libxcb.so.1 (0xb6793000)
        libdl.so.2 => /lib/arm-linux-gnueabihf/libdl.so.2 (0xb6780000)
        libXau.so.6 => /lib/arm-linux-gnueabihf/libXau.so.6 (0xb676d000)
        libXdmcp.so.6 => /lib/arm-linux-gnueabihf/libXdmcp.so.6 (0xb6758000)
        libbsd.so.0 => /lib/arm-linux-gnueabihf/libbsd.so.0 (0xb6730000)
        librt.so.1 => /lib/arm-linux-gnueabihf/librt.so.1 (0xb6719000)

Thats many of the same libraries - how are they being used this time?

$ objdump -R /lib/arm-linux-gnueabihf/libgd.so.3 | grep -vE 'GLIBC|ABS| gd'

/lib/arm-linux-gnueabihf/libgd.so.3:     file format elf32-littlearm

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE
00043fb4 R_ARM_GLOB_DAT    GD_COLOR_MAP_X11@@Base
00043fbc R_ARM_GLOB_DAT    _ITM_deregisterTMCloneTable
00043fe4 R_ARM_GLOB_DAT    __gmon_start__
00043ff8 R_ARM_GLOB_DAT    _ITM_registerTMCloneTable
00043ffc R_ARM_GLOB_DAT    jpeg_resync_to_restart@LIBJPEG_6.2
00043bb0 R_ARM_JUMP_SLOT   png_set_strip_16@PNG16_0
00043bb8 R_ARM_JUMP_SLOT   png_write_info@PNG16_0
00043bec R_ARM_JUMP_SLOT   compress
00043bf0 R_ARM_JUMP_SLOT   png_read_image@PNG16_0
00043c04 R_ARM_JUMP_SLOT   FT_Load_Glyph
00043c08 R_ARM_JUMP_SLOT   FcNameParse
00043c0c R_ARM_JUMP_SLOT   FT_Get_Kerning
00043c20 R_ARM_JUMP_SLOT   FT_New_Size
00043c24 R_ARM_JUMP_SLOT   FT_Done_Size
00043c3c R_ARM_JUMP_SLOT   png_set_compression_level@PNG16_0
00043c40 R_ARM_JUMP_SLOT   png_error@PNG16_0
00043c48 R_ARM_JUMP_SLOT   png_read_end@PNG16_0
00043c5c R_ARM_JUMP_SLOT   TIFFGetField@LIBTIFF_4.0
00043c60 R_ARM_JUMP_SLOT   XpmReadFileToXpmImage
00043c78 R_ARM_JUMP_SLOT   png_set_PLTE@PNG16_0
00043c7c R_ARM_JUMP_SLOT   png_get_valid@PNG16_0
00043c88 R_ARM_JUMP_SLOT   FcPatternDestroy
00043c8c R_ARM_JUMP_SLOT   uncompress
00043c94 R_ARM_JUMP_SLOT   png_write_image@PNG16_0
00043c9c R_ARM_JUMP_SLOT   FT_Done_Face
00043ca0 R_ARM_JUMP_SLOT   FT_Done_FreeType
00043cbc R_ARM_JUMP_SLOT   jpeg_start_decompress@LIBJPEG_6.2
00043ce4 R_ARM_JUMP_SLOT   FT_Activate_Size
00043cf8 R_ARM_JUMP_SLOT   FT_New_Face
00043cfc R_ARM_JUMP_SLOT   FT_Init_FreeType
00043d04 R_ARM_JUMP_SLOT   TIFFSetField@LIBTIFF_4.0
00043d0c R_ARM_JUMP_SLOT   FcConfigSubstitute
00043d18 R_ARM_JUMP_SLOT   png_get_rowbytes@PNG16_0
00043d24 R_ARM_JUMP_SLOT   FT_Set_Charmap
00043d2c R_ARM_JUMP_SLOT   jpeg_simple_progression@LIBJPEG_6.2
00043d34 R_ARM_JUMP_SLOT   jpeg_destroy@LIBJPEG_6.2

Ah there we go - now see see many functions starting with the library/image format names from earlier - png_, jpeg_, FT must be a part of libfreetype, etc.

So when compiling/linking the rateup binary, the compiler must have also pulled all of libgd’s dependencies with it! Neat.

Twitter, Facebook