Skip to content
 

Munin traffic accounting with iptables

The munin plugin described in this article can be downloaded here: traffic_accounting. Remember that it must be made executable once it's copied in place.

There seems to be no easy way (if at all) to glean detailed traffic information from the /proc virtual filesystem. General stats can be found there, like number of bytes or packets, but not much information about, for example, traffic from/to specific ports, or specific IP addresses, or specific ICMP types.

The usual trick to get that information is to use iptables and write non-terminating rules that match the traffic of interest; since iptables will keep counters for matching packets and bytes for each rule (if accounting is enabled, which it usually is), that provides a "natural" way to get the information we want. Anything that iptables can match, can be put into a (non-terminating) rule and counters obtained for it.

There's a minor problem, however. There's no official, standard way to access the counters in a defined format (that I know of, at least). While there are a couple of commands that output those counters, their output format may change in the future, although it seems to have been fairly stable so far. We have to live with that, but since we're lazy, and want to avoid parsing every possible output format, we rely on the user to add comments in a specific format to the accounting rules, and then parse only those comments. This is an acceptable tradeoff between implementation complexity and usage simplicity.

What we will do here is to write a munin plugin to collect and graph arbitrary traffic stats. There are other plugins floating around based on the same ideas, but as far as I can see, none as general as this.

General concepts

The basic idea is to create a special chain, that will only contain non-terminating rules, whose only purpose will be to collect counters. This will be done for IPv4 and IPv6 separately (since those are two different rulesets in netfilter terms, managed with two different commands). The suggested setup is to add a rule at the beginning of the INPUT and OUTPUT chains to jump to the management chain. If the machine is forwarding traffic, it may be added to the FORWARD chain (exclusively or in addition to INPUT and OUTPUT, depending on the exact circumstances). This allows a minimal impact on any existing firewall setup without having to mess around with the "real" main rules. Example:

# create accounting chain for IPv4
# iptables -N ACCT4
# insert rules in the INPUT and OUTPUT chains to jump to the accounting chain
# iptables -I INPUT 1 -j ACCT4
# iptables -I OUTPUT 1 -j ACCT4
# same as above, for IPv6
# ip6tables -N ACCT6
# ip6tables -I INPUT 1 -j ACCT6
# ip6tables -I OUTPUT 1 -j ACCT6
# do the same for FORWARD if/as needed

At this stage, non-terminating rules can be added to the ACCT4 and ACCT6 chains at will, without affecting the rest of the firewall.

To preserve counters across reboots, it's recommended that the firewall rules are saved on file using iptables-save/ip6tables-save upon shutdown, and restored using iptables-restore/ip6tables-restore at boot. This is what the majority of the distributions do anyway, so there should be no problem.

Requirements

For this project, we have some requirements:

  • It should be possible to choose whether to collect stats about traffic in terms of packets, bytes, or bits;
  • It should be possible to have "regular" graphs (ie with a single quantity graphed), or munin's "sent/received" style traffic (as used for example for network interfaces), with positive values representing outgoing traffic, and negative values representing incoming traffic;
  • Anything that iptables can match, it should be possible to graph;
  • It should be possible to "group" rules so they are totaled and graphed as a single entity;
  • The plugin should provide multigraph capabilities, but if the munin server does not support multigraph (old versions), it should be possible to use the plugin as a normal wildcard plugin by creating appropriately named symlinks to it. Thus the plugin defaults to multigraph if the server supports it, but the user can override this via environment variables.

Accounting rules

As said, to avoid parsing the full output of iptables, we rely on the user to add suitable comments to each rule, using iptables' -m comment option. These comments can be easily detected in the output of iptables-save or ip6tables-save because commented rules will contain something like

... -m comment --comment "comment here"

The main purposes of the comments are:

  • Identify rules that are relevant for graphing. Rules with no comments are discarded;
  • Assign a "tag" to each rule. The tag can be just anything, and identifies the entity to be graphed (which need not correspond to a single rule only, as we'll see: rules with the same tag are aggregated);
  • Provide a description to use for the graph captions.

To achieve these goals, the comments must have a predefined syntax, as follows:

=tag= Free descriptive text

Here, "tag" is the tag, and "Free descriptive text" is the comment that will be used for the graph (if you think your tag is descriptive enough for you, you can leave out the descriptive text and the plugin will use the tag as description). Multiple rules can have the same tag, as follows:

  • All the rules with the same tag in the same chain are aggregated, their stats summed, and graphed as a single value;
  • Using tag_up and tag_down will result in a sent/received style graph, with data matched by the rule tagged _up graphed in the upper half, and data matched by the rule tagged _down graphed in the lower half. Of course the plugin knows nothing about what the rules do, so this could also be abused to put arbitrary rules in the upper and lower half; it just seems more logical to pair rules that describe the two directions of the same traffic.

The two methods described above can be used together, so it's possible to have multiple tag_up rules (will be summed and graphed as a single value in the upper half) and tag_down rules (will be summed and graphed as a single value in the lower half).

An example will make the concepts clear.

Example

Suppose we have a server whose IPv4 address is 192.168.24.1, and its IPv6 address is 2001:db8:f00d::1. This server runs an HTTP daemon on TCP ports 80 and 443 (bound to the IPv4 and the IPv6 addresses), and it also runs an SSH server on port 22 (bound to the IPv4 address only). Access to SSH is restricted and is allowed from IP addresses in the range 10.2.3.0/24 only. What we want is to get a graph of HTTP traffic (in the sent/received style), and a regular graph of SSH traffic received from unauthorized addresses (will be basically just SYN packets for the most part, but for the example it is fine). As a final twist, we want HTTP stats for IPv4 to be aggregated (port 80 and 443 together), but stats for IPv6 separated (separate graphs for port 80 and port 443).

Here are the rules to be added to the accounting chains (line numbers added for readability):

 1 # iptables -A ACCT4 ! -s 10.2.3.0/24 -p tcp --dport 22 -m comment --comment "=unauthssh= Unauthorized SSH traffic received"
 2 # iptables -A ACCT4 -d 192.168.24.1 -p tcp --dport 80 -m comment --comment "=web4_down= Combined web traffic to/from local 80/443"
 3 # iptables -A ACCT4 -s 192.168.24.1 -p tcp --sport 80 -m comment --comment "=web4_up= dummy"
 4 # iptables -A ACCT4 -d 192.168.24.1 -p tcp --dport 443 -m comment --comment "=web4_down= dummy"
 5 # iptables -A ACCT4 -s 192.168.24.1 -p tcp --sport 443 -m comment --comment "=web4_up= dummy"
 6 # ip6tables -A ACCT6 -d 2001:db8:f00d::1 -p tcp --dport 80 -m comment --comment "=web6_down= Web traffic to/from local port 80"
 7 # ip6tables -A ACCT6 -s 2001:db8:f00d::1 -p tcp --sport 80 -m comment --comment "=web6_up= dummy"
 8 # ip6tables -A ACCT6 -d 2001:db8:f00d::1 -p tcp --dport 443 -m comment --comment "=web6ssl_down= Web traffic to/from local port 443"
 9 # ip6tables -A ACCT6 -s 2001:db8:f00d::1 -p tcp --sport 443 -m comment --comment "=web6ssl_up= dummy"

Rule 1 catches unauthorized SSH attempts over IPv4. The rule is tagged as unauthssh (it could be anything, in fact, although probably a descriptive tag is more readable), and the description that will appear in the graph is "Unauthorized SSH traffic received". The rule is the only one with that tag, so it will be graphed on its own graph.

Rules 2 and 3 catch IPv4 TCP traffic to and from local port 80 respectively. They're tagged web4_down and web4_up, so they will be in the same graph (sent/received style).
Rules 4 and 5 catch IPv4 TCP traffic to and from local port 443 respectively. Since we want this traffic to be aggregated with that of port 80, they're also tagged web4_down and web4_up. So overall, rules 2 to 5 will end up in the same graph; the upper half will graph the sum of rules 3 and 5 (outgoing traffic); the lower half will graph the sum of rules 2 and 4 (incoming traffic). This is to demonstrate summing; in practice, one would probably want to see the two graphs separately, as we'll do for IPv6 (see below). Regarding graph descriptions, the plugin takes the one it finds when it first encounter a new tag, so in this case the description from rule 2 will be used: "Combined web traffic to/from local 80/443". The descriptions in rules 3, 4 and 5 can be anything; here it's "dummy", but we could have left it empty. The plugin just ignores them, since they have the same tag as rule 2.

Rules 6 and 7 match IPv6 TCP traffic to/from local port 80 (similar to what rules 2 and 3 do for IPv4). They're tagged web6_down and web6_up, so they (and only they) will be in their own sent/received graph, whose description will be "Web traffic to/from local port 80".

Finally, rules 8 and 9 match IPv6 TCP traffic to/from local port 443 (like rules 4 and 5 did for IPv4). They're tagged web6ssl_down and web6ssl_up, which is a new tag, so they'll be in their own sent/received graph. The description will be "Web traffic to/from local port 443".

That's it for the iptables part for this example. For real cases, one should create all the rules that are relevant to the traffic that one wants to account, using the rules for tagging explained previously, depending on the desired graph layout.

Plugin configuration

Now what's left is to configure the munin plugin, and let it do its job. As said previously, the plugin can operate in multigraph mode, or in wildcard mode whereby one or more appropriately named symlink is created to it.

If multigraph is enabled, the plugin scans the configured IPv4 and iPv6 accounting chains and processes the rules according to the tags it finds; this results in a root cumulative graph being produced, along with all the constituent individual graphs.
If multigraph is disabled, the plugin can only graph a protocol/tag combination at a time, so it expects to be invoked via an appropriately named symlink. The symlink should be as follows:

traffic_accounting_protocol_tag

In our example, without multigraph one would create the following symlinks:

# ln -s /path/to/traffic_accounting /etc/munin/plugins/traffic_accounting_ipv4_unauthssh
# ln -s /path/to/traffic_accounting /etc/munin/plugins/traffic_accounting_ipv4_web4
# ln -s /path/to/traffic_accounting /etc/munin/plugins/traffic_accounting_ipv6_web6
# ln -s /path/to/traffic_accounting /etc/munin/plugins/traffic_accounting_ipv6_web6ssl

The plugin tries to detect if the server supports multigraph; if it doesn't, multigraph is disabled. If the server support multigraph, the plugin checks for the $multipath environment variable and uses its value ("yes" or "no"); if the variable is not present, it defaults to multigraph enabled.

So a typical configuration when using multigraph could be (the values shown for environment variables are the defaults, so they can all be omitted)

[traffic_accounting]
# needs root for iptables-save
user root
# accounting chain for IPv4
env.chain4 ACCT4
# accounting chain for IPv6
env.chain6 ACCT6
# location of iptables
env.iptables /sbin/iptables
# location of ip6tables
env.ip6tables /sbin/ip6tables
# use multigraph yes|no
env.multigraph yes
# what to graph bits|bytes|packets
env.what bytes

When not using multigraph, the configuration might look something like

[traffic_accounting_*]
# needs root for iptables-save
user root
# etc. same as before

and of course as usual with munin plugins more specific values that override the more general ones can be provided by giving a more specific plugin name specification, like

[traffic_accounting_ipv4_*]
# applies to all IPv4 stuff

[traffic_accounting_ipv6_web6ssl]
# applies only to the specific IPv6 SSL stuff
#etc.

That's it. Restart munin-node, wait some time until the data is collected, and look at your traffic.

10 Comments

  1. Max says:

    Where have I put config? This on: [traffic_accounting]....

    In which file?

    • waldner says:

      On most systems, that'll be where you configure all the plugins, eg /etc/munin/plugin-conf.d/munin-node or similar.

      • Max says:

        Thank you! So, I did changed /etc/munin/plugin-conf.d/munin-node as you suggested, but I still get no stats. I can see empty images where graphs suppose to be. So, script works but doesn't provide graphs. Could it be because of wrong right settings?

        Could you please tell me which rights and ownership I have to grand to to the script?

        • waldner says:

          This is what I have on a system where it works:

          -rwxr-xr-x 1 root root 10584 2011-04-08 11:53 traffic_accounting

          In the config file I have

          [traffic_accounting]
          user root
          env.chain4 ACCT4
          env.chain6 ACCT6

          (the above may of course be different for you). Other than that, check that your firewall rules have appropriate description tags, for example:

          -A ACCT4 -d 10.12.14.16/32 -p tcp -m tcp --dport 80 -m comment --comment "=tcp80_down= TCP to local 80"
          -A ACCT4 -s 10.12.14.16/32 -p tcp -m tcp --sport 80 -m comment --comment "=tcp80_up="

          and the same for the IPv6 accounting chain, if you have defined it. Also mind that the plugin was developed on munin 1.4, and although I haven't tried, I'm almost sure it won't work with munin 2.x.

  2. Le_Coyote says:

    A pleasure, really! I love how easy it is to graph whatever you want with just a couple of filtering rules :-)

  3. Le_Coyote says:

    Hi,

    I've just given a shot at your plugin, using munin 1.4.5 on x86_64.
    First of all, munin-update complains about "graph order":
    2011/05/06 16:15:38 [DEBUG] Protocol exception: unrecognized line 'graph_order' from traffic_accounting on hostname/ip/4949.
    So I commented it out from the plugin. Regardless, I get no graph at all, not even an empty one with nan's. munin-run reveals that the plugin generates no data, and since there's no debugging in there, I'm quite lost (short of digging into the code which will take some time). AFAICT, I've set up iptables accounting rules accodring to your guidelines, and I can't see what is wrong. I guess what I mean to say is: Help?
    Cheers!

    • waldner says:

      Hello,

      I use it on a 32-bit system, using munin 1.4.4. AFAIK, 1.4.5 should be no different. As you can see at this page, graph_order should definitely be a valid option, and indeed it works for me. Maybe the problem is on some nearby line.
      If it's not asking too much, can you paste the output (sanitized, if you prefer) of:

      iptables-save
      ip6tables-save

      any configuration option you've set for the plugin (eg under /etc/munin/plugin-conf.d/), and perhaps the output of "munin-run traffic_accounting config". This is all so I can try to reproduce your environment.

      • Le_Coyote says:

        Hi,

        While reviewing my config, it occurred to me that the "=Free descriptive text" might be necessary, or perhaps just the second "=" sign. Indeed, after experimenting a little, I found out that the equal sign is a delimiter, and not just a prefix. Problem solved :-)

        Thanks for the plugin!
        Cheers,
        Romain