Public RRD Graphs from pfSense

I wanted to get the graphs from my pfSense driven router onto a webpage, but there was some problems.

  • Could only be accessed from wan
  • Had a certificate
  • Protected with a login

To get around this I made a little php file that could fetch the image by using the build-in web server in my QNAP nas.

My result can be seen here: http://bld.is-a-geek.com/stats/

But first, we need a few files to make it all happen…

config.inc.php

This should be the only place you need to change anything to make this work.

Note: Graphs are only rebuild every minute, so no need to updating faster
<?php
	$username = "user";		//username for pfSense
	$password = "pass";		//password for pfSense
	$pfsenseip = "192.168.1.1";	//ip of pfSense
	$pfsenseprot = "https";		//http or https

	$refresh = 30; 			//Seconds between reload of the image

	$scale = array(
			0 => array("4h", "4 Hours", 30),
			1 => array("16h", "16 Hours", 30),
			2 => array("48h", "2 Days", 180),
			3 => array("32d", "1 Month", 180),
			4 => array("6m", "6 Months", 180),
			5 => array("16m", "1 Year", 180)
		       );

	$graphs = array(
			0 => array("wan-traffic.rrd", "WAN :: Traffic"),
			1 => array("wan-packets.rrd", "WAN :: Packets"),
			2 => array("wan-quality.rrd", "WAN :: Quality"),
			3 => array("system-processor.rrd", "System :: Processor ")
		       );
?>

.htaccess

Using .htaccess to secure the config.inc.php from direct access

<Files config.inc.php>
  order allow,deny
  deny from all
</Files>

functions.php

Holding the functions for generating the random string to make sure the picture is always reloaded, the url generator that will replace the value of specific query strings, and the function for the dynamic picture size.

<?php
function generatePassword($length=9, $strength=4)
{
	$vowels = 'aeuy';
	$consonants = 'bdghjmnpqrstvz';
	if ($strength >= 1)
	{
		$consonants .= 'BDGHJLMNPQRSTVWXZ';
	}
	if ($strength >= 2)
	{
		$vowels .= "AEUY";
	}
	if ($strength >= 4)
	{
		$consonants .= '23456789';
	}
	if ($strength >= 8 )
	{
		$consonants .= '@#$%';
	}

	$password = '';
	$alt = time() % 2;
	for ($i = 0; $i < $length; $i++)
	{
		if ($alt == 1) {
			$password .= $consonants[(rand() % strlen($consonants))];
			$alt = 0;
		}
		else
		{
			$password .= $vowels[(rand() % strlen($vowels))];
			$alt = 1;
		}
	}
	return $password;
}

function url($q="empty", $value="empty")
{
	$output = $_SERVER['QUERY_STRING'];
	if ($q != "empty" && $value != "empty")
	{
		if (strlen($output))
		{
			$temp = explode("&", $output);

			for($i = 0; $i < count($temp); $i++)
			{
				$subtemp = explode("=", $temp[$i]);
				if ($subtemp[0] == $q)
				{
					$output = str_replace($subtemp[0] . "=" . $subtemp[1], $subtemp[0] . "=" . $value, $output);
				}
			}
		}
	}
	return $output;
}

function dynsize($p=0, $t=0)
{
	$height = 349;
	$width  = 717;

	if($p)
	{
		$aspectRatio = $height / $width;
		$width = $width + $p;
		$height = (int)($aspectRatio * $width);
	}

	if ($t == "w")
	{
		return $width;
	}
	elseif ($t == "h")
	{
		return $height;
	}
}
?>

graph_rrd.php

This is the php file that actually fetches the image from pfSense and shows it to the visitor.

<?php
require_once("config.inc.php");

if (isset($_GET["file"]) && isset($_GET["interval"]))
{
	$curl_handle = curl_init();
	curl_setopt($curl_handle, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.12) Gecko/20070508 Firefox/1.5.0.12");

	curl_setopt($curl_handle, CURLOPT_SSL_VERIFYPEER, FALSE);
	curl_setopt($curl_handle, CURLOPT_SSL_VERIFYHOST, 2);

	curl_setopt($curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
	curl_setopt($curl_handle, CURLOPT_USERPWD, $username . ":" . $password);

	curl_setopt($curl_handle,CURLOPT_CONNECTTIMEOUT,2);
	curl_setopt($curl_handle,CURLOPT_RETURNTRANSFER,1);
	curl_setopt($curl_handle,CURLOPT_URL, $pfsenseprot . "://" . $pfsenseip . "/status_rrd_graph_img.php?interval=" . $_GET["interval"] . "&database=" . $_GET["file"] . "&style=inverse");
	$buffer = curl_exec($curl_handle);

	if(curl_errno($curl_handle))
	{
		echo 'Curl error: ' . curl_error($curl_handle);
	}
	else
	{
		if (empty($buffer))
		{
			print "No picture avalible.";
		}
		else
		{
			header('Content-Type: image/png');
			print $buffer;
		}
	}

	curl_close($curl_handle);
}
?>

index.php

The page itself that shows the selected graph and scale.

<?php
require_once("functions.php");
require_once("config.inc.php");

if (!isset($_GET["type"]) || !isset($_GET["interval"]) || !isset($_GET["px"]))
{
	header('Location: ?type=' . $graphs[0][0] . '&interval=' . $scale[0][0] . '&px=0') ;
}

?>
<html>
	<head>
		<title>pfSense stats @ <?php print $_SERVER['HTTP_HOST'] ?></title>

<style type="text/css">
 @import "style.css";
</style>

	</head>

	<body>

		<div id="top"></div>

		<div align="center" id="container">
			<br />
			<img src="logobig.jpg">
			<br />
<?php
print "[ ";

for ($i = 0; $i < count($graphs); $i++)
{
	$graphs = $GLOBALS['graphs'];
	$num = count($graphs);
	if ($num-1 == $i)
	{
		print "<a href='?" . url("type", $graphs[$i][0]) . "'>" . $graphs[$i][1] . "</a> ]";
	}
	else
	{
		print "<a href='?" . url("type", $graphs[$i][0]) . "'>" . $graphs[$i][1] . "</a> | ";
	}
}

print "<br /><br />[ ";

for ($i = 0; $i < count($scale); $i++)
{
	$scale = $GLOBALS['scale'];
	$num = count($scale);
	if ($num-1 == $i)
	{
		print "<a href='?" . url("interval", $scale[$i][0]) . "'>" . $scale[$i][1] . "</a> ]";
	}
	else
	{
		print "<a href='?" . url("interval", $scale[$i][0]) . "'>" . $scale[$i][1] . "</a> | ";
	}

	if ($scale[$i][0] == $_GET["interval"])
	{
		$refresh = $scale[$i][2];
	}
}
?>			<br /><br />
			<div style='border: 1px solid #000000; width: <?php print dynsize($_GET["px"], "w") ?> ; height: <?php print dynsize($_GET["px"], "h") ?>;'>
				<IMG src="graph_rrd.php?file=<?php print $_GET["type"] ?>&interval=<?php print $_GET["interval"] ?>&rand=<?php print generatePassword(); ?>" width="<?php print dynsize($_GET["px"], "w") ?>" height="<?php print dynsize($_GET["px"], "h") ?>" border="0" name="refresh">
			</div>

			<SCRIPT language="JavaScript" type="text/javascript">
				<!--
				image = "graph_rrd.php?file=<?php print $_GET["type"] ?>&interval=<?php print $_GET["interval"] ?>" //name of the image
				function Start()
				{
					tmp = new Date();
					tmp = "&"+tmp.getTime()
					document.images["refresh"].src = image+tmp
					setTimeout("Start()", <?php print $refresh*1000 ?>)
				}
				Start();
				// -->
			</SCRIPT>
		</div>

		<div align="right" id="footer">
			<a href="http://bld.is-a-geek.com/2010/09/24/public-rrd-graphs-from-pfsense/" target="_blank">pfSense stats</a> V0.25 by bld @ <a href="http://bld.is-a-geek.com/" target="_blank">bld.is-a-geek.com</a>&nbsp;
		</div>
	</body>
</html>

style.css

html, body
{
    height: 100%;
}

body
{
	margin: 0;
	padding: 0;
	background-color: #b0c4de; font-family: serif; font-size: 14px; font-weight: normal;
}

A
{
	font-family:serif; font-size: 14px; font-weight: bold; text-decoration: underline; color: #000000;
}

A:Visited
{
	font-family:serif; font-size: 14px; font-weight: bold; text-decoration: underline; color: #000000;
}

A:Hover
{
	font-family:serif; font-size: 14px; font-weight: bold; text-decoration: underline; color: #FFFFFF;
}

#top
{
	position: absolute;
}

#container
{
	min-height: 100%;
	margin-bottom: -20px;
}

* html #container
{
	height: 100%;
}

#footer-spacer
{
	height: 20px;
}

#footer
{
	border-top: 1px solid #000;
	height: 19x;
}

Only thing you need (if you want it on), is the pfSense logo, you can find it in the top of this page. 🙂

It isn’t pretty, it might be buggy, but feel free to help me to make it better. 🙂

Changelog

2010-09-24 V0.1

23:00 V0.11

First version added

2010-09-25 V0.2

00:20 V0.21

Updated to use arrays to print the scale and graph variables

00:30 V0.22

Added config option to change the refresh interval of the graph

01:10 V0.23

Added variable refresh timer to the scale array

Moved the CSS to a seperate file

15:50 V0.24

Changed name of config.php to config.inc.php

Added .htaccess security for the config file

20:30 V0.25

Added a function to handle query strings and replace the value of them

Added px that will offset the graph size from the original size

Removed background color from the css file

Minor changes to the password generator

45 Comments

Leave a Reply to Anonymous Cancel reply

Your email address will not be published. Required fields are marked *