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

15 comments

  • Flemming Jacobsen

    Hvorfor er det så lige at din graf viser negative værdier i henhold til trafik?

  • How do you edit the config.inc.php to use port: 8080 insted of default 80 ?

  • Does this work with pfSense 2.0?

  • What do you use instead of pfSense?

  • How could you LOSE THE CONTROL! lol. I though maybe you went to Untangle or RouterOS 🙂 lol. I’m loving pfSense, handles so many concurrent connections it gets me MONSTER download speeds on torrents!!! http://ywax.us/pfsense.jpg

    • I actually have had a routerboard before my Cisco, and a D-Link DIR655 before it too. The routerboard was awesome, but had too many settings, so easy to fuck it up. The DIR655 was so unstable, and my Cisco just works…

      The reason I Changed away from pfSense was because I could not find a solution fast enough to drive my 90/90 megabit fiber at full speed both ways at the same time, with the same power consumption as a more “normal” router. I also had massive problems with the ip loopback blocking so I could not see my own web server. With the D-Link and Cisco I just enabled it, and it started working exactly as I needed it to.

      • Yeah, I had the same IP loopback issues with pfSense, never EVER had an issue like that with DD-WRT! I run a few servers on my local LAN and it’d either not let me connect to them using their domain name or would report it blocked some DNS redirection attack. I posted on the pfSense forums and they got me to disable the DNS redirection attack and to change “NAT Reflection mode for port forwards” to “Pure NAT” and then all my internal servers worked fine for me (they always worked fine for everyone outside of my network). I too was looking for a router solution to handle my faster internet (300 megabit download) speed and be able to support tens of thousands of concurrent torrent connections at the same time. I couldn’t find any routers reasonably priced that were able to do it so I picked up a mini Dell desktop computer, Core2Duo something, 4gb ram for $50 delivered on ebay. Already had onboard gigabit card so just dropped in another gigabit pci express card and boom, perfect router, speeds are mind blowing 🙂 http://tafb.xxx/pfsense_status.png

  • This is fantastic, I will surely try this after work. Do you think it will work with v2.2.1?

  • I can confirm this will NOT work on 2.x and later version. There is a new security model implemented so http://name:password@192.168.1.1 will not work. A feature request to re-enable this is denied. What you can do i create a user and give only access to the RRD graphs and Logout page in the pfSense firewall. This could easy be a username ‘graph’ and password ‘graph’.

Leave a Reply