r4105 setSwitchVLANs(): replace log array tricks with oneLiner/mergeLogs
[racktables] / inc / gateways.php
CommitLineData
b325120a 1<?php
d33645ff
DO
2/*
3*
4* This file contains gateway functions for RackTables.
5* A gateway is an external executable, which provides
6* read-only or read-write access to some external entities.
7* Each gateway accepts its own list of command-line args
8* and then reads its stdin for requests. Each request consists
9* of one line and results in exactly one line of reply.
10* The replies must have the following syntax:
11* OK<space>any text up to the end of the line
12* ERR<space>any text up to the end of the line
13*
14*/
15
ad541266
DO
16// translating functions maps
17$gwrxlator = array();
ffd829af
DO
18$gwrxlator['getcdpstatus'] = array
19(
20 'ios12' => 'ios12ReadCDPStatus',
21 'nxos4' => 'ios12ReadCDPStatus',
22);
50636557
DO
23$gwrxlator['getlldpstatus'] = array
24(
1ebbf889 25 'ios12' => 'ios12ReadLLDPStatus',
50636557
DO
26 'xos12' => 'xos12ReadLLDPStatus',
27 'vrp53' => 'vrp53ReadLLDPStatus',
e11d4936 28 'vrp55' => 'vrp55ReadLLDPStatus',
50636557 29);
db64180c 30$gwrxlator['get8021q'] = array
ad541266
DO
31(
32 'ios12' => 'ios12ReadVLANConfig',
33 'fdry5' => 'fdry5ReadVLANConfig',
34 'vrp53' => 'vrp53ReadVLANConfig',
ec523868 35 'vrp55' => 'vrp55Read8021QConfig',
ad541266 36 'nxos4' => 'nxos4Read8021QConfig',
1aa648ca 37 'xos12' => 'xos12Read8021QConfig',
ad541266 38);
65e557dd
AA
39$gwrxlator['getportstatus'] = array
40(
41 'ios12' => 'ciscoReadInterfaceStatus',
42 'vrp53' => 'vrpReadInterfaceStatus',
43 'vrp55' => 'vrpReadInterfaceStatus',
44 'nxos4' => 'ciscoReadInterfaceStatus',
45);
46$gwrxlator['getmaclist'] = array
47(
48 'ios12' => 'ios12ReadMacList',
49 'vrp53' => 'vrp53ReadMacList',
50 'vrp55' => 'vrp55ReadMacList',
51 'nxos4' => 'nxos4ReadMacList',
52);
d33645ff 53
0328f6d6
DO
54$gwrxlator['gethndp']['vrp53'] = 'vrp53ReadHNDPStatus';
55
50520b40
DO
56$gwpushxlator = array
57(
58 'ios12' => 'ios12TranslatePushQueue',
59 'fdry5' => 'fdry5TranslatePushQueue',
60 'vrp53' => 'vrp53TranslatePushQueue',
ec523868 61 'vrp55' => 'vrp55TranslatePushQueue',
50520b40
DO
62 'nxos4' => 'ios12TranslatePushQueue', // employ syntax compatibility
63 'xos12' => 'xos12TranslatePushQueue',
64);
65
d33645ff
DO
66// This function launches specified gateway with specified
67// command-line arguments and feeds it with the commands stored
68// in the second arg as array.
69// The answers are stored in another array, which is returned
70// by this function. In the case when a gateway cannot be found,
71// finishes prematurely or exits with non-zero return code,
72// a single-item array is returned with the only "ERR" record,
73// which explains the reason.
2da7c9b0 74function queryGateway ($gwname, $questions)
d33645ff
DO
75{
76 $execpath = "./gateways/{$gwname}/main";
d33645ff
DO
77 $dspec = array
78 (
79 0 => array ("pipe", "r"),
80 1 => array ("pipe", "w"),
81 2 => array ("file", "/dev/null", "a")
82 );
83 $pipes = array();
2da7c9b0 84 $gateway = proc_open ($execpath, $dspec, $pipes);
d33645ff 85 if (!is_resource ($gateway))
4d2e93f2 86 return array ('ERR proc_open() failed in ' . __FUNCTION__);
d33645ff
DO
87
88// Dialogue starts. Send all questions.
89 foreach ($questions as $q)
90 fwrite ($pipes[0], "$q\n");
91 fclose ($pipes[0]);
92
93// Fetch replies.
2da7c9b0 94 $answers = array ();
d33645ff
DO
95 while (!feof($pipes[1]))
96 {
97 $a = fgets ($pipes[1]);
59a83bd8 98 if (!strlen ($a))
d33645ff 99 continue;
075aeb88
DO
100 // Somehow I got a space appended at the end. Kick it.
101 $answers[] = trim ($a);
d33645ff
DO
102 }
103 fclose($pipes[1]);
104
105 $retval = proc_close ($gateway);
106 if ($retval != 0)
3a089a44 107 throw new RTGatewayError ("gateway failed with code ${retval}");
7a60526f 108 if (!count ($answers))
3a089a44 109 throw new RTGatewayError ('no response from gateway');
7a60526f 110 if (count ($answers) != count ($questions))
3a089a44 111 throw new RTGatewayError ('protocol violation');
7a60526f 112 foreach ($answers as $a)
793ba0cc 113 if (strpos ($a, 'OK!') !== 0)
3a089a44 114 throw new RTGatewayError ("subcommand failed with status: ${a}");
d33645ff
DO
115 return $answers;
116}
117
0a1b1268 118// This functions returns an array for VLAN list, and an array for port list (both
18cb9495 119// form another array themselves) and another one with MAC address list.
0a1b1268
DO
120// The ports in the latter array are marked with either VLAN ID or 'trunk'.
121// We don't sort the port list, as the gateway is believed to have done this already
122// (or at least the underlying switch software ought to). This is important, as the
123// port info is transferred to/from form not by names, but by numbers.
124function getSwitchVLANs ($object_id = 0)
125{
126 global $remote_username;
6297d584 127 $objectInfo = spotEntity ('object', $object_id);
0a1b1268
DO
128 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
129 if (count ($endpoints) == 0)
3a089a44 130 throw new RTGatewayError ('no management address set');
0a1b1268 131 if (count ($endpoints) > 1)
3a089a44 132 throw new RTGatewayError ('cannot pick management address');
0a1b1268 133 $hwtype = $swtype = 'unknown';
7fa7047a 134 foreach (getAttrValues ($object_id) as $record)
0a1b1268 135 {
e609f5c8 136 if ($record['name'] == 'SW type' && strlen ($record['o_value']))
c9edf725 137 $swtype = str_replace (' ', '+', execGMarker ($record['o_value']));
e609f5c8 138 if ($record['name'] == 'HW type' && strlen ($record['o_value']))
c9edf725 139 $hwtype = str_replace (' ', '+', execGMarker ($record['o_value']));
0a1b1268 140 }
ee2174dd 141 $endpoint = str_replace (' ', '+', $endpoints[0]);
18cb9495 142 $commands = array
0a1b1268 143 (
ee2174dd 144 "connect ${endpoint} ${hwtype} ${swtype} ${remote_username}",
18cb9495
DO
145 'listvlans',
146 'listports',
147 'listmacs'
0a1b1268 148 );
18cb9495 149 $data = queryGateway ('switchvlans', $commands);
0a1b1268 150 if (strpos ($data[0], 'OK!') !== 0)
3a089a44 151 throw new RTGatewayError ("gateway failed with status: ${data[0]}.");
0a1b1268
DO
152 // Now we have VLAN list in $data[1] and port list in $data[2]. Let's sort this out.
153 $tmp = array_unique (explode (';', substr ($data[1], strlen ('OK!'))));
154 if (count ($tmp) == 0)
3a089a44 155 throw new RTGatewayError ('gateway returned no records');
0a1b1268
DO
156 $vlanlist = array();
157 foreach ($tmp as $record)
158 {
159 list ($vlanid, $vlandescr) = explode ('=', $record);
160 $vlanlist[$vlanid] = $vlandescr;
161 }
162 $portlist = array();
0a73c02f 163 foreach (explode (';', substr ($data[2], strlen ('OK!'))) as $pair)
0a1b1268 164 {
0a73c02f
DO
165 list ($portname, $pair2) = explode ('=', $pair);
166 list ($status, $vlanid) = explode (',', $pair2);
167 $portlist[] = array ('portname' => $portname, 'status' => $status, 'vlanid' => $vlanid);
0a1b1268
DO
168 }
169 if (count ($portlist) == 0)
3a089a44 170 throw new RTGatewayError ('gateway returned no records');
18cb9495
DO
171 $maclist = array();
172 foreach (explode (';', substr ($data[3], strlen ('OK!'))) as $pair)
173 {
174 list ($macaddr, $pair2) = explode ('=', $pair);
59a83bd8 175 if (!strlen ($pair2))
18cb9495
DO
176 continue;
177 list ($vlanid, $ifname) = explode ('@', $pair2);
178 $maclist[$ifname][$vlanid][] = $macaddr;
179 }
180 return array ($vlanlist, $portlist, $maclist);
0a1b1268
DO
181}
182
183function setSwitchVLANs ($object_id = 0, $setcmd)
184{
185 global $remote_username;
6297d584 186 $objectInfo = spotEntity ('object', $object_id);
0a1b1268
DO
187 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
188 if (count ($endpoints) == 0)
3f052a67 189 throw new RTGatewayError ('no management address set');
0a1b1268 190 if (count ($endpoints) > 1)
3f052a67 191 throw new RTGatewayError ('cannot pick management address');
0a1b1268 192 $hwtype = $swtype = 'unknown';
7fa7047a 193 foreach (getAttrValues ($object_id) as $record)
0a1b1268 194 {
e609f5c8 195 if ($record['name'] == 'SW type' && strlen ($record['o_value']))
c9edf725 196 $swtype = strtr (execGMarker ($record['o_value']), ' ', '+');
e609f5c8 197 if ($record['name'] == 'HW type' && strlen ($record['o_value']))
c9edf725 198 $hwtype = strtr (execGMarker ($record['o_value']), ' ', '+');
0a1b1268 199 }
15bb23dc 200 $endpoint = str_replace (' ', '+', $endpoints[0]);
0a1b1268
DO
201 $data = queryGateway
202 (
203 'switchvlans',
15bb23dc 204 array ("connect ${endpoint} ${hwtype} ${swtype} ${remote_username}", $setcmd)
0a1b1268 205 );
0a1b1268 206 // Finally we can parse the response into message array.
c0042a75 207 $log = emptyLog();
b591ac21 208 foreach (explode (';', substr ($data[1], strlen ('OK!'))) as $text)
0a1b1268 209 {
24dcb9d8
DO
210 if (strpos ($text, 'C!') === 0)
211 {
c0042a75 212 // gateway-encoded message
b591ac21 213 $tmp = explode ('!', $text);
f0ff4930
DO
214 array_shift ($tmp);
215 $code = array_shift ($tmp);
c0042a75 216 $log = mergeLogs ($log, oneLiner ($code, $tmp));
24dcb9d8
DO
217 }
218 elseif (strpos ($text, 'I!') === 0)
c0042a75 219 $log = mergeLogs ($log, oneLiner (62, array (substr ($text, 2)))); // generic gateway success
0a1b1268 220 elseif (strpos ($text, 'W!') === 0)
c0042a75 221 $log = mergeLogs ($log, oneLiner (202, array (substr ($text, 2)))); // generic gateway warning
0a1b1268 222 else // All improperly formatted messages must be treated as error conditions.
c0042a75 223 $log = mergeLogs ($log, oneLiner (166, array (substr ($text, 2)))); // generic gateway error
0a1b1268 224 }
c0042a75 225 return $log;
0a1b1268
DO
226}
227
e77d763c
DO
228// Drop a file off RackTables platform. The gateway will catch the file and pass it to the given
229// installer script.
1ee5488d 230function gwSendFile ($endpoint, $handlername, $filetext = array())
c030232f
DO
231{
232 global $remote_username;
1ee5488d 233 $tmpnames = array();
f3d274bf 234 $endpoint = str_replace (' ', '\ ', $endpoint); // the gateway dispatcher uses read (1) to assign arguments
1ee5488d
DO
235 $command = "submit ${remote_username} ${endpoint} ${handlername}";
236 foreach ($filetext as $text)
237 {
238 $name = tempnam ('', 'RackTables-sendfile-');
239 $tmpnames[] = $name;
01af4445 240 if (FALSE === $name or FALSE === file_put_contents ($name, $text))
1d34465d
DO
241 {
242 foreach ($tmpnames as $name)
243 unlink ($name);
3a089a44 244 throw new RTGatewayError ('failed to write to temporary file');
1d34465d 245 }
1ee5488d
DO
246 $command .= " ${name}";
247 }
e11d4936
DO
248 try
249 {
250 queryGateway ('sendfile', array ($command));
251 foreach ($tmpnames as $name)
252 unlink ($name);
253 }
254 catch (RTGatewayError $e)
255 {
256 foreach ($tmpnames as $name)
257 unlink ($name);
258 throw $e;
259 }
f3d274bf 260}
1b494df6 261
9bc28e53
DO
262// Query something through a gateway and get some text in return. Return that text.
263function gwRecvFile ($endpoint, $handlername, &$output)
264{
265 global $remote_username;
266 $tmpfilename = tempnam ('', 'RackTables-sendfile-');
267 $endpoint = str_replace (' ', '\ ', $endpoint); // the gateway dispatcher uses read (1) to assign arguments
e11d4936
DO
268 try
269 {
270 queryGateway ('sendfile', array ("submit ${remote_username} ${endpoint} ${handlername} ${tmpfilename}"));
271 $output = file_get_contents ($tmpfilename);
272 unlink ($tmpfilename);
273 }
274 catch (RTGatewayError $e)
275 {
276 unlink ($tmpfilename);
277 throw $e;
278 }
ec523868
DO
279 if ($output === FALSE)
280 throw new RTGatewayError ('failed to read temporary file');
9bc28e53
DO
281}
282
3f052a67 283function gwSendFileToObject ($object_id, $handlername, $filetext = '')
f3d274bf 284{
3f052a67
DO
285 if (!mb_strlen ($handlername))
286 throw new InvalidArgException ('$handlername');
6297d584 287 $objectInfo = spotEntity ('object', $object_id);
f3d274bf
DO
288 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
289 if (count ($endpoints) == 0)
3a089a44 290 throw new RTGatewayError ('no management address set');
f3d274bf 291 if (count ($endpoints) > 1)
3a089a44 292 throw new RTGatewayError ('cannot pick management address');
01af4445 293 gwSendFile (str_replace (' ', '+', $endpoints[0]), $handlername, array ($filetext));
c030232f
DO
294}
295
3f052a67 296function gwRecvFileFromObject ($object_id, $handlername, &$output)
9bc28e53 297{
3f052a67
DO
298 if (!mb_strlen ($handlername))
299 throw new InvalidArgException ('$handlername');
6297d584 300 $objectInfo = spotEntity ('object', $object_id);
9bc28e53
DO
301 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
302 if (count ($endpoints) == 0)
3f052a67 303 throw new RTGatewayError ('no management address set');
9bc28e53 304 if (count ($endpoints) > 1)
3f052a67
DO
305 throw new RTGatewayError ('cannot pick management address');
306 gwRecvFile (str_replace (' ', '+', $endpoints[0]), $handlername, $output);
9bc28e53
DO
307}
308
9dd73255 309function detectDeviceBreed ($object_id)
aff10dee 310{
ec523868
DO
311 $breed_by_swcode = array
312 (
313 251 => 'ios12',
314 252 => 'ios12',
315 254 => 'ios12',
316 963 => 'nxos4',
317 964 => 'nxos4',
3c6595fa 318 1365 => 'nxos4',
ec523868
DO
319 1352 => 'xos12',
320 1360 => 'vrp53',
321 1361 => 'vrp55',
908c2e7d 322 1369 => 'vrp55', // VRP versions 5.5 and 5.7 seem to be compatible
ec523868
DO
323 1363 => 'fdry5',
324 );
aff10dee 325 foreach (getAttrValues ($object_id) as $record)
ec523868
DO
326 if ($record['id'] == 4 and array_key_exists ($record['key'], $breed_by_swcode))
327 return $breed_by_swcode[$record['key']];
9dd73255
DO
328 return '';
329}
330
74d3f7da 331function getRunning8021QConfig ($object_id)
9dd73255 332{
db64180c 333 $ret = gwRetrieveDeviceConfig ($object_id, 'get8021q');
f740bd2f
DO
334 // Once there is no default VLAN in the parsed data, it means
335 // something else was parsed instead of config text.
336 if (!in_array (VLAN_DFL_ID, $ret['vlanlist']))
3a089a44 337 throw new RTGatewayError ('communication with device failed');
f740bd2f 338 return $ret;
dbc00990
DO
339}
340
9dd73255
DO
341function setDevice8021QConfig ($object_id, $pseudocode)
342{
343 if ('' == $breed = detectDeviceBreed ($object_id))
3a089a44 344 throw new RTGatewayError ('device breed unknown');
50520b40 345 global $gwpushxlator;
f82a94da 346 // FIXME: this is a perfect place to log intended changes
5017142e 347 gwDeployDeviceConfig ($object_id, $breed, unix2dos ($gwpushxlator[$breed] ($pseudocode)));
9dd73255
DO
348}
349
793ba0cc 350function gwRetrieveDeviceConfig ($object_id, $command)
dbc00990 351{
ad541266
DO
352 global $gwrxlator;
353 if (!array_key_exists ($command, $gwrxlator))
3a089a44 354 throw new RTGatewayError ('command unknown');
ad541266
DO
355 $breed = detectDeviceBreed ($object_id);
356 if (!array_key_exists ($breed, $gwrxlator[$command]))
3a089a44 357 throw new RTGatewayError ('device breed unknown');
dbc00990
DO
358 $objectInfo = spotEntity ('object', $object_id);
359 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
360 if (count ($endpoints) == 0)
3a089a44 361 throw new RTGatewayError ('no management address set');
dbc00990 362 if (count ($endpoints) > 1)
3a089a44 363 throw new RTGatewayError ('cannot pick management address');
dbc00990
DO
364 $endpoint = str_replace (' ', '\ ', str_replace (' ', '+', $endpoints[0]));
365 $tmpfilename = tempnam ('', 'RackTables-deviceconfig-');
e11d4936
DO
366 try
367 {
368 queryGateway ('deviceconfig', array ("${command} ${endpoint} ${breed} ${tmpfilename}"));
369 $configtext = file_get_contents ($tmpfilename);
370 unlink ($tmpfilename);
371 }
372 catch (RTGatewayError $e)
373 {
374 unlink ($tmpfilename);
375 throw $e;
376 }
ec523868
DO
377 if ($configtext === FALSE)
378 throw new RTGatewayError ('failed to read temporary file');
0f86f02c 379 // Being here means it was alright.
ec523868 380 return $gwrxlator[$command][$breed] (dos2unix ($configtext));
aff10dee
DO
381}
382
9dd73255
DO
383function gwDeployDeviceConfig ($object_id, $breed, $text)
384{
f82a94da
DO
385 if ($text == '')
386 throw new InvalidArgException ('text', '', 'deploy text is empty');
9dd73255
DO
387 $objectInfo = spotEntity ('object', $object_id);
388 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
389 if (count ($endpoints) == 0)
3a089a44 390 throw new RTGatewayError ('no management address set');
9dd73255 391 if (count ($endpoints) > 1)
3a089a44 392 throw new RTGatewayError ('cannot pick management address');
9dd73255
DO
393 $endpoint = str_replace (' ', '\ ', str_replace (' ', '+', $endpoints[0]));
394 $tmpfilename = tempnam ('', 'RackTables-deviceconfig-');
395 if (FALSE === file_put_contents ($tmpfilename, $text))
01af4445
DO
396 {
397 unlink ($tmpfilename);
3a089a44 398 throw new RTGatewayError ('failed to write to temporary file');
01af4445 399 }
e11d4936
DO
400 try
401 {
402 queryGateway ('deviceconfig', array ("deploy ${endpoint} ${breed} ${tmpfilename}"));
403 unlink ($tmpfilename);
404 }
405 catch (RTGatewayError $e)
406 {
407 unlink ($tmpfilename);
408 throw $e;
409 }
9dd73255
DO
410}
411
01b400d0
DO
412// Read provided output of "show cdp neighbors detail" command and
413// return a list of records with (translated) local port name,
414// remote device name and (translated) remote port name.
415function ios12ReadCDPStatus ($input)
416{
417 $ret = array();
01b400d0 418 foreach (explode ("\n", $input) as $line)
ffd829af
DO
419 {
420 $matches = array();
421 switch (TRUE)
422 {
1ebbf889
AA
423 case preg_match ('/^Device ID:\s*([A-Za-z0-9][A-Za-z0-9\.\-]*)/', $line, $matches):
424 case preg_match ('/^System Name:\s*([A-Za-z0-9][A-Za-z0-9\.\-]*)/', $line, $matches):
ffd829af
DO
425 $ret['current']['device'] = $matches[1];
426 break;
427 case preg_match ('/^Interface: (.+), ?Port ID \(outgoing port\): (.+)$/', $line, $matches):
428 if (array_key_exists ('device', $ret['current']))
1ebbf889 429 $ret[ios12ShortenIfName ($matches[1])][] = array
ffd829af
DO
430 (
431 'device' => $ret['current']['device'],
432 'port' => ios12ShortenIfName ($matches[2]),
433 );
434 unset ($ret['current']);
435 break;
436 default:
437 }
438 }
439 unset ($ret['current']);
01b400d0
DO
440 return $ret;
441}
442
1ebbf889
AA
443function ios12ReadLLDPStatus ($input)
444{
445 $ret = array();
af862b92 446 $got_header = FALSE;
1ebbf889
AA
447 foreach (explode ("\n", $input) as $line)
448 {
af862b92
DO
449 if (preg_match ("/^Device ID/", $line))
450 $got_header = TRUE;
451
452 if (!$got_header)
453 continue;
454
1ebbf889
AA
455 $matches = preg_split ('/\s+/', $line);
456
457 switch (count ($matches))
458 {
459 case 5:
460 list ($remote_name, $local_port, $ttl, $caps, $remote_port) = $matches;
461 $local_port = ios12ShortenIfName ($local_port);
462 $remote_port = ios12ShortenIfName ($remote_port);
49a7bb44 463 $ret[$local_port][] = array
1ebbf889
AA
464 (
465 'device' => $remote_name,
466 'port' => $remote_port,
467 );
468 break;
469 default:
470 }
471 }
472 return $ret;
473}
474
6e136397
DO
475function xos12ReadLLDPStatus ($input)
476{
477 $ret = array();
478 foreach (explode ("\n", $input) as $line)
479 {
480 $matches = array();
481 switch (TRUE)
482 {
1ebbf889 483 case preg_match ('/^LLDP Port ([[:digit:]]+) detected \d+ neighbor$/', $line, $matches):
6e136397
DO
484 $ret['current']['local_port'] = ios12ShortenIfName ($matches[1]);
485 break;
486 case preg_match ('/^ Port ID : "(.+)"$/', $line, $matches):
487 $ret['current']['remote_port'] = ios12ShortenIfName ($matches[1]);
488 break;
489 case preg_match ('/^ - System Name: "(.+)"$/', $line, $matches):
490 if
491 (
492 array_key_exists ('current', $ret) and
493 array_key_exists ('local_port', $ret['current']) and
494 array_key_exists ('remote_port', $ret['current'])
495 )
1ebbf889 496 $ret[$ret['current']['local_port']][] = array
6e136397
DO
497 (
498 'device' => $matches[1],
499 'port' => $ret['current']['remote_port'],
500 );
501 unset ($ret['current']);
502 default:
503 }
504 }
505 unset ($ret['current']);
506 return $ret;
507}
508
50636557
DO
509function vrp53ReadLLDPStatus ($input)
510{
511 $ret = array();
512 foreach (explode ("\n", $input) as $line)
513 {
514 $matches = array();
515 switch (TRUE)
516 {
1ebbf889 517 case preg_match ('/^(.+) has \d+ neighbors:$/', $line, $matches):
50636557
DO
518 $ret['current']['local_port'] = ios12ShortenIfName ($matches[1]);
519 break;
520 case preg_match ('/^(PortIdSubtype|PortId): ([^ ]+)/', $line, $matches):
521 $ret['current'][$matches[1]] = $matches[2];
522 break;
523 case preg_match ('/^SysName: (.+)$/', $line, $matches):
524 if
525 (
526 array_key_exists ('current', $ret) and
527 array_key_exists ('PortIdSubtype', $ret['current']) and
528 ($ret['current']['PortIdSubtype'] == 'interfaceAlias' or $ret['current']['PortIdSubtype'] == 'interfaceName') and
529 array_key_exists ('PortId', $ret['current']) and
530 array_key_exists ('local_port', $ret['current'])
531 )
1ebbf889 532 $ret[$ret['current']['local_port']][] = array
50636557
DO
533 (
534 'device' => $matches[1],
535 'port' => ios12ShortenIfName ($ret['current']['PortId']),
536 );
537 unset ($ret['current']);
538 break;
539 default:
540 }
541 }
542 unset ($ret['current']);
543 return $ret;
544}
545
e11d4936
DO
546function vrp55ReadLLDPStatus ($input)
547{
548 $ret = array();
549 foreach (explode ("\n", $input) as $line)
550 {
551 $matches = array();
552 switch (TRUE)
553 {
1ebbf889 554 case preg_match ('/^(.+) has \d+ neighbors:$/', $line, $matches):
e11d4936
DO
555 $ret['current']['local_port'] = ios12ShortenIfName ($matches[1]);
556 break;
557 case preg_match ('/^Port ID type :([^ ]+)/', $line, $matches):
558 $ret['current']['PortIdSubtype'] = $matches[1];
559 break;
560 case preg_match ('/^Port ID :(.+)$/', $line, $matches):
561 $ret['current']['PortId'] = $matches[1];
562 break;
563 case preg_match ('/^System name :(.+)$/', $line, $matches):
564 if
565 (
566 array_key_exists ('current', $ret) and
567 array_key_exists ('PortIdSubtype', $ret['current']) and
568 ($ret['current']['PortIdSubtype'] == 'interfaceAlias' or $ret['current']['PortIdSubtype'] == 'interfaceName') and
569 array_key_exists ('PortId', $ret['current']) and
570 array_key_exists ('local_port', $ret['current'])
571 )
1ebbf889 572 $ret[$ret['current']['local_port']][] = array
e11d4936
DO
573 (
574 'device' => $matches[1],
575 'port' => ios12ShortenIfName ($ret['current']['PortId']),
576 );
577 unset ($ret['current']);
578 break;
579 default:
580 }
581 }
582 unset ($ret['current']);
583 return $ret;
584}
585
0328f6d6
DO
586function vrp53ReadHNDPStatus ($input)
587{
588 $ret = array();
589 foreach (explode ("\n", $input) as $line)
590 {
591 $matches = array();
592 switch (TRUE)
593 {
594 case preg_match ('/^ Interface: (.+)$/', $line, $matches):
595 $ret['current']['local_port'] = ios12ShortenIfName ($matches[1]);
596 break;
597 case preg_match ('/^ Port Name : (.+)$/', $line, $matches):
598 $ret['current']['remote_port'] = ios12ShortenIfName ($matches[1]);
599 break;
600 case preg_match ('/^ Device Name : (.+)$/', $line, $matches):
601 if
602 (
603 array_key_exists ('current', $ret) and
604 array_key_exists ('local_port', $ret['current']) and
605 array_key_exists ('remote_port', $ret['current'])
606 )
1ebbf889 607 $ret[$ret['current']['local_port']][] = array
0328f6d6
DO
608 (
609 'device' => $matches[1],
610 'port' => $ret['current']['remote_port'],
611 );
612 unset ($ret['current']);
613 break;
614 default:
615 }
616 }
617 unset ($ret['current']);
618 return $ret;
619}
620
01b400d0
DO
621function ios12ReadVLANConfig ($input)
622{
623 $ret = array
624 (
625 'vlanlist' => array(),
626 'portdata' => array(),
627 );
628 $procfunc = 'ios12ScanTopLevel';
629 foreach (explode ("\n", $input) as $line)
630 $procfunc = $procfunc ($ret, $line);
631 return $ret;
632}
633
634function ios12ScanTopLevel (&$work, $line)
635{
636 $matches = array();
637 switch (TRUE)
638 {
639 case (preg_match ('@^interface ((Ethernet|FastEthernet|GigabitEthernet|TenGigabitEthernet|Port-channel)[[:digit:]]+(/[[:digit:]]+)*)$@', $line, $matches)):
640 $work['current'] = array ('port_name' => ios12ShortenIfName ($matches[1]));
65e557dd 641 $work['current']['config'][] = array ('type' => 'line-header', 'line' => $line);
01b400d0
DO
642 return 'ios12PickSwitchportCommand'; // switch to interface block reading
643 case (preg_match ('/^VLAN Name Status Ports$/', $line, $matches)):
644 return 'ios12PickVLANCommand';
645 default:
646 return __FUNCTION__; // continue scan
647 }
648}
649
650function ios12PickSwitchportCommand (&$work, $line)
651{
652 if ($line[0] != ' ') // end of interface section
653 {
654 // save work, if it makes sense
e11d4936 655 switch ($work['current']['mode'])
01b400d0 656 {
e11d4936 657 case 'access':
01b400d0
DO
658 if (!array_key_exists ('access vlan', $work['current']))
659 $work['current']['access vlan'] = 1;
660 $work['portdata'][$work['current']['port_name']] = array
661 (
662 'mode' => 'access',
663 'allowed' => array ($work['current']['access vlan']),
664 'native' => $work['current']['access vlan'],
665 );
666 break;
e11d4936 667 case 'trunk':
01b400d0
DO
668 if (!array_key_exists ('trunk native vlan', $work['current']))
669 $work['current']['trunk native vlan'] = 1;
670 if (!array_key_exists ('trunk allowed vlan', $work['current']))
671 $work['current']['trunk allowed vlan'] = range (VLAN_MIN_ID, VLAN_MAX_ID);
672 // Having configured VLAN as "native" doesn't mean anything
673 // as long as it's not listed on the "allowed" line.
674 $effective_native = in_array
675 (
676 $work['current']['trunk native vlan'],
677 $work['current']['trunk allowed vlan']
678 ) ? $work['current']['trunk native vlan'] : 0;
679 $work['portdata'][$work['current']['port_name']] = array
680 (
681 'mode' => 'trunk',
682 'allowed' => $work['current']['trunk allowed vlan'],
683 'native' => $effective_native,
684 );
685 break;
e11d4936
DO
686 case 'SKIP':
687 break;
688 case 'IP':
01b400d0
DO
689 default:
690 // dot1q-tunnel, dynamic, private-vlan or even none --
691 // show in returned config and let user decide, if they
692 // want to fix device config or work around these ports
693 // by means of VST.
694 $work['portdata'][$work['current']['port_name']] = array
695 (
696 'mode' => 'none',
697 'allowed' => array(),
698 'native' => 0,
699 );
700 break;
701 }
65e557dd
AA
702 if (isset ($work['portdata'][$work['current']['port_name']]))
703 $work['portdata'][$work['current']['port_name']]['config'] = $work['current']['config'];
01b400d0
DO
704 unset ($work['current']);
705 return 'ios12ScanTopLevel';
706 }
707 // not yet
708 $matches = array();
65e557dd 709 $line_class = 'line-8021q';
01b400d0
DO
710 switch (TRUE)
711 {
712 case (preg_match ('@^ switchport mode (.+)$@', $line, $matches)):
713 $work['current']['mode'] = $matches[1];
714 break;
715 case (preg_match ('@^ switchport access vlan (.+)$@', $line, $matches)):
716 $work['current']['access vlan'] = $matches[1];
717 break;
718 case (preg_match ('@^ switchport trunk native vlan (.+)$@', $line, $matches)):
719 $work['current']['trunk native vlan'] = $matches[1];
720 break;
721 case (preg_match ('@^ switchport trunk allowed vlan add (.+)$@', $line, $matches)):
722 $work['current']['trunk allowed vlan'] = array_merge
723 (
724 $work['current']['trunk allowed vlan'],
725 iosParseVLANString ($matches[1])
726 );
727 break;
728 case (preg_match ('@^ switchport trunk allowed vlan (.+)$@', $line, $matches)):
729 $work['current']['trunk allowed vlan'] = iosParseVLANString ($matches[1]);
730 break;
731 case preg_match ('@^ channel-group @', $line):
732 // port-channel subinterface config follows that of the master interface
e11d4936
DO
733 $work['current']['mode'] = 'SKIP';
734 break;
01b400d0
DO
735 case preg_match ('@^ ip address @', $line):
736 // L3 interface does no switchport functions
e11d4936 737 $work['current']['mode'] = 'IP';
01b400d0
DO
738 break;
739 default: // suppress warning on irrelevant config clause
65e557dd 740 $line_class = 'line-other';
01b400d0 741 }
65e557dd 742 $work['current']['config'][] = array ('type' => $line_class, 'line' => $line);
01b400d0
DO
743 return __FUNCTION__;
744}
745
746function ios12PickVLANCommand (&$work, $line)
747{
748 $matches = array();
749 switch (TRUE)
750 {
751 case ($line == '---- -------------------------------- --------- -------------------------------'):
752 // ignore the rest of VLAN table header;
753 break;
754 case (preg_match ('@! END OF VLAN LIST$@', $line)):
755 return 'ios12ScanTopLevel';
756 case (preg_match ('@^([[:digit:]]+) {1,4}.{32} active @', $line, $matches)):
757 if (!array_key_exists ($matches[1], $work['vlanlist']))
758 $work['vlanlist'][] = $matches[1];
759 break;
760 default:
761 }
762 return __FUNCTION__;
763}
764
765// Another finite automata to read a dialect of Foundry configuration.
766function fdry5ReadVLANConfig ($input)
767{
768 $ret = array
769 (
770 'vlanlist' => array(),
771 'portdata' => array(),
772 );
773 $procfunc = 'fdry5ScanTopLevel';
774 foreach (explode ("\n", $input) as $line)
775 $procfunc = $procfunc ($ret, $line);
776 return $ret;
777}
778
779function fdry5ScanTopLevel (&$work, $line)
780{
781 $matches = array();
782 switch (TRUE)
783 {
784 case (preg_match ('@^vlan ([[:digit:]]+)( name .+)? (by port)$@', $line, $matches)):
785 if (!array_key_exists ($matches[1], $work['vlanlist']))
786 $work['vlanlist'][] = $matches[1];
787 $work['current'] = array ('vlan_id' => $matches[1]);
788 return 'fdry5PickVLANSubcommand';
789 case (preg_match ('@^interface ethernet ([[:digit:]]+/[[:digit:]]+/[[:digit:]]+)$@', $line, $matches)):
790 $work['current'] = array ('port_name' => 'e' . $matches[1]);
791 return 'fdry5PickInterfaceSubcommand';
792 default:
793 return __FUNCTION__;
794 }
795}
796
797function fdry5PickVLANSubcommand (&$work, $line)
798{
799 if ($line[0] != ' ') // end of VLAN section
800 {
801 unset ($work['current']);
802 return 'fdry5ScanTopLevel';
803 }
804 // not yet
805 $matches = array();
806 switch (TRUE)
807 {
808 case (preg_match ('@^ tagged (.+)$@', $line, $matches)):
809 // add current VLAN to 'allowed' list of each mentioned port
810 foreach (fdry5ParsePortString ($matches[1]) as $port_name)
811 if (array_key_exists ($port_name, $work['portdata']))
812 $work['portdata'][$port_name]['allowed'][] = $work['current']['vlan_id'];
813 else
814 $work['portdata'][$port_name] = array
815 (
816 'mode' => 'trunk',
817 'allowed' => array ($work['current']['vlan_id']),
818 'native' => 0, // can be updated later
819 );
820 $work['portdata'][$port_name]['mode'] = 'trunk';
821 break;
822 case (preg_match ('@^ untagged (.+)$@', $line, $matches)):
823 // replace 'native' column of each mentioned port with current VLAN ID
824 foreach (fdry5ParsePortString ($matches[1]) as $port_name)
825 {
826 if (array_key_exists ($port_name, $work['portdata']))
827 {
828 $work['portdata'][$port_name]['native'] = $work['current']['vlan_id'];
829 $work['portdata'][$port_name]['allowed'][] = $work['current']['vlan_id'];
830 }
831 else
832 $work['portdata'][$port_name] = array
833 (
834 'mode' => 'access',
835 'allowed' => array ($work['current']['vlan_id']),
836 'native' => $work['current']['vlan_id'],
837 );
838 // Untagged ports are initially assumed to be access ports, and
839 // when this assumption is right, this is the final port mode state.
840 // When the port is dual-mode one, this is detected and justified
841 // later in "interface" section of config text.
842 $work['portdata'][$port_name]['mode'] = 'access';
843 }
844 break;
845 default: // nom-nom
846 }
847 return __FUNCTION__;
848}
849
850function fdry5PickInterfaceSubcommand (&$work, $line)
851{
852 if ($line[0] != ' ') // end of interface section
853 {
854 if (array_key_exists ('dual-mode', $work['current']))
855 {
856 if (array_key_exists ($work['current']['port_name'], $work['portdata']))
857 // update existing record
858 $work['portdata'][$work['current']['port_name']]['native'] = $work['current']['dual-mode'];
859 else
860 // add new
861 $work['portdata'][$work['current']['port_name']] = array
862 (
863 'allowed' => array ($work['current']['dual-mode']),
864 'native' => $work['current']['dual-mode'],
865 );
866 // a dual-mode port is always considered a trunk port
867 // (but not in the IronWare's meaning of "trunk") regardless of
868 // number of assigned tagged VLANs
869 $work['portdata'][$work['current']['port_name']]['mode'] = 'trunk';
870 }
871 unset ($work['current']);
872 return 'fdry5ScanTopLevel';
873 }
874 $matches = array();
875 switch (TRUE)
876 {
877 case (preg_match ('@^ dual-mode( +[[:digit:]]+ *)?$@', $line, $matches)):
878 // default VLAN ID for dual-mode command is 1
879 $work['current']['dual-mode'] = strlen (trim ($matches[1])) ? trim ($matches[1]) : 1;
880 break;
881 // FIXME: trunk/link-aggregate/ip address pulls port from 802.1Q field
882 default: // nom-nom
883 }
884 return __FUNCTION__;
885}
886
887function fdry5ParsePortString ($string)
888{
889 $ret = array();
890 $tokens = explode (' ', trim ($string));
891 while (count ($tokens))
892 {
893 $letters = array_shift ($tokens); // "ethe", "to"
894 $numbers = array_shift ($tokens); // "x", "x/x", "x/x/x"
895 switch ($letters)
896 {
897 case 'ethe':
898 if ($prev_numbers != NULL)
899 $ret[] = 'e' . $prev_numbers;
900 $prev_numbers = $numbers;
901 break;
902 case 'to':
903 $ret = array_merge ($ret, fdry5GenPortRange ($prev_numbers, $numbers));
904 $prev_numbers = NULL; // no action on next token
905 break;
906 default: // ???
907 return array();
908 }
909 }
910 // flush delayed item
911 if ($prev_numbers != NULL)
912 $ret[] = 'e' . $prev_numbers;
913 return $ret;
914}
915
916// Take two indices in form "x", "x/x" or "x/x/x" and return the range of
917// ports spanning from the first to the last. The switch software makes it
918// easier to perform, because "ethe x/x/x to y/y/y" ranges never cross
919// unit/slot boundary (every index except the last remains constant).
920function fdry5GenPortRange ($from, $to)
921{
922 $matches = array();
923 if (1 !== preg_match ('@^([[:digit:]]+/)?([[:digit:]]+/)?([[:digit:]]+)$@', $from, $matches))
924 return array();
925 $prefix = 'e' . $matches[1] . $matches[2];
926 $from_idx = $matches[3];
927 if (1 !== preg_match ('@^([[:digit:]]+/)?([[:digit:]]+/)?([[:digit:]]+)$@', $to, $matches))
928 return array();
929 $to_idx = $matches[3];
930 for ($i = $from_idx; $i <= $to_idx; $i++)
931 $ret[] = $prefix . $i;
932 return $ret;
933}
934
935// an implementation for Huawei syntax
936function vrp53ReadVLANConfig ($input)
937{
938 $ret = array
939 (
940 'vlanlist' => array(),
941 'portdata' => array(),
942 );
943 $procfunc = 'vrp53ScanTopLevel';
944 foreach (explode ("\n", $input) as $line)
945 $procfunc = $procfunc ($ret, $line);
946 return $ret;
947}
948
949function vrp53ScanTopLevel (&$work, $line)
950{
951 $matches = array();
952 switch (TRUE)
953 {
954 case (preg_match ('@^ vlan batch (.+)$@', $line, $matches)):
955 foreach (vrp53ParseVLANString ($matches[1]) as $vlan_id)
956 $work['vlanlist'][] = $vlan_id;
957 return __FUNCTION__;
958 case (preg_match ('@^interface ((GigabitEthernet|XGigabitEthernet|Eth-Trunk)([[:digit:]]+(/[[:digit:]]+)*))$@', $line, $matches)):
959 $matches[1] = preg_replace ('@^GigabitEthernet(.+)$@', 'gi\\1', $matches[1]);
960 $matches[1] = preg_replace ('@^XGigabitEthernet(.+)$@', 'xg\\1', $matches[1]);
961 $matches[1] = preg_replace ('@^Eth-Trunk(.+)$@', 'et\\1', $matches[1]);
962 $work['current'] = array ('port_name' => $matches[1]);
65e557dd 963 $work['current']['config'][] = array ('type' => 'line-header', 'line' => $line);
01b400d0
DO
964 return 'vrp53PickInterfaceSubcommand';
965 default:
966 return __FUNCTION__;
967 }
968}
969
970function vrp53ParseVLANString ($string)
971{
972 $string = preg_replace ('/ to /', '-', $string);
973 $string = preg_replace ('/ /', ',', $string);
974 return iosParseVLANString ($string);
975}
976
977function vrp53PickInterfaceSubcommand (&$work, $line)
978{
979 if ($line[0] == '#') // end of interface section
980 {
981 // Configuration Guide - Ethernet 3.3.4:
982 // "By default, the interface type is hybrid."
983 if (!array_key_exists ('link-type', $work['current']))
984 $work['current']['link-type'] = 'hybrid';
985 if (!array_key_exists ('allowed', $work['current']))
986 $work['current']['allowed'] = array();
987 if (!array_key_exists ('native', $work['current']))
988 $work['current']['native'] = 0;
989 switch ($work['current']['link-type'])
990 {
991 case 'access':
992 // VRP does not assign access ports to VLAN1 by default,
993 // leaving them blocked.
994 $work['portdata'][$work['current']['port_name']] =
995 $work['current']['native'] ? array
996 (
997 'allowed' => $work['current']['allowed'],
998 'native' => $work['current']['native'],
999 'mode' => 'access',
1000 ) : array
1001 (
1002 'mode' => 'none',
1003 'allowed' => array(),
1004 'native' => 0,
1005 );
1006 break;
1007 case 'trunk':
1008 $work['portdata'][$work['current']['port_name']] = array
1009 (
1010 'allowed' => $work['current']['allowed'],
1011 'native' => 0,
1012 'mode' => 'trunk',
1013 );
1014 break;
1015 case 'hybrid':
1016 $work['portdata'][$work['current']['port_name']] = array
1017 (
1018 'allowed' => $work['current']['allowed'],
1019 'native' => $work['current']['native'],
1020 'mode' => 'trunk',
1021 );
1022 break;
1023 default: // dot1q-tunnel ?
1024 }
65e557dd
AA
1025 if (isset ($work['portdata'][$work['current']['port_name']]))
1026 $work['portdata'][$work['current']['port_name']]['config'] = $work['current']['config'];
01b400d0
DO
1027 unset ($work['current']);
1028 return 'vrp53ScanTopLevel';
1029 }
1030 $matches = array();
65e557dd 1031 $line_class = 'line-8021q';
01b400d0
DO
1032 switch (TRUE)
1033 {
1034 case (preg_match ('@^ port default vlan ([[:digit:]]+)$@', $line, $matches)):
1035 $work['current']['native'] = $matches[1];
1036 if (!array_key_exists ('allowed', $work['current']))
1037 $work['current']['allowed'] = array();
1038 if (!in_array ($matches[1], $work['current']['allowed']))
1039 $work['current']['allowed'][] = $matches[1];
1040 break;
1041 case (preg_match ('@^ port link-type (.+)$@', $line, $matches)):
1042 $work['current']['link-type'] = $matches[1];
1043 break;
1044 case (preg_match ('@^ port trunk allow-pass vlan (.+)$@', $line, $matches)):
1045 if (!array_key_exists ('allowed', $work['current']))
1046 $work['current']['allowed'] = array();
1047 foreach (vrp53ParseVLANString ($matches[1]) as $vlan_id)
1048 if (!in_array ($vlan_id, $work['current']['allowed']))
1049 $work['current']['allowed'][] = $vlan_id;
1050 break;
1051 // TODO: make sure, that a port with "eth-trunk" clause always ends up in "none" mode
1052 default: // nom-nom
65e557dd 1053 $line_class = 'line-other';
01b400d0 1054 }
65e557dd 1055 $work['current']['config'][] = array('type' => $line_class, 'line' => $line);
01b400d0
DO
1056 return __FUNCTION__;
1057}
1058
ec523868
DO
1059function vrp55Read8021QConfig ($input)
1060{
1061 $ret = array
1062 (
1063 'vlanlist' => array (1), // VRP 5.50 hides VLAN1 from config text
1064 'portdata' => array(),
1065 );
1066 foreach (explode ("\n", $input) as $line)
1067 {
1068 $matches = array();
1069 // top level
1070 if (!array_key_exists ('current', $ret))
65e557dd 1071 {
ec523868
DO
1072 switch (TRUE)
1073 {
1074 case (preg_match ('@^ vlan batch (.+)$@', $line, $matches)):
1075 foreach (vrp53ParseVLANString ($matches[1]) as $vlan_id)
1076 $ret['vlanlist'][] = $vlan_id;
65e557dd 1077 break;
0e75eb8d 1078 case (preg_match ('@^interface ((GigabitEthernet|XGigabitEthernet|Eth-Trunk)([[:digit:]]+(/[[:digit:]]+)*))$@', $line, $matches)):
ec523868 1079 $matches[1] = preg_replace ('@^GigabitEthernet(.+)$@', 'gi\\1', $matches[1]);
0e75eb8d 1080 $matches[1] = preg_replace ('@^XGigabitEthernet(.+)$@', 'xg\\1', $matches[1]);
ec523868 1081 $ret['current'] = array ('port_name' => $matches[1]);
65e557dd
AA
1082 $ret['current']['config'][] = array ('type' => 'line-header', 'line' => $line);
1083 break;
ec523868 1084 }
65e557dd
AA
1085 continue;
1086 }
ec523868 1087 // inside an interface block
65e557dd 1088 $line_class = 'line-8021q';
ec523868
DO
1089 switch (TRUE)
1090 {
1091 case preg_match ('/^ port (link-type )?hybrid /', $line):
1092 throw new RTGatewayError ("unsupported configuration: ${line}");
1093 case preg_match ('/^ port link-type (.+)$/', $line, $matches):
1094 $ret['current']['link-type'] = $matches[1];
1095 break;
1096 // Native VLAN is configured differently for each link-type case, but
1097 // VRP is known to filter off clauses, which don't make sense for
1098 // current link-type. This way any interface section should contain
1099 // only one kind of "set native" clause (but if this constraint breaks,
1100 // we get a problem).
1101 case preg_match ('/^ port (default|trunk pvid) vlan ([[:digit:]]+)$/', $line, $matches):
1102 $ret['current']['native'] = $matches[2];
1103 if (!array_key_exists ('allowed', $ret['current']))
1104 $ret['current']['allowed'] = array();
1105 if (!in_array ($ret['current']['native'], $ret['current']['allowed']))
1106 $ret['current']['allowed'][] = $ret['current']['native'];
1107 break;
1108 case preg_match ('/^ port trunk allow-pass vlan (.+)$/', $line, $matches):
1109 if (!array_key_exists ('allowed', $ret['current']))
1110 $ret['current']['allowed'] = array();
1111 foreach (vrp53ParseVLANString ($matches[1]) as $vlan_id)
1112 if (!in_array ($vlan_id, $ret['current']['allowed']))
1113 $ret['current']['allowed'][] = $vlan_id;
1114 break;
1115 case $line == ' undo portswitch':
1116 case preg_match ('/^ ip address /', $line):
1117 $ret['current']['link-type'] = 'IP';
1118 break;
1119 case preg_match ('/^ eth-trunk /', $line):
1120 $ret['current']['link-type'] = 'SKIP';
1121 break;
1122 case substr ($line, 0, 1) == '#': // end of interface section
1123 if (!array_key_exists ('link-type', $ret['current']))
1124 throw new RTGatewayError ('unsupported configuration: link-type is neither trunk nor access for ' . $ret['current']['port_name']);
1125 if (!array_key_exists ('allowed', $ret['current']))
1126 $ret['current']['allowed'] = array();
1127 if (!array_key_exists ('native', $ret['current']))
1128 $ret['current']['native'] = 0;
1129 switch ($ret['current']['link-type'])
1130 {
1131 case 'access':
1132 // In VRP 5.50 an access port has default VLAN ID == 1
1133 $ret['portdata'][$ret['current']['port_name']] =
1134 $ret['current']['native'] ? array
1135 (
1136 'mode' => 'access',
1137 'allowed' => $ret['current']['allowed'],
1138 'native' => $ret['current']['native'],
1139 ) : array
1140 (
1141 'mode' => 'access',
1142 'allowed' => array (VLAN_DFL_ID),
1143 'native' => VLAN_DFL_ID,
1144 );
1145 break;
1146 case 'trunk':
1147 $ret['portdata'][$ret['current']['port_name']] = array
1148 (
1149 'mode' => 'trunk',
1150 'allowed' => $ret['current']['allowed'],
1151 'native' => $ret['current']['native'],
1152 );
1153 break;
1154 case 'IP':
1155 $ret['portdata'][$ret['current']['port_name']] = array
1156 (
1157 'mode' => 'none',
1158 'allowed' => array(),
1159 'native' => 0,
1160 );
1161 break;
1162 case 'SKIP':
1163 default: // dot1q-tunnel ?
1164 }
65e557dd
AA
1165 if (isset ($ret['portdata'][$ret['current']['port_name']]))
1166 $ret['portdata'][$ret['current']['port_name']]['config'] = $ret['current']['config'];
ec523868 1167 unset ($ret['current']);
65e557dd 1168 continue 2;
ec523868 1169 default: // nom-nom
65e557dd 1170 $line_class = 'line-other';
ec523868 1171 }
65e557dd 1172 $ret['current']['config'][] = array ('type' => $line_class, 'line' => $line);
ec523868
DO
1173 }
1174 return $ret;
1175}
1176
01b400d0
DO
1177function nxos4Read8021QConfig ($input)
1178{
1179 $ret = array
1180 (
1181 'vlanlist' => array(),
1182 'portdata' => array(),
1183 );
1184 $procfunc = 'nxos4ScanTopLevel';
1185 foreach (explode ("\n", $input) as $line)
1186 $procfunc = $procfunc ($ret, $line);
1187 return $ret;
1188}
1189
1190function nxos4ScanTopLevel (&$work, $line)
1191{
1192 $matches = array();
1193 switch (TRUE)
1194 {
abd1e9ac
DO
1195 case (preg_match ('@^interface ((Ethernet|Port-channel)[[:digit:]]+(/[[:digit:]]+)*)$@i', $line, $matches)):
1196 $matches[1] = preg_replace ('@^Ethernet(.+)$@i', 'e\\1', $matches[1]);
1197 $matches[1] = preg_replace ('@^Port-channel(.+)$@i', 'po\\1', $matches[1]);
01b400d0 1198 $work['current'] = array ('port_name' => $matches[1]);
65e557dd 1199 $work['current']['config'][] = array ('type' => 'line-header', 'line' => $line);
01b400d0 1200 return 'nxos4PickSwitchportCommand';
80fca17c
DO
1201 case (preg_match ('@^vlan (.+)$@', $line, $matches)):
1202 foreach (iosParseVLANString ($matches[1]) as $vlan_id)
1203 $work['vlanlist'][] = $vlan_id;
1204 return __FUNCTION__;
01b400d0
DO
1205 default:
1206 return __FUNCTION__; // continue scan
1207 }
1208}
1209
01b400d0
DO
1210function nxos4PickSwitchportCommand (&$work, $line)
1211{
1212 if ($line == '') // end of interface section
1213 {
1214 // fill in defaults
01b400d0
DO
1215 if (!array_key_exists ('mode', $work['current']))
1216 $work['current']['mode'] = 'access';
1217 // save work, if it makes sense
1218 switch ($work['current']['mode'])
1219 {
1220 case 'access':
1221 if (!array_key_exists ('access vlan', $work['current']))
1222 $work['current']['access vlan'] = 1;
1223 $work['portdata'][$work['current']['port_name']] = array
1224 (
1225 'mode' => 'access',
1226 'allowed' => array ($work['current']['access vlan']),
1227 'native' => $work['current']['access vlan'],
1228 );
1229 break;
1230 case 'trunk':
1231 if (!array_key_exists ('trunk native vlan', $work['current']))
1232 $work['current']['trunk native vlan'] = 1;
80fca17c 1233 // FIXME: NX-OS reserves VLANs 3968 through 4047 plus 4094 for itself
01b400d0
DO
1234 if (!array_key_exists ('trunk allowed vlan', $work['current']))
1235 $work['current']['trunk allowed vlan'] = range (VLAN_MIN_ID, VLAN_MAX_ID);
1236 // Having configured VLAN as "native" doesn't mean anything
1237 // as long as it's not listed on the "allowed" line.
1238 $effective_native = in_array
1239 (
1240 $work['current']['trunk native vlan'],
1241 $work['current']['trunk allowed vlan']
1242 ) ? $work['current']['trunk native vlan'] : 0;
1243 $work['portdata'][$work['current']['port_name']] = array
1244 (
1245 'mode' => 'trunk',
1246 'allowed' => $work['current']['trunk allowed vlan'],
1247 'native' => $effective_native,
1248 );
1249 break;
abd1e9ac
DO
1250 case 'SKIP':
1251 case 'fex-fabric': // associated port-channel
1252 break;
01b400d0 1253 default:
abd1e9ac 1254 // dot1q-tunnel, dynamic, private-vlan
80fca17c
DO
1255 $work['portdata'][$work['current']['port_name']] = array
1256 (
1257 'mode' => 'none',
1258 'allowed' => array(),
1259 'native' => 0,
1260 );
1261 // unset (routed), dot1q-tunnel, dynamic, private-vlan --- skip these
01b400d0 1262 }
65e557dd
AA
1263 if (isset ($work['portdata'][$work['current']['port_name']]))
1264 $work['portdata'][$work['current']['port_name']]['config'] = $work['current']['config'];
01b400d0
DO
1265 unset ($work['current']);
1266 return 'nxos4ScanTopLevel';
1267 }
1268 // not yet
1269 $matches = array();
65e557dd 1270 $line_class = 'line-8021q';
01b400d0
DO
1271 switch (TRUE)
1272 {
1273 case (preg_match ('@^ switchport mode (.+)$@', $line, $matches)):
1274 $work['current']['mode'] = $matches[1];
1275 break;
1276 case (preg_match ('@^ switchport access vlan (.+)$@', $line, $matches)):
1277 $work['current']['access vlan'] = $matches[1];
1278 break;
1279 case (preg_match ('@^ switchport trunk native vlan (.+)$@', $line, $matches)):
1280 $work['current']['trunk native vlan'] = $matches[1];
1281 break;
1282 case (preg_match ('@^ switchport trunk allowed vlan add (.+)$@', $line, $matches)):
1283 $work['current']['trunk allowed vlan'] = array_merge
1284 (
1285 $work['current']['trunk allowed vlan'],
1286 iosParseVLANString ($matches[1])
1287 );
1288 break;
1289 case (preg_match ('@^ switchport trunk allowed vlan (.+)$@', $line, $matches)):
1290 $work['current']['trunk allowed vlan'] = iosParseVLANString ($matches[1]);
1291 break;
abd1e9ac
DO
1292 case preg_match ('/^ +channel-group /', $line):
1293 $work['current']['mode'] = 'SKIP';
1294 break;
01b400d0 1295 default: // suppress warning on irrelevant config clause
65e557dd 1296 $line_class = 'line-other';
01b400d0 1297 }
65e557dd 1298 $work['current']['config'][] = array ('type' => $line_class, 'line' => $line);
01b400d0
DO
1299 return __FUNCTION__;
1300}
1301
1302// Get a list of VLAN management pseudo-commands and return a text
1303// of real vendor-specific commands, which implement the work.
1304// This work is done in two rounds:
1305// 1. For "add allowed" and "rem allowed" commands detect continuous
1306// sequences of VLAN IDs and replace them with ranges of form "A-B",
1307// where B>A.
1308// 2. Iterate over the resulting list and produce real CLI commands.
5017142e 1309function ios12TranslatePushQueue ($queue)
01b400d0 1310{
5017142e 1311 $ret = '';
01b400d0
DO
1312 foreach ($queue as $cmd)
1313 switch ($cmd['opcode'])
1314 {
1315 case 'create VLAN':
1316 $ret .= "vlan ${cmd['arg1']}\nexit\n";
1317 break;
1318 case 'destroy VLAN':
1319 $ret .= "no vlan ${cmd['arg1']}\n";
1320 break;
1321 case 'add allowed':
1322 case 'rem allowed':
1323 $clause = $cmd['opcode'] == 'add allowed' ? 'add' : 'remove';
1324 $ret .= "interface ${cmd['port']}\n";
1325 foreach (listToRanges ($cmd['vlans']) as $range)
1326 $ret .= "switchport trunk allowed vlan ${clause} " .
1327 ($range['from'] == $range['to'] ? $range['to'] : "${range['from']}-${range['to']}") .
1328 "\n";
1329 $ret .= "exit\n";
1330 break;
1331 case 'set native':
1332 $ret .= "interface ${cmd['arg1']}\nswitchport trunk native vlan ${cmd['arg2']}\nexit\n";
1333 break;
1334 case 'unset native':
1335 $ret .= "interface ${cmd['arg1']}\nno switchport trunk native vlan ${cmd['arg2']}\nexit\n";
1336 break;
1337 case 'set access':
1338 $ret .= "interface ${cmd['arg1']}\nswitchport access vlan ${cmd['arg2']}\nexit\n";
1339 break;
1340 case 'unset access':
1341 $ret .= "interface ${cmd['arg1']}\nno switchport access vlan\nexit\n";
1342 break;
1343 case 'set mode':
1344 $ret .= "interface ${cmd['arg1']}\nswitchport mode ${cmd['arg2']}\n";
1345 if ($cmd['arg2'] == 'trunk')
1346 $ret .= "no switchport trunk native vlan\nswitchport trunk allowed vlan none\n";
1347 $ret .= "exit\n";
1348 break;
5017142e
DO
1349 case 'begin configuration':
1350 $ret .= "configure terminal\n";
1351 break;
1352 case 'end configuration':
1353 $ret .= "end\n";
1354 break;
1355 case 'save configuration':
e778a629 1356 $ret .= "copy running-config startup-config\n\n";
5017142e
DO
1357 break;
1358 default:
1359 throw new InvalidArgException ('opcode', $cmd['opcode']);
01b400d0 1360 }
01b400d0
DO
1361 return $ret;
1362}
1363
5017142e 1364function fdry5TranslatePushQueue ($queue)
01b400d0 1365{
5017142e 1366 $ret = '';
01b400d0
DO
1367 foreach ($queue as $cmd)
1368 switch ($cmd['opcode'])
1369 {
1370 case 'create VLAN':
1371 $ret .= "vlan ${cmd['arg1']}\nexit\n";
1372 break;
1373 case 'destroy VLAN':
1374 $ret .= "no vlan ${cmd['arg1']}\n";
1375 break;
1376 case 'add allowed':
1377 foreach ($cmd['vlans'] as $vlan_id)
1378 $ret .= "vlan ${vlan_id}\ntagged ${cmd['port']}\nexit\n";
1379 break;
1380 case 'rem allowed':
1381 foreach ($cmd['vlans'] as $vlan_id)
1382 $ret .= "vlan ${vlan_id}\nno tagged ${cmd['port']}\nexit\n";
1383 break;
1384 case 'set native':
1385 $ret .= "interface ${cmd['arg1']}\ndual-mode ${cmd['arg2']}\nexit\n";
1386 break;
1387 case 'unset native':
1388 $ret .= "interface ${cmd['arg1']}\nno dual-mode ${cmd['arg2']}\nexit\n";
1389 break;
1390 case 'set access':
1391 $ret .= "vlan ${cmd['arg2']}\nuntagged ${cmd['arg1']}\nexit\n";
1392 break;
1393 case 'unset access':
1394 $ret .= "vlan ${cmd['arg2']}\nno untagged ${cmd['arg1']}\nexit\n";
1395 break;
1396 case 'set mode': // NOP
1397 break;
5017142e
DO
1398 case 'begin configuration':
1399 $ret .= "conf t\n";
1400 break;
1401 case 'end configuration':
1402 $ret .= "end\n";
1403 break;
1404 case 'save configuration':
1405 $ret .= "write memory\n";
1406 break;
1407 default:
1408 throw new InvalidArgException ('opcode', $cmd['opcode']);
01b400d0 1409 }
01b400d0
DO
1410 return $ret;
1411}
1412
5017142e 1413function vrp53TranslatePushQueue ($queue)
01b400d0 1414{
5017142e 1415 $ret = '';
01b400d0
DO
1416 foreach ($queue as $cmd)
1417 switch ($cmd['opcode'])
1418 {
1419 case 'create VLAN':
1420 $ret .= "vlan ${cmd['arg1']}\nquit\n";
1421 break;
1422 case 'destroy VLAN':
1423 $ret .= "undo vlan ${cmd['arg1']}\n";
1424 break;
1425 case 'add allowed':
1426 case 'rem allowed':
1427 $clause = $cmd['opcode'] == 'add allowed' ? '' : 'undo ';
1428 $ret .= "interface ${cmd['port']}\n";
1429 foreach (listToRanges ($cmd['vlans']) as $range)
1430 $ret .= "${clause}port trunk allow-pass vlan " .
1431 ($range['from'] == $range['to'] ? $range['to'] : "${range['from']} to ${range['to']}") .
1432 "\n";
1433 $ret .= "quit\n";
1434 break;
1435 case 'set native':
1436 case 'set access':
1437 $ret .= "interface ${cmd['arg1']}\nport default vlan ${cmd['arg2']}\nquit\n";
1438 break;
1439 case 'unset native':
1440 case 'unset access':
1441 $ret .= "interface ${cmd['arg1']}\nundo port default vlan\nquit\n";
1442 break;
1443 case 'set mode':
1444 $modemap = array ('access' => 'access', 'trunk' => 'hybrid');
1445 $ret .= "interface ${cmd['arg1']}\nport link-type " . $modemap[$cmd['arg2']] . "\n";
1446 if ($cmd['arg2'] == 'hybrid')
1447 $ret .= "undo port default vlan\nundo port trunk allow-pass vlan all\n";
1448 $ret .= "quit\n";
1449 break;
5017142e
DO
1450 case 'begin configuration':
1451 $ret .= "system-view\n";
1452 break;
1453 case 'end configuration':
1454 $ret .= "return\n";
1455 break;
1456 case 'save configuration':
1457 $ret .= "save\nY\n";
1458 break;
1459 default:
1460 throw new InvalidArgException ('opcode', $cmd['opcode']);
01b400d0 1461 }
01b400d0
DO
1462 return $ret;
1463}
1464
ec523868
DO
1465function vrp55TranslatePushQueue ($queue)
1466{
1467 $ret = '';
1468 foreach ($queue as $cmd)
1469 switch ($cmd['opcode'])
1470 {
1471 case 'create VLAN':
1472 if ($cmd['arg1'] != 1)
1473 $ret .= "vlan ${cmd['arg1']}\nquit\n";
1474 break;
1475 case 'destroy VLAN':
1476 if ($cmd['arg1'] != 1)
1477 $ret .= "undo vlan ${cmd['arg1']}\n";
1478 break;
1479 case 'add allowed':
1480 case 'rem allowed':
1481 $undo = $cmd['opcode'] == 'add allowed' ? '' : 'undo ';
1482 $ret .= "interface ${cmd['port']}\n";
1483 foreach (listToRanges ($cmd['vlans']) as $range)
1484 $ret .= "${undo}port trunk allow-pass vlan " .
1485 ($range['from'] == $range['to'] ? $range['to'] : "${range['from']} to ${range['to']}") .
1486 "\n";
1487 $ret .= "quit\n";
1488 break;
1489 case 'set native':
1490 $ret .= "interface ${cmd['arg1']}\nport trunk pvid vlan ${cmd['arg2']}\nquit\n";
1491 break;
1492 case 'set access':
1493 $ret .= "interface ${cmd['arg1']}\nport default vlan ${cmd['arg2']}\nquit\n";
1494 break;
1495 case 'unset native':
1496 $ret .= "interface ${cmd['arg1']}\nundo port trunk pvid vlan\nquit\n";
1497 break;
1498 case 'unset access':
1499 $ret .= "interface ${cmd['arg1']}\nundo port default vlan\nquit\n";
1500 break;
1501 case 'set mode':
1502 // VRP 5.50's meaning of "trunk" is much like the one of IOS
1503 // (unlike the way VRP 5.30 defines "trunk" and "hybrid"),
1504 // but it is necessary to undo configured VLANs on a port
1505 // for mode change command to succeed.
1506 $undo = array
1507 (
1508 'access' => "undo port trunk allow-pass vlan all\n" .
1509 "port trunk allow-pass vlan 1\n" .
1510 "undo port trunk pvid vlan\n",
1511 'trunk' => "undo port default vlan\n",
1512 );
1513 $ret .= "interface ${cmd['arg1']}\n" . $undo[$cmd['arg2']];
1514 $ret .= "port link-type ${cmd['arg2']}\nquit\n";
1515 break;
1516 case 'begin configuration':
1517 $ret .= "system-view\n";
1518 break;
1519 case 'end configuration':
1520 $ret .= "return\n";
1521 break;
1522 case 'save configuration':
1523 $ret .= "save\nY\n";
1524 break;
1525 default:
1526 throw new InvalidArgException ('opcode', $cmd['opcode']);
1527 }
1528 return $ret;
1529}
1530
5017142e 1531function xos12TranslatePushQueue ($queue)
50520b40
DO
1532{
1533 $ret = '';
1534 foreach ($queue as $cmd)
1535 switch ($cmd['opcode'])
1536 {
1537 case 'create VLAN':
1538 $ret .= "create vlan VLAN${cmd['arg1']}\n";
1539 $ret .= "configure vlan VLAN${cmd['arg1']} tag ${cmd['arg1']}\n";
1540 break;
1541 case 'destroy VLAN':
1542 $ret .= "delete vlan VLAN${cmd['arg1']}\n";
1543 break;
1544 case 'add allowed':
1545 foreach ($cmd['vlans'] as $vlan_id)
1546 {
1547 $vlan_name = $vlan_id == 1 ? 'Default' : "VLAN${vlan_id}";
1548 $ret .= "configure vlan ${vlan_name} add ports ${cmd['port']} tagged\n";
1549 }
1550 break;
1551 case 'rem allowed':
1552 foreach ($cmd['vlans'] as $vlan_id)
1553 {
1554 $vlan_name = $vlan_id == 1 ? 'Default' : "VLAN${vlan_id}";
1555 $ret .= "configure vlan ${vlan_name} delete ports ${cmd['port']}\n";
1556 }
1557 break;
1558 case 'set native':
1559 $vlan_name = $cmd['arg2'] == 1 ? 'Default' : "VLAN${cmd['arg2']}";
1560 $ret .= "configure vlan ${vlan_name} delete ports ${cmd['arg1']}\n";
1561 $ret .= "configure vlan ${vlan_name} add ports ${cmd['arg1']} untagged\n";
1562 break;
1563 case 'unset native':
1564 $vlan_name = $cmd['arg2'] == 1 ? 'Default' : "VLAN${cmd['arg2']}";
1565 $ret .= "configure vlan ${vlan_name} delete ports ${cmd['arg1']}\n";
1566 $ret .= "configure vlan ${vlan_name} add ports ${cmd['arg1']} tagged\n";
1567 break;
1568 case 'set access':
1569 $vlan_name = $cmd['arg2'] == 1 ? 'Default' : "VLAN${cmd['arg2']}";
1570 $ret .= "configure vlan ${vlan_name} add ports ${cmd['arg1']} untagged\n";
1571 break;
1572 case 'unset access':
1573 $vlan_name = $cmd['arg2'] == 1 ? 'Default' : "VLAN${cmd['arg2']}";
1574 $ret .= "configure vlan ${vlan_name} delete ports ${cmd['arg1']}\n";
1575 break;
5017142e
DO
1576 case 'set mode':
1577 case 'begin configuration':
1578 case 'end configuration':
1579 break; // NOP
1580 case 'save configuration':
1581 $ret .= "save configuration\ny\n";
50520b40 1582 break;
5017142e
DO
1583 default:
1584 throw new InvalidArgException ('opcode', $cmd['opcode']);
50520b40 1585 }
50520b40
DO
1586 return $ret;
1587}
1588
3284b28c
DO
1589function xos12Read8021QConfig ($input)
1590{
1591 $ret = array
1592 (
1593 'vlanlist' => array (1),
1594 'portdata' => array(),
1595 );
3284b28c
DO
1596 foreach (explode ("\n", $input) as $line)
1597 {
1598 $matches = array();
1599 switch (TRUE)
1600 {
1601 case (preg_match ('/^create vlan "([[:alnum:]]+)"$/', $line, $matches)):
1aa648ca 1602 if (!preg_match ('/^VLAN[[:digit:]]+$/', $matches[1]))
3a089a44 1603 throw new RTGatewayError ('unsupported VLAN name ' . $matches[1]);
3284b28c
DO
1604 break;
1605 case (preg_match ('/^configure vlan ([[:alnum:]]+) tag ([[:digit:]]+)$/', $line, $matches)):
1606 if (strtolower ($matches[1]) == 'default')
3a089a44 1607 throw new RTGatewayError ('default VLAN tag must be 1');
3284b28c 1608 if ($matches[1] != 'VLAN' . $matches[2])
3a089a44 1609 throw new RTGatewayError ("VLAN name ${matches[1]} does not match its tag ${matches[2]}");
1aa648ca 1610 $ret['vlanlist'][] = $matches[2];
3284b28c 1611 break;
1aa648ca
DO
1612 case (preg_match ('/^configure vlan ([[:alnum:]]+) add ports (.+) (tagged|untagged) */', $line, $matches)):
1613 $submatch = array();
1614 if ($matches[1] == 'Default')
1615 $matches[1] = 'VLAN1';
1616 if (!preg_match ('/^VLAN([[:digit:]]+)$/', $matches[1], $submatch))
3a089a44 1617 throw new RTGatewayError ('unsupported VLAN name ' . $matches[1]);
1aa648ca
DO
1618 $vlan_id = $submatch[1];
1619 foreach (iosParseVLANString ($matches[2]) as $port_name)
1620 {
1621 if (!array_key_exists ($port_name, $ret['portdata']))
1622 $ret['portdata'][$port_name] = array
1623 (
1624 'mode' => 'trunk',
1625 'allowed' => array(),
1626 'native' => 0,
1627 );
1628 $ret['portdata'][$port_name]['allowed'][] = $vlan_id;
1629 if ($matches[3] == 'untagged')
1630 $ret['portdata'][$port_name]['native'] = $vlan_id;
1631 }
3284b28c
DO
1632 break;
1633 default:
1634 }
1635 }
1636 return $ret;
1637}
1638
65e557dd
AA
1639function ciscoReadInterfaceStatus ($text)
1640{
1641 $result = array();
1642 $state = 'headerSearch';
1643 foreach (explode ("\n", $text) as $line)
1644 {
1645 switch ($state)
1646 {
1647 case 'headerSearch':
1648 if (preg_match('/^Port\s+Name\s+Status/', $line))
1649 {
1650 $name_field_borders = getColumnCoordinates($line, 'Name');
1651 if (isset ($name_field_borders['from']))
1652 $state = 'readPort';
1653 }
1654 break;
1655 case 'readPort':
1656 $portname = ios12ShortenIfName (trim (substr ($line, 0, $name_field_borders['from'])));
1657 $rest = trim (substr ($line, $name_field_borders['from'] + $name_field_borders['length'] + 1));
1658 $field_list = preg_split('/\s+/', $rest);
1659 if (count ($field_list) < 4)
1660 break;
1661 list ($status_raw, $vlan, $duplex, $speed, $type) = $field_list;
1662 if ($status_raw == 'connected' || $status_raw == 'up')
1663 $status = 'up';
1664 elseif ($status_raw == 'notconnect' || $status_raw == 'down')
1665 $status = 'down';
1666 else
1667 $status = 'disabled';
1668 $result[$portname] = array
1669 (
1670 'status' => $status,
1671 'speed' => $speed,
1672 'duplex' => $duplex,
1673 );
1674 break;
1675 }
1676 }
1677 return $result;
1678}
1679
1680function vrpReadInterfaceStatus ($text)
1681{
1682 $result = array();
1683 $state = 'headerSearch';
1684 foreach (explode ("\n", $text) as $line)
1685 {
1686 switch ($state)
1687 {
1688 case 'headerSearch':
1689 if (preg_match('/^Interface\s+Phy\w*\s+Protocol/i', $line))
1690 $state = 'readPort';
1691 break;
1692 case 'readPort':
1693 if (preg_match('/[\$><\]]/', $line))
1694 break 2;
1695 $field_list = preg_split('/\s+/', $line);
1696 if (count ($field_list) < 7)
1697 break;
1698 list ($portname, $status_raw) = $field_list;
1699 $portname = ios12ShortenIfName ($portname);
1700
1701 if ($status_raw == 'up' || $status_raw == 'down')
1702 $status = $status_raw;
1703 else
1704 $status = 'disabled';
1705 $result[$portname] = array
1706 (
1707 'status' => $status,
1708 );
1709 break;
1710 }
1711 }
1712 return $result;
1713}
1714
1715function maclist_sort ($a, $b)
1716{
1717 if ($a['vid'] == $b['vid'])
1718 return 0;
1719 return ($a['vid'] < $b['vid']) ? -1 : 1;
1720}
1721
1722function ios12ReadMacList ($text)
1723{
1724 $result = array();
1725 $state = 'headerSearch';
1726 foreach (explode ("\n", $text) as $line)
1727 {
1728 switch ($state)
1729 {
1730 case 'headerSearch':
1731 if (preg_match('/Vlan\s+Mac Address\s+Type.*Ports?\s*$/i', $line))
1732 $state = 'readPort';
1733 break;
1734 case 'readPort':
1735 if (! preg_match ('/(\d+)\s+([a-f0-9]{4}\.[a-f0-9]{4}\.[a-f0-9]{4})\s.*?(\S+)$/', trim ($line), $matches))
1736 break;
1737 $portname = ios12ShortenIfName ($matches[3]);
1738 $result[$portname][] = array
1739 (
1740 'mac' => $matches[2],
1741 'vid' => $matches[1],
1742 );
1743 break;
1744 }
1745 }
1746 foreach ($result as $portname => &$maclist)
1747 usort ($maclist, 'maclist_sort');
1748 return $result;
1749}
1750
1751function nxos4ReadMacList ($text)
1752{
1753 $result = array();
1754 $state = 'headerSearch';
1755 foreach (explode ("\n", $text) as $line)
1756 {
1757 switch ($state)
1758 {
1759 case 'headerSearch':
1760 if (preg_match('/VLAN\s+MAC Address\s+Type\s+age\s+Secure\s+NTFY\s+Ports/i', $line))
1761 $state = 'readPort';
1762 break;
1763 case 'readPort':
1764 if (! preg_match ('/(\d+)\s+([a-f0-9]{4}\.[a-f0-9]{4}\.[a-f0-9]{4})\s.*?(\S+)$/', trim ($line), $matches))
1765 break;
1766 $portname = ios12ShortenIfName ($matches[3]);
1767 $result[$portname][] = array
1768 (
1769 'mac' => $matches[2],
1770 'vid' => $matches[1],
1771 );
1772 break;
1773 }
1774 }
1775 foreach ($result as $portname => &$maclist)
1776 usort ($maclist, 'maclist_sort');
1777 return $result;
1778}
1779
1780function vrp53ReadMacList ($text)
1781{
1782 $result = array();
1783 $state = 'headerSearch';
1784 foreach (explode ("\n", $text) as $line)
1785 {
1786 switch ($state)
1787 {
1788 case 'headerSearch':
1789 if (preg_match('/MAC Address\s+VLAN\/VSI\s+Port/i', $line))
1790 $state = 'readPort';
1791 break;
1792 case 'readPort':
1793 if (! preg_match ('/([a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4})\s+(\d+)\s+(\S+)/', trim ($line), $matches))
1794 break;
1795 $portname = ios12ShortenIfName ($matches[3]);
1796 $result[$portname][] = array
1797 (
1798 'mac' => str_replace ('-', '.', $matches[1]),
1799 'vid' => $matches[2],
1800 );
1801 break;
1802 }
1803 }
1804 foreach ($result as $portname => &$maclist)
1805 usort ($maclist, 'maclist_sort');
1806 return $result;
1807}
1808
1809function vrp55ReadMacList ($text)
1810{
1811 $result = array();
1812 $state = 'headerSearch';
1813 foreach (explode ("\n", $text) as $line)
1814 {
1815 switch ($state)
1816 {
1817 case 'headerSearch':
1818 if (preg_match('/MAC Address\s+VLAN\/\S*\s+PEVLAN\s+CEVLAN\s+Port/i', $line))
1819 $state = 'readPort';
1820 break;
1821 case 'readPort':
1822 if (! preg_match ('/([a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4})\s+(\d+)(?:\s+\S+){2}\s+(\S+)/', trim ($line), $matches))
1823 break;
1824 $portname = ios12ShortenIfName ($matches[3]);
1825 $result[$portname][] = array
1826 (
1827 'mac' => str_replace ('-', '.', $matches[1]),
1828 'vid' => $matches[2],
1829 );
1830 break;
1831 }
1832 }
1833 foreach ($result as $portname => &$maclist)
1834 usort ($maclist, 'maclist_sort');
1835 return $result;
1836}
1837
d33645ff 1838?>