preload preload preload preload

Monitoring WMI From Linux via a C# Daemon and PHP


1st July 2007 NAMOS,Systems 0 Comments

WARNING: I am not a programmer, I don’t claim to be so it is quite likely that the code you will find here will horrify you!

As part of my drive to monitor as much as possible of my systems I realised that creating an intermediate daemon for WMI would allow me to access some of the WMI properties that wouldn’t normally be exposed via SNMP and graph them with MRTG.

I’m not going to cover WMI in any great detail so if you’d like to do a bit more reading I would recommend reading through the Microsoft Knowledge Base article on WMI.

As the title suggests the Windows Daemon has been written in C# and the basic idea behind it is that it will sit on the server listening on an specified UDP port waiting for a particular type of packet to arrive. That packet will contain the originating IP, the UDP port it will be waiting for a reply on, the WMI Class from which we will be extracting a property and the property itself.


//Receive DataGram
byte[] recData = server.Receive(ref ServerReceivePoint);
System.Text.ASCIIEncoding encode = new System.Text.ASCIIEncoding();
//Split it up [IP/Port/WMIClass/WMIQueryItem]
string[] temp = encode.GetString(recData).Split(new Char[] { '/' });
string Class = temp[2].ToString();
string Query = temp[3].ToString();
string WMIResult = "";
WMIResult = DoWMIQuery(Class, Query);

So far the code just sits waiting to receive some data, when it does it writes a nice little entry to a log and then throws the packet data into a string array named temp split on the / character (this shouldn’t appear in either a WMI Class or a Method / Property). The 3rd entry is the WMI Class and the 4th entry is the property we are asking for. These values are then passed to a function named DoWMIQuery (can you guess what that does?).

First we have to set up the options for connecting to WMI:

ConnectionOptions ConOpt = new ConnectionOptions();
string strNameSpace = @"\\";
strNameSpace += ".";
strNameSpace += @"\root\cimv2";

With that done a new Management Scope and Object Query can be created:

 System.Management.ManagementScope oMs =
  new System.Management.ManagementScope(strNameSpace, ConOpt);
 System.Management.ObjectQuery oQuery = oQuery =
  new System.Management.ObjectQuery("select " + QUERYITEM + " from " + CLASS);

‘Executing’ the query requires the utilisation of a Management Object Searcher (its searches for objects!!) and returns a nice array of objects (assuming of course there are more than one otherwise it’ll only return the one). By looping through the array I can grab all the requested data and then it can be returned.


 ManagementObjectSearcher oSearcher = new ManagementObjectSearcher(oMs, oQuery);

 try
 {
  //Get the results
  ManagementObjectCollection oReturnCollection = oSearcher.Get();

  //loop through and return what was asked for!
  foreach (ManagementObject oReturn in oReturnCollection)
  {
   ReturnValue = oReturn[QUERYITEM].ToString();
  }
  return ReturnValue;

There is no checking to make sure that ‘hostile’ WQL isn’t being passed mainly because I couldn’t be bothered but mostly because the inter-vlan firewalls don’t let clients send to these ports. Anyhow, basically all that happens is that the WMI property requested is extracted from the WMI class that was defined. If there are several instances (say for example free space on x amount of disk drives) then they get added together and then returned.


//Re-send the DataGram
byte[] sendData = encode.GetBytes(WMIResult);


//We use the IP and Port sent by the user to send the DataGram back
server.Send(sendData, sendData.Length, temp[0], Int32.Parse(temp[1]));

If anyone noticed the potential for foul play (Hint: Smurf stylee) then yes I was indeed too lazy to work out where the connection came from and send it back to that host.

Once the host is happily sitting there doing what it needs to do (listening for packets that is) we need to send it something. Whilst I love scripting in Bash I felt that PHP would be a better avenue to explore (mainly because I could create a few intranet pages with ‘on demand’ stats generation!).

First I grab the command line arguements that were passed and add a couple of my own:

$ScriptName = $argv[0]; // Script name
$TargetIP = $argv[1]; // Target IP
$Class1 = $argv[2]; // WMI Class1
$Prop1 = $argv[3]; // WMI Property1
$Class2 = $argv[4]; // WMI Class2
$Prop2 = $argv[5]; // WMI Property2
$UptimeClass = "Win32_PerfFormattedData_PerfOS_System";
$UptimeProperty = "SystemUpTime";

Then it calls the DoQuery function a couple of times and does a reverse DNS lookup of the host. This is because MRTG expects Input, Output, Uptime and System name as the 4 variables returned to it from an external script!

DoQuery($TargetIP,$Class1,$Prop1);
DoQuery($TargetIP,$Class2,$Prop2);
DoQuery($TargetIP,$UptimeClass,$UptimeProperty);
print(gethostbyaddr($TargetIP));

Create the SourcePort (a bit of randomisation as there could be 10′s of these triggering at the same time!) and the socket:

$SourcePort = rand(2200, 4500);
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);

With that done its time to create the packet:

$packet = "172.16.0.2/".$SourcePort."/".$WMIClass."/".$WMIProperty;

Sending the UDP request packet is painfully simple:

socket_sendto($socket, $packet, strlen($packet), 0x100, '172.16.0.3', 6868);

With that done we need to create the listening socket and then wait for a reply:

 $sock2 = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);

 if(!socket_bind($sock2, '172.16.0.2', $SourcePort))
 {
  socket_close($sock2);
  die( 'socket_bind failed: '.socket_strerror(socket_last_error()));
 }


 //Set up a nonblocking socket
 socket_set_nonblock($sock2);

I’ve decided on 2 seconds as an acceptable time out:

//Initiate the timeout
$timeout = time() + (2); // 2 seconds timeout


//While the time is less than 2 seconds from when we started this
while (time() <= $timeout)
{
while (@socket_recv($sock2, $data, $SourcePort, 0)) //8192
{
print($data);
print("\n");
}
usleep(100000); // 100ms wait
}
//Close the socket
socket_close($sock2);
?>

With all that done you can simply add something like the following to your MRTG scripts:

Directory[S411-1-cpu]: cpu
Target[S411-1-cpu]: `php /etc/mrtg/scripts/WMIUDPMRTG.php 172.16.0.3 Win32_PerfFormattedData_PerfOS_Processor PercentProcessorTime Win32_PerfFormattedData_PerfOS_Processor PercentUserTime`
MaxBytes[S411-1-cpu]: 10000
Options[S411-1-cpu]: gauge,growright,nopercent,absolute
ShortLegend[S411-1-cpu]: CPU
#LegendI[S411-1-cpu]:CPU Usage
WithPeak[S411-1-cpu]: wmy
LegendI[S411-1-cpu]:CPU Time
LegendO[S411-1-cpu]:User Time
YLegend[S411-1-cpu]:CPU Usage
Title[S411-1-cpu]: CPU Usage
PageTop[S411-1-cpu]: CPU Usage

And you’ll be able to graph previously unreachable data:

Win32_PerfFormattedData_PerfOS_Processor:
WMIUDPCPU1
WMIUDPCPU2
WMIUDPCPU3

Win32_PerfFormattedData_ASP_ActiveServerPages:
IIS Bytes Per Second

Win32_PerfFormattedData_RemoteAccess_RASTotal:
RAS Totals

The full source code (if you dare) is here. (edit: fixed link)

ToDo:

  • Stability / Security
  • Intelligent WQL Support
  • Not sucking