Jeff's Ranting Ground

Where life is the longing for better things, and the passing of days.

Those of you who are playing with SNMP under PHP can thank me later

One of my more recent projects involves SNMP monitoring of services. Nothing new you may say ... well, we're talking about a PHP based webservice. PHP includes some SNMP interaction functions, but nothing for sending Traps, so an hour or two of tinkering, packet analysis and educated guesswork later, we end up with ...

Requirements
  • PHP >= 5.2.0
  • PHP Socket Support

class snmp_Trap
{
  /**
   * The 'group' to which the message is relevant
   *
   * @var string
   */
  public $community = 'public';

  /**
   * The SNMP message type (6 for custom)
   *
   * @var int
   */
  public $genericType = 6;

  /**
   * The SNMP message sub-type (for custom, this is application specific)
   *
   * @var int
   */
  public $specificType = 1;

  /**
   * A message to go along with this Trap
   *
   * @var string
   */
  public $message;

  /**
   * An Object Identifier for this Trap
   *
   * @var string
   */
  public $oid = '.1.3.1';

  /**
   * How long the service this Trap relates to has been operational (in seconds)
   *
   * @var int
   */
  public $uptime;

  /**
   * The IP address from which this request should be said to originate
   *
   * @var string
   */
  public $ip;


  /**
   * Uses the SNMP core to send this message
   *
   */
  public function send()
  {
    snmp_Core::sendTrap($this);
  }

  public function __construct()
  {
    $this->ip = $_SERVER['SERVER_ADDR'];
  }
}

# ----

class snmp_Container
{
  public $type = 48;
  public $children = array();
  private $length_cache;

  public function getLength()
  {
    return $this->getChildLength() + strlen($this->getLengthData()) + 1;
  }

  private function getLengthData()
  {
    if( $this->length_cache )
      return $this->length_cache;
    return $this->length_cache = snmp_Core::getIntegerHigh($this->getChildLength());
  }

  private function getChildLength()
  {
    $length = 0;
    foreach( $this->children as $child )
      $length += $child->getLength();
    return $length;
  }

  private function getChildData()
  {
    $data = '';
    foreach( $this->children as $child )
      $data .= $child->getData();
    return $data;
  }

  public function getData()
  {
    return pack('c', $this->type) . $this->getLengthData() . $this->getChildData();
  }

  # ----

  /**
   * Adds a container, and returns it for modification
   *
   * @param int $type
   * @return snmp_Container
   */
  public function addContainer( $type = 48 )
  {
    $container = new snmp_Container();
    $container->type = $type;
    $this->children[] = $container;
    return $container;
  }

  public function addTime( $seconds )
  {
    $majorUnits = min((int)($seconds / 167772.1647), 255);
    $majorUnitRemainder = $seconds % 167772.1647;
    $mediumUnits = min((int)($majorUnitRemainder / 655.3647), 255);
    $mediumUnitRemainder = $majorUnitRemainder % 655.3647;
    $minorUnits = min((int)($mediumUnitRemainder / 2.5647), 255);

    $data = new snmp_Data();
    $data->type = 0x42;
    $data->data = pack('cccc', $majorUnits, $mediumUnits, $minorUnits, 0x83);

    $this->children[] = $data;
  }

  public function addOid( $version )
  {
    $versions = explode('.', $version);

    // -- Unset the first one if empty (OIDs usually start with a period)
    if( $versions[0] == '' )
      unset($versions[0]);

    $majorVersion = (int)array_shift($versions);
    $minorVersion = (int)array_shift($versions);

    $data = new snmp_Data(6);
    $data->data = pack('c', min((40 * $majorVersion) + $minorVersion, 255));

    $versionsCount = count($versions);
    $versionsKeys = array_keys($versions);

    for( $i = 0; $i < $versionsCount; $i++ )
      $data->data .= snmp_Core::getIntegerLow($versions[$versionsKeys[$i]]);

    $this->children[] = $data;
  }

  public function addString( $string )
  {
    $data = new snmp_Data(4);
    $data->data = pack('H*', bin2hex($string));
    $this->children[] = $data;
  }

  public function addInteger( $int )
  {
    $data = new snmp_Data(2);
    $data->data = snmp_Core::getIntegerHigh($int);
    $this->children[] = $data;
  }

  public function addIpAddress( $ip )
  {
    $segments = explode('.', $ip);
    $data = new snmp_Data(64);
    $data->data = pack('cccc', (int)$segments[0], (int)$segments[1], (int)$segments[2], (int)$segments[3]);
    $this->children[] = $data;
  }
}

class snmp_Data
{
  public $type;
  public $data = '';
  private $data_cache;
  private $length_cache;

  public function __construct( $type )
  {
    $this->type = $type;
  }

  public function getLength()
  {
    return $this->getDataLength() + strlen($this->getLengthData()) + 1;
  }

  private function getDataLength()
  {
    return strlen($this->data);
  }

  private function getLengthData()
  {
    if( $this->length_cache )
      return $this->length_cache;
    return $this->length_cache = snmp_Core::getIntegerHigh($this->getDataLength());
  }

  public function getData()
  {
    if( $this->data_cache )
      return $this->data_cache;
    return $this->data_cache = pack('c', $this->type) . $this->getLengthData() . $this->data;
  }
}

# ----

class snmp_Core
{
  public static function sendTrap( snmp_Trap $Trap )
  {
    $request = new snmp_Container();
    $container = $request->addContainer();

    $container->addInteger(0);
    $container->addString($Trap->community);

    $mainContainer = $container->addContainer(0xA4);
    $mainContainer->addOid($Trap->oid);
    $mainContainer->addIpAddress($Trap->ip);
    $mainContainer->addInteger($Trap->genericType);
    $mainContainer->addInteger($Trap->specificType);
    $mainContainer->addTime($Trap->uptime);

    $extraContainer = $mainContainer->addContainer();

    /**
     * Special case - attached message
     */
    if( $Trap->message )
    {
      $messageContainer = $extraContainer->addContainer();
      $messageContainer->addOid($Trap->oid);
      $messageContainer->addString($Trap->message);
    }

    // -- Prepare the data
    $broadcastString = $container->getData();

    // -- Transmit the data
    $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
    socket_set_option($sock, SOL_SOCKET, SO_BROADCAST, 1);
    socket_sendto($sock, $broadcastString, strlen($broadcastString), 0, '255.255.255.255', 162); // -- SNMP port
  }

  public static function getIntegerHigh( $value )
  {
    if( $value >= 256 )
      return pack('ccc', floor($value / 256) + 128, floor($value / 256), (int)$value % 256);
    elseif( $value >= 128 )
      return pack('cc', floor($value / 128) + 128, $value);
    else
      return pack('c', $value);
  }

  public static function getIntegerLow( $value )
  {
    if( $value >= 128 )
      return pack('cc', floor($value / 128) + 128, $value % 128);
    else
      return pack('c', $value);
  }
}

$trap = new snmp_Trap();
$trap->message = "This is a test of a character size restriction which seems to be placed on large messages.  There must be a way around it, I hope anyway ... time will tell ...";
$trap->oid = ".1.3.4.7.2000.2.1";
$trap->ip = '192.168.2.20';
$trap->send();

XKCDQualifying a statistic, a mystic art lost to the ages ...

Write a comment

New comments have been disabled for this post.