#! /usr/bin/perl -w

# CUGrapher.pl
# $Revision: 1.53 $
# Author: Matt Selsky <selsky@columbia.edu>
# Contact for help: <cuflow-users@columbia.edu>

# (c) 2002 - 2005 The Trustees of Columbia University in the City of New York
# License restrictions apply, see COPYING for details.

use strict;
use CGI::Pretty qw(-nosticky :standard);
use RRDs;
use Digest::MD5 qw(md5_hex);

### Local settings ###

# top 10 url's
my $aggregateScoreFile;
my $scoreboardFile;
# directory with rrd files
my $rrddir;
# default number of hours to go back
my $hours = 48;
# duration of graph, starting from $hours ago
my $duration;
# organization name
my $organization = "";
# default graph width
my $width = 640;
# default graph height
my $height = 320;
# default image type (png/gif)
my $imageType = 'png';
# default image display on startup
my @defaultGraph;
# Page title
my $title = "Well Known Protocols/Services";

### End local settings ###

## Read the configuration file
my $conffile = "/etc/flowscan/CUGrapher.cf";
if (open(FH,$conffile)) {
    while(<FH>) {
	s/\#.*$//;		# Strip out everything after a #
	next if /^\s*$/;	# Skip blank lines

	if (/^\s*OutputDir\s+(\S+)\s*$/) {
	    $rrddir = $1;
	} elsif (/^\s*AggregateScore\s+(\S.*)\s*$/) {
	    $aggregateScoreFile = $1;
	} elsif (/^\s*Scoreboard\s+(\S.*)\s*$/) {
	    $scoreboardFile = $1;
	} elsif (/^\s*Organization\s+(\S.*)\s*$/) {
	    $organization = $1;
	} elsif (/^\s*Width\s+(\S+)\s*$/) {
	    $width = $1;
	} elsif (/^\s*Height\s+(\S.*)\s*$/) {
	    $height = $1;
	} elsif (/^\s*ImageType\s+(\S.*)\s*$/) {
	    $imageType = $1;
	} elsif (/^\s*Hours\s+(\S.*)\s*$/) {
	    $hours = $1;
	} elsif (/^\s*Title\s+(\S.*)\s*$/) {
	    $title = $1;
	} elsif (/^\s*DefaultGraph\s+(\S*)\s*$/) {
	    $defaultGraph[@defaultGraph] = $1;
	} else {
	    &browserDie( "Invalid line $. in $conffile\n\t$_\n" );
	}
    }
    close(FH);
}

if (! $rrddir) {
    &browserDie( "OutputDir not set in $conffile\n" );
}

# auto-flush STDOUT
$| = 1;

# report -> proper name
my %reportName = ( 'bits' => 'bits',
		   'pkts' => 'packets',
		   'flows' => 'flows' );

unless( param() ) {
    &showMenu(@defaultGraph);
}

if (param('showmenu')) {
    Delete('showmenu');
    @defaultGraph = ( query_string() );
    &showMenu(@defaultGraph);
}

if (param("show-aggregateScore")) {
    &showFile('AggregateScore', $aggregateScoreFile);
}

if (param("show-scoreboard")) {
    &showFile('Scoreboard', $scoreboardFile);
}

# protocol/service -> filename
my %filename;
# lists of networks/protocols/services/routers
my (%network, %as, %protocol, %service, %tos, @router);
# should we show totals also?
my %total;
# hash for colors/CDEFs
my (%color, %cdef);
# are we in debug mode?
my $debug;

&getRouter();
&getProtocols();
&getServices();
&getTOS();
&getNetworks();
&getASs();
&getImageType();
&setColors();
&getHours();
&getDuration();
&getWidth();
&getHeight();
&getTitle();
&getTotal();
&getDebug();

&doReport();

################################################################

# Image generation and display

sub generateImage {
    my @args = @_;
    my ($err1, $err2);

    unless( $debug ) {
	print header( -type => "image/${imageType}", -expires => 'now' );
        RRDs::graph( '-', @args);
	$err1 = RRDs::error;
	if( $err1 ) { # log & attempt to create image w/ error message in it
	    warn $err1, "\n";
	    RRDs::graph( '-', "--width=".$width,
		       "COMMENT:graph failed ($err1)\\n");
	    $err2 = RRDs::error;
	    if( $err2 ) { # log to stderr since we are totally broken
		warn "graph failed ($err1) and warning failed ($err2)\n";
	    }
	}
    }
    else {
	print header, '<pre>', join(" \\\n", @args), "\n"; exit;
    }
}

sub showFile {
    my ($title, $fileName) = @_;
    my $q = new CGI::Pretty(""); # Avoid inheriting the parameter list

    print $q->header;

    open(HTMLFILE, $fileName) or
      &browserDie("Can't open ${title} ${fileName}");
    while (<HTMLFILE>) {
      print $_;
    }
    exit;
}

sub defParam {
  my ($name, $default) = @_;
  my $result = param($name);
  
  return $result if ($result);
  return $default;
}

sub showMenu {
    my (@graphs) = @_;
    my $q = new CGI::Pretty(""); # Avoid inheriting the parameter list
    my $url = $q->url(-relative => 1);
    my $default;
    

    print $q->header, $q->start_html( -title => 'Generate FlowScan graphs on the fly',
				      -bgcolor => 'ffffff' );
    
    print $q->h1({-align => "center"}, "NetFlow report for ${organization}");

    print $q->start_p({-align => "center"});
    if ($aggregateScoreFile) {
        print $q->a(
	  { -href => "${url}?show-aggregateScore=1" },
	  $q->font({-size => "+1"}, "See the Aggregate Scores"));
	print "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
    }
    if ($scoreboardFile) {
        print $q->a(
	  { -href => "${url}?show-scoreboard=1" },
	  $q->font({-size => "+1"}, "See the Scoreboard"));
    }
    print $q->end_p();

    foreach my $imguri (@graphs) {
	my $imgurl = $url . "?" . $imguri;

	if ($imgurl =~ /debug=1/) {
	    (my $cleanimgurl = $imgurl) =~ s/debug=1//;
	    print $q->center( $q->a( { href => $imgurl},
				     $q->img({ -src => $cleanimgurl, -align => 'center', 
					       -alt => 'The Graph you requested'}) ) );

	} else {
	    print $q->center( $q->img({ -src => $imgurl, -align => 'center', 
					-alt => 'The Graph you requested'}));
	}
    }
      
    print $q->start_form( -action => $url, -method => 'get' ); # Just the url, without query string

    print $q->start_table( { -align => 'center',
			     -cellspacing => '10' } );

    print $q->start_Tr( { -align => 'center',
			  -valign => 'top' } );

    print $q->td( { -rowspan => '2' },
		  "Report: ",
		  $q->popup_menu( -name => 'report',
				  -values => [sort keys %reportName],
				  -default => defParam('report', 'bin') ) );
    
    my %hours = ( 12 => '12 hours',
		  24 => '24 hours',
		  36 => '36 hours',
		  48 => '48 hours',
		  168 => '1 week',
		  720 => '1 month' );
    $hours{defParam('hours', $hours)} = defParam('hours', $hours) . " hours" 
      if (!$hours{defParam('hours', $hours)}); 

    print $q->td( { -align => 'right' },
		  "Time period: ",
		  $q->popup_menu( -name => 'hours',
				  -values => [sort {$a <=> $b} keys %hours],
				  -default => defParam('hours', $hours) + "",
				  -labels => \%hours ) );
    
    print $q->td( { -rowspan => '2' },
		  "Image type: ",
		  $q->popup_menu( -name => 'imageType',
				  -values => ['png', 'gif'],
				  -default => defParam('imageType', 'png') ) );
    
    $default = param
    print $q->td( { -rowspan => '2' },
		  "Width:",
		  $q->textfield( -name => "width",
				 -default => defParam('width', $width),
				 -size => 7 ) );
    
    print $q->td( { -rowspan => '2' },
		  "Height:",
		  $q->textfield( -name => "height",
				 -default => defParam('height', $height),
				 -size => 7 ) );

    print $q->end_Tr();

    print $q->start_Tr( { -align => 'center' } );

    print $q->td( { -align => 'right' },
		  "Duration: ",
		  $q->popup_menu( -name => 'duration',
				  -values => ['', sort {$a <=> $b} keys %hours],
				  -labels => \%hours ) );

    print $q->end_Tr();

    print $q->end_table();

    print $q->start_table( { align => 'center',
			     -border => '1' } );
    my @headerTableRow;
    push @headerTableRow, $q->td( i('Router') );
    push @headerTableRow, $q->td( i('Protocol') ), $q->td( i('All Protos') );
    push @headerTableRow, $q->td( i('Service') ), $q->td( i('All Svcs') );
    if (scalar &getTOSList()) {
	push @headerTableRow, $q->td( i('TOS') ), $q->td( i('All TOS') );
    }
    if (scalar &getASList()) {
	push @headerTableRow, $q->td( i('AS') ), $q->td( i('All ASes') );
    }
    push @headerTableRow, $q->td( i('Network') );
    push @headerTableRow, $q->td( i('Total') );

    print $q->Tr( { -align => 'center' }, @headerTableRow );

    foreach my $router ( 'all', sort &getRouterList() ) {
	print $q->start_Tr;

	print $q->td( { -align => 'center' }, $q->b($router),
		      $q->hidden( -name => 'router', -default => $router ) );

	print $q->td( $q->scrolling_list( -name => "${router}_protocol",
					  -values => [sort &getProtocolList()],
					  -size => 5,
					  -multiple => 'true' ) );

	print $q->td( $q->checkbox( -name => "${router}_all_protocols",
				    -value => '1',
				    -label => 'Yes' ) );
	
	print $q->td( $q->scrolling_list( -name => "${router}_service",
					  -values => [sort &getServiceList()],
					  -size => 5,
					  -multiple => 'true' ) );

	print $q->td( $q->checkbox( -name => "${router}_all_services",
				    -value => '1',
				    -label => 'Yes' ) );

	if (scalar &getTOSList()) {
	    print $q->td( $q->scrolling_list( -name => "${router}_tos",
					      -values => [sort &getTOSList()],
					      -size => 5,
					      -multiple => 'true' ) );

	    print $q->td( $q->checkbox( -name => "${router}_all_tos",
					-value => '1',
					-label => 'Yes' ) );
	}

	if (scalar &getASList()) {
	    print $q->td( $q->scrolling_list( -name => "${router}_as",
					      -values => [sort &getASList()],
					      -size => 5,
					      -multiple => 'true' ) );

	    print $q->td( $q->checkbox( -name => "${router}_all_as",
					-value => '1',
					-label => 'Yes' ) );
	}

	if ($router eq 'all') {
	    print $q->td( $q->scrolling_list( -name => "${router}_network",
					      -values => [sort &getNetworkList()],
					      -size => 5,
					      -multiple => 'true' ) );
	} else {
	    print $q->td( '&nbsp;' );
	}
	
	print $q->td( $q->checkbox( -name => "${router}_total",
				    -value => '1',
				    -label => 'Yes') );
	
	print $q->end_Tr;
    }
    print $q->end_table();
    
    print $q->br;

    print $q->hidden('showmenu','1');
    print $q->hidden('title',$title);

    print $q->center( $q->submit( -name => '',
				  -value => 'Generate graph' ),
		      $q->checkbox( -name => 'legend',
				    -checked => 'ON',
				    -value => '1',
				    -label => 'Legend?' ) );

    print $q->end_form;

    print $q->end_html;    
    exit;
}

sub browserDie {
    print header;
    print start_html(-title => 'Error Occurred',
		     -bgcolor => 'ffffff');
    print '<pre>', "\n";
    print @_;
    print "\n", '</pre>', "\n";
    exit;
}

## Parse param()

sub getImageType {
    if( param('imageType') ) {
	if( param('imageType') eq 'png' || param('imageType') eq 'gif' ) {
	    $imageType = param('imageType');
	} else { &browserDie('Invalid imageType parameter') }
    }
}

sub getRouter {
    if( !param('router') ) {
	push @router, 'all';
    }
    # XXX how much is tainting a problem? .. attacks, etc
    else {
	foreach ( param('router') ) {
	    s/\.\./_/g;
	    if( $_ eq 'all' ) { push @router, 'all' }
	    elsif( -d $rrddir.'/'.$_ ) { push @router, $_ }
	    else { &browserDie('Invalid router parameter') }
	}
    }
}

sub getHours {
    if( param('hours') ) {
	if( param('hours') =~ /^\d+$/ ) { $hours = param('hours') }
	else { &browserDie( "Invalid hours parameter" ) }
    }
}

sub getDuration {
    if( param('duration') ) {
	if( param('duration') =~ /^\d+$/ ) { $duration = param('duration') }
	else { &browserDie( "Invalid duration parameter" ) }
    } else { $duration = $hours; }
}

sub getWidth {
    if( param('width') ) {
	if( param('width') =~ /^\d+$/ ) { $width = param('width') }
	else { &browserDie( "Invalid width parameter" ) }
    }
}

sub getHeight {
    if( param('height') ) {
	if( param('height') =~ /^\d+$/ ) { $height = param('height') }
	else { &browserDie( "Invalid height parameter" ) }
    }
}

sub getTotal {
    foreach my $r (@router) {
	if( param("${r}_all") ) {
	    $total{$r} = 1;
	}
	elsif( param("${r}_total") ) {
	    $total{$r} = param("${r}_total");
	}
    }
}

sub getTitle {
  if ( param('title') ) {
    $title = param('title');
  }
}

sub getDebug {
    if( param('debug') && param('debug') eq '1' ) {
	$debug = 1;
    } else { $debug = 0 }
}

sub doReport {
    defined param('report') or &browserDie( "You must specify report type" );

    return &generateImage( &io_report( param('report'), param('legend') ) );
}

# Generate list of protocols and resolve filenames
sub getProtocols {
    foreach my $r (@router) {
	if( param("${r}_all_protocols") ) {
	    push @{$protocol{$r}}, &getProtocolList();
	}
	elsif( param("${r}_protocol") ) {
	    push @{$protocol{$r}}, param("${r}_protocol");
	}
	foreach my $p ( @{$protocol{$r}} ) {
	    my $file;
	    if( $r eq 'all' ) { $file = "${rrddir}/protocol_${p}.rrd"}
	    else { $file =  "${rrddir}/${r}/protocol_${p}.rrd"}
	    -f $file or &browserDie("cannot find $file");
	    $filename{$r}{$p} = $file;
	}
	if( $r eq 'all' ) { $filename{$r}{'total'} = "${rrddir}/total.rrd" }
	else { $filename{$r}{'total'} = "${rrddir}/${r}/total.rrd" }
    }
}

# Generate list of services and resolve filenames
sub getServices {
    foreach my $r (@router) {
	if( param("${r}_all_services") ) {
	    push @{$service{$r}}, &getServiceList();
	}
	elsif( param("${r}_service") ) {
	    push @{$service{$r}}, param("${r}_service");
	}
	foreach my $s ( @{$service{$r}} ) {
	    my ($file_base, $file_src, $file_dst);
	    if( $r eq 'all' ) {
		$file_base = "${rrddir}/service_${s}";
		$file_src = "${rrddir}/service_${s}_src.rrd";
		$file_dst = "${rrddir}/service_${s}_dst.rrd";
	    } else {
		$file_base = "${rrddir}/${r}/service_${s}";
		$file_src = "${rrddir}/${r}/service_${s}_src.rrd";
		$file_dst = "${rrddir}/${r}/service_${s}_dst.rrd";
	    }
	    -f $file_src or &browserDie("cannot find $file_src");
	    -f $file_dst or &browserDie("cannot find $file_dst");
	    $filename{$r}{$s} = $file_base;
	}
    }
}

# Generate list of TOS and resolve filenames
sub getTOS {
    foreach my $r (@router) {
	if( param("${r}_all_tos") ) {
	    push @{$tos{$r}}, &getTOSList();
	}
	elsif( param("${r}_tos") ) {
	    push @{$tos{$r}}, param("${r}_tos");
	}
	foreach my $t ( @{$tos{$r}} ) {
	    my $file;
	    if( $r eq 'all' ) { $file = "${rrddir}/tos_${t}.rrd"}
	    else { $file =  "${rrddir}/${r}/tos_${t}.rrd"}
	    -f $file or &browserDie("cannot find $file");
	    $filename{$r}{$t} = $file;
	}
	if( $r eq 'all' ) { $filename{$r}{'total'} = "${rrddir}/total.rrd" }
	else { $filename{$r}{'total'} = "${rrddir}/${r}/total.rrd" }
    }
}

# Generate list of networks and resolve filenames
sub getNetworks {
    # Networks are only in the all category, for total traffic

    if( param("all_network") ) {
	push @{$network{'all'}}, param("all_network");
    }
    
    foreach my $n ( param("all_network") ) {
	my $file = "${rrddir}/network_${n}.rrd";

	-f $file or &browserDie("cannot find $file");

	$filename{'all'}{$n} = $file;
    }
}

# Generate a list of ASes and resolve filenames
sub getASs {
    foreach my $r (@router) {
	if( param("${r}_all_as") ) {
	    push @{$as{$r}}, &getASList();
	}
	elsif( param("${r}_as") ) {
	    push @{$as{$r}}, param("${r}_as");
	}
	foreach my $t ( @{$as{$r}} ) {
	    my $file;
	    if( $r eq 'all' ) { $file = "${rrddir}/as_${t}.rrd"}
	    else { $file =  "${rrddir}/${r}/as_${t}.rrd"}
	    -f $file or &browserDie("cannot find $file");
	    $filename{$r}{$t} = $file;
	}
	if( $r eq 'all' ) { $filename{$r}{'total'} = "${rrddir}/total.rrd" }
	else { $filename{$r}{'total'} = "${rrddir}/${r}/total.rrd" }
    }
}

## Assign each protocol/service a color

sub setColors {
    # "nice" colors. taken from Packeteer PacketShaper's web interface
    # (via Dave Plonka) and other places
    my @safe_colors = ( 0x746FAE, # lavender
			0x993366, # maroon
  			0xB8860B, # dark goldenrod
  			0xCCFFFF, # lt. cyan
  			0x660066, # purple
  			0xFF6666, # orange
  			0x0066CC, # med. blue
  			0xCCCCFF, # pale lavender
  			0x000066, # dk. blue
  			0x0000FF, # blue
  			0xFFFF00 # yellow
  			);

    foreach my $r (@router) {
	foreach my $n (@{$network{$r}}) {
	    $color{$r}{$n} = &iterateColor(\@safe_colors);
	}

	foreach my $a (@{$as{$r}}) {
	    $color{$r}{$a} = &iterateColor(\@safe_colors);
	}

	foreach my $p (@{$protocol{$r}}) {
	    $color{$r}{$p} = &iterateColor(\@safe_colors);
	}
	
	foreach my $s (@{$service{$r}}) {
	    $color{$r}{$s}{'src'} = &iterateColor(\@safe_colors);
	    $color{$r}{$s}{'dst'} = &iterateColor(\@safe_colors);
	}

	foreach my $t (@{$tos{$r}}) {
	    $color{$r}{$t} = &iterateColor(\@safe_colors);
	}

	$color{$r}{'total'} = &iterateColor(\@safe_colors);
    }
}

# use a color and move it to the back
sub iterateColor {
    my $color = shift @{$_[0]};
    push @{$_[0]}, $color;

    return sprintf('#%06x', $color);
}

# Generate list of available protocols
sub getProtocolList {
    opendir( DIR, $rrddir ) or &browserDie("open $rrddir failed ($!)");
    @_ = grep { /^protocol_.*\.rrd$/ } readdir( DIR );
    closedir DIR;
    
    foreach (@_) {
	s/^protocol_(.*)\.rrd$/$1/;
    }

    return @_;
}

# Generate list of available services
sub getServiceList {
    opendir( DIR, $rrddir ) or &browserDie("open $rrddir failed ($!)");
    @_ = grep { /^service_.*_src\.rrd$/ } readdir( DIR );
    closedir DIR;

    foreach (@_) {
	s/^service_(.*)_src\.rrd$/$1/;
    }

    return @_;
}

# Generate list of available TOS
sub getTOSList {
    opendir( DIR, $rrddir ) or &browserDie("open $rrddir failed ($!)");
    @_ = grep { /^tos_.*\.rrd$/ } readdir( DIR );
    closedir DIR;

    foreach (@_) {
	s/^tos_(.*)\.rrd$/$1/;
    }

    return @_;
}

# Generate list of available ASes
sub getASList {
    opendir( DIR, $rrddir ) or &browserDie("open $rrddir failed ($!)");
    @_ = grep { /^as_.*\.rrd$/ } readdir( DIR );
    closedir DIR;

    foreach (@_) {
	s/^as_(.*)\.rrd$/$1/;
    }

    return @_;
}

# Generate list of available networks
sub getNetworkList {
    opendir( DIR, $rrddir ) or &browserDie("open $rrddir failed ($!)");
    @_ = grep { /^network_.*\.rrd$/ } readdir( DIR );
    closedir DIR;

    foreach (@_) {
	s/^network_(.*)\.rrd$/$1/;
    }

    return @_;
}

# Generate list of available routers
sub getRouterList {
    opendir( DIR, $rrddir ) or &browserDie("open $rrddir failed ($!)");
    while( $_ = readdir( DIR ) ) {
	if( !/^\.\.?/ && -d $rrddir.'/'.$_ ) {
	    push @_, $_;
	}
    }
    closedir DIR;

    return @_;
}

# rrdtool has annoying format rules so just use MD5 like cricket does
sub cleanDEF {
    my $def = shift;

    return $def if $debug;

    unless( exists $cdef{$def} ) {
	$cdef{$def} = substr( md5_hex($def), 0, 29);
    }
    return $cdef{$def};
}

# make service labels a consistent length
sub cleanServiceLabel {
    my $labelLength = 15;
    my $s = shift;
    my $txt = shift;
    return uc($s) . ' ' x ($labelLength - length $s) . $txt;
}

# make protocol labels a consistent length
sub cleanProtocolLabel {
    my $labelLength = 47;
    my $p = shift;
    return uc($p) . ' ' x ($labelLength - length $p);
}

# make other percentage labels a consistent length
sub cleanOtherLabel {
    my $labelLength = 51;
    my $label = shift;
    my $format = shift;
    return $label . ' ' x ($labelLength - length $label) . $format;
}

sub io_report {
    my $reportType = shift;
    my $legend = shift;
    my @args;
    my @str;

    unless( exists $reportName{$reportType} ) {
	&browserDie('invalid report parameter');
    }

    push @args, ('--interlaced',
		 '--imgformat='.uc($imageType),
		 '--vertical-label='.$reportName{$reportType}.' per second',
		 "--title=${title}, \u${reportName{$reportType}}, +out/-in",
		 "--start=".(time - $hours*60*60),
		 "--end=".(time - $hours*60*60 + $duration*60*60),
		 "--width=${width}",
		 "--height=${height}",
		 '--alt-autoscale');

    push @args, '--no-legend' unless($legend);

    # CDEF for total
    foreach my $r (@router) {
	if( $reportType eq 'bits' ) {
	    push @args, ('DEF:'.&cleanDEF("${r}_total_out_bytes").'='.$filename{$r}{'total'}.':out_bytes:AVERAGE',
			 'DEF:'.&cleanDEF("${r}_total_in_bytes").'='.$filename{$r}{'total'}.':in_bytes:AVERAGE',
			 'CDEF:'.&cleanDEF("${r}_total_out_bits").'='.&cleanDEF("${r}_total_out_bytes").',8,*',
			 'CDEF:'.&cleanDEF("${r}_total_in_bits").'='.&cleanDEF("${r}_total_in_bytes").',8,*',
			 'CDEF:'.&cleanDEF("${r}_total_in_bits_neg").'='.&cleanDEF("${r}_total_in_bits").',-1,*');
	} else {
	    push @args, ('DEF:'.&cleanDEF("${r}_total_out_${reportType}").'='.$filename{$r}{'total'}.":out_${reportType}:AVERAGE",
			 'DEF:'.&cleanDEF("${r}_total_in_${reportType}").'='.$filename{$r}{'total'}.":in_${reportType}:AVERAGE",
			 'CDEF:'.&cleanDEF("${r}_total_in_${reportType}_neg").'='.&cleanDEF("${r}_total_in_${reportType}").',-1,*');
	}
    }
	
    # CDEFs for each service
    foreach my $r (@router) {
	foreach my $s (@{$service{$r}}) {
	    if( $reportType eq 'bits' ) {
		push @args, ('DEF:'.&cleanDEF("${r}_${s}_src_out_bytes").'='.$filename{$r}{$s}.'_src.rrd:out_bytes:AVERAGE',
			     'DEF:'.&cleanDEF("${r}_${s}_src_in_bytes").'='.$filename{$r}{$s}.'_src.rrd:in_bytes:AVERAGE',
			     'CDEF:'.&cleanDEF("${r}_${s}_src_out_bits").'='.&cleanDEF("${r}_${s}_src_out_bytes").',8,*',
			     'CDEF:'.&cleanDEF("${r}_${s}_src_in_bits").'='.&cleanDEF("${r}_${s}_src_in_bytes").',8,*',
			     'CDEF:'.&cleanDEF("${r}_${s}_src_in_bits_neg").'='.&cleanDEF("${r}_${s}_src_in_bytes").',8,*,-1,*',
			     'DEF:'.&cleanDEF("${r}_${s}_dst_out_bytes").'='.$filename{$r}{$s}.'_dst.rrd:out_bytes:AVERAGE',
			     'DEF:'.&cleanDEF("${r}_${s}_dst_in_bytes").'='.$filename{$r}{$s}.'_dst.rrd:in_bytes:AVERAGE',
			     'CDEF:'.&cleanDEF("${r}_${s}_dst_out_bits").'='.&cleanDEF("${r}_${s}_dst_out_bytes").',8,*',
			     'CDEF:'.&cleanDEF("${r}_${s}_dst_in_bits").'='.&cleanDEF("${r}_${s}_dst_in_bytes").',8,*',
			     'CDEF:'.&cleanDEF("${r}_${s}_dst_in_bits_neg").'='.&cleanDEF("${r}_${s}_dst_in_bytes").',8,*,-1,*');
	    } else {
		push @args, ('DEF:'.&cleanDEF("${r}_${s}_src_out_${reportType}").'='.$filename{$r}{$s}."_src.rrd:out_${reportType}:AVERAGE",
			     'DEF:'.&cleanDEF("${r}_${s}_src_in_${reportType}").'='.$filename{$r}{$s}."_src.rrd:in_${reportType}:AVERAGE",
			     'CDEF:'.&cleanDEF("${r}_${s}_src_in_${reportType}_neg").'='.&cleanDEF("${r}_${s}_src_in_${reportType}").',-1,*',
			     'DEF:'.&cleanDEF("${r}_${s}_dst_out_${reportType}").'='.$filename{$r}{$s}."_dst.rrd:out_${reportType}:AVERAGE",
			     'DEF:'.&cleanDEF("${r}_${s}_dst_in_${reportType}").'='.$filename{$r}{$s}."_dst.rrd:in_${reportType}:AVERAGE",
			     'CDEF:'.&cleanDEF("${r}_${s}_dst_in_${reportType}_neg").'='.&cleanDEF("${r}_${s}_dst_in_${reportType}").',-1,*');
	    }
	}
    }

    # CDEFs for service by number, percentage
    foreach my $r (@router) {
	if( scalar @{$service{$r}} ) {
	    foreach my $s ( @{$service{$r}} ) {
		push @args, 'CDEF:'.&cleanDEF("${r}_${s}_in_nr").'='.&cleanDEF("${r}_${s}_src_in_${reportType}").','.&cleanDEF("${r}_${s}_dst_in_${reportType}").',+';
		push @args, 'CDEF:'.&cleanDEF("${r}_${s}_in_pct").'='.&cleanDEF("${r}_${s}_src_in_${reportType}").','.&cleanDEF("${r}_${s}_dst_in_${reportType}").',+,'.&cleanDEF("${r}_total_in_${reportType}").',/,100,*';
	    }
	    
	    $str[0] = 'CDEF:'.&cleanDEF("${r}_other_service_in_nr").'='.&cleanDEF("${r}_total_in_${reportType}");
	    $str[1] = 'CDEF:'.&cleanDEF("${r}_other_service_in_pct").'=100';
	    foreach my $s ( @{$service{$r}} ) {
		$str[0] .= ','.&cleanDEF("${r}_${s}_in_nr").',-';
		$str[1] .= ','.&cleanDEF("${r}_${s}_in_pct").',-';
	    }
	    push @args, @str;
	    
	    foreach my $s ( @{$service{$r}} ) {
		push @args, 'CDEF:'.&cleanDEF("${r}_${s}_out_nr").'='.&cleanDEF("${r}_${s}_src_out_${reportType}").','.&cleanDEF("${r}_${s}_dst_out_${reportType}").',+';
		push @args, 'CDEF:'.&cleanDEF("${r}_${s}_out_pct").'='.&cleanDEF("${r}_${s}_src_out_${reportType}").','.&cleanDEF("${r}_${s}_dst_out_${reportType}").',+,'.&cleanDEF("${r}_total_out_${reportType}").',/,100,*';
	    }
	    
	    $str[0] = 'CDEF:'.&cleanDEF("${r}_other_service_out_nr").'='.&cleanDEF("${r}_total_out_${reportType}");
	    $str[1] = 'CDEF:'.&cleanDEF("${r}_other_service_out_pct").'=100';
	    foreach my $s ( @{$service{$r}} ) {
		$str[0] .= ','.&cleanDEF("${r}_${s}_out_pct").',-';
		$str[1] .= ','.&cleanDEF("${r}_${s}_out_pct").',-';
	    }
	    push @args, @str;
	}
    }
	
    # CDEFs for each protocol
    foreach my $r (@router) {
	foreach my $p ( @{$protocol{$r}} ) {
	    if( $reportType eq 'bits' ) {
		push @args, ('DEF:'.&cleanDEF("${r}_${p}_out_bytes").'='.$filename{$r}{$p}.':out_bytes:AVERAGE',
			     'DEF:'.&cleanDEF("${r}_${p}_in_bytes").'='.$filename{$r}{$p}.':in_bytes:AVERAGE',
			     'CDEF:'.&cleanDEF("${r}_${p}_out_bits").'='.&cleanDEF("${r}_${p}_out_bytes").',8,*',
			     'CDEF:'.&cleanDEF("${r}_${p}_in_bits").'='.&cleanDEF("${r}_${p}_in_bytes").',8,*',
			     'CDEF:'.&cleanDEF("${r}_${p}_in_bits_neg").'='.&cleanDEF("${r}_${p}_in_bytes").',8,*,-1,*');
	    } else {
		push @args, ('DEF:'.&cleanDEF("${r}_${p}_out_${reportType}").'='.$filename{$r}{$p}.":out_${reportType}:AVERAGE",
			     'DEF:'.&cleanDEF("${r}_${p}_in_${reportType}").'='.$filename{$r}{$p}.":in_${reportType}:AVERAGE",
			     'CDEF:'.&cleanDEF("${r}_${p}_in_${reportType}_neg").'='.&cleanDEF("${r}_${p}_in_${reportType}").',-1,*');
	    }
	}
    }

    # CDEFs for protocol by percentage
    foreach my $r (@router) {
	if( scalar @{$protocol{$r}} ) { 
	    foreach my $p ( @{$protocol{$r}} ) {
		push @args, 'CDEF:'.&cleanDEF("${r}_${p}_in_pct").'='.&cleanDEF("${r}_${p}_in_${reportType}").','.&cleanDEF("${r}_total_in_${reportType}").',/,100,*';
	    }
	    
	    $str[0] = 'CDEF:'.&cleanDEF("${r}_other_protocol_in_nr").'='.&cleanDEF("${r}_total_in_${reportType}");
	    $str[1] = 'CDEF:'.&cleanDEF("${r}_other_protocol_in_pct").'=100';
	    foreach my $p ( @{$protocol{$r}} ) {
		$str[0] .= ','.&cleanDEF("${r}_${p}_in_".$reportType).',-';
		$str[1] .= ','.&cleanDEF("${r}_${p}_in_pct").',-';
	    }
	    push @args, @str;
	    
	    foreach my $p ( @{$protocol{$r}} ) {
		push @args, 'CDEF:'.&cleanDEF("${r}_${p}_out_pct").'='.&cleanDEF("${r}_${p}_out_${reportType}").','.&cleanDEF("${r}_total_out_${reportType}").',/,100,*';
	    }
	    
	    $str[0] = 'CDEF:'.&cleanDEF("${r}_other_protocol_out_nr").'='.&cleanDEF("${r}_total_out_${reportType}");
	    $str[1] = 'CDEF:'.&cleanDEF("${r}_other_protocol_out_pct").'=100';
	    foreach my $p ( @{$protocol{$r}} ) {
		$str[0] .= ','.&cleanDEF("${r}_${p}_out_".$reportType).',-';
		$str[1] .= ','.&cleanDEF("${r}_${p}_out_pct").',-';
	    }
	    push @args, @str;
	}
    }

    # CDEFs for each AS
    foreach my $r (@router) {
	foreach my $p ( @{$as{$r}} ) {
	    if( $reportType eq 'bits' ) {
		push @args, ('DEF:'.&cleanDEF("${r}_${p}_out_bytes").'='.$filename{$r}{$p}.':out_bytes:AVERAGE',
			     'DEF:'.&cleanDEF("${r}_${p}_in_bytes").'='.$filename{$r}{$p}.':in_bytes:AVERAGE',
			     'CDEF:'.&cleanDEF("${r}_${p}_out_bits").'='.&cleanDEF("${r}_${p}_out_bytes").',8,*',
			     'CDEF:'.&cleanDEF("${r}_${p}_in_bits").'='.&cleanDEF("${r}_${p}_in_bytes").',8,*',
			     'CDEF:'.&cleanDEF("${r}_${p}_in_bits_neg").'='.&cleanDEF("${r}_${p}_in_bytes").',8,*,-1,*');
	    } else {
		push @args, ('DEF:'.&cleanDEF("${r}_${p}_out_${reportType}").'='.$filename{$r}{$p}.":out_${reportType}:AVERAGE",
			     'DEF:'.&cleanDEF("${r}_${p}_in_${reportType}").'='.$filename{$r}{$p}.":in_${reportType}:AVERAGE",
			     'CDEF:'.&cleanDEF("${r}_${p}_in_${reportType}_neg").'='.&cleanDEF("${r}_${p}_in_${reportType}").',-1,*');
	    }
	}
    }

    # CDEFs for AS by percentage
    foreach my $r (@router) {
	if( scalar @{$as{$r}} ) { 
	    foreach my $p ( @{$as{$r}} ) {
		push @args, 'CDEF:'.&cleanDEF("${r}_${p}_in_pct").'='.&cleanDEF("${r}_${p}_in_${reportType}").','.&cleanDEF("${r}_total_in_${reportType}").',/,100,*';
	    }
	    
	    $str[0] = 'CDEF:'.&cleanDEF("${r}_other_as_in_nr").'='.&cleanDEF("${r}_total_in_${reportType}");
	    $str[1] = 'CDEF:'.&cleanDEF("${r}_other_as_in_pct").'=100';
	    foreach my $p ( @{$as{$r}} ) {
		$str[0] .= ','.&cleanDEF("${r}_${p}_in_".$reportType).',-';
		$str[1] .= ','.&cleanDEF("${r}_${p}_in_pct").',-';
	    }
	    push @args, @str;
	    
	    foreach my $p ( @{$as{$r}} ) {
		push @args, 'CDEF:'.&cleanDEF("${r}_${p}_out_pct").'='.&cleanDEF("${r}_${p}_out_${reportType}").','.&cleanDEF("${r}_total_out_${reportType}").',/,100,*';
	    }
	    
	    $str[0] = 'CDEF:'.&cleanDEF("${r}_other_as_out_nr").'='.&cleanDEF("${r}_total_out_${reportType}");
	    $str[1] = 'CDEF:'.&cleanDEF("${r}_other_as_out_pct").'=100';
	    foreach my $p ( @{$as{$r}} ) {
		$str[0] .= ','.&cleanDEF("${r}_${p}_out_".$reportType).',-';
		$str[1] .= ','.&cleanDEF("${r}_${p}_out_pct").',-';
	    }
	    push @args, @str;
	}
    }

    # CDEFs for each TOS
    foreach my $r (@router) {
	foreach my $t ( @{$tos{$r}} ) {
	    if( $reportType eq 'bits' ) {
		push @args, ('DEF:'.&cleanDEF("${r}_${t}_out_bytes").'='.$filename{$r}{$t}.':out_bytes:AVERAGE',
			     'DEF:'.&cleanDEF("${r}_${t}_in_bytes").'='.$filename{$r}{$t}.':in_bytes:AVERAGE',
			     'CDEF:'.&cleanDEF("${r}_${t}_out_bits").'='.&cleanDEF("${r}_${t}_out_bytes").',8,*',
			     'CDEF:'.&cleanDEF("${r}_${t}_in_bits").'='.&cleanDEF("${r}_${t}_in_bytes").',8,*',
			     'CDEF:'.&cleanDEF("${r}_${t}_in_bits_neg").'='.&cleanDEF("${r}_${t}_in_bytes").',8,*,-1,*');
	    } else {
		push @args, ('DEF:'.&cleanDEF("${r}_${t}_out_${reportType}").'='.$filename{$r}{$t}.":out_${reportType}:AVERAGE",
			     'DEF:'.&cleanDEF("${r}_${t}_in_${reportType}").'='.$filename{$r}{$t}.":in_${reportType}:AVERAGE",
			     'CDEF:'.&cleanDEF("${r}_${t}_in_${reportType}_neg").'='.&cleanDEF("${r}_${t}_in_${reportType}").',-1,*');
	    }
	}
    }

    # CDEFs for TOS by percentage
    foreach my $r (@router) {
	if( scalar @{$tos{$r}} ) { 
	    foreach my $t ( @{$tos{$r}} ) {
		push @args, 'CDEF:'.&cleanDEF("${r}_${t}_in_pct").'='.&cleanDEF("${r}_${t}_in_${reportType}").','.&cleanDEF("${r}_total_in_${reportType}").',/,100,*';
	    }
	    
	    $str[0] = 'CDEF:'.&cleanDEF("${r}_other_tos_in_nr").'='.&cleanDEF("${r}_total_in_${reportType}");
	    $str[1] = 'CDEF:'.&cleanDEF("${r}_other_tos_in_pct").'=100';
	    foreach my $t ( @{$tos{$r}} ) {
		$str[0] .= ','.&cleanDEF("${r}_${t}_in_".$reportType).',-';
		$str[1] .= ','.&cleanDEF("${r}_${t}_in_pct").',-';
	    }
	    push @args, @str;
	    
	    foreach my $t ( @{$tos{$r}} ) {
		push @args, 'CDEF:'.&cleanDEF("${r}_${t}_out_pct").'='.&cleanDEF("${r}_${t}_out_${reportType}").','.&cleanDEF("${r}_total_out_${reportType}").',/,100,*';
	    }
	    
	    $str[0] = 'CDEF:'.&cleanDEF("${r}_other_tos_out_nr").'='.&cleanDEF("${r}_total_out_${reportType}");
	    $str[1] = 'CDEF:'.&cleanDEF("${r}_other_tos_out_pct").'=100';
	    foreach my $t ( @{$tos{$r}} ) {
		$str[0] .= ','.&cleanDEF("${r}_${t}_out_".$reportType).',-';
		$str[1] .= ','.&cleanDEF("${r}_${t}_out_pct").',-';
	    }
	    push @args, @str;
	}
    }

    # CDEFs for each network
    foreach my $n ( @{$network{'all'}} ) {
	if( $reportType eq 'bits' ) {
	    push @args, ('DEF:'.&cleanDEF("all_${n}_out_bytes").'='.$filename{'all'}{$n}.':out_bytes:AVERAGE',
			 'DEF:'.&cleanDEF("all_${n}_in_bytes").'='.$filename{'all'}{$n}.':in_bytes:AVERAGE',
			 'CDEF:'.&cleanDEF("all_${n}_out_bits").'='.&cleanDEF("all_${n}_out_bytes").',8,*',
			 'CDEF:'.&cleanDEF("all_${n}_in_bits").'='.&cleanDEF("all_${n}_in_bytes").',8,*',
			 'CDEF:'.&cleanDEF("all_${n}_in_bits_neg").'='.&cleanDEF("all_${n}_in_bytes").',8,*,-1,*');
	} else {
	    push @args, ('DEF:'.&cleanDEF("all_${n}_out_${reportType}").'='.$filename{'all'}{$n}.":out_${reportType}:AVERAGE",
			 'DEF:'.&cleanDEF("all_${n}_in_${reportType}").'='.$filename{'all'}{$n}.":in_${reportType}:AVERAGE",
			 'CDEF:'.&cleanDEF("all_${n}_in_${reportType}_neg").'='.&cleanDEF("all_${n}_in_${reportType}").',-1,*');
	}
    }

    # CDEFs for network by percentage
    if( scalar @{$network{'all'}} ) { 
	foreach my $n ( @{$network{'all'}} ) {
	    push @args, 'CDEF:'.&cleanDEF("all_${n}_in_pct").'='.&cleanDEF("all_${n}_in_${reportType}").','.&cleanDEF("all_total_in_${reportType}").',/,100,*';
	}
	    
	$str[0] = 'CDEF:'.&cleanDEF("all_other_network_in_nr").'='.&cleanDEF("all_total_in_${reportType}");
	$str[1] = 'CDEF:'.&cleanDEF("all_other_network_in_pct").'=100';
	foreach my $n ( @{$network{'all'}} ) {
	    $str[0] .= ','.&cleanDEF("all_${n}_in_".$reportType).',-';
	    $str[1] .= ','.&cleanDEF("all_${n}_in_pct").',-';
	}
	push @args, @str;
	    
	foreach my $n ( @{$network{'all'}} ) {
	    push @args, 'CDEF:'.&cleanDEF("all_${n}_out_pct").'='.&cleanDEF("all_${n}_out_${reportType}").','.&cleanDEF("all_total_out_${reportType}").',/,100,*';
	}
	    
	$str[0] = 'CDEF:'.&cleanDEF("all_other_network_out_nr").'='.&cleanDEF("all_total_out_${reportType}");
	$str[1] = 'CDEF:'.&cleanDEF("all_other_network_out_pct").'=100';
	foreach my $n ( @{$network{'all'}} ) {
	    $str[0] .= ','.&cleanDEF("all_${n}_out_".$reportType).',-';
	    $str[1] .= ','.&cleanDEF("all_${n}_out_pct").',-';
	}
	push @args, @str;
    }
	
    # Graph commands
    my $count;
	
    foreach my $r (@router) {
	$count = 0;

	# router name
	if( scalar @{$service{$r}} || scalar @{$protocol{$r}} || scalar @{$tos{$r}}
	    || exists $total{$r} ) {
	    if( $RRDs::VERSION >= 1.2 ) {
		push @args, 'COMMENT: Router\: '.$r.'\n';
	    } else {
		push @args, 'COMMENT: Router: '.$r.'\n';
	    }
	}

	# service outbound, numbers & percentages
	foreach my $s ( @{$service{$r}} ) {
	    $count++;
	    if( $count == 1 ) {
		push @args, 'AREA:'.&cleanDEF("${r}_${s}_src_out_".$reportType).$color{$r}{$s}{'src'}.':'.&cleanServiceLabel($s, ' src  +');
	    } else {
		push @args, 'STACK:'.&cleanDEF("${r}_${s}_src_out_".$reportType).$color{$r}{$s}{'src'}.':'.&cleanServiceLabel($s, ' src  +');
	    }
	    push @args, 'STACK:'.&cleanDEF("${r}_${s}_dst_out_".$reportType).$color{$r}{$s}{'dst'}.':'.&cleanServiceLabel($s, ' dst  ');
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${s}_out_nr").':AVERAGE:%.2lf%S';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${s}_out_pct").':AVERAGE:(%.1lf%%) Out ';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${s}_in_nr").':AVERAGE:%.1lf%S';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${s}_in_pct").':AVERAGE:(%.1lf%%) In\n';
	}
	
	# protocol outbound, numbers & percentages
	foreach my $p ( @{$protocol{$r}} ) {
	    $count++;
	    if( $count == 1 ) {
		push @args, 'AREA:'.&cleanDEF("${r}_${p}_out_".$reportType).$color{$r}{$p}.':'.&cleanProtocolLabel($p);
	    } else {
		push @args, 'STACK:'.&cleanDEF("${r}_${p}_out_".$reportType).$color{$r}{$p}.':'.&cleanProtocolLabel($p);
	    }
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${p}_out_".$reportType).':AVERAGE:%.2lf%S';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${p}_out_pct").':AVERAGE:(%.1lf%%) Out ';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${p}_in_".$reportType).':AVERAGE:%.2lf%S';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${p}_in_pct").':AVERAGE:(%.1lf%%) In\n';
	}

	# tos outbound, numbers & percentages
	foreach my $t ( @{$tos{$r}} ) {
	    $count++;
	    if( $count == 1 ) {
		push @args, 'AREA:'.&cleanDEF("${r}_${t}_out_".$reportType).$color{$r}{$t}.':'.&cleanProtocolLabel($t);
	    } else {
		push @args, 'STACK:'.&cleanDEF("${r}_${t}_out_".$reportType).$color{$r}{$t}.':'.&cleanProtocolLabel($t);
	    }
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${t}_out_".$reportType).':AVERAGE:%.2lf%S';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${t}_out_pct").':AVERAGE:(%.1lf%%) Out ';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${t}_in_".$reportType).':AVERAGE:%.2lf%S';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${t}_in_pct").':AVERAGE:(%.1lf%%) In\n';
	}
	
	# network outbound, numbers & percentages
	foreach my $n ( @{$network{$r}} ) {
	    $count++;
	    if( $count == 1 ) {
		push @args, 'AREA:'.&cleanDEF("${r}_${n}_out_".$reportType).$color{$r}{$n}.':'.&cleanProtocolLabel($n);
	    } else {
		push @args, 'STACK:'.&cleanDEF("${r}_${n}_out_".$reportType).$color{$r}{$n}.':'.&cleanProtocolLabel($n);
	    }
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${n}_out_".$reportType).':AVERAGE:%.2lf%S';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${n}_out_pct").':AVERAGE:(%.1lf%%) Out ';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${n}_in_".$reportType).':AVERAGE:%.2lf%S';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${n}_in_pct").':AVERAGE:(%.1lf%%) In\n';
	}

	# AS outbound, numbers & percentages
	foreach my $n ( @{$as{$r}} ) {
	    $count++;
	    if( $count == 1 ) {
		push @args, 'AREA:'.&cleanDEF("${r}_${n}_out_".$reportType).$color{$r}{$n}.':'.&cleanProtocolLabel($n);
	    } else {
		push @args, 'STACK:'.&cleanDEF("${r}_${n}_out_".$reportType).$color{$r}{$n}.':'.&cleanProtocolLabel($n);
	    }
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${n}_out_".$reportType).':AVERAGE:%.2lf%S';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${n}_out_pct").':AVERAGE:(%.1lf%%) Out ';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${n}_in_".$reportType).':AVERAGE:%.2lf%S';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_${n}_in_pct").':AVERAGE:(%.1lf%%) In\n';
	}

	# service other, numbers & percentages
	if( scalar @{$service{$r}} ) {
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_service_out_nr").':AVERAGE:'.&cleanOtherLabel('Other services','%.2lf%S');
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_service_out_pct").':AVERAGE:(%.1lf%%) Out ';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_service_in_nr").':AVERAGE:%.2lf%S';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_service_in_pct").':AVERAGE:(%.1lf%%) In\n';
	}
	# protocol other, numbers & percentages
	if( scalar @{$protocol{$r}} ) {
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_protocol_out_pct").':AVERAGE:'.&cleanOtherLabel('Other protocols','%.2lf%S');
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_protocol_out_pct").':AVERAGE:(%.1lf%%) Out ';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_protocol_in_pct").':AVERAGE:%.2lf%S';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_protocol_in_pct").':AVERAGE:(%.1lf%%) In\n';
	}
	# tos other, numbers & percentages
	if( scalar @{$tos{$r}} ) {
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_tos_out_pct").':AVERAGE:'.&cleanOtherLabel('Other TOS','%.2lf%S');
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_tos_out_pct").':AVERAGE:(%.1lf%%) Out ';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_tos_in_pct").':AVERAGE:%.2lf%S';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_tos_in_pct").':AVERAGE:(%.1lf%%) In\n';
	}
	# network other, numbers & percentages
	if( scalar @{$network{$r}} ) {
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_network_out_pct").':AVERAGE:'.&cleanOtherLabel('Other networks','%.2lf%S');
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_network_out_pct").':AVERAGE:(%.1lf%%) Out ';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_network_in_pct").':AVERAGE:%.2lf%S';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_network_in_pct").':AVERAGE:%.1lf%% In\n';
	}
	# AS other, numbers & percentages
	if( scalar @{$as{$r}} ) {
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_as_out_pct").':AVERAGE:'.&cleanOtherLabel('Other ASes','%.2lf%S');
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_as_out_pct").':AVERAGE:(%.1lf%%) Out ';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_as_in_pct").':AVERAGE:%.2lf%S';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_other_as_in_pct").':AVERAGE:(%.1lf%%) In\n';
	}
	# total outbound, numbers
	if( exists $total{$r} ) {
	    push @args, 'LINE1:'.&cleanDEF("${r}_total_out_".$reportType).$color{$r}{'total'}.':'.&cleanProtocolLabel('TOTAL');
	    push @args, 'GPRINT:'.&cleanDEF("${r}_total_out_".$reportType).':AVERAGE:%.2lf%S Out ';
	    push @args, 'GPRINT:'.&cleanDEF("${r}_total_in_".$reportType).':AVERAGE:%.2lf%S In\n';
	}
	
	$count = 0;

	# service inbound
	foreach my $s ( @{$service{$r}} ) {
	    $count++;
	    if( $count == 1 ) {
		push @args, 'AREA:'.&cleanDEF("${r}_${s}_src_in_".$reportType.'_neg').$color{$r}{$s}{'src'};
	    } else {
		push @args, 'STACK:'.&cleanDEF("${r}_${s}_src_in_".$reportType.'_neg').$color{$r}{$s}{'src'};
	    }
	    push @args, 'STACK:'.&cleanDEF("${r}_${s}_dst_in_".$reportType.'_neg').$color{$r}{$s}{'dst'};
	}

	# protocol inbound
	foreach my $p ( @{$protocol{$r}} ) {
	    $count++;
	    if( $count == 1 ) {
		push @args, 'AREA:'.&cleanDEF("${r}_${p}_in_".$reportType.'_neg').$color{$r}{$p};
	    } else {
		push @args, 'STACK:'.&cleanDEF("${r}_${p}_in_".$reportType.'_neg').$color{$r}{$p};
	    }
	}

	# TOS inbound
	foreach my $t ( @{$tos{$r}} ) {
	    $count++;
	    if( $count == 1 ) {
		push @args, 'AREA:'.&cleanDEF("${r}_${t}_in_".$reportType.'_neg').$color{$r}{$t};
	    } else {
		push @args, 'STACK:'.&cleanDEF("${r}_${t}_in_".$reportType.'_neg').$color{$r}{$t};
	    }
	}

	# network inbound
	foreach my $n ( @{$network{$r}} ) {
	    $count++;
	    if( $count == 1 ) {
		push @args, 'AREA:'.&cleanDEF("${r}_${n}_in_".$reportType.'_neg').$color{$r}{$n};
	    } else {
		push @args, 'STACK:'.&cleanDEF("${r}_${n}_in_".$reportType.'_neg').$color{$r}{$n};
	    }
	}

	# AS inbound
	foreach my $n ( @{$as{$r}} ) {
	    $count++;
	    if( $count == 1 ) {
		push @args, 'AREA:'.&cleanDEF("${r}_${n}_in_".$reportType.'_neg').$color{$r}{$n};
	    } else {
		push @args, 'STACK:'.&cleanDEF("${r}_${n}_in_".$reportType.'_neg').$color{$r}{$n};
	    }
	}

	# total inbound
	if( exists $total{$r} ) {
	    push @args, 'LINE1:'.&cleanDEF("${r}_total_in_".$reportType.'_neg').$color{$r}{'total'};
	}

	# blank line after router
	if( scalar @{$service{$r}} || scalar @{$protocol{$r}} || scalar @{$tos{$r}} ||
	    exists $total{$r} ) {
	    push @args, 'COMMENT:\n';
	}
    }

    push @args, 'HRULE:0#000000';
	
    return @args;
}
