Wie man eine IP-Steckdosenleiste grundsätzlich per SNMP ansteuert, hatte ich im letzten Beitrag beschrieben. Dort haben wir das Gerät über die Bash-Kommandozeile angesprochen. Heute wollen wir einen Blick hinter die Kulissen der Web-Applikation werfen. Die Applikation soll alle Ports der Steckdosenleiste grafisch mit ihrem Schaltzustand anzeigen. Neben den Ports soll die Grafik mit den angeschlossenen Geräten beschriftet werden. Ein Klick auf das jeweilige Steckdosensymbol soll einen Schaltvorgang auslösen. Dazu brauchen wir also ein PHP-Backend-Modul, das mit der APC PDU per SNMP kommuniziert. Die Darstellung erfolgt im Frontend mittels HTML5 und ein wenig JavaScript, das per Ajax mit dem Backend redet.
Schauen wir uns zunächst das Backend an. Zur Vorbereitung müssen wir die snmp-Extension zu PHP hinzufügen (apt install php7.4-snmp
). Danach legen wir uns für die SNMP-Kommunikation eine abstrakte Klasse an:
<?php interface SNMP_Agent { public function get( $oid ); public function set( $oid, $type, $value ); public function walk( $oid ); }
get() liest die übergebene SNMP-OID aus, set() beschreibt sie und walk() liefert das Ergebnis eines Table-Walks. Nun implementieren wir dieses Interface für SNMPv1:
class SNMPv1_Agent implements SNMP_Agent { //settings protected $ip; protected $community; public function __construct( $ip, $community ) { $this->ip = $ip; $this->community = $community; } public function get( $oid ) { return snmpget( $this->ip, $this->community, $oid ); } public function set( $oid, $type, $value ) { return snmpset( $this->ip, $this->community, $oid, $type, $value ); } public function walk( $oid ) { return snmpwalk( $this->ip, $this->community, $oid ); } }
Und für SNMPv3:
class SNMPv3_Agent implements SNMP_Agent { //settings protected $ip; protected $user; protected $pw; protected $auth; protected $authAlg; protected $priv; protected $privAlg; public function __construct( $ip, $user, $pw, $auth, $authAlg, $priv, $privAlg ) { $this->ip = $ip; $this->user = $user; $this->pw = $pw; $this->auth = $auth; $this->authAlg = $authAlg; $this->priv = $priv; $this->privAlg = $privAlg; } public function get( $oid ) { return snmp3_get( $this->ip, $this->user, $this->auth, $this->authAlg, $this->pw, $this->privAlg, $this->priv, $oid ); } public function set( $oid, $type, $value ) { return snmp3_set( $this->ip, $this->user, $this->auth, $this->authAlg, $this->pw, $this->privAlg, $this->priv, $oid, $type, $value ); } public function walk( $oid ) { return snmp3_walk( $this->ip, $this->user, $this->auth, $this->authAlg, $this->pw, $this->privAlg, $this->priv, $oid ); } }
Diese Abstraktion ist zwar nicht unbedingt notwendig, erlaubt es aber, während der Entwicklung schnell zwischen den beiden Versionen hin- und herzuwechseln, falls man Probleme bei der SNMP-Authentifizierung vermutet.
Nun bauen wir uns eine Klasse für ein allgemeines SNMP-Device:
class SNMP_Device { protected $snmpAgent; protected function __construct( $snmpAgent ) { $this->snmpAgent = $snmpAgent; } private function stripPrefix( $value ) { return preg_replace( '/^STRING: "|"$/', "" , $value ); } protected function getRaw( $oid ) { return $this->snmpAgent->get( $oid ); } protected function get( $oid ) { return $this->stripPrefix( $this->snmpAgent->get( $oid ) ); } protected function set( $oid, $type, $value ) { return $this->snmpAgent->set( $oid, $type, $value ); } protected function walkRaw( $oid ) { return $this->snmpAgent->walk( $oid ); } protected function walk( $oid ) { $result = $this->snmpAgent->walk( $oid ); if ( !$result ) { return 'Fehler'; } foreach ( $result as $k => $v ) { $result[ $k ] = $this->stripPrefix( $v ); } return $result; } }
Diese Deviceklasse bekommt im Construktor eine Implementierung der Schnittstelle SNMP_Agent übergeben. Sie reicht deren Methoden im wesentlich durch. Diese Kapselung ermöglicht die einfache Umschaltung zwischen den verschiedenen SNMP-Versionen. Bei get() und walk() bearbeitet sie die Rückgabewerte mit der Methode stripPrefix() nach, um Typinformationen zu entfernen.
Von dieser Klasse SNMP_Device leiten wir nun die konkretet Variante APCPDU für die APC PDU 7920 ab:
class APCPDU extends SNMP_Device { public function __construct( $snmpAgent ) { parent::__construct( $snmpAgent ); snmp_read_mib( './powernet441.mib' ); } public function getPDUName() { return $this->get( 'PowerNet-MIB::sPDUMasterConfigPDUName.0' ); } public function getPDUIdentModelNumber() { return $this->get( 'PowerNet-MIB::sPDUIdentModelNumber.0' ); } public function getIdentFirmwareRev() { return $this->get( 'PowerNet-MIB::sPDUIdentFirmwareRev.0' ); } public function setOutlet( $port, $mode ) { return $this->set( 'PowerNet-MIB::rPDUOutletControlOutletCommand.' . $port, 'i', $mode ? 1 : 2 ); } public function getOutletNames() { return $this->walk( 'PowerNet-MIB::sPDUOutletCtlName' ); } public function getOutletStates() { return $this->walk( 'PowerNet-MIB::rPDUOutletStatusOutletState' ); } }
Der Constructor liest zusätzlich noch die PowerNet-MIB ein, damit die OID per Name angesprochen werden können. Dann implementiert sie Zugriffsmethoden auf einige SNMP-Variablen, um etwa Geräteinformationen abzurufen, Statusinformationen über geschaltete Steckdosen abzurufen oder Steckdosen schalten zu können. Dafür nutzt sie die Zugriffsmethoden der Basis-Klasse SNMP_Device und füttert die Aufrufe mit den symbolischen Namen aus der PowerNet-MIB.
Nun können wir die Klasse instanziieren:
# SNMPv1: #$apc = new APCPDU( new SNMPv1_Agent( '10.10.99.1', 'private' ) ); # SNMPv3: $apc = new APCPDU( new SNMPv3_Agent( '10.10.99.1', 'apc snmp profile1', 'AuthenticationPassphrase', 'authPriv', 'MD5', 'PrivacyPassphrase', 'DES' ) );
Über das $apc-Objekt können wir nun bequem auf die Funktionen der Steckdosenleiste zugreifen, indem wir z. B. $apc->setOutlet( 5, true )
aufrufen, um die Steckdose 5 einzuschalten. Damit haben wir uns eine Basis für das Backend geschaffen. Wie diese konkret genutzt wird, zeigt Teil 2 dieser Serie.