CDP.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. <?php
  2. /*
  3. Copyright (c) 2012, Open Source Solutions Limited, Dublin, Ireland
  4. All rights reserved.
  5. Contact: Barry O'Donovan - barry (at) opensolutions (dot) ie
  6. http://www.opensolutions.ie/
  7. Redistribution and use in source and binary forms, with or without
  8. modification, are permitted provided that the following conditions are met:
  9. * Redistributions of source code must retain the above copyright
  10. notice, this list of conditions and the following disclaimer.
  11. * Redistributions in binary form must reproduce the above copyright
  12. notice, this list of conditions and the following disclaimer in the
  13. documentation and/or other materials provided with the distribution.
  14. * Neither the name of Open Source Solutions Limited nor the
  15. names of its contributors may be used to endorse or promote products
  16. derived from this software without specific prior written permission.
  17. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  18. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  19. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  20. DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  21. DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  22. (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  23. LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  24. ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  26. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27. */
  28. namespace OSS\SNMP\MIBS\Cisco;
  29. /**
  30. * A class for performing SNMP V2 queries on Cisco devices
  31. *
  32. * @copyright Copyright (c) 2012, Open Source Solutions Limited, Dublin, Ireland
  33. * @author Barry O'Donovan <barry@opensolutions.ie>
  34. */
  35. class CDP extends \OSS\SNMP\MIBS\Cisco
  36. {
  37. const OID_CDP_INTERFACE_ENABLED = '.1.3.6.1.4.1.9.9.23.1.1.1.1.2';
  38. const OID_CDP_INTERFACE_NAME = '.1.3.6.1.4.1.9.9.23.1.1.1.1.6';
  39. const OID_CDP_NEIGHBOUR_ID = '.1.3.6.1.4.1.9.9.23.1.2.1.1.6';
  40. const OID_CDP_NEIGHBOUR_PORT = '.1.3.6.1.4.1.9.9.23.1.2.1.1.7';
  41. const OID_CDP_NEIGHBOUR_CAPABILITY = '.1.3.6.1.4.1.9.9.23.1.2.1.1.9';
  42. const OID_CDP_DEVICE_ID = '.1.3.6.1.4.1.9.9.23.1.3.4.0';
  43. /**
  44. * Get the device's CDP (Cisco Discovery Protocol) ID
  45. *
  46. * @return string The device's CDP (Cisco Discovery Protocol) ID
  47. */
  48. public function id()
  49. {
  50. return $this->getSNMP()->get( self::OID_CDP_DEVICE_ID );
  51. }
  52. /**
  53. * Get the device's interfaces CDP enabled status
  54. *
  55. * Applies the TruthValue post processor (self::ppTruthValue()) to turn
  56. * SNMP values into true / false.
  57. *
  58. * @return array The device's interfaces CDP enabled status' (as boolean true / false)
  59. */
  60. public function interfaceEnabled()
  61. {
  62. return $this->getSNMP()->ppTruthValue( $this->getSNMP()->walk1d( self::OID_CDP_INTERFACE_ENABLED ) );
  63. }
  64. /**
  65. * Get the device's interface names as seen in CDP
  66. *
  67. * @return array The device's interface names as seen in CDP
  68. */
  69. public function interfaceNames()
  70. {
  71. return $this->getSNMP()->walk1d( self::OID_CDP_INTERFACE_NAME );
  72. }
  73. /**
  74. * Get the device's CDP neighbours (by their CDP ID) indexed by the current device's port ID
  75. *
  76. * @return array The device's CDP neighbours (by their CDP ID) indexed by the current device's port ID
  77. */
  78. public function neighbourId()
  79. {
  80. return $this->getSNMP()->subOidWalk( self::OID_CDP_NEIGHBOUR_ID, 15 );
  81. }
  82. /**
  83. * Get the device's CDP neighbours connected port *description* indexed by the current device's port ID
  84. *
  85. * E.g. a sample call may return:
  86. *
  87. * Array
  88. * (
  89. * [10101] => GigabitEthernet0/1
  90. * [10102] => FastEthernet0/2
  91. * [10103] => GigabitEthernet1/0/24
  92. * [10105] => GigabitEthernet1/0/2
  93. * )
  94. *
  95. * meaning, for example, that our local port with ID 10101 is connected to port GigabitEthernet0/1 on the neighbour
  96. * connected to that local port. You can discover the neighbour ID via neighbourId().
  97. *
  98. * @see neighbourId()
  99. * @return array The device's CDP neighbours connected port *description* indexed by the current device's port ID
  100. */
  101. public function neighbourPort()
  102. {
  103. return $this->getSNMP()->subOidWalk( self::OID_CDP_NEIGHBOUR_PORT, 15 );
  104. }
  105. /**
  106. * Get the device's CDP neighbour capabilities (as a decimal integer) indexed by the current device's port ID
  107. *
  108. * @return array The device's CDP neighbour capabilities (as a decimal integer) indexed by the current device's port ID
  109. */
  110. public function neighbourCapability()
  111. {
  112. $rtn = $this->getSNMP()->subOidWalk( self::OID_CDP_NEIGHBOUR_CAPABILITY, 15 );
  113. foreach( $rtn as $k => $v )
  114. $rtn[$k] = (int)hexdec( $v );
  115. return $rtn;
  116. }
  117. /**
  118. * CDP utility function to get all CDP neighbours and their connected ports.
  119. *
  120. * Returns an array of neighbours indexed by the neighbour CDP ID. For example:
  121. *
  122. *
  123. * Array
  124. * (
  125. * [cr-sw03.ixdub1.opensolutions.ie] => Array
  126. * (
  127. * [0] => Array
  128. * (
  129. * [localPortId] => 10101
  130. * [localPort] => GigabitEthernet1/0/1
  131. * [remotePort] => GigabitEthernet0/1
  132. * )
  133. *
  134. * [1] => Array
  135. * (
  136. * [localPortId] => 10102
  137. * [localPort] => GigabitEthernet1/0/2
  138. * [remotePort] => FastEthernet0/2
  139. * )
  140. *
  141. * )
  142. * [ ... ]
  143. * )
  144. *
  145. * @see neighbourId()
  146. * @see \OSS\SNMP\MIBS\Interface::descriptions()
  147. * @see neighbourPort()
  148. * @return array CDP neighbours and their connected ports
  149. */
  150. public function neighbours()
  151. {
  152. $neighbours = array();
  153. foreach( $this->neighbourId() as $localPortId => $neighbourCdpId )
  154. {
  155. if( !isset( $neighbours[ $neighbourCdpId ] ) )
  156. {
  157. $neighbours[ $neighbourCdpId ] = array();
  158. $count = 0;
  159. }
  160. else
  161. $count = count( $neighbours[ $neighbourCdpId ] );
  162. $neighbours[ $neighbourCdpId ][$count]['localPortId'] = $localPortId;
  163. $neighbours[ $neighbourCdpId ][$count]['localPort'] = $this->getSNMP()->useIface()->descriptions()[$localPortId];
  164. $neighbours[ $neighbourCdpId ][$count]['remotePort'] = $this->neighbourPort()[$localPortId];
  165. }
  166. return $neighbours;
  167. }
  168. /**
  169. * Recursivily crawls all CDP neighbours to build up a flat array of all devices
  170. * indexed by the CDP device id.
  171. *
  172. * Array form is same as that returned by neighbours()
  173. *
  174. * @see neighbours()
  175. * @param array $devices Unless you're doing something funky, just pass an empty array. This is where the result will be found.
  176. * @param string $device CDP device ID of next host to crawl. On first pass, set to null - used internally when recursing
  177. * @param array $ignore An array of CDP device IDs to *ignore*. I.e. to not include in recursive crawling
  178. * @return array The resultant array of all crawled devices (same as that passed in the @param $devices parameter)
  179. */
  180. public function crawl( &$devices = array(), $device = null, $ignore = array() )
  181. {
  182. if( !count( $devices ) )
  183. {
  184. $device = $this->id();
  185. $devices[ $device ] = $this->neighbours();
  186. }
  187. foreach( $devices[ $device ] as $feNeighbour => $feConnections )
  188. {
  189. if( in_array( $feNeighbour, $ignore ) )
  190. {
  191. if( isset( $devices[ $device ][$feNeighbour] ) )
  192. unset( $devices[ $device ][$feNeighbour] );
  193. continue;
  194. }
  195. if( !isset( $devices[ $feNeighbour ] ) )
  196. {
  197. $snmp = new \OSS\SNMP( $feNeighbour, $this->getSNMP()->getCommunity() );
  198. $devices[ $feNeighbour ] = $snmp->useCisco_CDP()->neighbours();
  199. unset( $snmp );
  200. $this->crawl( $devices, $feNeighbour, $ignore );
  201. }
  202. }
  203. return $devices;
  204. }
  205. /**
  206. * Find the layer 2 topology as a flat array with no link mentioned more than once.
  207. *
  208. * Huh? This function:
  209. *
  210. * * takes the result of crawl() or calls crawl() to get the CDP topology
  211. * * foreach device, builds an array of device to device links
  212. * * SO LONG as that link has already not been accounted for in the other direction.
  213. *
  214. * I.e. if a link is found A -> B, then the same B -> A link will not be included
  215. *
  216. * The array returned is, for example:
  217. *
  218. * [cr-sw04.degkcp.example.ie] => Array
  219. * (
  220. * [cd-sw02.degkcp.example.ie] => Array
  221. * (
  222. * [GigabitEthernet1/0/3] => FastEthernet0/1
  223. * )
  224. *
  225. * [cr-sw03.degkcp.example.ie] => Array
  226. * (
  227. * [GigabitEthernet1/0/23] => GigabitEthernet1/0/23
  228. * [GigabitEthernet1/0/24] => GigabitEthernet1/0/24
  229. * )
  230. * )
  231. *
  232. * This tells us that cr-sw04(GigabitEthernet1/0/3) is connected to cd-sw02(FastEthernet0/1).
  233. *
  234. * It also tells us that cr-sw04 has two connections to cr-sw03.
  235. *
  236. * @see crawl()
  237. * @param array $devices The result of crawl() (if null, this function performs a crawl())
  238. * @return array L2 topology as described above.
  239. */
  240. public function linkTopology( $devices = null )
  241. {
  242. if( $devices == null )
  243. $devices = $this->crawl();
  244. $links = array();
  245. foreach( $devices as $feDevice => $feNeighbours )
  246. {
  247. foreach( $feNeighbours as $fe2Device => $fe2Links )
  248. {
  249. foreach( $fe2Links as $fe2Link )
  250. {
  251. // have we already accounted for this link on the other side?
  252. if( isset( $links[ $fe2Device ][ $feDevice ][ $fe2Link['remotePort'] ] ) )
  253. continue;
  254. if( !isset( $links[ $feDevice ] ) )
  255. $links[ $feDevice ] = array();
  256. if( !isset( $links[ $feDevice ][ $fe2Device ] ) )
  257. $links[ $feDevice ][ $fe2Device ] = array();
  258. $links[ $feDevice ][ $fe2Device ][ $fe2Link['localPort'] ] = $fe2Link['remotePort'];
  259. }
  260. }
  261. }
  262. return $links;
  263. }
  264. }