3 // Read provided output of "show cdp neighbors detail" command and
4 // return a list of records with (translated) local port name,
5 // remote device name and (translated) remote port name.
6 function ios12ReadCDPStatus ($input)
9 foreach (explode ("\n", $input) as $line)
14 case preg_match ('/^Device ID:\s*([A-Za-z0-9][A-Za-z0-9\.\-]*)/', $line, $matches):
15 case preg_match ('/^System Name:\s*([A-Za-z0-9][A-Za-z0-9\.\-]*)/', $line, $matches):
16 $ret['current']['device'] = $matches[1];
18 case preg_match ('/^Interface: (.+), ?Port ID \(outgoing port\): (.+)$/', $line, $matches):
19 if (array_key_exists ('device', $ret['current']))
20 $ret[ios12ShortenIfName ($matches[1])][] = array
22 'device' => $ret['current']['device'],
23 'port' => ios12ShortenIfName ($matches[2]),
25 unset ($ret['current']);
30 unset ($ret['current']);
34 function ios12ReadLLDPStatus ($input)
38 foreach (explode ("\n", $input) as $line)
40 if (preg_match ("/^Device ID/", $line))
46 $matches = preg_split ('/\s+/', $line);
48 switch (count ($matches))
51 list ($remote_name, $local_port, $ttl, $caps, $remote_port) = $matches;
52 $local_port = ios12ShortenIfName ($local_port);
53 $remote_port = ios12ShortenIfName ($remote_port);
54 $ret[$local_port][] = array
56 'device' => $remote_name,
57 'port' => $remote_port,
66 function xos12ReadLLDPStatus ($input)
69 foreach (explode ("\n", $input) as $line)
74 case preg_match ('/^LLDP Port ([[:digit:]]+) detected \d+ neighbor$/', $line, $matches):
75 $ret['current']['local_port'] = ios12ShortenIfName ($matches[1]);
77 case preg_match ('/^ Port ID : "(.+)"$/', $line, $matches):
78 $ret['current']['remote_port'] = ios12ShortenIfName ($matches[1]);
80 case preg_match ('/^ - System Name: "(.+)"$/', $line, $matches):
83 array_key_exists ('current', $ret) and
84 array_key_exists ('local_port', $ret['current']) and
85 array_key_exists ('remote_port', $ret['current'])
87 $ret[$ret['current']['local_port']][] = array
89 'device' => $matches[1],
90 'port' => $ret['current']['remote_port'],
92 unset ($ret['current']);
96 unset ($ret['current']);
100 function vrp53ReadLLDPStatus ($input)
103 foreach (explode ("\n", $input) as $line)
108 case preg_match ('/^(.+) has \d+ neighbors:$/', $line, $matches):
109 $ret['current']['local_port'] = ios12ShortenIfName ($matches[1]);
111 case preg_match ('/^(PortIdSubtype|PortId): ([^ ]+)/', $line, $matches):
112 $ret['current'][$matches[1]] = $matches[2];
114 case preg_match ('/^SysName: (.+)$/', $line, $matches):
117 array_key_exists ('current', $ret) and
118 array_key_exists ('PortIdSubtype', $ret['current']) and
119 ($ret['current']['PortIdSubtype'] == 'interfaceAlias' or $ret['current']['PortIdSubtype'] == 'interfaceName') and
120 array_key_exists ('PortId', $ret['current']) and
121 array_key_exists ('local_port', $ret['current'])
123 $ret[$ret['current']['local_port']][] = array
125 'device' => $matches[1],
126 'port' => ios12ShortenIfName ($ret['current']['PortId']),
128 unset ($ret['current']);
133 unset ($ret['current']);
137 function vrp55ReadLLDPStatus ($input)
140 foreach (explode ("\n", $input) as $line)
145 case preg_match ('/^(.+) has \d+ neighbors:$/', $line, $matches):
146 $ret['current']['local_port'] = ios12ShortenIfName ($matches[1]);
148 case preg_match ('/^Port ID type :([^ ]+)/', $line, $matches):
149 $ret['current']['PortIdSubtype'] = $matches[1];
151 case preg_match ('/^Port ID :(.+)$/', $line, $matches):
152 $ret['current']['PortId'] = $matches[1];
154 case preg_match ('/^System name :(.+)$/', $line, $matches):
157 array_key_exists ('current', $ret) and
158 array_key_exists ('PortIdSubtype', $ret['current']) and
159 ($ret['current']['PortIdSubtype'] == 'interfaceAlias' or $ret['current']['PortIdSubtype'] == 'interfaceName') and
160 array_key_exists ('PortId', $ret['current']) and
161 array_key_exists ('local_port', $ret['current'])
163 $ret[$ret['current']['local_port']][] = array
165 'device' => $matches[1],
166 'port' => ios12ShortenIfName ($ret['current']['PortId']),
168 unset ($ret['current']);
173 unset ($ret['current']);
177 function vrp53ReadHNDPStatus ($input)
180 foreach (explode ("\n", $input) as $line)
185 case preg_match ('/^ Interface: (.+)$/', $line, $matches):
186 $ret['current']['local_port'] = ios12ShortenIfName ($matches[1]);
188 case preg_match ('/^ Port Name : (.+)$/', $line, $matches):
189 $ret['current']['remote_port'] = ios12ShortenIfName ($matches[1]);
191 case preg_match ('/^ Device Name : (.+)$/', $line, $matches):
194 array_key_exists ('current', $ret) and
195 array_key_exists ('local_port', $ret['current']) and
196 array_key_exists ('remote_port', $ret['current'])
198 $ret[$ret['current']['local_port']][] = array
200 'device' => $matches[1],
201 'port' => $ret['current']['remote_port'],
203 unset ($ret['current']);
208 unset ($ret['current']);
212 function ios12ReadVLANConfig ($input)
216 'vlanlist' => array(),
217 'portdata' => array(),
219 $procfunc = 'ios12ScanTopLevel';
220 foreach (explode ("\n", $input) as $line)
221 $procfunc = $procfunc ($ret, $line);
225 function ios12ScanTopLevel (&$work, $line)
230 case (preg_match ('@^interface ((Ethernet|FastEthernet|GigabitEthernet|TenGigabitEthernet|Port-channel)[[:digit:]]+(/[[:digit:]]+)*)$@', $line, $matches)):
231 $work['current'] = array ('port_name' => ios12ShortenIfName ($matches[1]));
232 $work['current']['config'][] = array ('type' => 'line-header', 'line' => $line);
233 return 'ios12PickSwitchportCommand'; // switch to interface block reading
234 case (preg_match ('/^VLAN Name Status Ports$/', $line, $matches)):
235 return 'ios12PickVLANCommand';
237 return __FUNCTION__
; // continue scan
241 function ios12PickSwitchportCommand (&$work, $line)
243 if ($line[0] != ' ') // end of interface section
245 // save work, if it makes sense
246 switch (@$work['current']['mode'])
249 if (!array_key_exists ('access vlan', $work['current']))
250 $work['current']['access vlan'] = 1;
251 $work['portdata'][$work['current']['port_name']] = array
254 'allowed' => array ($work['current']['access vlan']),
255 'native' => $work['current']['access vlan'],
259 if (!array_key_exists ('trunk native vlan', $work['current']))
260 $work['current']['trunk native vlan'] = 1;
261 if (!array_key_exists ('trunk allowed vlan', $work['current']))
262 $work['current']['trunk allowed vlan'] = range (VLAN_MIN_ID
, VLAN_MAX_ID
);
263 // Having configured VLAN as "native" doesn't mean anything
264 // as long as it's not listed on the "allowed" line.
265 $effective_native = in_array
267 $work['current']['trunk native vlan'],
268 $work['current']['trunk allowed vlan']
269 ) ?
$work['current']['trunk native vlan'] : 0;
270 $work['portdata'][$work['current']['port_name']] = array
273 'allowed' => $work['current']['trunk allowed vlan'],
274 'native' => $effective_native,
281 // dot1q-tunnel, dynamic, private-vlan or even none --
282 // show in returned config and let user decide, if they
283 // want to fix device config or work around these ports
285 $work['portdata'][$work['current']['port_name']] = array
288 'allowed' => array(),
293 if (isset ($work['portdata'][$work['current']['port_name']]))
294 $work['portdata'][$work['current']['port_name']]['config'] = $work['current']['config'];
295 unset ($work['current']);
296 return 'ios12ScanTopLevel';
300 $line_class = 'line-8021q';
303 case (preg_match ('@^ switchport mode (.+)$@', $line, $matches)):
304 $work['current']['mode'] = $matches[1];
306 case (preg_match ('@^ switchport access vlan (.+)$@', $line, $matches)):
307 $work['current']['access vlan'] = $matches[1];
309 case (preg_match ('@^ switchport trunk native vlan (.+)$@', $line, $matches)):
310 $work['current']['trunk native vlan'] = $matches[1];
312 case (preg_match ('@^ switchport trunk allowed vlan add (.+)$@', $line, $matches)):
313 $work['current']['trunk allowed vlan'] = array_merge
315 $work['current']['trunk allowed vlan'],
316 iosParseVLANString ($matches[1])
319 case (preg_match ('@^ switchport trunk allowed vlan (.+)$@', $line, $matches)):
320 $work['current']['trunk allowed vlan'] = iosParseVLANString ($matches[1]);
322 case preg_match ('@^ channel-group @', $line):
323 // port-channel subinterface config follows that of the master interface
324 $work['current']['mode'] = 'SKIP';
326 case preg_match ('@^ ip address @', $line):
327 // L3 interface does no switchport functions
328 $work['current']['mode'] = 'IP';
330 default: // suppress warning on irrelevant config clause
331 $line_class = 'line-other';
333 $work['current']['config'][] = array ('type' => $line_class, 'line' => $line);
337 function ios12PickVLANCommand (&$work, $line)
342 case ($line == '---- -------------------------------- --------- -------------------------------'):
343 // ignore the rest of VLAN table header;
345 case (preg_match ('@! END OF VLAN LIST$@', $line)):
346 return 'ios12ScanTopLevel';
347 case (preg_match ('@^([[:digit:]]+) {1,4}.{32} active @', $line, $matches)):
348 if (!array_key_exists ($matches[1], $work['vlanlist']))
349 $work['vlanlist'][] = $matches[1];
356 // Another finite automata to read a dialect of Foundry configuration.
357 function fdry5ReadVLANConfig ($input)
361 'vlanlist' => array(),
362 'portdata' => array(),
364 $procfunc = 'fdry5ScanTopLevel';
365 foreach (explode ("\n", $input) as $line)
366 $procfunc = $procfunc ($ret, $line);
370 function fdry5ScanTopLevel (&$work, $line)
375 case (preg_match ('@^vlan ([[:digit:]]+)( name .+)? (by port)$@', $line, $matches)):
376 if (!array_key_exists ($matches[1], $work['vlanlist']))
377 $work['vlanlist'][] = $matches[1];
378 $work['current'] = array ('vlan_id' => $matches[1]);
379 return 'fdry5PickVLANSubcommand';
380 case (preg_match ('@^interface ethernet ([[:digit:]]+/[[:digit:]]+/[[:digit:]]+)$@', $line, $matches)):
381 $work['current'] = array ('port_name' => 'e' . $matches[1]);
382 return 'fdry5PickInterfaceSubcommand';
388 function fdry5PickVLANSubcommand (&$work, $line)
390 if ($line[0] != ' ') // end of VLAN section
392 unset ($work['current']);
393 return 'fdry5ScanTopLevel';
399 case (preg_match ('@^ tagged (.+)$@', $line, $matches)):
400 // add current VLAN to 'allowed' list of each mentioned port
401 foreach (fdry5ParsePortString ($matches[1]) as $port_name)
402 if (array_key_exists ($port_name, $work['portdata']))
403 $work['portdata'][$port_name]['allowed'][] = $work['current']['vlan_id'];
405 $work['portdata'][$port_name] = array
408 'allowed' => array ($work['current']['vlan_id']),
409 'native' => 0, // can be updated later
411 $work['portdata'][$port_name]['mode'] = 'trunk';
413 case (preg_match ('@^ untagged (.+)$@', $line, $matches)):
414 // replace 'native' column of each mentioned port with current VLAN ID
415 foreach (fdry5ParsePortString ($matches[1]) as $port_name)
417 if (array_key_exists ($port_name, $work['portdata']))
419 $work['portdata'][$port_name]['native'] = $work['current']['vlan_id'];
420 $work['portdata'][$port_name]['allowed'][] = $work['current']['vlan_id'];
423 $work['portdata'][$port_name] = array
426 'allowed' => array ($work['current']['vlan_id']),
427 'native' => $work['current']['vlan_id'],
429 // Untagged ports are initially assumed to be access ports, and
430 // when this assumption is right, this is the final port mode state.
431 // When the port is dual-mode one, this is detected and justified
432 // later in "interface" section of config text.
433 $work['portdata'][$port_name]['mode'] = 'access';
441 function fdry5PickInterfaceSubcommand (&$work, $line)
443 if ($line[0] != ' ') // end of interface section
445 if (array_key_exists ('dual-mode', $work['current']))
447 if (array_key_exists ($work['current']['port_name'], $work['portdata']))
448 // update existing record
449 $work['portdata'][$work['current']['port_name']]['native'] = $work['current']['dual-mode'];
452 $work['portdata'][$work['current']['port_name']] = array
454 'allowed' => array ($work['current']['dual-mode']),
455 'native' => $work['current']['dual-mode'],
457 // a dual-mode port is always considered a trunk port
458 // (but not in the IronWare's meaning of "trunk") regardless of
459 // number of assigned tagged VLANs
460 $work['portdata'][$work['current']['port_name']]['mode'] = 'trunk';
462 unset ($work['current']);
463 return 'fdry5ScanTopLevel';
468 case (preg_match ('@^ dual-mode( +[[:digit:]]+ *)?$@', $line, $matches)):
469 // default VLAN ID for dual-mode command is 1
470 $work['current']['dual-mode'] = strlen (trim ($matches[1])) ?
trim ($matches[1]) : 1;
472 // FIXME: trunk/link-aggregate/ip address pulls port from 802.1Q field
478 function fdry5ParsePortString ($string)
481 $tokens = explode (' ', trim ($string));
482 while (count ($tokens))
484 $letters = array_shift ($tokens); // "ethe", "to"
485 $numbers = array_shift ($tokens); // "x", "x/x", "x/x/x"
489 if ($prev_numbers != NULL)
490 $ret[] = 'e' . $prev_numbers;
491 $prev_numbers = $numbers;
494 $ret = array_merge ($ret, fdry5GenPortRange ($prev_numbers, $numbers));
495 $prev_numbers = NULL; // no action on next token
501 // flush delayed item
502 if ($prev_numbers != NULL)
503 $ret[] = 'e' . $prev_numbers;
507 // Take two indices in form "x", "x/x" or "x/x/x" and return the range of
508 // ports spanning from the first to the last. The switch software makes it
509 // easier to perform, because "ethe x/x/x to y/y/y" ranges never cross
510 // unit/slot boundary (every index except the last remains constant).
511 function fdry5GenPortRange ($from, $to)
514 if (1 !== preg_match ('@^([[:digit:]]+/)?([[:digit:]]+/)?([[:digit:]]+)$@', $from, $matches))
516 $prefix = 'e' . $matches[1] . $matches[2];
517 $from_idx = $matches[3];
518 if (1 !== preg_match ('@^([[:digit:]]+/)?([[:digit:]]+/)?([[:digit:]]+)$@', $to, $matches))
520 $to_idx = $matches[3];
521 for ($i = $from_idx; $i <= $to_idx; $i++
)
522 $ret[] = $prefix . $i;
526 // an implementation for Huawei syntax
527 function vrp53ReadVLANConfig ($input)
531 'vlanlist' => array(),
532 'portdata' => array(),
534 $procfunc = 'vrp53ScanTopLevel';
535 foreach (explode ("\n", $input) as $line)
536 $procfunc = $procfunc ($ret, $line);
540 function vrp53ScanTopLevel (&$work, $line)
545 case (preg_match ('@^ vlan batch (.+)$@', $line, $matches)):
546 foreach (vrp53ParseVLANString ($matches[1]) as $vlan_id)
547 $work['vlanlist'][] = $vlan_id;
549 case (preg_match ('@^interface ((GigabitEthernet|XGigabitEthernet|Eth-Trunk)([[:digit:]]+(/[[:digit:]]+)*))$@', $line, $matches)):
550 $matches[1] = preg_replace ('@^GigabitEthernet(.+)$@', 'gi\\1', $matches[1]);
551 $matches[1] = preg_replace ('@^XGigabitEthernet(.+)$@', 'xg\\1', $matches[1]);
552 $matches[1] = preg_replace ('@^Eth-Trunk(.+)$@', 'et\\1', $matches[1]);
553 $work['current'] = array ('port_name' => $matches[1]);
554 $work['current']['config'][] = array ('type' => 'line-header', 'line' => $line);
555 return 'vrp53PickInterfaceSubcommand';
561 function vrp53ParseVLANString ($string)
563 $string = preg_replace ('/ to /', '-', $string);
564 $string = preg_replace ('/ /', ',', $string);
565 return iosParseVLANString ($string);
568 function vrp53PickInterfaceSubcommand (&$work, $line)
570 if ($line[0] == '#') // end of interface section
572 // Configuration Guide - Ethernet 3.3.4:
573 // "By default, the interface type is hybrid."
574 if (!array_key_exists ('link-type', $work['current']))
575 $work['current']['link-type'] = 'hybrid';
576 if (!array_key_exists ('allowed', $work['current']))
577 $work['current']['allowed'] = array();
578 if (!array_key_exists ('native', $work['current']))
579 $work['current']['native'] = 0;
580 switch ($work['current']['link-type'])
583 // VRP does not assign access ports to VLAN1 by default,
584 // leaving them blocked.
585 $work['portdata'][$work['current']['port_name']] =
586 $work['current']['native'] ?
array
588 'allowed' => $work['current']['allowed'],
589 'native' => $work['current']['native'],
594 'allowed' => array(),
599 $work['portdata'][$work['current']['port_name']] = array
601 'allowed' => $work['current']['allowed'],
607 $work['portdata'][$work['current']['port_name']] = array
609 'allowed' => $work['current']['allowed'],
610 'native' => $work['current']['native'],
614 default: // dot1q-tunnel ?
616 if (isset ($work['portdata'][$work['current']['port_name']]))
617 $work['portdata'][$work['current']['port_name']]['config'] = $work['current']['config'];
618 unset ($work['current']);
619 return 'vrp53ScanTopLevel';
622 $line_class = 'line-8021q';
625 case (preg_match ('@^ port default vlan ([[:digit:]]+)$@', $line, $matches)):
626 $work['current']['native'] = $matches[1];
627 if (!array_key_exists ('allowed', $work['current']))
628 $work['current']['allowed'] = array();
629 if (!in_array ($matches[1], $work['current']['allowed']))
630 $work['current']['allowed'][] = $matches[1];
632 case (preg_match ('@^ port link-type (.+)$@', $line, $matches)):
633 $work['current']['link-type'] = $matches[1];
635 case (preg_match ('@^ port trunk allow-pass vlan (.+)$@', $line, $matches)):
636 if (!array_key_exists ('allowed', $work['current']))
637 $work['current']['allowed'] = array();
638 foreach (vrp53ParseVLANString ($matches[1]) as $vlan_id)
639 if (!in_array ($vlan_id, $work['current']['allowed']))
640 $work['current']['allowed'][] = $vlan_id;
642 // TODO: make sure, that a port with "eth-trunk" clause always ends up in "none" mode
644 $line_class = 'line-other';
646 $work['current']['config'][] = array('type' => $line_class, 'line' => $line);
650 function vrp55Read8021QConfig ($input)
654 'vlanlist' => array (1), // VRP 5.50 hides VLAN1 from config text
655 'portdata' => array(),
657 foreach (explode ("\n", $input) as $line)
661 if (!array_key_exists ('current', $ret))
665 case (preg_match ('@^ vlan batch (.+)$@', $line, $matches)):
666 foreach (vrp53ParseVLANString ($matches[1]) as $vlan_id)
667 $ret['vlanlist'][] = $vlan_id;
669 case (preg_match ('@^interface ((GigabitEthernet|XGigabitEthernet|Eth-Trunk)([[:digit:]]+(/[[:digit:]]+)*))$@', $line, $matches)):
670 $matches[1] = preg_replace ('@^GigabitEthernet(.+)$@', 'gi\\1', $matches[1]);
671 $matches[1] = preg_replace ('@^XGigabitEthernet(.+)$@', 'xg\\1', $matches[1]);
672 $ret['current'] = array ('port_name' => $matches[1]);
673 $ret['current']['config'][] = array ('type' => 'line-header', 'line' => $line);
678 // inside an interface block
679 $line_class = 'line-8021q';
682 case preg_match ('/^ port (link-type )?hybrid /', $line):
683 throw new RTGatewayError ("unsupported configuration: ${line}");
684 case preg_match ('/^ port link-type (.+)$/', $line, $matches):
685 $ret['current']['link-type'] = $matches[1];
687 // Native VLAN is configured differently for each link-type case, but
688 // VRP is known to filter off clauses, which don't make sense for
689 // current link-type. This way any interface section should contain
690 // only one kind of "set native" clause (but if this constraint breaks,
691 // we get a problem).
692 case preg_match ('/^ port (default|trunk pvid) vlan ([[:digit:]]+)$/', $line, $matches):
693 $ret['current']['native'] = $matches[2];
694 if (!array_key_exists ('allowed', $ret['current']))
695 $ret['current']['allowed'] = array();
696 if (!in_array ($ret['current']['native'], $ret['current']['allowed']))
697 $ret['current']['allowed'][] = $ret['current']['native'];
699 case preg_match ('/^ port trunk allow-pass vlan (.+)$/', $line, $matches):
700 if (!array_key_exists ('allowed', $ret['current']))
701 $ret['current']['allowed'] = array();
702 foreach (vrp53ParseVLANString ($matches[1]) as $vlan_id)
703 if (!in_array ($vlan_id, $ret['current']['allowed']))
704 $ret['current']['allowed'][] = $vlan_id;
706 case $line == ' undo portswitch':
707 case preg_match ('/^ ip address /', $line):
708 $ret['current']['link-type'] = 'IP';
710 case preg_match ('/^ eth-trunk /', $line):
711 $ret['current']['link-type'] = 'SKIP';
713 case substr ($line, 0, 1) == '#': // end of interface section
714 if (!array_key_exists ('link-type', $ret['current']))
715 throw new RTGatewayError ('unsupported configuration: link-type is neither trunk nor access for ' . $ret['current']['port_name']);
716 if (!array_key_exists ('allowed', $ret['current']))
717 $ret['current']['allowed'] = array();
718 if (!array_key_exists ('native', $ret['current']))
719 $ret['current']['native'] = 0;
720 switch ($ret['current']['link-type'])
723 // In VRP 5.50 an access port has default VLAN ID == 1
724 $ret['portdata'][$ret['current']['port_name']] =
725 $ret['current']['native'] ?
array
728 'allowed' => $ret['current']['allowed'],
729 'native' => $ret['current']['native'],
733 'allowed' => array (VLAN_DFL_ID
),
734 'native' => VLAN_DFL_ID
,
738 $ret['portdata'][$ret['current']['port_name']] = array
741 'allowed' => $ret['current']['allowed'],
742 'native' => $ret['current']['native'],
746 $ret['portdata'][$ret['current']['port_name']] = array
749 'allowed' => array(),
754 default: // dot1q-tunnel ?
756 if (isset ($ret['portdata'][$ret['current']['port_name']]))
757 $ret['portdata'][$ret['current']['port_name']]['config'] = $ret['current']['config'];
758 unset ($ret['current']);
761 $line_class = 'line-other';
763 $ret['current']['config'][] = array ('type' => $line_class, 'line' => $line);
768 function nxos4Read8021QConfig ($input)
772 'vlanlist' => array(),
773 'portdata' => array(),
775 $procfunc = 'nxos4ScanTopLevel';
776 foreach (explode ("\n", $input) as $line)
777 $procfunc = $procfunc ($ret, $line);
781 function nxos4ScanTopLevel (&$work, $line)
786 case (preg_match ('@^interface ((Ethernet|Port-channel)[[:digit:]]+(/[[:digit:]]+)*)$@i', $line, $matches)):
787 $matches[1] = preg_replace ('@^Ethernet(.+)$@i', 'e\\1', $matches[1]);
788 $matches[1] = preg_replace ('@^Port-channel(.+)$@i', 'po\\1', $matches[1]);
789 $work['current'] = array ('port_name' => $matches[1]);
790 $work['current']['config'][] = array ('type' => 'line-header', 'line' => $line);
791 return 'nxos4PickSwitchportCommand';
792 case (preg_match ('@^vlan (.+)$@', $line, $matches)):
793 foreach (iosParseVLANString ($matches[1]) as $vlan_id)
794 $work['vlanlist'][] = $vlan_id;
797 return __FUNCTION__
; // continue scan
801 function nxos4PickSwitchportCommand (&$work, $line)
803 if ($line == '') // end of interface section
806 if (!array_key_exists ('mode', $work['current']))
807 $work['current']['mode'] = 'access';
808 // save work, if it makes sense
809 switch ($work['current']['mode'])
812 if (!array_key_exists ('access vlan', $work['current']))
813 $work['current']['access vlan'] = 1;
814 $work['portdata'][$work['current']['port_name']] = array
817 'allowed' => array ($work['current']['access vlan']),
818 'native' => $work['current']['access vlan'],
822 if (!array_key_exists ('trunk native vlan', $work['current']))
823 $work['current']['trunk native vlan'] = 1;
824 // FIXME: NX-OS reserves VLANs 3968 through 4047 plus 4094 for itself
825 if (!array_key_exists ('trunk allowed vlan', $work['current']))
826 $work['current']['trunk allowed vlan'] = range (VLAN_MIN_ID
, VLAN_MAX_ID
);
827 // Having configured VLAN as "native" doesn't mean anything
828 // as long as it's not listed on the "allowed" line.
829 $effective_native = in_array
831 $work['current']['trunk native vlan'],
832 $work['current']['trunk allowed vlan']
833 ) ?
$work['current']['trunk native vlan'] : 0;
834 $work['portdata'][$work['current']['port_name']] = array
837 'allowed' => $work['current']['trunk allowed vlan'],
838 'native' => $effective_native,
842 case 'fex-fabric': // associated port-channel
845 // dot1q-tunnel, dynamic, private-vlan
846 $work['portdata'][$work['current']['port_name']] = array
849 'allowed' => array(),
852 // unset (routed), dot1q-tunnel, dynamic, private-vlan --- skip these
854 if (isset ($work['portdata'][$work['current']['port_name']]))
855 $work['portdata'][$work['current']['port_name']]['config'] = $work['current']['config'];
856 unset ($work['current']);
857 return 'nxos4ScanTopLevel';
861 $line_class = 'line-8021q';
864 case (preg_match ('@^ switchport mode (.+)$@', $line, $matches)):
865 $work['current']['mode'] = $matches[1];
867 case (preg_match ('@^ switchport access vlan (.+)$@', $line, $matches)):
868 $work['current']['access vlan'] = $matches[1];
870 case (preg_match ('@^ switchport trunk native vlan (.+)$@', $line, $matches)):
871 $work['current']['trunk native vlan'] = $matches[1];
873 case (preg_match ('@^ switchport trunk allowed vlan add (.+)$@', $line, $matches)):
874 $work['current']['trunk allowed vlan'] = array_merge
876 $work['current']['trunk allowed vlan'],
877 iosParseVLANString ($matches[1])
880 case (preg_match ('@^ switchport trunk allowed vlan (.+)$@', $line, $matches)):
881 $work['current']['trunk allowed vlan'] = iosParseVLANString ($matches[1]);
883 case preg_match ('/^ +channel-group /', $line):
884 $work['current']['mode'] = 'SKIP';
886 default: // suppress warning on irrelevant config clause
887 $line_class = 'line-other';
889 $work['current']['config'][] = array ('type' => $line_class, 'line' => $line);
893 // Get a list of VLAN management pseudo-commands and return a text
894 // of real vendor-specific commands, which implement the work.
895 // This work is done in two rounds:
896 // 1. For "add allowed" and "rem allowed" commands detect continuous
897 // sequences of VLAN IDs and replace them with ranges of form "A-B",
899 // 2. Iterate over the resulting list and produce real CLI commands.
900 function ios12TranslatePushQueue ($queue)
903 foreach ($queue as $cmd)
904 switch ($cmd['opcode'])
907 $ret .= "vlan ${cmd['arg1']}\nexit\n";
910 $ret .= "no vlan ${cmd['arg1']}\n";
914 $clause = $cmd['opcode'] == 'add allowed' ?
'add' : 'remove';
915 $ret .= "interface ${cmd['port']}\n";
916 foreach (listToRanges ($cmd['vlans']) as $range)
917 $ret .= "switchport trunk allowed vlan ${clause} " .
918 ($range['from'] == $range['to'] ?
$range['to'] : "${range['from']}-${range['to']}") .
923 $ret .= "interface ${cmd['arg1']}\nswitchport trunk native vlan ${cmd['arg2']}\nexit\n";
926 $ret .= "interface ${cmd['arg1']}\nno switchport trunk native vlan ${cmd['arg2']}\nexit\n";
929 $ret .= "interface ${cmd['arg1']}\nswitchport access vlan ${cmd['arg2']}\nexit\n";
932 $ret .= "interface ${cmd['arg1']}\nno switchport access vlan\nexit\n";
935 $ret .= "interface ${cmd['arg1']}\nswitchport mode ${cmd['arg2']}\n";
936 if ($cmd['arg2'] == 'trunk')
937 $ret .= "no switchport trunk native vlan\nswitchport trunk allowed vlan none\n";
940 case 'begin configuration':
941 $ret .= "configure terminal\n";
943 case 'end configuration':
946 case 'save configuration':
947 $ret .= "copy running-config startup-config\n\n";
950 throw new InvalidArgException ('opcode', $cmd['opcode']);
955 function fdry5TranslatePushQueue ($queue)
958 foreach ($queue as $cmd)
959 switch ($cmd['opcode'])
962 $ret .= "vlan ${cmd['arg1']}\nexit\n";
965 $ret .= "no vlan ${cmd['arg1']}\n";
968 foreach ($cmd['vlans'] as $vlan_id)
969 $ret .= "vlan ${vlan_id}\ntagged ${cmd['port']}\nexit\n";
972 foreach ($cmd['vlans'] as $vlan_id)
973 $ret .= "vlan ${vlan_id}\nno tagged ${cmd['port']}\nexit\n";
976 $ret .= "interface ${cmd['arg1']}\ndual-mode ${cmd['arg2']}\nexit\n";
979 $ret .= "interface ${cmd['arg1']}\nno dual-mode ${cmd['arg2']}\nexit\n";
982 $ret .= "vlan ${cmd['arg2']}\nuntagged ${cmd['arg1']}\nexit\n";
985 $ret .= "vlan ${cmd['arg2']}\nno untagged ${cmd['arg1']}\nexit\n";
987 case 'set mode': // NOP
989 case 'begin configuration':
992 case 'end configuration':
995 case 'save configuration':
996 $ret .= "write memory\n";
999 throw new InvalidArgException ('opcode', $cmd['opcode']);
1004 function vrp53TranslatePushQueue ($queue)
1007 foreach ($queue as $cmd)
1008 switch ($cmd['opcode'])
1011 $ret .= "vlan ${cmd['arg1']}\nquit\n";
1013 case 'destroy VLAN':
1014 $ret .= "undo vlan ${cmd['arg1']}\n";
1018 $clause = $cmd['opcode'] == 'add allowed' ?
'' : 'undo ';
1019 $ret .= "interface ${cmd['port']}\n";
1020 foreach (listToRanges ($cmd['vlans']) as $range)
1021 $ret .= "${clause}port trunk allow-pass vlan " .
1022 ($range['from'] == $range['to'] ?
$range['to'] : "${range['from']} to ${range['to']}") .
1028 $ret .= "interface ${cmd['arg1']}\nport default vlan ${cmd['arg2']}\nquit\n";
1030 case 'unset native':
1031 case 'unset access':
1032 $ret .= "interface ${cmd['arg1']}\nundo port default vlan\nquit\n";
1035 $modemap = array ('access' => 'access', 'trunk' => 'hybrid');
1036 $ret .= "interface ${cmd['arg1']}\nport link-type " . $modemap[$cmd['arg2']] . "\n";
1037 if ($cmd['arg2'] == 'hybrid')
1038 $ret .= "undo port default vlan\nundo port trunk allow-pass vlan all\n";
1041 case 'begin configuration':
1042 $ret .= "system-view\n";
1044 case 'end configuration':
1047 case 'save configuration':
1048 $ret .= "save\nY\n";
1051 throw new InvalidArgException ('opcode', $cmd['opcode']);
1056 function vrp55TranslatePushQueue ($queue)
1059 foreach ($queue as $cmd)
1060 switch ($cmd['opcode'])
1063 if ($cmd['arg1'] != 1)
1064 $ret .= "vlan ${cmd['arg1']}\nquit\n";
1066 case 'destroy VLAN':
1067 if ($cmd['arg1'] != 1)
1068 $ret .= "undo vlan ${cmd['arg1']}\n";
1072 $undo = $cmd['opcode'] == 'add allowed' ?
'' : 'undo ';
1073 $ret .= "interface ${cmd['port']}\n";
1074 foreach (listToRanges ($cmd['vlans']) as $range)
1075 $ret .= "${undo}port trunk allow-pass vlan " .
1076 ($range['from'] == $range['to'] ?
$range['to'] : "${range['from']} to ${range['to']}") .
1081 $ret .= "interface ${cmd['arg1']}\nport trunk pvid vlan ${cmd['arg2']}\nquit\n";
1084 $ret .= "interface ${cmd['arg1']}\nport default vlan ${cmd['arg2']}\nquit\n";
1086 case 'unset native':
1087 $ret .= "interface ${cmd['arg1']}\nundo port trunk pvid vlan\nquit\n";
1089 case 'unset access':
1090 $ret .= "interface ${cmd['arg1']}\nundo port default vlan\nquit\n";
1093 // VRP 5.50's meaning of "trunk" is much like the one of IOS
1094 // (unlike the way VRP 5.30 defines "trunk" and "hybrid"),
1095 // but it is necessary to undo configured VLANs on a port
1096 // for mode change command to succeed.
1099 'access' => "undo port trunk allow-pass vlan all\n" .
1100 "port trunk allow-pass vlan 1\n" .
1101 "undo port trunk pvid vlan\n",
1102 'trunk' => "undo port default vlan\n",
1104 $ret .= "interface ${cmd['arg1']}\n" . $undo[$cmd['arg2']];
1105 $ret .= "port link-type ${cmd['arg2']}\nquit\n";
1107 case 'begin configuration':
1108 $ret .= "system-view\n";
1110 case 'end configuration':
1113 case 'save configuration':
1114 $ret .= "save\nY\n";
1117 throw new InvalidArgException ('opcode', $cmd['opcode']);
1122 function xos12TranslatePushQueue ($queue)
1125 foreach ($queue as $cmd)
1126 switch ($cmd['opcode'])
1129 $ret .= "create vlan VLAN${cmd['arg1']}\n";
1130 $ret .= "configure vlan VLAN${cmd['arg1']} tag ${cmd['arg1']}\n";
1132 case 'destroy VLAN':
1133 $ret .= "delete vlan VLAN${cmd['arg1']}\n";
1136 foreach ($cmd['vlans'] as $vlan_id)
1138 $vlan_name = $vlan_id == 1 ?
'Default' : "VLAN${vlan_id}";
1139 $ret .= "configure vlan ${vlan_name} add ports ${cmd['port']} tagged\n";
1143 foreach ($cmd['vlans'] as $vlan_id)
1145 $vlan_name = $vlan_id == 1 ?
'Default' : "VLAN${vlan_id}";
1146 $ret .= "configure vlan ${vlan_name} delete ports ${cmd['port']}\n";
1150 $vlan_name = $cmd['arg2'] == 1 ?
'Default' : "VLAN${cmd['arg2']}";
1151 $ret .= "configure vlan ${vlan_name} delete ports ${cmd['arg1']}\n";
1152 $ret .= "configure vlan ${vlan_name} add ports ${cmd['arg1']} untagged\n";
1154 case 'unset native':
1155 $vlan_name = $cmd['arg2'] == 1 ?
'Default' : "VLAN${cmd['arg2']}";
1156 $ret .= "configure vlan ${vlan_name} delete ports ${cmd['arg1']}\n";
1157 $ret .= "configure vlan ${vlan_name} add ports ${cmd['arg1']} tagged\n";
1160 $vlan_name = $cmd['arg2'] == 1 ?
'Default' : "VLAN${cmd['arg2']}";
1161 $ret .= "configure vlan ${vlan_name} add ports ${cmd['arg1']} untagged\n";
1163 case 'unset access':
1164 $vlan_name = $cmd['arg2'] == 1 ?
'Default' : "VLAN${cmd['arg2']}";
1165 $ret .= "configure vlan ${vlan_name} delete ports ${cmd['arg1']}\n";
1168 case 'begin configuration':
1169 case 'end configuration':
1171 case 'save configuration':
1172 $ret .= "save configuration\ny\n";
1175 throw new InvalidArgException ('opcode', $cmd['opcode']);
1180 function jun10TranslatePushQueue ($queue, $vlan_names)
1183 if (! isset ($vlan_names))
1184 $vlan_names = array();
1186 foreach ($queue as $cmd)
1187 switch ($cmd['opcode'])
1190 $ret .= "set vlans VLAN${cmd['arg1']} vlan-id ${cmd['arg1']}\n";
1192 case 'destroy VLAN':
1193 if (isset ($vlan_names[$cmd['arg1']]))
1194 $ret .= "delete vlans " . $vlan_names[$cmd['arg1']] . "\n";
1198 $del = ($cmd['opcode'] == 'rem allowed');
1199 $pre = ($del ?
'delete' : 'set') .
1200 " interfaces ${cmd['port']} unit 0 family ethernet-switching vlan members";
1201 if (count ($cmd['vlans']) > VLAN_MAX_ID
- VLAN_MIN_ID
)
1202 $ret .= "$pre " . ($del ?
'' : 'all') . "\n";
1204 while (! empty ($cmd['vlans']))
1206 $vlan = array_shift ($cmd['vlans']);
1207 $ret .= "$pre $vlan\n";
1208 if ($del and isset ($vlan_names[$vlan]))
1209 $ret .= "$pre ${vlan_names[$vlan]}\n";
1213 $ret .= "set interfaces ${cmd['arg1']} unit 0 family ethernet-switching native-vlan-id ${cmd['arg2']}\n";
1215 case 'unset native':
1216 $ret .= "delete interfaces ${cmd['arg1']} unit 0 family ethernet-switching native-vlan-id\n";
1219 $ret .= "set interfaces ${cmd['arg1']} unit 0 family ethernet-switching vlan members ${cmd['arg2']}\n";
1221 case 'unset access':
1222 $ret .= "delete interfaces ${cmd['arg1']} unit 0 family ethernet-switching vlan members\n";
1225 $ret .= "set interfaces ${cmd['arg1']} unit 0 family ethernet-switching port-mode ${cmd['arg2']}\n";
1227 case 'begin configuration':
1228 $ret .= "configure exclusive\n";
1230 case 'end configuration':
1231 $ret .= "commit confirmed 120\n";
1233 case 'save configuration':
1234 break; // JunOS can`t apply configuration without saving it
1236 throw new InvalidArgException ('opcode', $cmd['opcode']);
1241 function xos12Read8021QConfig ($input)
1245 'vlanlist' => array (1),
1246 'portdata' => array(),
1248 foreach (explode ("\n", $input) as $line)
1253 case (preg_match ('/^create vlan "([[:alnum:]]+)"$/', $line, $matches)):
1254 if (!preg_match ('/^VLAN[[:digit:]]+$/', $matches[1]))
1255 throw new RTGatewayError ('unsupported VLAN name ' . $matches[1]);
1257 case (preg_match ('/^configure vlan ([[:alnum:]]+) tag ([[:digit:]]+)$/', $line, $matches)):
1258 if (strtolower ($matches[1]) == 'default')
1259 throw new RTGatewayError ('default VLAN tag must be 1');
1260 if ($matches[1] != 'VLAN' . $matches[2])
1261 throw new RTGatewayError ("VLAN name ${matches[1]} does not match its tag ${matches[2]}");
1262 $ret['vlanlist'][] = $matches[2];
1264 case (preg_match ('/^configure vlan ([[:alnum:]]+) add ports (.+) (tagged|untagged) */', $line, $matches)):
1265 $submatch = array();
1266 if ($matches[1] == 'Default')
1267 $matches[1] = 'VLAN1';
1268 if (!preg_match ('/^VLAN([[:digit:]]+)$/', $matches[1], $submatch))
1269 throw new RTGatewayError ('unsupported VLAN name ' . $matches[1]);
1270 $vlan_id = $submatch[1];
1271 foreach (iosParseVLANString ($matches[2]) as $port_name)
1273 if (!array_key_exists ($port_name, $ret['portdata']))
1274 $ret['portdata'][$port_name] = array
1277 'allowed' => array(),
1280 $ret['portdata'][$port_name]['allowed'][] = $vlan_id;
1281 if ($matches[3] == 'untagged')
1282 $ret['portdata'][$port_name]['native'] = $vlan_id;
1291 function jun10Read8021QConfig ($input)
1295 'vlanlist' => array (1),
1296 'vlannames' => array (1 => 'default'),
1297 'portdata' => array(),
1299 $lines = explode ("\n", $input);
1302 $vlans = array('default' => 1);
1304 while (count ($lines))
1306 $line = trim (array_shift ($lines));
1307 if (FALSE !== strpos ($line, '# END OF VLAN LIST'))
1309 if (preg_match ('/^VLAN: (.*), 802.1Q Tag: (\d+)/', $line, $m))
1311 $ret['vlannames'][$m[2]] = $m[1];
1312 $vlans[$m[1]] = $m[2];
1315 $ret['vlanlist'] = array_values ($vlans);
1317 // get config groups list - throw an exception if a group contains ether-switching config
1318 $current_group = NULL;
1319 while (count ($lines))
1321 $line = array_shift ($lines);
1322 if (FALSE !== strpos ($line, '# END OF GROUP LIST'))
1324 elseif (preg_match ('/^(\S+)(?:\s+{|;)$/', $line, $m))
1325 $current_group = $m[1];
1326 elseif (isset ($current_group) and preg_match ('/^\s*family ethernet-switching\b/', $line))
1327 throw new RTGatewayError ("Config-group '$current_group' contains switchport commands, which is not supported");
1330 // get interfaces config
1333 'is_range' => FALSE,
1334 'is_ethernet' => FALSE,
1339 while (count ($lines))
1341 $line = array_shift ($lines);
1342 if (preg_match ('/# END OF CONFIG|^(interface-range )?(\S+)\s+{$/', $line, $m)) // line starts with interface name
1343 { // found interface section opening, or end-of-file
1344 if (isset ($current['name']) and $current['is_ethernet'])
1346 // add previous interface to the results
1347 if (! isset ($current['config']['mode']))
1348 $current['config']['mode'] = 'access';
1349 if (! isset ($current['config']['native']))
1350 $current['config']['native'] = $current['config']['native'] = 0;
1351 if (! isset ($current['config']['allowed']))
1353 if ($current['config']['mode'] == 'access')
1354 $current['config']['allowed'] = array (1);
1356 $current['config']['allowed'] = array();
1359 $current['config']['mode'] == 'trunk' and
1360 $current['config']['native'] != 0 and
1361 ! in_array ($current['config']['native'], $current['config']['allowed'])
1363 $current['config']['allowed'][] = $current['config']['native'];
1364 elseif ($current['config']['mode'] == 'access')
1365 $current['config']['native'] = $current['config']['allowed'][0];
1366 $ret['portdata'][$current['name']] = $current['config'];
1369 if (! empty ($m[2]))
1370 { // new interface section begins
1371 $current['is_ethernet'] = FALSE;
1372 $current['is_range'] = ! empty ($m[1]);
1373 $current['name'] = $m[2];
1374 $current['config'] = array (
1379 $current['indent'] = NULL;
1382 elseif (preg_match ('/^(\s+)family ethernet-switching\b/', $line, $m))
1384 if ($current['is_range'])
1385 throw new RTGatewayError ("interface-range '${current['name']}' contains switchport commands, which is not supported");
1386 $current['is_ethernet'] = TRUE;
1387 $current['indent'] = $m[1];
1389 elseif (isset ($current['indent']) and $line == $current['indent'] . '}')
1390 $current['indent'] = NULL;
1391 elseif ($current['is_ethernet'] and isset ($current['indent']))
1393 if (preg_match ('/^\s+port-mode (trunk|access);/', $line, $m))
1394 $current['config']['mode'] = $m[1];
1395 elseif (preg_match ('/^\s+native-vlan-id (\d+);/', $line, $m))
1396 $current['config']['native'] = $m[1];
1397 elseif (preg_match ('/^\s+members \[?(.*)\]?;$/', $line, $m))
1400 foreach (explode (' ', $m[1]) as $item)
1402 $item = trim ($item);
1403 if (preg_match ('/^(\d+)(?:-(\d+))?$/', $item, $m))
1405 if (isset ($m[2]) and $m[2] > $m[1])
1406 $members = array_merge (range ($m[1], $m[2]), $members);
1410 elseif (isset ($vlans[$item]))
1411 $members[] = $vlans[$item];
1412 elseif ($item == 'all')
1413 $members = array_merge (range (VLAN_MIN_ID
, VLAN_MAX_ID
), $members);
1415 $current['config']['allowed'] = array_unique ($members);
1423 function ciscoReadInterfaceStatus ($text)
1426 $state = 'headerSearch';
1427 foreach (explode ("\n", $text) as $line)
1431 case 'headerSearch':
1432 if (preg_match('/^Port\s+Name\s+Status/', $line))
1434 $name_field_borders = getColumnCoordinates($line, 'Name');
1435 if (isset ($name_field_borders['from']))
1436 $state = 'readPort';
1440 $portname = ios12ShortenIfName (trim (substr ($line, 0, $name_field_borders['from'])));
1441 $rest = trim (substr ($line, $name_field_borders['from'] +
$name_field_borders['length'] +
1));
1442 $field_list = preg_split('/\s+/', $rest);
1443 if (count ($field_list) < 4)
1445 list ($status_raw, $vlan, $duplex, $speed, $type) = $field_list;
1446 if ($status_raw == 'connected' ||
$status_raw == 'up')
1448 elseif ($status_raw == 'notconnect' ||
$status_raw == 'down')
1451 $status = 'disabled';
1452 $result[$portname] = array
1454 'status' => $status,
1456 'duplex' => $duplex,
1464 function vrpReadInterfaceStatus ($text)
1467 $state = 'headerSearch';
1468 foreach (explode ("\n", $text) as $line)
1472 case 'headerSearch':
1473 if (preg_match('/^Interface\s+Phy\w*\s+Protocol/i', $line))
1474 $state = 'readPort';
1477 if (preg_match('/[\$><\]]/', $line))
1479 $field_list = preg_split('/\s+/', $line);
1480 if (count ($field_list) < 7)
1482 list ($portname, $status_raw) = $field_list;
1483 $portname = ios12ShortenIfName ($portname);
1485 if ($status_raw == 'up' ||
$status_raw == 'down')
1486 $status = $status_raw;
1488 $status = 'disabled';
1489 $result[$portname] = array
1491 'status' => $status,
1499 function maclist_sort ($a, $b)
1501 if ($a['vid'] == $b['vid'])
1503 return ($a['vid'] < $b['vid']) ?
-1 : 1;
1506 function ios12ReadMacList ($text)
1509 $state = 'headerSearch';
1510 foreach (explode ("\n", $text) as $line)
1514 case 'headerSearch':
1515 if (preg_match('/Vlan\s+Mac Address\s+Type.*Ports?\s*$/i', $line))
1516 $state = 'readPort';
1519 if (! preg_match ('/(\d+)\s+([a-f0-9]{4}\.[a-f0-9]{4}\.[a-f0-9]{4})\s.*?(\S+)$/', trim ($line), $matches))
1521 $portname = ios12ShortenIfName ($matches[3]);
1522 $result[$portname][] = array
1524 'mac' => $matches[2],
1525 'vid' => $matches[1],
1530 foreach ($result as $portname => &$maclist)
1531 usort ($maclist, 'maclist_sort');
1535 function nxos4ReadMacList ($text)
1538 $state = 'headerSearch';
1539 foreach (explode ("\n", $text) as $line)
1543 case 'headerSearch':
1544 if (preg_match('/VLAN\s+MAC Address\s+Type\s+age\s+Secure\s+NTFY\s+Ports/i', $line))
1545 $state = 'readPort';
1548 if (! preg_match ('/(\d+)\s+([a-f0-9]{4}\.[a-f0-9]{4}\.[a-f0-9]{4})\s.*?(\S+)$/', trim ($line), $matches))
1550 $portname = ios12ShortenIfName ($matches[3]);
1551 $result[$portname][] = array
1553 'mac' => $matches[2],
1554 'vid' => $matches[1],
1559 foreach ($result as $portname => &$maclist)
1560 usort ($maclist, 'maclist_sort');
1564 function vrp53ReadMacList ($text)
1567 $state = 'headerSearch';
1568 foreach (explode ("\n", $text) as $line)
1572 case 'headerSearch':
1573 if (preg_match('/MAC Address\s+VLAN\/VSI\s+Port/i', $line))
1574 $state = 'readPort';
1577 if (! preg_match ('/([a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4})\s+(\d+)\s+(\S+)/', trim ($line), $matches))
1579 $portname = ios12ShortenIfName ($matches[3]);
1580 $result[$portname][] = array
1582 'mac' => str_replace ('-', '.', $matches[1]),
1583 'vid' => $matches[2],
1588 foreach ($result as $portname => &$maclist)
1589 usort ($maclist, 'maclist_sort');
1593 function vrp55ReadMacList ($text)
1596 $state = 'headerSearch';
1597 foreach (explode ("\n", $text) as $line)
1601 case 'headerSearch':
1602 if (preg_match('/MAC Address\s+VLAN\/\S*\s+PEVLAN\s+CEVLAN\s+Port/i', $line))
1603 $state = 'readPort';
1606 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))
1608 $portname = ios12ShortenIfName ($matches[3]);
1609 $result[$portname][] = array
1611 'mac' => str_replace ('-', '.', $matches[1]),
1612 'vid' => $matches[2],
1617 foreach ($result as $portname => &$maclist)
1618 usort ($maclist, 'maclist_sort');