* AS-Stats modifications for public release

This commit is contained in:
Manuel Kasper 2008-02-19 15:43:28 +00:00
parent 9b65e833bd
commit f0daa95e46
15 changed files with 39398 additions and 72 deletions

145
README Normal file
View File

@ -0,0 +1,145 @@
AS-Stats v1 (2008-02-19)
a simple tool to generate per-AS traffic graphs from NetFlow records
by Manuel Kasper, Monzoon Networks AG <mkasper@monzoon.net>
--------------------------------------------------------------------
How it works
------------
A Perl script (netflow-asstatd.pl) collects NetFlow v8 AS aggregation records
from one or more routers. It caches them for about a minute (to prevent
excessive writes to RRD files), identifies the link that each record refers
to (by means of the SNMP in/out interface index), maps it to a corresponding
"known link" and RRD data source, and then runs RRDtool. To avoid losing
new NetFlow records while the RRD files are updated, the update task is
run in a separate process.
For each AS, a separate RRD file is created as needed. It contains two data
sources for each link - one for inbound and one for outbound traffic.
In generated per-AS traffic graphs, inbound traffic is shown as positive,
while outbound traffic is shown as negative values.
Another Perl script, rrd-extractstats.pl, is meant to run about once per hour.
It sums up per-AS and link traffic during the last 24 hours, sorts the ASes
by total traffic (descending) and writes the results to a text file. This
is then used to display the "top N AS" and other stats by the provided PHP
scripts.
Prerequisites
-------------
- Perl 5.8
- RRDtool 1.2 (with Perl "RRDs" library)
- web server with PHP 5
- one or more routers than can generate NetFlow v8 AS aggregation records
Installation
------------
- Copy the perl scripts netflow-asstatd.pl and rrd-extractstats.pl to the
machine that will collect NetFlow records
- Create a "known links" file with the following information about each
link that you want to appear in your AS stats:
- IP address of router (= source IP of NetFlow datagrams)
- SNMP interface index of interface (use "show snmp mib ifmib ifindex"
to find out)
- a short "tag" (15 chars max., alphanumerics only) that will be used
internally (e.g. for RRD DS names)
- a human-readable description (will appear in the generated graphs)
- a color code for the graphs (HTML style, 6 hex digits)
See the example file provided (netflow-knownlinks) for the format.
- Create a directory to hold per-AS RRD files. For each AS, about 128 KB of
storage are required, and there could be (in theory) up to 64511 ASes.
- Start netflow-asstatd.pl in the background (or, better yet, write a
startup script for your operating system to automatically start
netflow-asstatd.pl on boot):
nohup netflow-asstatd.pl /path/to/rrd/dir /path/to/knownlinks &
By default, netflow-asstatd.pl will listen on port 9000 (UDP) for NetFlow
datagrams. Edit $server_port in the script if you want to change that.
It's a good idea to make sure only UDP datagrams from your trusted routers
will reach the machine running netflow-asstatd.pl (firewall etc.).
- Have your router(s) send NetFlow v8 AS aggregation records to your machine.
This is typically done with commands like the following (Cisco IOS):
ip flow-cache timeout active 5
! enable ip flow ingress on all interfaces listed in your
! "known links" file
int Gi0/x.y
ip flow ingress
ip flow-export source <source interface>
ip flow-export version 5 origin-as
ip flow-aggregation cache as
cache timeout active 5
export destination <IP address of server running AS stats> 9000
enabled
Note that the version has to be specified as 5, even though the AS
aggregation records will actually be v8. Also, setting the global flow
cache timeout to 5 minutes is necessary to get "smooth" traffic graphs
(default is 30 minutes), as a flow is only counted when it expires from
the cache. Decreasing the flow-cache timeout may result in a slight
increase in CPU usage (and NetFlow AS aggregation takes its fair share of
CPU as well, of course).
- Wait 1-2 minutes. You should then see new RRD files popping up in the
directory that you defined/created earlier on. If not, make sure that
netflow-asstatd.pl is running, not spewing out any error messages, and that
the NetFlow datagrams are actually reaching your machine (tcpdump...).
- Add a cronjob to run the following command every hour:
rrd-extractstats.pl /path/to/rrd/dir /path/to/knownlinks \
/path/to/asstats_day.txt
That script will go through all RRD files and collect per-link summary
stats for each AS, sort them by total traffic (descending), and write them
to a text file. The "top N AS" page uses this to determine which ASes to show.
- Copy the contents of the "www" directory to somewhere within your web server's
document root and change file paths in func.inc as necessary.
- Wait a few hours for data to accumulate. :)
- Access the provided PHP scripts via your web server and marvel at the
(hopefully) beautiful graphs.
Adding a new link
-----------------
Adding a new link involves adding two new data sources to all RRD files.
This is a bit of a PITA since RRDtool itself doesn't provide a command to do
that. A simple (but slow) Perl script that is meant to be used with RRDtool's
XML dump/restore feature is provided (add_ds_proc.pl, add_ds.sh). Note that
netflow-asstatd.pl should be stopped while modifying RRD files, to avoid
breaking them with concurrent modifications.
Changing the RRAs
-----------------
By default, the created RRDs keep data as follows:
* 48 hours at 5 minute resolution
* 1 week at 1 hour resolution
* 1 year at 1 day resolution
If you want to change that, modify the getrrdfile() function in
netflow-asstatd.pl and delete any old RRD files.
To do
-----
- rrd-extractstats.pl uses a lot of memory and could probably use some
optimization.

View File

@ -2,29 +2,29 @@
#
# $Id$
#
# (c) 2008 Monzoon Networks AG. All rights reserved.
# written by Manuel Kasper, Monzoon Networks AG <mkasper@monzoon.net>
use strict;
use IO::Socket;
use RRDs;
my %knownlinks = (
# key format is "<router IP>_<SNMP ifindex>"
# max. alias length is 16 characters; only [a-zA-Z0-9] allowed
'80.254.79.250_44' => 'tix',
'80.254.79.250_45' => 'sunrise',
'80.254.79.250_47' => 'swissixzrh',
'80.254.79.250_65' => 'dtag',
'80.254.79.251_7' => 'colt',
'80.254.79.251_8' => 'swissixglb'
);
if ($#ARGV != 1) {
die("Usage: $0 <path to RRD file directory> <path to known links file>\n");
}
my $rrdpath = $ARGV[0];
my $knownlinksfile = $ARGV[1];
if (! -d $rrdpath) {
die("$rrdpath does not exist or is not a directory\n");
}
my %knownlinks;
my $ascache = {};
my $ascache_lastflush = 0;
my $ascache_flush_interval = 60;
my $rrdpath = "/var/db/netflow/rrd";
my $server_port = 9000;
my $MAXREAD = 8192;
my $header_len = 28;
@ -47,6 +47,9 @@ sub TERM {
exit 0;
}
# read known links file
read_knownlinks();
# prepare to listen for NetFlow UDP packets
my $server = IO::Socket::INET->new(LocalPort => $server_port, Proto => "udp")
or die "Couldn't be a udp server on port $server_port : $@\n";
@ -195,9 +198,9 @@ sub getrrdfile {
push(@args, "DS:${alias}_in:ABSOLUTE:300:U:U");
push(@args, "DS:${alias}_out:ABSOLUTE:300:U:U");
}
push(@args, "RRA:AVERAGE:0:1:576"); # 48 hours at 5 minute resolution
push(@args, "RRA:AVERAGE:0:12:168"); # 1 week at 1 hour resolution
push(@args, "RRA:AVERAGE:0:288:366"); # 1 year at 1 day resolution
push(@args, "RRA:AVERAGE:0.99999:1:576"); # 48 hours at 5 minute resolution
push(@args, "RRA:AVERAGE:0.99999:12:168"); # 1 week at 1 hour resolution
push(@args, "RRA:AVERAGE:0.99999:288:366"); # 1 year at 1 day resolution
RRDs::create($rrdfile, "--start", $startts, @args);
my $ERR = RRDs::error;
@ -209,3 +212,15 @@ sub getrrdfile {
return $rrdfile;
}
sub read_knownlinks {
open(KLFILE, $knownlinksfile) or die("Cannot open $knownlinksfile!");
while (<KLFILE>) {
chomp;
next if (/(^\s*#)|(^\s*$)/); # empty line or comment
my ($routerip,$ifindex,$tag,$descr,$color) = split(/\t+/);
$knownlinks{"${routerip}_${ifindex}"} = $tag;
}
close(KLFILE);
}

View File

@ -2,28 +2,24 @@
#
# $Id$
#
# (c) 2008 Monzoon Networks AG. All rights reserved.
# written by Manuel Kasper, Monzoon Networks AG <mkasper@monzoon.net>
use strict;
use RRDs;
if ($#ARGV != 0) {
die("Usage: $0 outfile\n");
if ($#ARGV != 2) {
die("Usage: $0 <path to RRD file directory> <path to known links file> outfile\n");
}
my %knownlinks = (
# key format is "<router IP>_<SNMP ifindex>"
# max. alias length is 16 characters; only [a-zA-Z0-9] allowed
'80.254.79.250_44' => 'tix',
'80.254.79.250_45' => 'sunrise',
'80.254.79.250_47' => 'swissixzrh',
'80.254.79.250_65' => 'dtag',
'80.254.79.251_7' => 'colt',
'80.254.79.251_8' => 'swissixglb'
);
my @links = values %knownlinks;
my $rrdpath = $ARGV[0];
my $knownlinksfile = $ARGV[1];
my $statsfile = $ARGV[2];
my $rrdpath = "/var/db/netflow/rrd";
my $statsfile = $ARGV[0];
my %knownlinks;
read_knownlinks();
my @links = values %knownlinks;
# walk through all RRD files in the given path and extract stats for all links
# from them; write the stats to a text file, sorted by total traffic
@ -63,7 +59,7 @@ my @asorder = sort {
return $total_b <=> $total_a;
} keys %$astraffic;
open(STATSFILE, ">$statsfile");
open(STATSFILE, ">$statsfile.tmp");
# print header line
print STATSFILE "as";
@ -86,6 +82,8 @@ foreach my $as (@asorder) {
close(STATSFILE);
rename("$statsfile.tmp", $statsfile);
sub gettraffic {
my $as = shift;
@ -132,3 +130,15 @@ sub gettraffic {
return $retdata;
}
sub read_knownlinks {
open(KLFILE, $knownlinksfile) or die("Cannot open $knownlinksfile!");
while (<KLFILE>) {
chomp;
next if (/(^\s*#)|(^\s*$)/); # empty line or comment
my ($routerip,$ifindex,$tag,$descr,$color) = split(/\t+/);
$knownlinks{"${routerip}_${ifindex}"} = $tag;
}
close(KLFILE);
}

View File

@ -1,8 +1,9 @@
# Router IP SNMP ifindex tag description color
# note: tabs must be used to separate fields (not spaces)
80.254.79.250 45 sunrise Sunrise D41C0E
80.254.79.250 65 dtag DTAG E45605
80.254.79.251 7 colt Colt FECF12
80.254.79.250 44 tix TIX 0A4484
80.254.79.250 47 swissixzrh SwissIX zrh 0A7484
80.254.79.251 8 swissixglb SwissIX glb 4CB4C4
192.0.2.1 15 uplink1 Uplink 1 D41C0E
192.0.2.1 23 uplink2 Uplink 2 E45605
192.0.2.2 4 uplink3 Uplink 3 FECF12
192.0.2.3 11 uplink4 Uplink 4 5EA631
192.0.2.2 42 peering1 IXP 1 0A4484
192.0.2.2 45 peering2 IXP 2 0A7484
192.0.2.3 6 peering3 IXP 3 4CB4C4

8
tools/add_ds.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/sh
for f in *.rrd; do
echo "file: $f"
mv $f $f.old
rrdtool dump $f.old | /path/to/add_ds_proc.pl | rrdtool restore - $f.new
mv $f.new $f
done

82
tools/add_ds_proc.pl Executable file
View File

@ -0,0 +1,82 @@
#!/usr/bin/perl
use strict;
my $ds = 2;
# change the name(s) of the new data sources here
my @dsnames = ('newlink_in', 'newlink_out');
my $default_val = 'NaN';
my $type = 'ABSOLUTE';
my $heartbeat = '300';
my $rrdmin = 'NaN';
my $rrdmax = 'NaN';
my $cdp_prep_end = '</cdp_prep>';
my $row_end = '</row>';
my $name = '<name>';
my $name_end = '</name>';
my $field = '<v> ' . $default_val . ' </v>';
my $found_ds = 0;
my $num_sources = 0;
my $last;
my $fields = " ";
my $datasource;
my $x;
while (<STDIN>) {
if (($_ =~ s/$row_end$/$fields$row_end/) && $found_ds) {
# need to hit <ds> types first, if we don't, we're screwed
print $_;
} elsif (/$cdp_prep_end/) {
for (my $j = 0; $j < $ds; $j++) {
print "\t\t\t<ds>\n" .
"\t\t\t<primary_value> 0.0000000000e+00 </primary_value>\n" .
"\t\t\t<secondary_value> 0.0000000000e+00 </secondary_value>\n" .
"\t\t\t<value> NaN </value>\n" .
"\t\t\t<unknown_datapoints> 0 </unknown_datapoints>\n" .
"\t\t\t</ds>\n";
}
print $_;
} elsif (/$name_end$/) {
($datasource) = /$name (\w+)/;
$found_ds++;
print $_;
} elsif (/Round Robin Archives/) {
# print out additional datasource definitions
($num_sources) = ($datasource =~ /(\d+)/);
for ($x = $num_sources+1; $x < $num_sources+$ds+1; $x++) {
$fields .= $field;
print "\n\t<ds>\n";
print "\t\t<name> " . $dsnames[$x-1] . " <\/name>\n";
print "\t\t<type> $type <\/type>\n";
print "\t\t<minimal_heartbeat> $heartbeat <\/minimal_heartbeat>\n";
print "\t\t<min> $rrdmin <\/min>\n";
print "\t\t<max> $rrdmax <\/max>\n\n";
print "\t\t<!-- PDP Status-->\n";
print "\t\t<last_ds> U <\/last_ds>\n";
print "\t\t<value> NaN <\/value>\n";
print "\t\t<unknown_sec> 0 <\/unknown_sec>\n";
print "\t<\/ds>\n\n";
}
print $_;
} else {
print $_;
}
$last = $_;
}

39023
www/asinfo.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,28 +2,62 @@
/*
* $Id$
*
* (c) 2008 Monzoon Networks AG. All rights reserved.
* written by Manuel Kasper, Monzoon Networks AG <mkasper@monzoon.net>
*/
$dbpath = "/var/db/netflow";
$rrdpath = "$dbpath/rrd";
$rrdpath = "/var/db/netflow/rrd";
$daystatsfile = "/var/db/netflow/asstats_day.txt";
$rrdtool = "/usr/local/bin/rrdtool";
$knownlinksfile = "/monzoon/conf/netflow-knownlinks";
$topasdayfile = "$dbpath/top50_day.txt";
$link = mysql_connect("localhost", "netflow", "ycCeDm71ck");
mysql_select_db("netflow", $link);
$asinfofile = "asinfo.txt";
$knownlinksfile = "/etc/netflow-knownlinks";
/* note: you might want to put the data from asinfo.txt into an SQL
database to avoid having to read the whole file all the time */
function getASInfo($asnum) {
$row = mysql_fetch_array(mysql_query("select * from asnums where asn='" . addslashes($asnum) . "'"));
global $asinfodb;
if (!isset($asinfodb))
$asinfodb = readasinfodb();
if ($asinfodb[$asnum])
return $asinfodb[$asnum];
else
return array(name => "AS$asnum", descr => "AS $asnum");
/*$row = mysql_fetch_array(mysql_query("select * from asnums where asn='" . addslashes($asnum) . "'"));
if ($row) {
return array(name => $row['asname'], descr => $row['descr'], country => $row['country']);
} else {
/* not found */
return array(name => "AS$asnum", descr => "AS $asnum");
}*/
}
function readasinfodb() {
global $asinfofile;
if (!file_exists($asinfofile))
return array();
$fd = fopen($asinfofile, "r");
$asinfodb = array();
while (!feof($fd)) {
$line = trim(fgets($fd));
if (preg_match("/(^\\s*#)|(^\\s*$)/", $line))
continue; /* empty line or comment */
list($asn,$asname,$descr,$country) = explode("\t", $line);
$asinfodb[$asn] = array(
name => $asname,
descr => $descr,
country => $country
);
}
fclose($fd);
return $asinfodb;
}
function getknownlinks() {
@ -52,10 +86,10 @@ function getknownlinks() {
}
function getasstats_top($ntop) {
global $dbpath;
global $daystatsfile;
/* first step: walk the data for all ASes to determine the top 5 for the given link */
$fd = fopen("$dbpath/asstats_day.txt", "r");
$fd = fopen($daystatsfile, "r");
if (!$fd)
return array();
$cols = explode("\t", trim(fgets($fd)));
@ -74,7 +108,7 @@ function getasstats_top($ntop) {
$tot_out = 0;
for ($i = 1; $i < count($els); $i++) {
if (strpos($cols[$i], "in") !== false)
if (strpos($cols[$i], "_in") !== false)
$tot_in += $els[$i];
else
$tot_out += $els[$i];
@ -101,5 +135,4 @@ function format_bytes($bytes) {
return "$bytes bytes";
}
?>

View File

@ -2,7 +2,7 @@
/*
* $Id$
*
* (c) 2008 Monzoon Networks AG. All rights reserved.
* written by Manuel Kasper, Monzoon Networks AG <mkasper@monzoon.net>
*/
require_once('func.inc');

View File

@ -2,7 +2,7 @@
/*
* $Id$
*
* (c) 2008 Monzoon Networks AG. All rights reserved.
* written by Manuel Kasper, Monzoon Networks AG <mkasper@monzoon.net>
*/
require_once('func.inc');
@ -12,10 +12,12 @@ if ($as)
$asinfo = getASInfo($as);
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Refresh" content="300" />
<title>History for AS<?php echo $as; ?>: <?php echo $asinfo['descr']; ?></title>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
@ -28,7 +30,7 @@ if ($as)
<div class="pgtitle">History for AS<?php echo $as; ?>: <?php echo $asinfo['descr']; ?></div>
<?php if (!file_exists("$rrdpath/$as.rrd")): ?>
No data found for AS <?php echo $as; ?>
<p>No data found for AS <?php echo $as; ?></p>
<?php else: ?>
<div class="title">Daily</div>
<img class="detailgraph" src="gengraph.php?as=<?php echo $as; ?>" alt="daily graph" />
@ -46,14 +48,14 @@ No data found for AS <?php echo $as; ?>
<div class="pgtitle">View history for an AS</div>
<form action="" method="GET">
AS: <input type="text" name="as" size="6">
<input type="submit" value="Go">
<form action="" method="get">
AS: <input type="text" name="as" size="6" />
<input type="submit" value="Go" />
</form>
<?php endif; ?>
<div id="footer">
&copy; 2008 Monzoon Networks AG. All rights reserved.
AS-Stats v1 written by Manuel Kasper, Monzoon Networks AG.
</div>
</body>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 B

View File

@ -2,7 +2,7 @@
/*
* $Id$
*
* (c) 2008 Monzoon Networks AG. All rights reserved.
* written by Manuel Kasper, Monzoon Networks AG <mkasper@monzoon.net>
*/
header("Location: top.php");

View File

@ -2,12 +2,15 @@
/*
* $Id$
*
* (c) 2008 Monzoon Networks AG. All rights reserved.
* written by Manuel Kasper, Monzoon Networks AG <mkasper@monzoon.net>
*/
require_once('func.inc');
$numtop = 10;
if ($_GET['numtop'] && $_GET['numtop'] <= 50)
$numtop = $_GET['numtop'];
$ascolors = array("D41C0E", "E45605", "FECF12", "2FA11C", "19BB7C", "0A4484", "0A7484", "4CB4C4", "971928", "1f348c");
$link = $_GET['link'];
@ -15,7 +18,7 @@ if (!preg_match("/^[a-z0-9]+$/", $link))
die("Invalid link");
/* first step: walk the data for all ASes to determine the top 5 for the given link */
$fd = fopen("$dbpath/asstats_day.txt", "r");
$fd = fopen($daystatsfile, "r");
$cols = explode("\t", trim(fgets($fd)));
$asstats = array();

View File

@ -2,7 +2,7 @@
/*
* $Id$
*
* (c) 2008 Monzoon Networks AG. All rights reserved.
* written by Manuel Kasper, Monzoon Networks AG <mkasper@monzoon.net>
*/
require_once('func.inc');
@ -10,10 +10,12 @@ require_once('func.inc');
$knownlinks = getknownlinks();
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Refresh" content="300" />
<title>Link usage</title>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
@ -35,7 +37,7 @@ $class = (($i % 2) == 0) ? "even" : "odd";
</div>
</th>
<td>
<img src="linkgraph.php?link=<?php echo $link['tag']; ?>&width=500&height=300" width="581" height="494" border="0">
<img alt="link graph" src="linkgraph.php?link=<?php echo $link['tag']; ?>&width=500&height=300" width="581" height="494" border="0" />
</td>
</tr>
<?php $i++; endforeach; ?>
@ -43,7 +45,7 @@ $class = (($i % 2) == 0) ? "even" : "odd";
</table>
<div id="footer">
&copy; 2008 Monzoon Networks AG. All rights reserved.
AS-Stats v1 written by Manuel Kasper, Monzoon Networks AG.
</div>
</body>

View File

@ -17,10 +17,12 @@ if ($ntop > 200)
$topas = getasstats_top($ntop);
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Refresh" content="300" />
<title>Top <?php echo $ntop; ?> AS</title>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
@ -28,7 +30,7 @@ $topas = getasstats_top($ntop);
<body>
<div id="nav">
<form action="" method="GET">
<form action="" method="get">
Number of AS:
<input type="text" name="n" size="4" value="<?php echo $ntop; ?>" />
<input type="submit" value="Go" style="margin-right: 2em" />
@ -62,7 +64,7 @@ $class = (($i % 2) == 0) ? "even" : "odd";
</div>
</th>
<td>
<a href="history.php?as=<?php echo $as; ?>" target="_blank"><img src="gengraph.php?as=<?php echo $as; ?>&width=500&height=150&nolegend=1" width="581" height="204" border="0"></a>
<a href="history.php?as=<?php echo $as; ?>" target="_blank"><img alt="AS graph" src="gengraph.php?as=<?php echo $as; ?>&width=500&height=150&nolegend=1" width="581" height="204" border="0" /></a>
</td>
</tr>
<?php $i++; endforeach; ?>
@ -88,7 +90,7 @@ foreach ($knownlinks as $link) {
</div>
<div id="footer">
&copy; 2008 Monzoon Networks AG. All rights reserved.
AS-Stats v1 written by Manuel Kasper, Monzoon Networks AG.
</div>
</body>