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:
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:
- Enable
SSH
ing into the router - Update
opkg
, and install thesnmpd
package - Edit the
/etc/config/snmpd
file for your own contact details & customizations - 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.