Okay i spent some time trying to figure this out, Asking on IRC, on stackoverflow, and superuser. No help from anywhere, So fiddling with iptables i figured it out.. Okay. here is the script used to create the firewall rules that do the IP Traffic Accounting..

#!/bin/bash
for line in $(cat /etc/openvpn/ipp.txt);do
CLIENT=$(echo $line|cut -d',' -f1)
VPN_IP=$(echo $line|cut -d',' -f2)
echo "
iptables -N ${CLIENT}_IN
iptables -N ${CLIENT}_OUT
iptables -A ${CLIENT}_IN -j RETURN
iptables -A ${CLIENT}_OUT -j RETURN
iptables -I ${CLIENT}_IN -d ${VPN_IP}
iptables -I ${CLIENT}_OUT -s ${VPN_IP}
iptables -A FORWARD -j ${CLIENT}_in
iptables -A FORWARD -j ${CLIENT}_out
"

echo "OUTGOING=\$(iptables -v -x -L ${CLIENT}_out|grep -E \"RETURN\"|cut -d' ' -f5)"
echo "INCOMING=\$(iptables -v -x -L ${CLIENT}_in|grep -E \"10\"|cut -d' ' -f5)"

done
accouting.sh

It will output something like this..

[root@vpn-01:~]# bash accounting.sh

iptables -N black_IN
iptables -N black_OUT
iptables -A black_IN -j RETURN
iptables -A black_OUT -j RETURN
iptables -I black_IN -d 10.8.0.2
iptables -I black_OUT -s 10.8.0.2
iptables -A FORWARD -j black_in
iptables -A FORWARD -j black_out

OUTGOING=$(iptables -v -x -L black_out|grep -E "RETURN"|cut -d' ' -f5)
INCOMING=$(iptables -v -x -L black_in|grep -E "10"|cut -d' ' -f5)
[root@vpn-01:~]# 
accounting.sh output

So you just run the iptables command, and if you use CentOS type

service iptables save

that will make the rules persistent and survive booting. There shouldnt be any reason to reset this. As the graphs we're going to make do 1 day, 1 week, 1 month, 1 year graphs.

Okay. Here is the website end.. For your VirtualHost. This is what you'll need to do.

yum install mod_perl rrdtool rrdtool-perl

Then your OpenVPNGraphs.conf should look like this

<VirtualHost *:80>
    DocumentRoot "/srv/beyondhd.me/"
    ServerName beyondhd.me
    ServerAlias www.beyondhd.me
	LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
	LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common
	CustomLog /var/log/httpd/beyondhd.me-access.log vhost_combined
	LogLevel warn
	ErrorLog /var/log/httpd/beyondhd.me-error.log
	<Directory /srv/beyondhd.me/>
		DirectoryIndex index.php index.cgi
		Options +ExecCGI -Indexes +FollowSymLinks 
		AllowOverride All
		Order allow,deny
		Allow from all
	</Directory>
</VirtualHost>
OpenVPNGraphs.conf

You need to adjust your values as per your system setup. Im leaving mine here, because its a working copy of this.

Now, inside your web directory, mine is /srv/beyondhd.me/. You need to put this inside index.cgi

#!/usr/bin/perl
my @graphs;
my ($name, $descr);
push (@graphs, "eth0","tun0","vpn-black");
my $svrname = $ENV{'SERVER_NAME'};

my @values = split(/&/, $ENV{'QUERY_STRING'});
foreach my $i (@values) {
	($varname, $mydata) = split(/=/, $i);
	if ($varname eq 'trend') { $name = $mydata; }
}

if ($name eq '') { $descr = "summary"; } else { $descr = "$name"; }

print "Content-type: text/html;\n\n";
print <<END
<html>
<head>
  <TITLE>$svrname network traffic :: $descr</TITLE>
  <META HTTP-EQUIV="Refresh" CONTENT="600">
  <META HTTP-EQUIV="Cache-Control" content="no-cache">
  <META HTTP-EQUIV="Pragma" CONTENT="no-cache">
  <style>
	body { topMargin: 5; align: center; background: #fff; color: #000; font-family: Tahoma, Arial, Helvetica, Sans-Serif; font-size: 0.900em; font-color: #000; }
	a { text-decoration: none; }
	a:hover { text-decoration: underline bold; }
	table { margin: auto; width: 70%; border-collapse: separate; border:solid white 1px; border-radius:9px; -moz-border-radius:9px; padding-left: 10px; padding-right: 10px; background: #242424; border: 1px solid #fff; white-space: pre-line; }
	td.main { color: #fff; background: #242424; font-size: 0.900em; padding-top: 3px; padding-bottom: 3px; white-space: pre-line; }
	td.main a { font-size: 0.900em; }
	td.main a:hover { color: #fff; font-weight: bold; }
	td { background: #242424; color: #fff; }
	tr.main td { padding-top: 2px; padding-bottom: 2px; vertical-align: top; padding-left: 10px; padding-right: 10px; white-space: pre-line; }
	pre { font-family: monospace; width: 100%; border: 1px dashed #454545; !important; }
	p { text-align: center; }

  </style>
</head>

<a href="javascript:history.go(-1)"><span class='header'>$svrname $type $descr</span></a>
<br><br>
END
;

if ($name eq '') {
	print "Daily Graphs (5 minute averages and maximums)";
	print "<br>";
		print "<a href='?trend=cpu'><img src='cpu-day.png' border='1'></a><br><br>\n";
		print "<a href='?trend=mem'><img src='mem-day.png' border='1'></a><br><br>\n";
	foreach $graph (@graphs)
	{
		print "<a href='?trend=$graph'><img src='$graph-day.png' border='1'></a><br><br>\n";
		print "<br>";
	}
} elsif ($name eq 'vpn') {
print <<END
        Daily Graph (5 minute averages and maximums)<br>
        <img src='$name-day.png'><br>
        Weekly Graph (30 minute averages and maximums)<br>
        <img src='$name-week.png'><br>
        Monthly Graph (2 hour averages and maximums)<br>
        <img src='$name-month.png'><br>
        Yearly Graph (12 hour averages and maximums)<br>
        <img src='$name-year.png'>

END
} elsif ($name eq 'memory') {
print <<END
        Daily Graph (5 minute averages and maximums)<br>
        <img src='$name-day.png'><br>
        Weekly Graph (30 minute averages and maximums)<br>
        <img src='$name-week.png'><br>
        Monthly Graph (2 hour averages and maximums)<br>
        <img src='$name-month.png'><br>
        Yearly Graph (12 hour averages and maximums)<br>
        <img src='$name-year.png'>
END
;
} elsif ($name eq 'cpu') {
print <<END
	Daily Graph (5 minute averages and maximums)<br>
	<img src='$name-day.png'><br>
	Weekly Graph (30 minute averages and maximums)<br>
	<img src='$name-week.png'><br>
	Monthly Graph (2 hour averages and maximums)<br>
	<img src='$name-month.png'><br>
	Yearly Graph (12 hour averages and maximums)<br>
	<img src='$name-year.png'>
END
;
} else {
print <<END
	Daily Graph (5 minute averages and maximums)<br>
	<img src='$name-day.png'><br>
	Weekly Graph (30 minute averages and maximums)<br>
	<img src='$name-week.png'><br>
	Monthly Graph (2 hour averages and maximums)<br>
	<img src='$name-month.png'><br>
	Yearly Graph (12 hour averages and maximums)<br>
	<img src='$name-year.png'>
END
;
}

print <<END
<br><br>
</body>
</html>
END
;
index.cgi

There is two more files we need to create.. So we're almost done. I promise :)

#!/usr/bin/perl
use RRDs;
my $rrd = '/srv/beyondhd.me/';
my $img = '/srv/beyondhd.me/';
my $rrdtool = '/usr/bin/rrdtool';
my $debug = '1';
my $name = $ARGV[0];
my $INVAL = $name."_IN";
my $OUTVAL = $name."_OUT";
my $in = `iptables -v -x -L $INVAL|grep -E "10"|cut -d' ' -f5`;
my $out = `iptables -v -x -L $OUTVAL|grep -E "RETURN"|cut -d' ' -f5`;

&ProcessVPNInterface($name, $in, $out);
sub ProcessVPNInterface
{
	chomp($in);
	chomp($out);
	if ($debug eq "1") { print "$_[0] -> in: $in out: $out\n"; }
	if (! -e "$rrd/vpn-$_[0].rrd")
	{
		print "creating rrd database for $_[0] interface...\n";
		RRDs::create "$rrd/vpn-$_[0].rrd",
			"-s", "300",
			"DS:in:DERIVE:600:0:U",
			"DS:out:DERIVE:600:0:U",
			"RRA:AVERAGE:0.5:1:576",
			"RRA:MAX:0.5:1:576",
			"RRA:AVERAGE:0.5:6:672",
			"RRA:MAX:0.5:6:672",
			"RRA:AVERAGE:0.5:24:732",
			"RRA:MAX:0.5:24:732",
			"RRA:AVERAGE:0.5:144:1460",
			"RRA:MAX:0.5:144:1460";
		if ($ERROR = RRDs::error) { print "$0: unable to create $rrd/$_[0].rrd: $ERROR\n"; }
	}
	RRDs::update "$rrd/vpn-$_[0].rrd",
		"-t", "in:out",
		"N:$in:$out";
	if ($ERROR = RRDs::error) { print "$0: unable to insert data into $rrd/vpn-$_[0].rrd: $ERROR\n"; }
	&CreateVPNGraph($_[0], "day", $_[1]);
	&CreateVPNGraph($_[0], "week", $_[1]);
	&CreateVPNGraph($_[0], "month", $_[1]); 
	&CreateVPNGraph($_[0], "year", $_[1]);
}

sub CreateVPNGraph
{
#	  $_[1]: interval (ie, day, week, month, year)
#	  $_[2]: interface description 
	RRDs::graph "$img/vpn-$_[0]-$_[1].png",
		"-s -1$_[1]",
		"-t traffic on vpn-$_[0] :: $_[1]",
		"--lazy",
		"-h", "80", "-w", "600",
		"-l 0",
		"-a", "PNG",
		"-v bytes/sec",
		"--slope-mode",
		"--color", "BACK#ffffff",
		"--color", "CANVAS#ffffff",
		"--font", "LEGEND:7",
		"DEF:in=$rrd/vpn-$_[0].rrd:in:AVERAGE",
		"DEF:maxin=$rrd/vpn-$_[0].rrd:in:MAX",
		"DEF:out=$rrd/vpn-$_[0].rrd:out:AVERAGE",
		"DEF:maxout=$rrd/vpn-$_[0].rrd:out:MAX",
		"CDEF:out_neg=out,-1,*",
		"CDEF:maxout_neg=maxout,-1,*",
		"AREA:in#32CD32:Incoming",
		"LINE1:maxin#336600",
		"GPRINT:in:MAX:  Max\\: %6.1lf %s",
		"GPRINT:in:AVERAGE: Avg\\: %6.1lf %S",
		"GPRINT:in:LAST: Current\\: %6.1lf %SBytes/sec\\n",
		"AREA:out_neg#4169E1:Outgoing",
		"LINE1:maxout_neg#0033CC",
		"GPRINT:maxout:MAX:  Max\\: %6.1lf %S",
		"GPRINT:out:AVERAGE: Avg\\: %6.1lf %S",
		"GPRINT:out:LAST: Current\\: %6.1lf %SBytes/sec\\n",
		"HRULE:0#000000";
	if ($ERROR = RRDs::error) { print "$0: unable to generate vpn-$_[0] graph: $ERROR\n"; }
}
vpnuser.pl

Now lets make it executable. Ready?

chmod +x /srv/beyondhd.me/vpnuser.pl
chmod

Now that we've created that file, lets create system.pl for CPU/MEM/NET graphing. You dont have too.. But index.cgi is configured for it, so why not, right?

#!/usr/bin/perl
use RRDs;
my $rrd = '/srv/beyondhd.me/';
my $img = '/srv/beyondhd.me/';
my $rrdtool = '/usr/bin/rrdtool';
my $debug = '1';
&ProcessInterface("eth0", "network");
&ProcessInterface("tun0", "OpenVPN");

sub ProcessInterface
{
	my $in = `/sbin/ifconfig $_[0]|grep "RX bytes"|cut -d':' -f2|cut -d' ' -f1`;
	my $out = `/sbin/ifconfig $_[0] | grep "TX bytes"|cut -d':' -f3|cut -d' ' -f1`;

#my $inawk = q{"cat /proc/net/dev|grep $_[0]|awk '{print $2}'"};

#	my $in = system("cat /proc/net/dev|grep $_[0]|cut -d' ' -f4");
#	my $out = system("cat /proc/net/dev|grep $_[0]|cut -d' ' -f45");
#print $in;
#print $out;
	chomp($in);
	chomp($out);
	if ($debug eq "1") { print "$_[0] -> in: $in out: $out\n"; }
	if (! -e "$rrd/$_[0].rrd")
	{
		print "creating rrd database for $_[0] interface...\n";
		RRDs::create "$rrd/$_[0].rrd",
			"-s", "300",
			"DS:in:DERIVE:600:0:U",
			"DS:out:DERIVE:600:0:U",
			"RRA:AVERAGE:0.5:1:576",
			"RRA:MAX:0.5:1:576",
			"RRA:AVERAGE:0.5:6:672",
			"RRA:MAX:0.5:6:672",
			"RRA:AVERAGE:0.5:24:732",
			"RRA:MAX:0.5:24:732",
			"RRA:AVERAGE:0.5:144:1460",
			"RRA:MAX:0.5:144:1460";
		if ($ERROR = RRDs::error) { print "$0: unable to create $rrd/$_[0].rrd: $ERROR\n"; }
	}
	RRDs::update "$rrd/$_[0].rrd",
		"-t", "in:out",
		"N:$in:$out";
	if ($ERROR = RRDs::error) { print "$0: unable to insert data into $rrd/$_[0].rrd: $ERROR\n"; }
	&CreateGraph($_[0], "day", $_[1]);
	&CreateGraph($_[0], "week", $_[1]);
	&CreateGraph($_[0], "month", $_[1]); 
	&CreateGraph($_[0], "year", $_[1]);
}

sub CreateGraph
{
#	  $_[1]: interval (ie, day, week, month, year)
#	  $_[2]: interface description 

	RRDs::graph "$img/$_[0]-$_[1].png",
		"-s -1$_[1]",
		"-t traffic on $_[0] :: $_[2]",
		"--lazy",
		"-h", "80", "-w", "600",
		"-l 0",
		"-a", "PNG",
		"-v bytes/sec",
		"--slope-mode",
		"--color", "BACK#ffffff",
		"--color", "CANVAS#ffffff",
		"--font", "LEGEND:7",
		"DEF:in=$rrd/$_[0].rrd:in:AVERAGE",
		"DEF:maxin=$rrd/$_[0].rrd:in:MAX",
		"DEF:out=$rrd/$_[0].rrd:out:AVERAGE",
		"DEF:maxout=$rrd/$_[0].rrd:out:MAX",
		"CDEF:out_neg=out,-1,*",
		"CDEF:maxout_neg=maxout,-1,*",
		"AREA:in#32CD32:Incoming",
		"LINE1:maxin#336600",
		"GPRINT:in:MAX:  Max\\: %6.1lf %s",
		"GPRINT:in:AVERAGE: Avg\\: %6.1lf %S",
		"GPRINT:in:LAST: Current\\: %6.1lf %SBytes/sec\\n",
		"AREA:out_neg#4169E1:Outgoing",
		"LINE1:maxout_neg#0033CC",
		"GPRINT:maxout:MAX:  Max\\: %6.1lf %S",
		"GPRINT:out:AVERAGE: Avg\\: %6.1lf %S",
		"GPRINT:out:LAST: Current\\: %6.1lf %SBytes/sec\\n",
		"HRULE:0#000000";
	if ($ERROR = RRDs::error) { print "$0: unable to generate $_[0] graph: $ERROR\n"; }
}

my $mem = `free -b |grep Mem`;
my $swap = `free -b |grep Swap |cut -c19-29 |sed 's/ //g'`;
my @mema = split(/\s+/, $mem);
my $buffers = $mema[5];
my $cached = $mema[6];
$mem = $mema[3] + $buffers + $cached;
chomp($swap);

if (! -e "$rrd/mem.rrd")
{
	print "creating rrd database for memory usage...\n";
	system("$rrdtool create $rrd/mem.rrd -s 300"
		." DS:mem:GAUGE:600:0:U"
		." DS:buf:GAUGE:600:0:U"
		." DS:cache:GAUGE:600:0:U"
		." DS:swap:GAUGE:600:0:U"
		." RRA:AVERAGE:0.5:1:576"
		." RRA:AVERAGE:0.5:6:672"
		." RRA:AVERAGE:0.5:24:732"
		." RRA:AVERAGE:0.5:144:1460");
}

`$rrdtool update $rrd/mem.rrd -t mem:buf:cache:swap N:$mem:$buffers:$cached:$swap`;

if ($debug eq "1") { print "memory -> free: $mem buffers: $buffers cached: $cached swap: $swap\n"; }

&CreateGraphMemory("day");
&CreateGraphMemory("week");
&CreateGraphMemory("month"); 
&CreateGraphMemory("year");

sub CreateGraphMemory
{

	system("$rrdtool graph $img/mem-$_[0].png"
		." -s \"-1$_[0]\""
		." -t \"memory usage over the last $_[0]\""
		." --lazy"
		." -h 80 -w 600"
		." -l 0"
		." -a PNG"
		." -v \"bytes\""
		." -b 1024"
		." DEF:mem=$rrd/mem.rrd:mem:AVERAGE"
		." DEF:buf=$rrd/mem.rrd:buf:AVERAGE"
		." DEF:cache=$rrd/mem.rrd:cache:AVERAGE"
		." DEF:swap=$rrd/mem.rrd:swap:AVERAGE"
		." CDEF:total=mem,swap,buf,cache,+,+,+"
		." CDEF:res=mem,buf,cache,+,+"
		." AREA:mem#FFCC66:\"Physical Memory Usage\""
		." STACK:buf#FF9999:\"Buffers\""
		." STACK:cache#FF0099:\"Cache\""
		." STACK:swap#FF9900:\"Swap Memory Usage\\n\""
		." GPRINT:mem:MAX:\"Residental  Max\\: %5.1lf %s\""
		." GPRINT:mem:AVERAGE:\" Avg\\: %5.1lf %s\""
		." GPRINT:mem:LAST:\" Current\\: %5.1lf %s\\n\""
		." GPRINT:buf:MAX:\"Buffers     Max\\: %5.1lf %s\""
		." GPRINT:buf:AVERAGE:\" Avg\\: %5.1lf %s\""
		." GPRINT:buf:LAST:\" Current\\: %5.1lf %s\\n\""
		." GPRINT:cache:MAX:\"Cache       Max\\: %5.1lf %s\""
		." GPRINT:cache:AVERAGE:\" Avg\\: %5.1lf %s\""
		." GPRINT:cache:LAST:\" Current\\: %5.1lf %s\\n\""
		." GPRINT:swap:MAX:\"Swap        Max\\: %5.1lf %s\""
		." GPRINT:swap:AVERAGE:\" Avg\\: %5.1lf %s\""
		." GPRINT:swap:LAST:\" Current\\: %5.1lf %s\\n\""
		." GPRINT:total:MAX:\"Total       Max\\: %5.1lf %s\""
		." GPRINT:total:AVERAGE:\" Avg\\: %5.1lf %s\""
		." GPRINT:total:LAST:\" Current\\: %5.1lf %s\\n\""
		." LINE1:res#CC9966"
		." LINE1:total#CC6600 > /dev/null");
}

updatecpudata();
updatecpugraph('day');
updatecpugraph('week');
updatecpugraph('month');
updatecpugraph('year');

sub updatecpugraph {
        my $period    = $_[0];

        RRDs::graph ("$img/cpu-$period.png",
                "--start", "-1$period", "-aPNG", "-i", "-z",
                "--alt-y-grid", "-w 600", "-h 80", "-l 0", "-r",
                "-t cpu usage per $period",
                "-v perecent",
                "DEF:user=$rrd/cpu.rrd:user:AVERAGE",
                "DEF:system=$rrd/cpu.rrd:system:AVERAGE",
                "DEF:idle=$rrd/cpu.rrd:idle:AVERAGE",
                "DEF:io=$rrd/cpu.rrd:io:AVERAGE",
                "DEF:irq=$rrd/cpu.rrd:irq:AVERAGE",
                "CDEF:total=user,system,idle,io,irq,+,+,+,+",
                "CDEF:userpct=100,user,total,/,*",
                "CDEF:systempct=100,system,total,/,*",
                "CDEF:iopct=100,io,total,/,*",
                "CDEF:irqpct=100,irq,total,/,*",
                "AREA:userpct#0000FF:user cpu usage\\j",
                "STACK:systempct#FF0000:system cpu usage\\j",
                "STACK:iopct#FFFF00:iowait cpu usage\\j",
                "STACK:irqpct#00FFFF:irq cpu usage\\j",
                "GPRINT:userpct:MAX:maximal user cpu\\:%3.2lf%%",
                "GPRINT:userpct:AVERAGE:average user cpu\\:%3.2lf%%",
                "GPRINT:userpct:LAST:current user cpu\\:%3.2lf%%\\j",
                "GPRINT:systempct:MAX:maximal system cpu\\:%3.2lf%%",
                "GPRINT:systempct:AVERAGE:average system cpu\\:%3.2lf%%",
                "GPRINT:systempct:LAST:current system cpu\\:%3.2lf%%\\j",
                "GPRINT:iopct:MAX:maximal iowait cpu\\:%3.2lf%%",
                "GPRINT:iopct:AVERAGE:average iowait cpu\\:%3.2lf%%",
                "GPRINT:iopct:LAST:current iowait cpu\\:%3.2lf%%\\j",
                "GPRINT:irqpct:MAX:maximal irq cpu\\:%3.2lf%%",
                "GPRINT:irqpct:AVERAGE:average irq cpu\\:%3.2lf%%",
                "GPRINT:irqpct:LAST:current irq cpu\\:%3.2lf%%\\j");
        $ERROR = RRDs::error;
        print "Error in RRD::graph for cpu: $ERROR\n" if $ERROR;
}

sub updatecpudata {
        if ( ! -e "$rrd/cpu.rrd") {
                print "Creating cpu.rrd";
                RRDs::create ("$rrd/cpu.rrd", "--step=60",
                        "DS:user:COUNTER:600:0:U",
                        "DS:system:COUNTER:600:0:U",
                        "DS:idle:COUNTER:600:0:U",
                        "DS:io:COUNTER:600:0:U",
                        "DS:irq:COUNTER:600:0:U",
                        "RRA:AVERAGE:0.5:1:576",
                        "RRA:AVERAGE:0.5:6:672",
                        "RRA:AVERAGE:0.5:24:732",
                        "RRA:AVERAGE:0.5:144:1460");
                $ERROR = RRDs::error;
                print "Error in RRD::create for cpu: $ERROR\n" if $ERROR;
        }

        my ($cpu, $user, $nice, $system, $idle, $io, $irq, $softirq);

        open STAT, "/proc/stat";
        while(<STAT>) {
                chomp;
                /^cpu\s/ or next;
                ($cpu, $user, $nice, $system, $idle, $io, $irq, $softirq) = split /\s+/;
                last;
        }
        close STAT;
        $user += $nice;
        $irq  += $softirq;

        RRDs::update ("$rrd/cpu.rrd",
                "-t", "user:system:idle:io:irq", 
                "N:$user:$system:$idle:$io:$irq");
        $ERROR = RRDs::error;
        print "Error in RRD::update for cpu: $ERROR\n" if $ERROR;

        if ($debug eq "1") {  print "cpu -> user: $user system: $system idle: $idle iowait: $io irq: $irq\n"; }
}
system.pl

Okay. Lastly we need to make them update every 5 minutes. So type

crontab -e

Add these lines. Changing USERNAME to your OpenVPN Client

*/5 * * * * /srv/beyondhd.me/system.pl >/dev/null 2>&1
*/5 * * * * /srv/beyondhd.me/vpnuser.pl USERNAME >/dev/null

Thats it, then you just goto http://your.domain.here/ and let it populate.

Mine is OpenVPN Graphs

Any questions? Leave me a comment!