From bf181b9dc2ef0b2df5a4e874fbf7dfe3810e346e Mon Sep 17 00:00:00 2001 From: PipoCanaja <38363551+PipoCanaja@users.noreply.github.com> Date: Sun, 17 Nov 2019 16:30:43 +0100 Subject: [PATCH] Added support for routing table collection in discovery (#10182) * Clean broken VRF lite code * Change DB table for route discovery * Add VRF simple support * add port_id to db and discovery * static-fy the translation arrays * sort and search cleaning * Sorting refactor and validation * formatItem shortened * Handle ifIndex==0 meaning no next hop defined (MPLS) * Sync all create/updates * purge in daily * remove old route table * get rid of inetCidrRouteNextHop_device_id * fix wonky column orders * add route snmprec * fix sorting by interface * Move to new config * rename to route the new table * Properly display ipv6 compressed addresses * Translation before merge ./lnms translation:generate * Update manifest --- LibreNMS/Util/IPv6.php | 21 +- LibreNMS/Util/Rewrite.php | 5 + .../Table/RoutesTablesController.php | 195 ++++++++ app/Models/Route.php | 51 +++ daily.php | 5 +- daily.sh | 1 + .../2019_04_22_220000_update_route_table.php | 64 +++ doc/Support/Discovery Support.md | 4 +- html/mix-manifest.json | 12 +- includes/discovery/route.inc.php | 418 ++++++++++++------ includes/functions.php | 8 + includes/html/pages/device.inc.php | 5 + includes/html/pages/device/routing.inc.php | 1 + .../html/pages/device/routing/routes.inc.php | 54 +++ misc/config_definitions.json | 14 + misc/db_schema.yaml | 27 +- resources/lang/en/settings.php | 11 +- routes/web.php | 1 + tests/module_tables.yaml | 4 + tests/snmpsim/vrp_route.snmprec | 152 +++++++ 20 files changed, 902 insertions(+), 151 deletions(-) create mode 100644 app/Http/Controllers/Table/RoutesTablesController.php create mode 100644 app/Models/Route.php create mode 100644 database/migrations/2019_04_22_220000_update_route_table.php create mode 100644 includes/html/pages/device/routing/routes.inc.php create mode 100644 tests/snmpsim/vrp_route.snmprec diff --git a/LibreNMS/Util/IPv6.php b/LibreNMS/Util/IPv6.php index 445af00e88..78675810e2 100644 --- a/LibreNMS/Util/IPv6.php +++ b/LibreNMS/Util/IPv6.php @@ -46,6 +46,21 @@ class IPv6 extends IP $this->ip = $this->compressed(); // store in compressed format } + /** + * Convert a MySQL binary v6 (16-byte) IP address to a printable string. + * @param string $ip A binary string containing an IP address, as returned from MySQL's INET6_ATON function + * @return string Empty if not valid. + */ + // Fuction is from http://uk3.php.net/manual/en/function.inet-ntop.php + public static function ntop($ip) + { + $len = strlen($ip); + if ($len == 16) { + return inet_ntop(pack('A' . $len, $ip)); + } + return ''; + } + /** * Check if the supplied IP is valid. * @param string $ipv6 @@ -68,7 +83,7 @@ class IPv6 extends IP */ public function compressed() { - return inet6_ntop(inet_pton($this->ip)); + return self::ntop(inet_pton($this->ip)); } /** @@ -94,7 +109,7 @@ class IPv6 extends IP } } array_unshift($net_bytes, 'n*'); // add pack format - return inet6_ntop(call_user_func_array('pack', $net_bytes)); + return self::ntop(call_user_func_array('pack', $net_bytes)); } /** @@ -144,7 +159,7 @@ class IPv6 extends IP // zero pad $parts = explode(':', $ip, 8); return implode(':', array_map(function ($section) { - return zeropad($section, 4); + return Rewrite::zeropad($section, 4); }, $parts)); } diff --git a/LibreNMS/Util/Rewrite.php b/LibreNMS/Util/Rewrite.php index 842004f569..08197fd445 100644 --- a/LibreNMS/Util/Rewrite.php +++ b/LibreNMS/Util/Rewrite.php @@ -364,4 +364,9 @@ class Rewrite return $guests[$guest_id] ?? $guest_id; } + + public static function zeropad($num, $length = 2) + { + return str_pad($num, $length, '0', STR_PAD_LEFT); + } } diff --git a/app/Http/Controllers/Table/RoutesTablesController.php b/app/Http/Controllers/Table/RoutesTablesController.php new file mode 100644 index 0000000000..eb5d9d1667 --- /dev/null +++ b/app/Http/Controllers/Table/RoutesTablesController.php @@ -0,0 +1,195 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2019 PipoCanaja + * @author PipoCanaja + */ + +namespace App\Http\Controllers\Table; + +use App\Models\Ipv4Address; +use App\Models\Ipv4Network; +use App\Models\Ipv6Address; +use App\Models\Route; +use App\Models\Port; +use App\Models\Device; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; +use LibreNMS\Util\IP; +use LibreNMS\Util\Rewrite; +use LibreNMS\Util\Url; +use LibreNMS\Exceptions\InvalidIpException; + +class RoutesTablesController extends TableController +{ + protected $ipCache = []; + + protected function rules() + { + return [ + 'device_id' => 'nullable|integer', + 'searchby' => 'in:inetCidrRouteNextHop,inetCidrRouteDest', + ]; + } + + protected function filterFields($request) + { + return [ + 'route.context_name' => 'context_name', + 'route.inetCidrRouteProto' => 'proto', + ]; + } + + /** + * Defines the base query for this resource + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder + */ + protected function baseQuery($request) + { + $join = function ($query) { + $query->on('ports.port_id', 'route.port_id'); + }; + $showAllRoutes = trim(\Request::get('showAllRoutes')); + if ($request->device_id && $showAllRoutes == 'false') { + $query=Route::hasAccess($request->user()) + ->leftJoin('ports', $join) + ->where('route.device_id', $request->device_id) + ->where('updated_at', Route::hasAccess($request->user()) + ->where('route.device_id', $request->device_id) + ->select('updated_at') + ->max('updated_at')); + return $query; + } + if ($request->device_id && $showAllRoutes == 'true') { + $query=Route::hasAccess($request->user()) + ->leftJoin('ports', $join) + ->where('route.device_id', $request->device_id); + return $query; + } + return Route::hasAccess($request->user()) + ->leftJoin('ports', $join); + } + + /** + * @param string $search + * @param Builder $query + * @param array $fields + * @return Builder|\Illuminate\Database\Query\Builder + */ + protected function search($search, $query, $fields = []) + { + if ($search = trim(\Request::get('searchPhrase'))) { + $searchLike = '%' . $search . '%'; + return $query->where('route.inetCidrRouteNextHop', 'like', $searchLike) + ->orWhere('route.inetCidrRouteDest', 'like', $searchLike); + } + return $query; + } + + /** + * @param Request $request + * @param Builder $query + * @return Builder + */ + public function sort($request, $query) + { + $sort = $request->get('sort'); + if (isset($sort['inetCidrRouteIfIndex'])) { + $query->orderBy('ifDescr', $sort['inetCidrRouteIfIndex']) + ->orderBy('inetCidrRouteIfIndex', $sort['inetCidrRouteIfIndex']); + } + // Simple fields to sort + $s_fields = [ + 'inetCidrRouteDestType', + 'inetCidrRouteType', + 'inetCidrRouteMetric1', + 'inetCidrRoutePfxLen', + 'inetCidrRouteNextHop', + 'updated_at', + 'created_at', + 'context_name', + 'inetCidrRouteDest' + ]; + foreach ($s_fields as $s_field) { + if (isset($sort[$s_field])) { + $query->orderBy($s_field, $sort[$s_field]); + } + } + return $query; + } + + public function formatItem($route_entry) + { + $item = $route_entry->toArray(); + + if ($route_entry->updated_at) { + $item['updated_at'] = $route_entry->updated_at->diffForHumans(); + } + if ($route_entry->created_at) { + $item['created_at'] = $route_entry->created_at->toDateTimeString(); + } + if ($item['inetCidrRouteIfIndex'] == 0) { + $item['inetCidrRouteIfIndex'] = 'Undefined'; + } + if ($route_entry->inetCidrRouteNextHop) { + try { + $obj_inetCidrRouteNextHop = IP::parse($route_entry->inetCidrRouteNextHop); + $item['inetCidrRouteNextHop'] = $obj_inetCidrRouteNextHop->compressed(); + } catch (Exception $e) { + $item['inetCidrRouteNextHop'] = $route_entry->inetCidrRouteNextHop; + } + } + if ($route_entry->inetCidrRouteDest) { + try { + $obj_inetCidrRouteDest = IP::parse($route_entry->inetCidrRouteDest); + $item['inetCidrRouteDest'] = $obj_inetCidrRouteDest->compressed(); + } catch (Exception $e) { + $item['inetCidrRouteDest'] = $route_entry->inetCidrRouteDest; + } + } + $item['inetCidrRouteIfIndex'] = 'ifIndex ' . $item['inetCidrRouteIfIndex']; + if ($port = $route_entry->port()->first()) { + $item['inetCidrRouteIfIndex'] = Url::portLink($port, $port->getShortLabel()); + } + $device = Device::findByIp($route_entry->inetCidrRouteNextHop); + if ($device) { + if ($device->device_id == $route_entry->device_id || in_array($route_entry->inetCidrRouteNextHop, ['127.0.0.1', '::1'])) { + $item['inetCidrRouteNextHop'] = Url::deviceLink($device, "localhost"); + } else { + $item['inetCidrRouteNextHop'] = $item['inetCidrRouteNextHop'] . "
(" . Url::deviceLink($device) . ")"; + } + } + if ($route_entry->inetCidrRouteProto && $route_entry::$translateProto[$route_entry->inetCidrRouteProto]) { + $item['inetCidrRouteProto'] = $route_entry::$translateProto[$route_entry->inetCidrRouteProto]; + } + if ($route_entry->inetCidrRouteType && $route_entry::$translateType[$route_entry->inetCidrRouteType]) { + $item['inetCidrRouteType'] = $route_entry::$translateType[$route_entry->inetCidrRouteType]; + } + $item['context_name'] = '[global]'; + if ($route_entry->context_name != '') { + $item['context_name'] = '' . $route_entry->context_name . '' ; + } + return $item; + } +} diff --git a/app/Models/Route.php b/app/Models/Route.php new file mode 100644 index 0000000000..13242b7fdf --- /dev/null +++ b/app/Models/Route.php @@ -0,0 +1,51 @@ +belongsTo('App\Models\Device', 'device_id', 'device_id'); + } + + public function port() + { + return $this->belongsTo('App\Models\Port', 'port_id', 'port_id'); + } +} diff --git a/daily.php b/daily.php index 7de5822dd6..d3061363e7 100644 --- a/daily.php +++ b/daily.php @@ -96,7 +96,10 @@ if ($options['f'] === 'ports_fdb') { $ret = lock_and_purge('ports_fdb', 'updated_at < DATE_SUB(NOW(), INTERVAL ? DAY)'); exit($ret); } - +if ($options['f'] === 'route') { + $ret = lock_and_purge('route', 'updated_at < DATE_SUB(NOW(), INTERVAL ? DAY)'); + exit($ret); +} if ($options['f'] === 'eventlog') { $ret = lock_and_purge('eventlog', 'datetime < DATE_SUB(NOW(), INTERVAL ? DAY)'); exit($ret); diff --git a/daily.sh b/daily.sh index a2d1aa4920..e40744295b 100755 --- a/daily.sh +++ b/daily.sh @@ -275,6 +275,7 @@ main () { "alert_log" "rrd_purge" "ports_fdb" + "route" "ports_purge"); call_daily_php "${options[@]}"; ;; diff --git a/database/migrations/2019_04_22_220000_update_route_table.php b/database/migrations/2019_04_22_220000_update_route_table.php new file mode 100644 index 0000000000..d388081e5d --- /dev/null +++ b/database/migrations/2019_04_22_220000_update_route_table.php @@ -0,0 +1,64 @@ +increments('route_id'); + $table->timestamps(); + $table->unsignedInteger('device_id'); + $table->unsignedInteger('port_id'); + $table->string('context_name')->nullable(); + $table->bigInteger('inetCidrRouteIfIndex'); + $table->unsignedInteger('inetCidrRouteType'); + $table->unsignedInteger('inetCidrRouteProto'); + $table->unsignedInteger('inetCidrRouteNextHopAS'); + $table->unsignedInteger('inetCidrRouteMetric1'); + $table->string('inetCidrRouteDestType'); + $table->string('inetCidrRouteDest'); + $table->string('inetCidrRouteNextHopType'); + $table->string('inetCidrRouteNextHop'); + $table->string('inetCidrRoutePolicy'); + $table->unsignedInteger('inetCidrRoutePfxLen'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('route'); + // Create the old route table to reverse this. + Schema::create('route', function (Blueprint $table) { + $table->unsignedInteger('device_id'); + $table->string('context_name', 128); + $table->string('ipRouteDest', 39); + $table->string('ipRouteIfIndex', 256)->nullable(); + $table->string('ipRouteMetric', 256); + $table->string('ipRouteNextHop', 39); + $table->string('ipRouteType', 256); + $table->string('ipRouteProto', 256); + $table->unsignedInteger('discoveredAt'); + $table->string('ipRouteMask', 256); + $table->index(['device_id','context_name','ipRouteDest','ipRouteNextHop'], 'device'); + }); + } +} diff --git a/doc/Support/Discovery Support.md b/doc/Support/Discovery Support.md index 55d362a38e..cd7ece2a46 100644 --- a/doc/Support/Discovery Support.md +++ b/doc/Support/Discovery Support.md @@ -145,7 +145,9 @@ configured to be ignored by config options. `ipv6-addresses`: IPv6 Address detection -`route`: Route detection +`route`: This module will load the routing table of the device. The default route + limit is 1000 (configurable in config.php with + ```$config['routes']['max_number'] = 1000;```), with history data. `sensors`: Sensor detection such as Temperature, Humidity, Voltages + More diff --git a/html/mix-manifest.json b/html/mix-manifest.json index a1187fa80f..0e1976a2fb 100644 --- a/html/mix-manifest.json +++ b/html/mix-manifest.json @@ -3,10 +3,10 @@ "/css/app.css": "/css/app.css?id=17e56994706c74ee9663", "/js/manifest.js": "/js/manifest.js?id=3c768977c2574a34506e", "/js/vendor.js": "/js/vendor.js?id=00c1d21ecfea78860e09", - "/js/lang/de.js": "/js/lang/de.js?id=18b0b0e06813d1afed92", - "/js/lang/en.js": "/js/lang/en.js?id=a31a978859a3e4fe73c7", - "/js/lang/fr.js": "/js/lang/fr.js?id=07da32f987ba907e1f7f", - "/js/lang/ru.js": "/js/lang/ru.js?id=e10e85f321f1395378b6", - "/js/lang/uk.js": "/js/lang/uk.js?id=c8d4937e3ca47b60b7ac", - "/js/lang/zh-TW.js": "/js/lang/zh-TW.js?id=2160244d8105c946db9b" + "/js/lang/de.js": "/js/lang/de.js?id=e0623715e8df0895188b", + "/js/lang/en.js": "/js/lang/en.js?id=dce9919ef5fa35e3073a", + "/js/lang/fr.js": "/js/lang/fr.js?id=2d1159debd99a1909f12", + "/js/lang/ru.js": "/js/lang/ru.js?id=b007ddce75134acbe635", + "/js/lang/uk.js": "/js/lang/uk.js?id=146819d3cf1dfb16672d", + "/js/lang/zh-TW.js": "/js/lang/zh-TW.js?id=f57574a3892e5990ecbc" } diff --git a/includes/discovery/route.inc.php b/includes/discovery/route.inc.php index 151462c356..46a07001e3 100644 --- a/includes/discovery/route.inc.php +++ b/includes/discovery/route.inc.php @@ -14,154 +14,314 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +//We can use RFC1213 or IP-FORWARD-MIB or MPLS-L3VPN-STD-MIB -//This file is a litle diferent, because the route depend of the vrf, not of the context, -//like the others, so if i use the context, you will have the same information n time the context how have the same VRF -global $debug; +use App\Models\Device; +use LibreNMS\Util\IPv4; +$ipForwardMibRoutesNumber = snmp_get($device, 'IP-FORWARD-MIB::inetCidrRouteNumber.0', '-Osqn'); -$ids = array(); +$ipForwardNb = snmp_get_multi($device, ['inetCidrRouteNumber.0', 'ipCidrRouteNumber.0'], '-OQUs', 'IP-FORWARD-MIB'); -// For the moment only will be cisco and the version 3 -if ($device['os_group'] == "cisco") { +//Get the configured max routes number +$max_routes = 1000; +if (null != (Config::get('routes_max_number'))) { + $max_routes = Config::get('routes_max_number'); +} + +//Init update/create tables; +$create_row = []; +$update_row = []; +$delete_row = []; + +//store timestamp so all update / creation will be synced on same timestamp +$update_timestamp = dbFetchRows('select now() as now')[0]['now']; + +//Load current DB entries: +$dbRoute = dbFetchRows('select * from `route` where `device_id` = ?', array($device['device_id'])); +foreach ($dbRoute as $dbRow) { + $current = $mixed[$dbRow['context_name']][$dbRow['inetCidrRouteDestType']][$dbRow['inetCidrRouteDest']][$dbRow['inetCidrRoutePfxLen']][$dbRow['inetCidrRoutePolicy']][$dbRow['inetCidrRouteNextHopType']][$dbRow['inetCidrRouteNextHop']]; + if (isset($current) && isset($current['db']) && count($current['db']) > 0) { + //We have duplicate routes in DB, we'll clean that. + $delete_row[$dbRow['route_id']] = 1; + $delete_row_data[$dbRow['route_id']] = $dbRow; //DEBUG DATA ONLY + } else { + $mixed[$dbRow['context_name']][$dbRow['inetCidrRouteDestType']][$dbRow['inetCidrRouteDest']][$dbRow['inetCidrRoutePfxLen']][$dbRow['inetCidrRoutePolicy']][$dbRow['inetCidrRouteNextHopType']][$dbRow['inetCidrRouteNextHop']]['db'] = $dbRow; + } +} + +//Not a single route will be discovered if the amount is over maximum +// To prevent any bad behaviour on routers holding the full internet table + +//if the device does not support IP-FORWARD-MIB, we can still discover the ipv4 (only) +//routes using RFC1213 but no way to limit the amount of routes here !! + +if (! isset($ipForwardNb['0']['inetCidrRouteNumber'])) { //RFC1213-MIB $mib = "RFC1213-MIB"; - //IpRouteEntry - $vrfs_lite_cisco = array(); - - if (key_exists('vrf_lite_cisco', $device) && (count($device['vrf_lite_cisco']) != 0)) { - //i will only get one context of vrf, read the begin of this file - foreach ($device['vrf_lite_cisco'] as $vrf_lite) { - if (!key_exists($vrf_lite['vrf_name'], $vrfs_lite_cisco)) { - $vrfs_lite_cisco[$vrf_lite['vrf_name']] = $vrf_lite; - } - } - } else { - $vrfs_lite_cisco = array(array('context_name' => null)); - } - $tableRoute = array(); - foreach ($vrfs_lite_cisco as $vrf_lite) { - $device['context_name'] = $vrf_lite['context_name']; - - ////////////////ipRouteDest////////////////// - $oid = '.1.3.6.1.2.1.4.21.1.1'; - $resultHelp = snmp_walk($device, $oid, "-Osqn", $mib, null); - $resultHelp = trim($resultHelp); - $resultHelp = str_replace("$oid.", "", $resultHelp); - - foreach (explode("\n", $resultHelp) as $ipRouteDest) { - list($ip, $value) = explode(" ", $ipRouteDest); - $tableRoute[$ip]['ipRouteDest'] = $value; + $oid = '.1.3.6.1.2.1.4.21'; + $ipRoute = snmpwalk_group($device, $oid, $mib, 1, []); + d_echo($res); + d_echo('Table routage'); + d_echo($ipRoute); + echo "RFC1213 "; + foreach ($tableRoute as $ipRoute) { + if (empty($ipRoute['ipRouteDest']) || $ipRoute['ipRouteDest'] == '') { + continue; } - /////////////////ipRouteIfIndex////////////// - $oid = '.1.3.6.1.2.1.4.21.1.2'; - $resultHelp = snmp_walk($device, $oid, "-Osqn", $mib, null); - $resultHelp = trim($resultHelp); - $resultHelp = str_replace("$oid.", "", $resultHelp); - - foreach (explode("\n", $resultHelp) as $ipRouteIfIndex) { - list($ip, $value) = explode(" ", $ipRouteIfIndex); - $tableRoute[$ip]['ipRouteIfIndex'] = $value; + unset($entryClean); + $entryClean['inetCidrRouteDestType'] = 'ipv4'; + $entryClean['inetCidrRouteDest'] = $ipRoute['ipRouteDest']; + $inetCidrRoutePfxLen = IPv4::netmask2cidr($ipRoute['ipRouteMask']); //CONVERT + $entryClean['inetCidrRoutePfxLen'] = $inetCidrRoutePfxLen; + $entryClean['inetCidrRoutePolicy'] = $ipRoute['ipRouteInfo']; + $entryClean['inetCidrRouteNextHopType'] = 'ipv4'; + $entryClean['inetCidrRouteNextHop'] = $ipRoute['ipRouteNextHop']; + $entryClean['inetCidrRouteMetric1'] = $ipRoute['ipRouteMetric1']; + $entryClean['inetCidrRouteNextHopAS'] = '0'; + $entryClean['inetCidrRouteProto'] = $ipRoute['ipRouteProto']; + $entryClean['inetCidrRouteType'] = $ipRoute['ipRouteType']; + $entryClean['inetCidrRouteIfIndex'] = $ipRoute['ipRouteIfIndex']; + $entryClean['context_name'] = ''; + $entryClean['device_id'] = $device['device_id']; + $entryClean['port_id'] = Device::find($device['device_id'])->ports()->where('ifIndex', '=', $entryClean['inetCidrRouteIfIndex'])->first()->port_id; + $entryClean['updated_at'] = $update_timestamp; + $current = $mixed['']['ipv4'][$inetCidrRouteDest][$inetCidrRoutePfxLen][$entryClean['inetCidrRoutePolicy']]['ipv4'][$inetCidrRouteNextHop]; + if (isset($current) && isset($current['db']) && count($current['db']) > 0 && $delete_row[$current['db']['route_id']] != 1) { + //we already have a row in DB + $entryClean['route_id'] = $current['db']['route_id']; + $update_row[] = $entryClean; + } else { + $entry['created_at'] = array('NOW()'); + $create_row[] = $entryClean; } + } +} - ///////////////ipRouteMetric1/////////////// - $oid = '.1.3.6.1.2.1.4.21.1.3'; - $resultHelp = snmp_walk($device, $oid, "-Osqn", $mib, null); - $resultHelp = trim($resultHelp); - $resultHelp = str_replace("$oid.", "", $resultHelp); +// Not a single route will be discovered if the amount is over maximum +// To prevent any bad behaviour on routers holding the full internet table - foreach (explode("\n", $resultHelp) as $ipRouteMetric) { - list($ip, $value) = explode(" ", $ipRouteMetric); - $tableRoute[$ip]['ipRouteMetric'] = $value; - } - - ////////////ipRouteNextHop////////////////// - $oid = '.1.3.6.1.2.1.4.21.1.7'; - $resultHelp = snmp_walk($device, $oid, "-Osqn", $mib, null); - $resultHelp = trim($resultHelp); - $resultHelp = str_replace("$oid.", "", $resultHelp); - foreach (explode("\n", $resultHelp) as $ipRouteNextHop) { - list($ip, $value) = explode(" ", $ipRouteNextHop); - $tableRoute[$ip]['ipRouteNextHop'] = $value; - } - - ////////////ipRouteType///////////////////// - $oid = '.1.3.6.1.2.1.4.21.1.8'; - $resultHelp = snmp_walk($device, $oid, "-Osqn", $mib, null); - $resultHelp = trim($resultHelp); - $resultHelp = str_replace("$oid.", "", $resultHelp); - - foreach (explode("\n", $resultHelp) as $ipRouteType) { - list($ip, $value) = explode(" ", $ipRouteType); - $tableRoute[$ip]['ipRouteType'] = $value; - } - - ///////////ipRouteProto////////////////////// - $oid = '.1.3.6.1.2.1.4.21.1.9'; - $resultHelp = snmp_walk($device, $oid, "-Osqn", $mib, null); - $resultHelp = trim($resultHelp); - $resultHelp = str_replace("$oid.", "", $resultHelp); +// IP-FORWARD-MIB with inetCidrRouteTable - foreach (explode("\n", $resultHelp) as $ipRouteProto) { - list($ip, $value) = explode(" ", $ipRouteProto); - $tableRoute[$ip]['ipRouteProto'] = $value; - } - - /* - ///////////ipRouteAge////////////////////// - $oid = '.1.3.6.1.2.1.4.21.1.10'; - $resultHelp = snmp_walk($device, $oid, "-Osqn", $mib, NULL); - $resultHelp = str_replace("$oid.", "", $resultHelp); - - foreach (explode("\n", $resultHelp) as $ipRouteAge) { - list($ip,$value)=explode(" ",$ipRouteAge); - $tableRoute[$ip]['ipRouteAge']=$value; - } */ - - ///////////ipRouteMask////////////////////// - $oid = '.1.3.6.1.2.1.4.21.1.11'; - $resultHelp = snmp_walk($device, $oid, ['-Osq', '-Ln'], $mib, null); - $resultHelp = trim($resultHelp); - $resultHelp = str_replace("$oid.", "", $resultHelp); - - foreach (explode("\n", $resultHelp) as $ipRouteMask) { - list($ip, $value) = explode(" ", $ipRouteMask); - $tableRoute[$ip]['ipRouteMask'] = $value; - } - - if ($debug) { - echo 'Table routage'; - var_dump($tableRoute); - } - - foreach ($tableRoute as $ipRoute) { - if (empty($ipRoute['ipRouteDest']) || $ipRoute['ipRouteDest'] == '') { - continue; - } - - $oldRouteRow = dbFetchRow('select * from route where device_id = ? AND ipRouteDest = ? AND context_name = ?', array($device['device_id'], $ipRoute['ipRouteDest'], $device['context_name'])); - if (!empty($oldRouteRow)) { - unset($oldRouteRow['discoveredAt']); - $changeRoute = array(); - foreach ($ipRoute as $key => $value) { - if ($oldRouteRow[$key] != $value) { - $changeRoute[$key] = $value; +if (isset($ipForwardNb['0']['inetCidrRouteNumber']) && $ipForwardNb['0']['inetCidrRouteNumber'] < $max_routes) { + // We have ip forward mib available + d_echo('IP FORWARD MIB (with inetCidr support)'); + $mib = 'IP-FORWARD-MIB'; + $oid = '.1.3.6.1.2.1.4.24.7.1'; + $res = snmpwalk_group($device, $oid, $mib, 6, []); + $ipForwardNb['0']['inetCidrRouteNumber'] = count($res); // Some cisco devices report ipv4+ipv6 but only include ipv6 in this table + echo "inetCidrRoute "; + foreach ($res as $inetCidrRouteDestType => $next1) { + //ipv4 or ipv6 + foreach ($next1 as $inetCidrRouteDest => $next2) { + //we have only 1 child here, the mask + $inetCidrRoutePfxLen = array_keys($next2)[0]; + $next3 = array_values($next2)[0]; + $inetCidrRoutePolicy = array_keys($next3)[0]; + $next4 = array_values($next3)[0]; + foreach ($next4 as $inetCidrRouteNextHopType => $next5) { + foreach ($next5 as $inetCidrRouteNextHop => $entry) { + $entry['inetCidrRouteDestType'] = $inetCidrRouteDestType; + $entry['inetCidrRouteDest'] = normalize_snmp_ip_address($inetCidrRouteDest); + $entry['inetCidrRoutePfxLen'] = $inetCidrRoutePfxLen; + $entry['inetCidrRoutePolicy'] = $inetCidrRoutePolicy; + $entry['inetCidrRouteNextHopType'] = $inetCidrRouteNextHopType; + $entry['inetCidrRouteNextHop'] = normalize_snmp_ip_address($inetCidrRouteNextHop); + $entry['context_name'] = ''; + $entry['device_id'] = $device['device_id']; + $entry['port_id'] = Device::find($device['device_id'])->ports()->where('ifIndex', '=', $entry['inetCidrRouteIfIndex'])->first()->port_id; + $entry['updated_at'] = $update_timestamp; + unset($entry['inetCidrRouteAge']); + unset($entry['inetCidrRouteMetric2']); + unset($entry['inetCidrRouteMetric3']); + unset($entry['inetCidrRouteMetric4']); + unset($entry['inetCidrRouteMetric5']); + unset($entry['inetCidrRouteStatus']); + $entryPerType[$inetCidrRouteDestType]++; + $current = $mixed[''][$inetCidrRouteDestType][$inetCidrRouteDest][$inetCidrRoutePfxLen][$inetCidrRoutePolicy][$inetCidrRouteNextHopType][$inetCidrRouteNextHop]; + if (isset($current) && isset($current['db']) && count($current['db']) > 0 && $delete_row[$current['db']['route_id']] != 1) { + //we already have a row in DB + $entry['route_id'] = $current['db']['route_id']; + $update_row[] = $entry; + } else { + d_echo(isset($current)); + d_echo(isset($current['db'])); + d_echo($current['db']); + d_echo(count($current['db'])); + d_echo($delete_row[$current['db']['route_id']]); + $entry['created_at'] = array('NOW()'); + $create_row[] = $entry; } } - if (!empty($changeRoute)) { - dbUpdate($changeRoute, 'route', 'device_id = ? and ipRouteDest = ? and context_name = ?', array($device['device_id'], $ipRoute['ipRouteDest'], $device['context_name'])); - } - } else { - $toInsert = array_merge($ipRoute, array('device_id' => $device['device_id'], 'context_name' => $device['context_name'], 'discoveredAt' => time())); - dbInsert($toInsert, 'route'); } } - // unset($tableRoute); } - unset($vrfs_lite_cisco); + $ipForwardNb['0']['inetCidrRouteNumber'] = $entryPerType['ipv4']; + // Some cisco devices report ipv4+ipv6 in inetCidrRouteNumber + // But only include ipv6 in inetCidrRoute + // So we count the real amount of ipv4 we get, in order to get the missing ipv4 from ipCidrRouteTable if needed } + +// IP-FORWARD-MIB with ipCidrRouteTable in case ipCidrRouteTable has more entries than inetCidrRouteTable (Some older routers) + +if (isset($ipForwardNb['0']['ipCidrRouteNumber']) && $ipForwardNb['0']['ipCidrRouteNumber'] > $ipForwardNb['0']['inetCidrRouteNumber'] && $ipForwardNb['0']['ipCidrRouteNumber'] < $max_routes) { + //device uses only ipCidrRoute and not inetCidrRoute + d_echo('IP FORWARD MIB (without inetCidr support)'); + $mib = 'IP-FORWARD-MIB'; + $oid = '.1.3.6.1.2.1.4.24.4.1'; + $ipCidrTable = snmpwalk_group($device, $oid, $mib, 6, []); + echo "ipCidrRouteTable "; + // we need to translate the values to inetCidr structure; + //d_echo($ipCidrTable); + foreach ($ipCidrTable as $inetCidrRouteDest => $next1) { + foreach ($next1 as $ipCidrRouteMask => $next2) { + foreach ($next2 as $ipCidrRouteTos => $next3) { + foreach ($next3 as $inetCidrRouteNextHop => $entry) { + unset($entryClean); + $entryClean['inetCidrRouteDestType'] = 'ipv4'; + $entryClean['inetCidrRouteDest'] = $inetCidrRouteDest; + $inetCidrRoutePfxLen = IPv4::netmask2cidr($entry['ipCidrRouteMask']); //CONVERT + $entryClean['inetCidrRoutePfxLen'] = $inetCidrRoutePfxLen; + $entryClean['inetCidrRoutePolicy'] = $entry['ipCidrRouteInfo']; + $entryClean['inetCidrRouteNextHopType'] = 'ipv4'; + $entryClean['inetCidrRouteNextHop'] = $inetCidrRouteNextHop; + $entryClean['inetCidrRouteMetric1'] = $entry['ipCidrRouteMetric1']; + $entryClean['inetCidrRouteProto'] = $entry['ipCidrRouteProto']; + $entryClean['inetCidrRouteType'] = $entry['ipCidrRouteType']; + $entryClean['inetCidrRouteIfIndex'] = $entry['ipCidrRouteIfIndex']; + $entryClean['inetCidrRouteNextHopAS'] = $entry['ipCidrRouteNextHopAS']; + $entryClean['context_name'] = ''; + $entryClean['device_id'] = $device['device_id']; + $entryClean['port_id'] = Device::find($device['device_id'])->ports()->where('ifIndex', '=', $entryClean['inetCidrRouteIfIndex'])->first()->port_id; + $entryClean['updated_at'] = $update_timestamp; + $current = $mixed['']['ipv4'][$inetCidrRouteDest][$inetCidrRoutePfxLen][$entryClean['inetCidrRoutePolicy']]['ipv4'][$inetCidrRouteNextHop]; + if (isset($current) && isset($current['db']) && count($current['db']) > 0 && $delete_row[$current['db']['route_id']] != 1) { + //we already have a row in DB + $entryClean['route_id'] = $current['db']['route_id']; + $update_row[] = $entryClean; + } else { + $entryClean['created_at'] = array('NOW()'); + $create_row[] = $entryClean; + } + } + } + } + } +} + +// We can now check if we have MPLS VPN routing table available : +// MPLS-L3VPN-STD-MIB::mplsL3VpnVrfRteTable +// Route numbers : MPLS-L3VPN-STD-MIB::mplsL3VpnVrfPerfCurrNumRoutes + + +$mib = 'MPLS-L3VPN-STD-MIB'; +$oid = 'mplsL3VpnVrfPerfCurrNumRoutes'; +$mpls_vpn_route_nb = snmpwalk_group($device, $oid, $mib, 6, []); + +foreach ($mpls_vpn_route_nb as $vpnId => $route_nb) { + if ($route_nb['mplsL3VpnVrfPerfCurrNumRoutes'] > $max_routes) { + echo("Skipping all MPLS routes because vpn instance $vpnId has more than $max_routes routes."); + $mpls_skip = 1; + } +} + +if ($mpls_skip != 1) { + echo "mplsL3VpnVrfRteTable "; + // We can discover the routes; + $oid = 'mplsL3VpnVrfRteTable'; + $mpls_route_table = snmpwalk_group($device, $oid, $mib, 7, []); + foreach ($mpls_route_table as $vpnId => $inetCidrRouteTable) { + foreach ($inetCidrRouteTable as $inetCidrRouteDestType => $next1) { + //ipv4 or ipv6 + foreach ($next1 as $inetCidrRouteDest => $next2) { + //we have only 1 child here, the mask + $inetCidrRoutePfxLen = array_keys($next2)[0]; + $next3 = array_values($next2)[0]; + $inetCidrRoutePolicy = array_keys($next3)[0]; + $next4 = array_values($next3)[0]; + foreach ($next4 as $inetCidrRouteNextHopType => $next5) { + foreach ($next5 as $inetCidrRouteNextHop => $entry) { + $entry['inetCidrRouteDestType'] = $inetCidrRouteDestType; + $entry['inetCidrRouteDest'] = normalize_snmp_ip_address($inetCidrRouteDest); + $entry['inetCidrRoutePfxLen'] = $inetCidrRoutePfxLen; + $entry['inetCidrRoutePolicy'] = $inetCidrRoutePolicy; + $entry['inetCidrRouteNextHopType'] = $inetCidrRouteNextHopType; + $entry['inetCidrRouteNextHop'] = normalize_snmp_ip_address($inetCidrRouteNextHop); + $entry['context_name'] = $vpnId; + $entry['device_id'] = $device['device_id']; + $entry['port_id'] = Device::find($device['device_id'])->ports()->where('ifIndex', '=', $entry['inetCidrRouteIfIndex'])->first()->port_id; + $entry['updated_at'] = $update_timestamp; + $entry['inetCidrRouteIfIndex'] = $entry['mplsL3VpnVrfRteInetCidrIfIndex']; + $entry['inetCidrRouteType'] = $entry['mplsL3VpnVrfRteInetCidrType']; + $entry['inetCidrRouteProto'] = $entry['mplsL3VpnVrfRteInetCidrProto']; + $entry['inetCidrRouteMetric1'] = $entry['mplsL3VpnVrfRteInetCidrMetric1']; + $entry['inetCidrRouteNextHopAS'] = $entry['mplsL3VpnVrfRteInetCidrNextHopAS']; + unset($entry['mplsL3VpnVrfRteXCPointer']); + unset($entry['mplsL3VpnVrfRteInetCidrMetric1']); + unset($entry['mplsL3VpnVrfRteInetCidrMetric2']); + unset($entry['mplsL3VpnVrfRteInetCidrMetric3']); + unset($entry['mplsL3VpnVrfRteInetCidrMetric4']); + unset($entry['mplsL3VpnVrfRteInetCidrMetric5']); + unset($entry['mplsL3VpnVrfRteInetCidrAge']); + unset($entry['mplsL3VpnVrfRteInetCidrProto']); + unset($entry['mplsL3VpnVrfRteInetCidrType']); + unset($entry['mplsL3VpnVrfRteInetCidrStatus']); + unset($entry['mplsL3VpnVrfRteInetCidrIfIndex']); + unset($entry['mplsL3VpnVrfRteInetCidrNextHopAS']); + $current = $mixed[$vpnId][$inetCidrRouteDestType][$inetCidrRouteDest][$inetCidrRoutePfxLen][$inetCidrRoutePolicy][$inetCidrRouteNextHopType][$inetCidrRouteNextHop]; + if (isset($current) && isset($current['db']) && count($current['db']) > 0 && $delete_row[$current['db']['route_id']] != 1) { + //we already have a row in DB + $entry['route_id'] = $current['db']['route_id']; + $update_row[] = $entry; + } else { + d_echo(isset($current)); + d_echo(isset($current['db'])); + d_echo($current['db']); + d_echo(count($current['db'])); + d_echo($delete_row[$current['db']['route_id']]); + $entry['created_at'] = array('NOW()'); + $create_row[] = $entry; + } + } + } + } + } + } +} +echo "\nProcessing: "; + +// We can now process the data into the DB +foreach ($delete_row as $k => $v) { + if ($v > 0) { + dbDelete( + 'route', + '`route_id` = ?', + array($k) + ); + echo '-'; + d_echo($delete_row_data[$k]); + } +} + +foreach ($update_row as $upd_entry) { + dbUpdate( + $upd_entry, + 'route', + '`route_id` = ?', + array($upd_entry['route_id']) + ); + echo '.'; +} + +foreach ($create_row as $new_entry) { + $new_entry['created_at'] = $update_timestamp; + dbInsert($new_entry, 'route'); + echo '+'; +} + +// EOF diff --git a/includes/functions.php b/includes/functions.php index 305abe92a2..1e6b6da5cd 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -1323,6 +1323,14 @@ function convert_delay($delay) return($delay_sec); } +function normalize_snmp_ip_address($data) +{ + // $data is received from snmpwalk, can be ipv4 xxx.xxx.xxx.xxx or ipv6 xx:xx:...:xx (16 chunks) + // ipv4 is returned unchanged, ipv6 is returned with one ':' removed out of two, like + // xxxx:xxxx:...:xxxx (8 chuncks) + return (preg_replace('/([0-9a-fA-F]{2}):([0-9a-fA-F]{2})/', '\1\2', explode('%', $data, 2)[0])); +} + function guidv4($data) { // http://stackoverflow.com/questions/2040240/php-function-to-generate-v4-uuid#15875555 diff --git a/includes/html/pages/device.inc.php b/includes/html/pages/device.inc.php index 9184878de3..a3a0c78b67 100644 --- a/includes/html/pages/device.inc.php +++ b/includes/html/pages/device.inc.php @@ -269,6 +269,11 @@ if (device_permitted($vars['device']) || $permitted_by_port) { $routing_tabs[] = 'cisco-otv'; } + $device_routing_count['routes'] = dbFetchCell('SELECT COUNT(*) FROM `route` WHERE `device_id` = ?', array($device['device_id'])); + if ($device_routing_count['routes']) { + $routing_tabs[] = 'routes'; + } + if (is_array($routing_tabs)) { echo '
  • diff --git a/includes/html/pages/device/routing.inc.php b/includes/html/pages/device/routing.inc.php index c0ffdaf547..c01e884d70 100644 --- a/includes/html/pages/device/routing.inc.php +++ b/includes/html/pages/device/routing.inc.php @@ -20,6 +20,7 @@ $type_text['bgp'] = 'BGP'; $type_text['cef'] = 'CEF'; $type_text['ospf'] = 'OSPF'; $type_text['vrf'] = 'VRFs'; +$type_text['routes'] = 'Routing Table'; $type_text['cisco-otv'] = 'OTV'; $type_text['mpls'] = 'MPLS'; diff --git a/includes/html/pages/device/routing/routes.inc.php b/includes/html/pages/device/routing/routes.inc.php new file mode 100644 index 0000000000..6e33774875 --- /dev/null +++ b/includes/html/pages/device/routing/routes.inc.php @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + +
    VRFProtoDestinationMaskNext hopInterfaceMetricTypeProtoFirst seenLast seen
    +
    Warning: Routing Table is only retrieved during device discovery. Devices are skipped if they have more than routes.
    + diff --git a/misc/config_definitions.json b/misc/config_definitions.json index 8fb090d999..f62938583f 100644 --- a/misc/config_definitions.json +++ b/misc/config_definitions.json @@ -3878,6 +3878,20 @@ }, "type": "array" }, + "routes_max_number": { + "default": 1000, + "group": "discovery", + "section": "route", + "order": 3, + "type": "integer" + }, + "route_purge": { + "default": 10, + "group": "system", + "section": "cleanup", + "order": 2, + "type": "integer" + }, "rrd.heartbeat": { "default": 600, "group": "poller", diff --git a/misc/db_schema.yaml b/misc/db_schema.yaml index c6d44d8c1e..afa066d05f 100644 --- a/misc/db_schema.yaml +++ b/misc/db_schema.yaml @@ -1552,18 +1552,25 @@ pseudowires: PRIMARY: { Name: PRIMARY, Columns: [pseudowire_id], Unique: true, Type: BTREE } route: Columns: + - { Field: route_id, Type: 'int(10) unsigned', 'Null': false, Extra: auto_increment } + - { Field: created_at, Type: timestamp, 'Null': true, Extra: '' } + - { Field: updated_at, Type: timestamp, 'Null': true, Extra: '' } - { Field: device_id, Type: 'int(10) unsigned', 'Null': false, Extra: '' } - - { Field: context_name, Type: varchar(128), 'Null': false, Extra: '' } - - { Field: ipRouteDest, Type: varchar(39), 'Null': false, Extra: '' } - - { Field: ipRouteIfIndex, Type: varchar(256), 'Null': true, Extra: '' } - - { Field: ipRouteMetric, Type: varchar(256), 'Null': false, Extra: '' } - - { Field: ipRouteNextHop, Type: varchar(39), 'Null': false, Extra: '' } - - { Field: ipRouteType, Type: varchar(256), 'Null': false, Extra: '' } - - { Field: ipRouteProto, Type: varchar(256), 'Null': false, Extra: '' } - - { Field: discoveredAt, Type: 'int(10) unsigned', 'Null': false, Extra: '' } - - { Field: ipRouteMask, Type: varchar(256), 'Null': false, Extra: '' } + - { Field: port_id, Type: 'int(10) unsigned', 'Null': false, Extra: '' } + - { Field: context_name, Type: varchar(255), 'Null': true, Extra: '' } + - { Field: inetCidrRouteIfIndex, Type: bigint(20), 'Null': false, Extra: '' } + - { Field: inetCidrRouteType, Type: 'int(10) unsigned', 'Null': false, Extra: '' } + - { Field: inetCidrRouteProto, Type: 'int(10) unsigned', 'Null': false, Extra: '' } + - { Field: inetCidrRouteNextHopAS, Type: 'int(10) unsigned', 'Null': false, Extra: '' } + - { Field: inetCidrRouteMetric1, Type: 'int(10) unsigned', 'Null': false, Extra: '' } + - { Field: inetCidrRouteDestType, Type: varchar(255), 'Null': false, Extra: '' } + - { Field: inetCidrRouteDest, Type: varchar(255), 'Null': false, Extra: '' } + - { Field: inetCidrRouteNextHopType, Type: varchar(255), 'Null': false, Extra: '' } + - { Field: inetCidrRouteNextHop, Type: varchar(255), 'Null': false, Extra: '' } + - { Field: inetCidrRoutePolicy, Type: varchar(255), 'Null': false, Extra: '' } + - { Field: inetCidrRoutePfxLen, Type: 'int(10) unsigned', 'Null': false, Extra: '' } Indexes: - device: { Name: device, Columns: [device_id, context_name, ipRouteDest, ipRouteNextHop], Unique: false, Type: BTREE } + PRIMARY: { Name: PRIMARY, Columns: [route_id], Unique: true, Type: BTREE } sensors: Columns: - { Field: sensor_id, Type: 'int(10) unsigned', 'Null': false, Extra: auto_increment } diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php index 43d46b60f2..30633dd31e 100644 --- a/resources/lang/en/settings.php +++ b/resources/lang/en/settings.php @@ -24,7 +24,8 @@ return [ 'ldap' => 'LDAP Settings' ], 'discovery' => [ - 'general' => 'General Discovery Settings' + 'general' => 'General Discovery Settings', + 'route' => 'Routes Discovery Module', ], 'external' => [ 'binaries' => 'Binary Locations', @@ -596,6 +597,14 @@ return [ 'description' => 'Show status publicly', 'help' => 'Shows the status of some devices on the logon page without authentication.' ], + 'routes_max_number' => [ + 'description' => 'Max number of routes allowed for discovery', + 'help' => 'No route will be discovered if the size of the routing table is bigger than this number' + ], + 'route_purge' => [ + 'description' => 'Route entries older than (days)', + 'help' => 'Cleanup done by daily.sh' + ], 'rrd' => [ 'heartbeat' => [ 'description' => 'Change the rrd heartbeat value (default 600)' diff --git a/routes/web.php b/routes/web.php index 7259df32df..b9cd85c4cf 100644 --- a/routes/web.php +++ b/routes/web.php @@ -102,6 +102,7 @@ Route::group(['middleware' => ['auth', '2fa'], 'guard' => 'auth'], function () { Route::post('device', 'DeviceController'); Route::post('eventlog', 'EventlogController'); Route::post('fdb-tables', 'FdbTablesController'); + Route::post('routes', 'RoutesTablesController'); Route::post('graylog', 'GraylogController'); Route::post('location', 'LocationController'); Route::post('port-nac', 'PortNacController'); diff --git a/tests/module_tables.yaml b/tests/module_tables.yaml index 11c7105058..c0a644ee4a 100644 --- a/tests/module_tables.yaml +++ b/tests/module_tables.yaml @@ -61,6 +61,10 @@ ports: joins: - { left: ports.port_id, right: ports_statistics.port_id } order_by: ports.ifIndex, ports.ifDescr, ports.ifName +route: + route: + excluded_fields: [device_id, route_id, created_at, updated_at] + order_by: inetCidrRouteDest nac: ports_nac: excluded_fields: [ports_nac_id, device_id, port_id] diff --git a/tests/snmpsim/vrp_route.snmprec b/tests/snmpsim/vrp_route.snmprec new file mode 100644 index 0000000000..49196256f6 --- /dev/null +++ b/tests/snmpsim/vrp_route.snmprec @@ -0,0 +1,152 @@ +1.3.6.1.2.1.1.1.0|4x|53353732302d3536432d5057522d45492d41430a48756177656920566572736174696c6520526f7574696e6720506c6174666f726d20536f667477617265200d0a205652502028522920736f6674776172652c56657273696f6e20352e3137302028533537323020563230305230313143313053504336303029200d0a20436f707972696768742028432920323030372048756177656920546563686e6f6c6f6769657320436f2e2c204c74642e +1.3.6.1.2.1.1.2.0|6|1.3.6.1.4.1.2011.2.23.291 +1.3.6.1.2.1.1.3.0|67|103903498 +1.3.6.1.2.1.1.4.0|4| +1.3.6.1.2.1.1.5.0|4| +1.3.6.1.2.1.1.6.0|4| +1.3.6.1.2.1.4.24.3.0|66|5 +1.3.6.1.2.1.4.24.6.0|66|5 +1.3.6.1.2.1.4.24.7.1.7.1.4.0.0.0.0.0.2.0.0.1.4.10.199.0.1|2|58 +1.3.6.1.2.1.4.24.7.1.7.1.4.10.101.155.0.24.2.0.0.1.4.10.199.0.1|2|118 +1.3.6.1.2.1.4.24.7.1.7.1.4.10.120.0.0.24.2.0.0.1.4.10.199.0.1|2|118 +1.3.6.1.2.1.4.24.7.1.7.1.4.10.121.2.0.24.2.0.0.1.4.10.199.0.1|2|118 +1.3.6.1.2.1.4.24.7.1.7.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.50|2|58 +1.3.6.1.2.1.4.24.7.1.7.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.103|2|118 +1.3.6.1.2.1.4.24.7.1.7.1.4.10.199.0.50.32.2.0.0.1.4.127.0.0.1|2|58 +1.3.6.1.2.1.4.24.7.1.7.1.4.10.199.0.103.32.2.0.0.1.4.127.0.0.1|2|118 +1.3.6.1.2.1.4.24.7.1.7.1.4.10.199.0.255.32.2.0.0.1.4.127.0.0.1|2|118 +1.3.6.1.2.1.4.24.7.1.7.1.4.127.0.0.0.8.2.0.0.1.4.127.0.0.1|2|1 +1.3.6.1.2.1.4.24.7.1.7.1.4.127.0.0.1.32.2.0.0.1.4.127.0.0.1|2|1 +1.3.6.1.2.1.4.24.7.1.7.1.4.127.255.255.255.32.2.0.0.1.4.127.0.0.1|2|3 +1.3.6.1.2.1.4.24.7.1.7.1.4.255.255.255.255.32.2.0.0.1.4.127.0.0.1|2|3 +1.3.6.1.2.1.4.24.7.1.8.1.4.0.0.0.0.0.2.0.0.1.4.10.199.0.1|2|4 +1.3.6.1.2.1.4.24.7.1.8.1.4.10.101.155.0.24.2.0.0.1.4.10.199.0.1|2|4 +1.3.6.1.2.1.4.24.7.1.8.1.4.10.120.0.0.24.2.0.0.1.4.10.199.0.1|2|4 +1.3.6.1.2.1.4.24.7.1.8.1.4.10.121.2.0.24.2.0.0.1.4.10.199.0.1|2|4 +1.3.6.1.2.1.4.24.7.1.8.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.50|2|3 +1.3.6.1.2.1.4.24.7.1.8.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.103|2|3 +1.3.6.1.2.1.4.24.7.1.8.1.4.10.199.0.50.32.2.0.0.1.4.127.0.0.1|2|3 +1.3.6.1.2.1.4.24.7.1.8.1.4.10.199.0.103.32.2.0.0.1.4.127.0.0.1|2|3 +1.3.6.1.2.1.4.24.7.1.8.1.4.10.199.0.255.32.2.0.0.1.4.127.0.0.1|2|3 +1.3.6.1.2.1.4.24.7.1.8.1.4.127.0.0.0.8.2.0.0.1.4.127.0.0.1|2|3 +1.3.6.1.2.1.4.24.7.1.8.1.4.127.0.0.1.32.2.0.0.1.4.127.0.0.1|2|3 +1.3.6.1.2.1.4.24.7.1.8.1.4.127.255.255.255.32.2.0.0.1.4.127.0.0.1|2|3 +1.3.6.1.2.1.4.24.7.1.8.1.4.255.255.255.255.32.2.0.0.1.4.127.0.0.1|2|3 +1.3.6.1.2.1.4.24.7.1.9.1.4.0.0.0.0.0.2.0.0.1.4.10.199.0.1|2|3 +1.3.6.1.2.1.4.24.7.1.9.1.4.10.101.155.0.24.2.0.0.1.4.10.199.0.1|2|3 +1.3.6.1.2.1.4.24.7.1.9.1.4.10.120.0.0.24.2.0.0.1.4.10.199.0.1|2|3 +1.3.6.1.2.1.4.24.7.1.9.1.4.10.121.2.0.24.2.0.0.1.4.10.199.0.1|2|3 +1.3.6.1.2.1.4.24.7.1.9.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.50|2|2 +1.3.6.1.2.1.4.24.7.1.9.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.103|2|2 +1.3.6.1.2.1.4.24.7.1.9.1.4.10.199.0.50.32.2.0.0.1.4.127.0.0.1|2|2 +1.3.6.1.2.1.4.24.7.1.9.1.4.10.199.0.103.32.2.0.0.1.4.127.0.0.1|2|2 +1.3.6.1.2.1.4.24.7.1.9.1.4.10.199.0.255.32.2.0.0.1.4.127.0.0.1|2|2 +1.3.6.1.2.1.4.24.7.1.9.1.4.127.0.0.0.8.2.0.0.1.4.127.0.0.1|2|2 +1.3.6.1.2.1.4.24.7.1.9.1.4.127.0.0.1.32.2.0.0.1.4.127.0.0.1|2|2 +1.3.6.1.2.1.4.24.7.1.9.1.4.127.255.255.255.32.2.0.0.1.4.127.0.0.1|2|2 +1.3.6.1.2.1.4.24.7.1.9.1.4.255.255.255.255.32.2.0.0.1.4.127.0.0.1|2|2 +1.3.6.1.2.1.4.24.7.1.10.1.4.0.0.0.0.0.2.0.0.1.4.10.199.0.1|66|1038806 +1.3.6.1.2.1.4.24.7.1.10.1.4.10.101.155.0.24.2.0.0.1.4.10.199.0.1|66|5477268 +1.3.6.1.2.1.4.24.7.1.10.1.4.10.120.0.0.24.2.0.0.1.4.10.199.0.1|66|5477268 +1.3.6.1.2.1.4.24.7.1.10.1.4.10.121.2.0.24.2.0.0.1.4.10.199.0.1|66|5477268 +1.3.6.1.2.1.4.24.7.1.10.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.50|66|1038806 +1.3.6.1.2.1.4.24.7.1.10.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.103|66|5477268 +1.3.6.1.2.1.4.24.7.1.10.1.4.10.199.0.50.32.2.0.0.1.4.127.0.0.1|66|1038806 +1.3.6.1.2.1.4.24.7.1.10.1.4.10.199.0.103.32.2.0.0.1.4.127.0.0.1|66|5477268 +1.3.6.1.2.1.4.24.7.1.10.1.4.10.199.0.255.32.2.0.0.1.4.127.0.0.1|66|5477268 +1.3.6.1.2.1.4.24.7.1.10.1.4.127.0.0.0.8.2.0.0.1.4.127.0.0.1|66|1038854 +1.3.6.1.2.1.4.24.7.1.10.1.4.127.0.0.1.32.2.0.0.1.4.127.0.0.1|66|1038854 +1.3.6.1.2.1.4.24.7.1.10.1.4.127.255.255.255.32.2.0.0.1.4.127.0.0.1|66|5905686 +1.3.6.1.2.1.4.24.7.1.10.1.4.255.255.255.255.32.2.0.0.1.4.127.0.0.1|66|5905686 +1.3.6.1.2.1.4.24.7.1.11.1.4.0.0.0.0.0.2.0.0.1.4.10.199.0.1|66|0 +1.3.6.1.2.1.4.24.7.1.11.1.4.10.101.155.0.24.2.0.0.1.4.10.199.0.1|66|0 +1.3.6.1.2.1.4.24.7.1.11.1.4.10.120.0.0.24.2.0.0.1.4.10.199.0.1|66|0 +1.3.6.1.2.1.4.24.7.1.11.1.4.10.121.2.0.24.2.0.0.1.4.10.199.0.1|66|0 +1.3.6.1.2.1.4.24.7.1.11.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.50|66|0 +1.3.6.1.2.1.4.24.7.1.11.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.103|66|0 +1.3.6.1.2.1.4.24.7.1.11.1.4.10.199.0.50.32.2.0.0.1.4.127.0.0.1|66|0 +1.3.6.1.2.1.4.24.7.1.11.1.4.10.199.0.103.32.2.0.0.1.4.127.0.0.1|66|0 +1.3.6.1.2.1.4.24.7.1.11.1.4.10.199.0.255.32.2.0.0.1.4.127.0.0.1|66|0 +1.3.6.1.2.1.4.24.7.1.11.1.4.127.0.0.0.8.2.0.0.1.4.127.0.0.1|66|0 +1.3.6.1.2.1.4.24.7.1.11.1.4.127.0.0.1.32.2.0.0.1.4.127.0.0.1|66|0 +1.3.6.1.2.1.4.24.7.1.11.1.4.127.255.255.255.32.2.0.0.1.4.127.0.0.1|66|0 +1.3.6.1.2.1.4.24.7.1.11.1.4.255.255.255.255.32.2.0.0.1.4.127.0.0.1|66|0 +1.3.6.1.2.1.4.24.7.1.12.1.4.0.0.0.0.0.2.0.0.1.4.10.199.0.1|2|0 +1.3.6.1.2.1.4.24.7.1.12.1.4.10.101.155.0.24.2.0.0.1.4.10.199.0.1|2|0 +1.3.6.1.2.1.4.24.7.1.12.1.4.10.120.0.0.24.2.0.0.1.4.10.199.0.1|2|0 +1.3.6.1.2.1.4.24.7.1.12.1.4.10.121.2.0.24.2.0.0.1.4.10.199.0.1|2|0 +1.3.6.1.2.1.4.24.7.1.12.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.50|2|0 +1.3.6.1.2.1.4.24.7.1.12.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.103|2|0 +1.3.6.1.2.1.4.24.7.1.12.1.4.10.199.0.50.32.2.0.0.1.4.127.0.0.1|2|0 +1.3.6.1.2.1.4.24.7.1.12.1.4.10.199.0.103.32.2.0.0.1.4.127.0.0.1|2|0 +1.3.6.1.2.1.4.24.7.1.12.1.4.10.199.0.255.32.2.0.0.1.4.127.0.0.1|2|0 +1.3.6.1.2.1.4.24.7.1.12.1.4.127.0.0.0.8.2.0.0.1.4.127.0.0.1|2|0 +1.3.6.1.2.1.4.24.7.1.12.1.4.127.0.0.1.32.2.0.0.1.4.127.0.0.1|2|0 +1.3.6.1.2.1.4.24.7.1.12.1.4.127.255.255.255.32.2.0.0.1.4.127.0.0.1|2|0 +1.3.6.1.2.1.4.24.7.1.12.1.4.255.255.255.255.32.2.0.0.1.4.127.0.0.1|2|0 +1.3.6.1.2.1.4.24.7.1.13.1.4.0.0.0.0.0.2.0.0.1.4.10.199.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.13.1.4.10.101.155.0.24.2.0.0.1.4.10.199.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.13.1.4.10.120.0.0.24.2.0.0.1.4.10.199.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.13.1.4.10.121.2.0.24.2.0.0.1.4.10.199.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.13.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.50|2|-1 +1.3.6.1.2.1.4.24.7.1.13.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.103|2|-1 +1.3.6.1.2.1.4.24.7.1.13.1.4.10.199.0.50.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.13.1.4.10.199.0.103.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.13.1.4.10.199.0.255.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.13.1.4.127.0.0.0.8.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.13.1.4.127.0.0.1.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.13.1.4.127.255.255.255.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.13.1.4.255.255.255.255.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.14.1.4.0.0.0.0.0.2.0.0.1.4.10.199.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.14.1.4.10.101.155.0.24.2.0.0.1.4.10.199.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.14.1.4.10.120.0.0.24.2.0.0.1.4.10.199.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.14.1.4.10.121.2.0.24.2.0.0.1.4.10.199.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.14.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.50|2|-1 +1.3.6.1.2.1.4.24.7.1.14.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.103|2|-1 +1.3.6.1.2.1.4.24.7.1.14.1.4.10.199.0.50.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.14.1.4.10.199.0.103.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.14.1.4.10.199.0.255.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.14.1.4.127.0.0.0.8.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.14.1.4.127.0.0.1.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.14.1.4.127.255.255.255.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.14.1.4.255.255.255.255.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.15.1.4.0.0.0.0.0.2.0.0.1.4.10.199.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.15.1.4.10.101.155.0.24.2.0.0.1.4.10.199.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.15.1.4.10.120.0.0.24.2.0.0.1.4.10.199.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.15.1.4.10.121.2.0.24.2.0.0.1.4.10.199.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.15.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.50|2|-1 +1.3.6.1.2.1.4.24.7.1.15.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.103|2|-1 +1.3.6.1.2.1.4.24.7.1.15.1.4.10.199.0.50.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.15.1.4.10.199.0.103.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.15.1.4.10.199.0.255.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.15.1.4.127.0.0.0.8.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.15.1.4.127.0.0.1.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.15.1.4.127.255.255.255.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.15.1.4.255.255.255.255.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.16.1.4.0.0.0.0.0.2.0.0.1.4.10.199.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.16.1.4.10.101.155.0.24.2.0.0.1.4.10.199.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.16.1.4.10.120.0.0.24.2.0.0.1.4.10.199.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.16.1.4.10.121.2.0.24.2.0.0.1.4.10.199.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.16.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.50|2|-1 +1.3.6.1.2.1.4.24.7.1.16.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.103|2|-1 +1.3.6.1.2.1.4.24.7.1.16.1.4.10.199.0.50.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.16.1.4.10.199.0.103.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.16.1.4.10.199.0.255.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.16.1.4.127.0.0.0.8.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.16.1.4.127.0.0.1.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.16.1.4.127.255.255.255.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.16.1.4.255.255.255.255.32.2.0.0.1.4.127.0.0.1|2|-1 +1.3.6.1.2.1.4.24.7.1.17.1.4.0.0.0.0.0.2.0.0.1.4.10.199.0.1|2|1 +1.3.6.1.2.1.4.24.7.1.17.1.4.10.101.155.0.24.2.0.0.1.4.10.199.0.1|2|1 +1.3.6.1.2.1.4.24.7.1.17.1.4.10.120.0.0.24.2.0.0.1.4.10.199.0.1|2|1 +1.3.6.1.2.1.4.24.7.1.17.1.4.10.121.2.0.24.2.0.0.1.4.10.199.0.1|2|1 +1.3.6.1.2.1.4.24.7.1.17.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.50|2|1 +1.3.6.1.2.1.4.24.7.1.17.1.4.10.199.0.0.24.2.0.0.1.4.10.199.0.103|2|1 +1.3.6.1.2.1.4.24.7.1.17.1.4.10.199.0.50.32.2.0.0.1.4.127.0.0.1|2|1 +1.3.6.1.2.1.4.24.7.1.17.1.4.10.199.0.103.32.2.0.0.1.4.127.0.0.1|2|1 +1.3.6.1.2.1.4.24.7.1.17.1.4.10.199.0.255.32.2.0.0.1.4.127.0.0.1|2|1 +1.3.6.1.2.1.4.24.7.1.17.1.4.127.0.0.0.8.2.0.0.1.4.127.0.0.1|2|1 +1.3.6.1.2.1.4.24.7.1.17.1.4.127.0.0.1.32.2.0.0.1.4.127.0.0.1|2|1 +1.3.6.1.2.1.4.24.7.1.17.1.4.127.255.255.255.32.2.0.0.1.4.127.0.0.1|2|1 +1.3.6.1.2.1.4.24.7.1.17.1.4.255.255.255.255.32.2.0.0.1.4.127.0.0.1|2|1 +1.3.6.1.6.3.10.2.1.3.0|2|1038865