3 # This file is a part of RackTables, a datacenter and server room management
4 # framework. See accompanying file "COPYING" for the full copyright and
5 # licensing information.
7 // Read provided output of "show cdp neighbors detail" command and
8 // return a list of records with (translated) local port name,
9 // remote device name and (translated) remote port name.
10 function ios12ReadCDPStatus ($input)
13 foreach (explode ("\n", $input) as $line)
18 case preg_match ('/^Device ID:\s*([A-Za-z0-9][A-Za-z0-9\.\-]*)/', $line, $matches):
19 case preg_match ('/^System Name:\s*([A-Za-z0-9][A-Za-z0-9\.\-]*)/', $line, $matches):
20 $ret['current']['device'] = $matches[1];
22 case preg_match ('/^Interface: (.+), ?Port ID \(outgoing port\): (.+)$/', $line, $matches):
23 if (array_key_exists ('device', $ret['current']))
24 $ret[shortenIfName ($matches[1])][] = array
26 'device' => $ret['current']['device'],
27 'port' => $matches[2],
29 unset ($ret['current']);
34 unset ($ret['current']);
38 function ios12ReadLLDPStatus ($input)
42 foreach (explode ("\n", $input) as $line)
44 if (preg_match ("/^Device ID/", $line))
50 $matches = preg_split ('/\s+/', trim ($line));
52 switch (count ($matches))
55 list ($remote_name, $local_port, $ttl, $caps, $remote_port) = $matches;
56 $local_port = shortenIfName ($local_port);
57 $ret[$local_port][] = array
59 'device' => $remote_name,
60 'port' => $remote_port,
69 function xos12ReadLLDPStatus ($input)
72 foreach (explode ("\n", $input) as $line)
77 case preg_match ('/^LLDP Port ([[:digit:]]+) detected \d+ neighbor$/', $line, $matches):
78 $ret['current']['local_port'] = shortenIfName ($matches[1]);
80 case preg_match ('/^ Port ID : "(.+)"$/', $line, $matches):
81 $ret['current']['remote_port'] = $matches[1];
83 case preg_match ('/^ - System Name: "(.+)"$/', $line, $matches):
86 array_key_exists ('current', $ret) and
87 array_key_exists ('local_port', $ret['current']) and
88 array_key_exists ('remote_port', $ret['current'])
90 $ret[$ret['current']['local_port']][] = array
92 'device' => $matches[1],
93 'port' => $ret['current']['remote_port'],
95 unset ($ret['current']);
99 unset ($ret['current']);
103 function vrpReadLLDPStatus ($input)
106 $valid_subtypes = array
115 foreach (explode ("\n", $input) as $line)
120 case preg_match ('/^(.+) has \d+ neighbor(\(s\)|s):$/', $line, $matches):
121 $ret['current']['local_port'] = shortenIfName (trim ($matches[1]));
123 case preg_match ('/^Port ?ID ?(?:sub)?type\s*:\s*(.*)$/i', $line, $matches):
124 $ret['current']['PortIdSubtype'] = trim ($matches[1]);
126 case preg_match ('/^Port ?ID\s*:\s*(.+)$/i', $line, $matches):
127 $ret['current']['PortId'] = trim ($matches[1]);
129 case preg_match ('/^Port description\s*:\s*(.*)$/i', $line, $matches):
130 $ret['current']['PortDescription'] = trim ($matches[1]);
132 case preg_match ('/^Sys(?:tem)? ?name\s*:\s*(.+)$/i', $line, $matches):
135 array_key_exists ('current', $ret) and
136 array_key_exists ('PortIdSubtype', $ret['current']) and
137 array_key_exists ('local_port', $ret['current'])
141 if (array_key_exists ('PortId', $ret['current']) && in_array ($ret['current']['PortIdSubtype'], $valid_subtypes))
142 $port = $ret['current']['PortId'];
143 elseif (array_key_exists ('PortDescription', $ret['current']) && 'local' == $ret['current']['PortIdSubtype'])
144 $port = $ret['current']['PortDescription'];
146 $ret[$ret['current']['local_port']][] = array
148 'device' => trim ($matches[1]),
152 unset ($ret['current']);
157 unset ($ret['current']);
161 function ftos8ReadLLDPStatus ($input)
164 $valid_subtypes = array
166 'Interface name (5)',
167 'Interface Alias (1)',
168 'Locally assigned (7)',
170 foreach (explode ("\n", $input) as $line)
175 case preg_match ('/^ Local Interface (.+) has \d+ neighbor/', $line, $matches):
176 $ret['current']['local_port'] = strtolower (str_replace (' ', '', $matches[1])); # "Gi 0/19" => "gi0/19"
178 case preg_match ('/^ Remote Port Subtype: (.+)$/', $line, $matches):
179 $ret['current']['remote_subtype'] = $matches[1];
181 case preg_match ('/^ Remote Port ID: (.+)$/i', $line, $matches):
182 $ret['current']['remote_port'] = $matches[1];
184 case preg_match ('/^ Remote System Name: (.+)$/', $line, $matches):
187 array_key_exists ('current', $ret) and
188 array_key_exists ('remote_subtype', $ret['current']) and
189 in_array ($ret['current']['remote_subtype'], $valid_subtypes) and
190 array_key_exists ('remote_port', $ret['current']) and
191 array_key_exists ('local_port', $ret['current'])
193 $ret[$ret['current']['local_port']][] = array
195 'device' => $matches[1],
196 'port' => $ret['current']['remote_port'],
198 unset ($ret['current']['remote_subtype']);
199 unset ($ret['current']['remote_port']);
204 unset ($ret['current']);
208 function eos4ReadLLDPStatus ($input)
211 $valid_subtypes = array
213 'Interface name (5)',
215 foreach (explode ("\n", $input) as $line)
220 case preg_match ('/^Interface (.+) detected \d+ LLDP neighbors/', $line, $matches):
221 $ret['current']['local_port'] = shortenIfName ($matches[1]);
223 case preg_match ('/^ - Port ID type: (.+)$/', $line, $matches):
224 $ret['current']['remote_subtype'] = $matches[1];
226 case preg_match ('/^ Port ID : "(.+)"$/', $line, $matches):
227 $ret['current']['remote_port'] = $matches[1];
229 case preg_match ('/^ - System Name: "(.+)"$/', $line, $matches):
232 array_key_exists ('current', $ret) and
233 array_key_exists ('remote_subtype', $ret['current']) and
234 in_array ($ret['current']['remote_subtype'], $valid_subtypes) and
235 array_key_exists ('remote_port', $ret['current']) and
236 array_key_exists ('local_port', $ret['current'])
238 $ret[$ret['current']['local_port']][] = array
240 'device' => $matches[1],
241 'port' => $ret['current']['remote_port'],
243 unset ($ret['current']['remote_subtype']);
244 unset ($ret['current']['remote_port']);
249 unset ($ret['current']);
253 function ros11ReadLLDPStatus ($input)
256 foreach (explode ("\n", $input) as $line)
260 case preg_match ('/^Local port: (.+)$/', $line, $m):
261 $ret['current']['local_port'] = shortenIfName ($m[1]);
263 case preg_match ('/^Port ID: (.+)$/', $line, $m):
264 $ret['current']['remote_port'] = $m[1];
266 case preg_match ('/^System Name: (.+)$/', $line, $m):
269 array_key_exists ('current', $ret) and
270 array_key_exists ('remote_port', $ret['current']) and
271 array_key_exists ('local_port', $ret['current'])
273 $ret[$ret['current']['local_port']][] = array
276 'port' => $ret['current']['remote_port'],
278 unset ($ret['current']['remote_port']);
283 unset ($ret['current']);
287 function ios12ReadVLANConfig ($input)
291 'vlanlist' => array(),
292 'portdata' => array(),
293 'portconfig' => array(),
296 if (preg_match ('/\nUnable to get configuration. Try again later/s', $input))
297 throw new ERetryNeeded ("device is busy. 'show run' did not work");
300 $nextfunc = 'ios12-get8021q-swports';
301 foreach (explode ("\n", $input) as $line)
302 $nextfunc = $breedfunc[$nextfunc] ($ret, $line);
304 // clear $ret from temporary keys created by parser functions
305 foreach ($ret as $key => $value)
306 if (! isset ($schema[$key]))
311 function ios12ScanTopLevel (&$work, $line)
316 case (preg_match ('@^interface ((Ethernet|FastEthernet|GigabitEthernet|TenGigabitEthernet|[Pp]ort-channel)[[:digit:]]+(/[[:digit:]]+)*)$@', $line, $matches)):
317 $port_name = shortenIfName ($matches[1]);
318 $work['current'] = array ('port_name' => $port_name);
319 $work['portconfig'][$port_name][] = array ('type' => 'line-header', 'line' => $line);
320 return 'ios12-get8021q-readport'; // switch to interface block reading
321 case (preg_match ('/^VLAN Name Status Ports$/', $line, $matches)):
322 return 'ios12-get8021q-readvlan';
324 return 'ios12-get8021q-top'; // continue scan
328 function ios12ReadSwitchPortList (&$work, $line)
330 if (0 < strpos ($line, '! END OF SWITCHPORTS'))
331 return 'ios12-get8021q-top';
332 if (preg_match ('@^(?:\s*|vdc .*)Name:\s+(\S+)@', $line, $m))
333 $work['current_switchport'] = $m[1];
334 elseif (preg_match ('@^\s*Switchport:\s+(Enabled)@', $line, $m) && isset ($work['current_switchport']))
336 $work['switchports'][] = shortenIfName ($work['current_switchport']);
337 unset ($work['current_switchport']);
339 return 'ios12-get8021q-swports';
342 function ios12PickSwitchportCommand (&$work, $line)
344 $port_name = $work['current']['port_name'];
345 if (! strlen ($line) ||
$line[0] != ' ') // end of interface section
347 $work['portconfig'][$port_name][] = array ('type' => 'line-header', 'line' => $line);
349 // save work, if it makes sense
350 if (! in_array ($port_name, $work['switchports']))
351 $work['current']['mode'] = 'SKIP'; // skip not switched ports
354 if (! isset ($work['current']['mode']))
355 $work['current']['mode'] = 'access';
357 switch (@$work['current']['mode'])
360 if (!array_key_exists ('access vlan', $work['current']))
361 $work['current']['access vlan'] = 1;
362 $work['portdata'][$port_name] = array
365 'allowed' => array ($work['current']['access vlan']),
366 'native' => $work['current']['access vlan'],
370 if (!array_key_exists ('trunk native vlan', $work['current']))
371 $work['current']['trunk native vlan'] = 1;
372 if (!array_key_exists ('trunk allowed vlan', $work['current']))
373 $work['current']['trunk allowed vlan'] = range (VLAN_MIN_ID
, VLAN_MAX_ID
);
374 // Having configured VLAN as "native" doesn't mean anything
375 // as long as it's not listed on the "allowed" line.
376 $effective_native = in_array
378 $work['current']['trunk native vlan'],
379 $work['current']['trunk allowed vlan']
380 ) ?
$work['current']['trunk native vlan'] : 0;
381 $work['portdata'][$port_name] = array
384 'allowed' => $work['current']['trunk allowed vlan'],
385 'native' => $effective_native,
389 case 'fex-fabric': // associated port-channel
393 // dot1q-tunnel, dynamic, private-vlan or even none --
394 // show in returned config and let user decide, if they
395 // want to fix device config or work around these ports
397 $work['portdata'][$port_name] = array
400 'allowed' => array(),
405 unset ($work['current']);
406 return 'ios12-get8021q-top';
410 $line_class = 'line-8021q';
413 case (preg_match ('@^\s+switchport mode (.+)$@', $line, $matches)):
414 $work['current']['mode'] = $matches[1];
416 case (preg_match ('@^\s+switchport access vlan (.+)$@', $line, $matches)):
417 $work['current']['access vlan'] = $matches[1];
419 case (preg_match ('@^\s+switchport trunk native vlan (.+)$@', $line, $matches)):
420 $work['current']['trunk native vlan'] = $matches[1];
422 case (preg_match ('@^\s+switchport trunk allowed vlan add (.+)$@', $line, $matches)):
423 $work['current']['trunk allowed vlan'] = array_merge
425 $work['current']['trunk allowed vlan'],
426 iosParseVLANString ($matches[1])
429 case preg_match ('@^\s+switchport trunk allowed vlan none$@', $line, $matches):
430 $work['current']['trunk allowed vlan'] = array();
432 case (preg_match ('@^\s+switchport trunk allowed vlan (.+)$@', $line, $matches)):
433 $work['current']['trunk allowed vlan'] = iosParseVLANString ($matches[1]);
435 case preg_match ('@^\s+channel-group @', $line):
436 // port-channel subinterface config follows that of the master interface
437 $work['current']['mode'] = 'SKIP';
439 case preg_match ('@^\s+ip address @', $line):
440 // L3 interface does no switchport functions
441 $work['current']['mode'] = 'IP';
443 default: // suppress warning on irrelevant config clause
444 $line_class = 'line-other';
446 $work['portconfig'][$port_name][] = array ('type' => $line_class, 'line' => $line);
447 return 'ios12-get8021q-readport';
450 function ios12PickVLANCommand (&$work, $line)
455 case (preg_match ('@! END OF VLAN LIST$@', $line)):
456 return 'ios12-get8021q-top';
457 case (preg_match ('@^([[:digit:]]+) {1,4}.{32} active @', $line, $matches)):
458 $work['vlanlist'][] = $matches[1];
462 return 'ios12-get8021q-readvlan';
465 // Another finite automata to read a dialect of Foundry configuration.
466 function fdry5ReadVLANConfig ($input)
470 'vlanlist' => array(),
471 'portdata' => array(),
472 'portconfig' => array(),
475 $nextfunc = 'fdry5-get8021q-top';
476 foreach (explode ("\n", $input) as $line)
477 $nextfunc = $breedfunc[$nextfunc] ($ret, $line);
481 function fdry5ScanTopLevel (&$work, $line)
486 case (preg_match ('@^vlan ([[:digit:]]+)( name .+)? (by port)$@', $line, $matches)):
487 if (!array_key_exists ($matches[1], $work['vlanlist']))
488 $work['vlanlist'][] = $matches[1];
489 $work['current'] = array ('vlan_id' => $matches[1]);
490 return 'fdry5-get8021q-readvlan';
491 case (preg_match ('@^interface ethernet ([[:digit:]]+/[[:digit:]]+/[[:digit:]]+)$@', $line, $matches)):
492 $port_name = 'e' . $matches[1];
493 $work['current'] = array ('port_name' => $port_name);
494 $work['portconfig'][$port_name][] = array ('type' => 'line-header', 'line' => $line);
495 return 'fdry5-get8021q-readport';
497 return 'fdry5-get8021q-top';
501 function fdry5PickVLANSubcommand (&$work, $line)
503 if ($line[0] != ' ') // end of VLAN section
505 unset ($work['current']);
506 return 'fdry5-get8021q-top';
512 case (preg_match ('@^ tagged (.+)$@', $line, $matches)):
513 // add current VLAN to 'allowed' list of each mentioned port
514 foreach (fdry5ParsePortString ($matches[1]) as $port_name)
515 if (array_key_exists ($port_name, $work['portdata']))
516 $work['portdata'][$port_name]['allowed'][] = $work['current']['vlan_id'];
518 $work['portdata'][$port_name] = array
521 'allowed' => array ($work['current']['vlan_id']),
522 'native' => 0, // can be updated later
524 $work['portdata'][$port_name]['mode'] = 'trunk';
526 case (preg_match ('@^ untagged (.+)$@', $line, $matches)):
527 // replace 'native' column of each mentioned port with current VLAN ID
528 foreach (fdry5ParsePortString ($matches[1]) as $port_name)
530 if (array_key_exists ($port_name, $work['portdata']))
532 $work['portdata'][$port_name]['native'] = $work['current']['vlan_id'];
533 $work['portdata'][$port_name]['allowed'][] = $work['current']['vlan_id'];
536 $work['portdata'][$port_name] = array
539 'allowed' => array ($work['current']['vlan_id']),
540 'native' => $work['current']['vlan_id'],
542 // Untagged ports are initially assumed to be access ports, and
543 // when this assumption is right, this is the final port mode state.
544 // When the port is dual-mode one, this is detected and justified
545 // later in "interface" section of config text.
546 $work['portdata'][$port_name]['mode'] = 'access';
551 return 'fdry5-get8021q-readvlan';
554 function fdry5PickInterfaceSubcommand (&$work, $line)
556 $port_name = $work['current']['port_name'];
557 if ($line[0] != ' ') // end of interface section
559 $work['portconfig'][$port_name][] = array ('type' => 'line-header', 'line' => $line);
560 if (array_key_exists ('dual-mode', $work['current']))
562 if (array_key_exists ($port_name, $work['portdata']))
563 // update existing record
564 $work['portdata'][$port_name]['native'] = $work['current']['dual-mode'];
567 $work['portdata'][$port_name] = array
569 'allowed' => array ($work['current']['dual-mode']),
570 'native' => $work['current']['dual-mode'],
572 // a dual-mode port is always considered a trunk port
573 // (but not in the IronWare's meaning of "trunk") regardless of
574 // number of assigned tagged VLANs
575 $work['portdata'][$port_name]['mode'] = 'trunk';
577 unset ($work['current']);
578 return 'fdry5-get8021q-top';
583 case (preg_match ('@^ dual-mode( +[[:digit:]]+ *)?$@', $line, $matches)):
584 // default VLAN ID for dual-mode command is 1
585 $work['current']['dual-mode'] = strlen (trim ($matches[1])) ?
trim ($matches[1]) : 1;
587 // FIXME: trunk/link-aggregate/ip address pulls port from 802.1Q field
590 $work['portconfig'][$port_name][] = array ('type' => 'line-other', 'line' => $line);
591 return 'fdry5-get8021q-readport';
594 # Produce a list of interfaces from a string in the following format:
595 # ethe 1 ethe 3 ethe 5 to 7 ethe 9
596 # ethe 1/1 to 1/24 ethe 2/1 to 2/24 ethe 3/1 ethe 3/3 ethe 3/5 to 3/8
597 # ethe 1/1/1 to 1/1/10 ethe 1/1/12 ethe 1/1/15 to 1/1/20 ethe 2/1/1 to 2/1/24 ethe 3/1/1
598 function fdry5ParsePortString ($string)
601 $tokens = explode (' ', trim ($string));
602 while (count ($tokens))
604 $letters = array_shift ($tokens); // "ethe", "to"
605 $numbers = array_shift ($tokens); // "x", "x/x", "x/x/x"
609 if ($prev_numbers != NULL)
610 $ret[] = 'e' . $prev_numbers;
611 $prev_numbers = $numbers;
614 $ret = array_merge ($ret, fdry5GenPortRange ($prev_numbers, $numbers));
615 $prev_numbers = NULL; // no action on next token
618 throw new InvalidArgException ('string', $string, 'format mismatch');
621 // flush delayed item
622 if ($prev_numbers != NULL)
623 $ret[] = 'e' . $prev_numbers;
627 // Take two indices in form "x", "x/x" or "x/x/x" and return the range of
628 // ports spanning from the first to the last. The switch software makes it
629 // easier to perform, because "ethe x/x/x to y/y/y" ranges never cross
630 // unit/slot boundary (every index except the last remains constant).
631 function fdry5GenPortRange ($from, $to)
634 if (1 !== preg_match ('@^([[:digit:]]+/)?([[:digit:]]+/)?([[:digit:]]+)$@', $from, $matches))
636 $prefix = 'e' . $matches[1] . $matches[2];
637 $from_idx = $matches[3];
638 if (1 !== preg_match ('@^([[:digit:]]+/)?([[:digit:]]+/)?([[:digit:]]+)$@', $to, $matches))
640 $to_idx = $matches[3];
641 for ($i = $from_idx; $i <= $to_idx; $i++
)
642 $ret[] = $prefix . $i;
646 # Produce a list of interfaces from a string in the following format:
647 # gi0/1-5,gi0/7,gi0/9-11,gi0/13,gi0/15,gi0/24
648 function ros11ParsePortString ($string)
651 foreach (explode (',', $string) as $item)
652 if (preg_match ('#^[a-z]+\d+/\d+$#', $item)) # a single interface
654 elseif (preg_match ('#^([a-z]+\d+/)(\d+)-(\d+)$#', $item, $matches)) # a range
656 # Produce a list of interfaces from the given base interface
657 # name and upper index.
658 if ($matches[3] <= $matches[2])
659 throw new InvalidArgException ('string', $string, "format error in '${item}'");
660 for ($i = $matches[2]; $i <= $matches[3]; $i++
)
661 $ret[] = "${matches[1]}{$i}";
664 throw new InvalidArgException ('string', $string, "format error in '${item}'");
668 // an implementation for Huawei syntax
669 function vrp53ReadVLANConfig ($input)
673 'vlanlist' => array(),
674 'portdata' => array(),
675 'portconfig' => array(),
678 $nextfunc = 'vrp53-get8021q-top';
679 foreach (explode ("\n", $input) as $line)
680 $nextfunc = $breedfunc[$nextfunc] ($ret, $line);
684 function vrp53ScanTopLevel (&$work, $line)
689 case (preg_match ('@^ vlan batch (.+)$@', $line, $matches)):
690 foreach (vrp53ParseVLANString ($matches[1]) as $vlan_id)
691 $work['vlanlist'][] = $vlan_id;
692 return 'vrp53-get8021q-top';
693 case (preg_match ('@^interface ((Ethernet|GigabitEthernet|XGigabitEthernet|Eth-Trunk)([[:digit:]]+(/[[:digit:]]+)*))$@', $line, $matches)):
694 $port_name = shortenIfName ($matches[1]);
695 $work['current'] = array ('port_name' => $port_name);
696 $work['portconfig'][$port_name][] = array ('type' => 'line-header', 'line' => $line);
697 return 'vrp53-get8021q-readport';
699 return 'vrp53-get8021q-top';
703 # Produce a list of integers from a string in the following format:
704 # A B C to D E F to G H to I J to K L ...
705 function vrp53ParseVLANString ($string)
707 $string = preg_replace ('/ to /', '-', $string);
708 $string = preg_replace ('/ /', ',', $string);
709 return iosParseVLANString ($string);
712 function vrp53PickInterfaceSubcommand (&$work, $line)
714 $port_name = $work['current']['port_name'];
715 if ($line[0] == '#') // end of interface section
717 $work['portconfig'][$port_name][] = array ('type' => 'line-header', 'line' => $line);
718 // Configuration Guide - Ethernet 3.3.4:
719 // "By default, the interface type is hybrid."
720 if (!array_key_exists ('link-type', $work['current']))
721 $work['current']['link-type'] = 'hybrid';
722 if (!array_key_exists ('allowed', $work['current']))
723 $work['current']['allowed'] = array();
724 if (!array_key_exists ('native', $work['current']))
725 $work['current']['native'] = 0;
726 switch ($work['current']['link-type'])
729 // VRP does not assign access ports to VLAN1 by default,
730 // leaving them blocked.
731 $work['portdata'][$port_name] =
732 $work['current']['native'] ?
array
734 'allowed' => $work['current']['allowed'],
735 'native' => $work['current']['native'],
740 'allowed' => array(),
745 $work['portdata'][$port_name] = array
747 'allowed' => $work['current']['allowed'],
753 $work['portdata'][$port_name] = array
755 'allowed' => $work['current']['allowed'],
756 'native' => in_array ($work['current']['native'], $work['current']['allowed']) ?
$work['current']['native'] : 0,
761 default: // dot1q-tunnel ?
763 unset ($work['current']);
764 return 'vrp53-get8021q-top';
767 $line_class = 'line-8021q';
770 case (preg_match ('@^ port default vlan ([[:digit:]]+)$@', $line, $matches)):
771 $work['current']['native'] = $matches[1];
772 if (!array_key_exists ('allowed', $work['current']))
773 $work['current']['allowed'] = array();
775 case (preg_match ('@^ port link-type (.+)$@', $line, $matches)):
776 $work['current']['link-type'] = $matches[1];
778 case (preg_match ('@^ port trunk allow-pass vlan (.+)$@', $line, $matches)):
779 if (!array_key_exists ('allowed', $work['current']))
780 $work['current']['allowed'] = array();
781 foreach (vrp53ParseVLANString ($matches[1]) as $vlan_id)
782 if (!in_array ($vlan_id, $work['current']['allowed']))
783 $work['current']['allowed'][] = $vlan_id;
785 case preg_match ('/^\s*eth-trunk \d+/', $line):
786 $work['current']['link-type'] = 'SKIP';
789 $line_class = 'line-other';
791 $work['portconfig'][$port_name][] = array('type' => $line_class, 'line' => $line);
792 return 'vrp53-get8021q-readport';
795 function vrp55Read8021QConfig ($input)
799 'vlanlist' => array (1), // VRP 5.50 hides VLAN1 from config text
800 'portdata' => array(),
801 'portconfig' => array(),
803 foreach (explode ("\n", $input) as $line)
807 if (!array_key_exists ('current', $ret))
811 case (preg_match ('@^ vlan batch (.+)$@', $line, $matches)):
812 foreach (vrp53ParseVLANString ($matches[1]) as $vlan_id)
813 $ret['vlanlist'][] = $vlan_id;
815 case (preg_match ('@^interface ((Ethernet|GigabitEthernet|XGigabitEthernet|Eth-Trunk)([[:digit:]]+(/[[:digit:]]+)*))$@', $line, $matches)):
816 $port_name = shortenIfName ($matches[1]);
817 $ret['current'] = array
819 'port_name' => $port_name,
820 'allowed' => array (VLAN_DFL_ID
),
821 'native' => VLAN_DFL_ID
,
823 $ret['portconfig'][$port_name][] = array ('type' => 'line-header', 'line' => $line);
828 $port_name = $ret['current']['port_name'];
829 // inside an interface block
830 $line_class = 'line-8021q';
833 case preg_match ('/^ port (link-type )?hybrid /', $line):
834 throw new RTGatewayError ("unsupported hybrid link-type for $port_name: ${line}");
835 case preg_match ('/^ port link-type (.+)$/', $line, $matches):
836 $ret['current']['link-type'] = $matches[1];
838 // Native VLAN is configured differently for each link-type case, but
839 // VRP is known to filter off clauses that don't make sense for
840 // current link-type. This way any interface section should contain
841 // only one kind of "set native" clause (but if this constraint breaks,
842 // we get a problem).
843 case preg_match ('/^ port (default|trunk pvid) vlan ([[:digit:]]+)$/', $line, $matches):
844 $ret['current']['native'] = $matches[2];
846 case preg_match ('/^ port trunk allow-pass vlan (.+)$/', $line, $matches):
847 foreach (vrp53ParseVLANString ($matches[1]) as $vlan_id)
848 if (!in_array ($vlan_id, $ret['current']['allowed']))
849 $ret['current']['allowed'][] = $vlan_id;
851 case preg_match ('/^ undo port trunk allow-pass vlan (.+)$/', $line, $matches):
852 $ret['current']['allowed'] = array_diff ($ret['current']['allowed'], vrp53ParseVLANString ($matches[1]));
854 case $line == ' undo portswitch':
855 case preg_match ('/^ ip address /', $line):
856 case preg_match ('/^ service type /', $line):
857 $ret['current']['link-type'] = 'IP';
859 case preg_match ('/^ eth-trunk /', $line):
860 $ret['current']['link-type'] = 'SKIP';
862 case substr ($line, 0, 1) == '#': // end of interface section
863 $line_class = 'line-header';
864 if (!array_key_exists ('link-type', $ret['current']))
865 $ret['current']['link-type'] = 'hybrid';
866 switch ($ret['current']['link-type'])
869 // In VRP 5.50 an access port has default VLAN ID == 1
870 $ret['portdata'][$port_name] =
871 $ret['current']['native'] ?
array
874 'allowed' => array ($ret['current']['native']),
875 'native' => $ret['current']['native'],
879 'allowed' => array (VLAN_DFL_ID
),
880 'native' => VLAN_DFL_ID
,
884 $ret['portdata'][$port_name] = array
887 'allowed' => $ret['current']['allowed'],
888 'native' => in_array ($ret['current']['native'], $ret['current']['allowed']) ?
$ret['current']['native'] : 0,
894 case 'hybrid': // hybrid ports are not supported
895 default: // dot1q-tunnel ?
896 $ret['portdata'][$port_name] = array
899 'allowed' => array(),
904 unset ($ret['current']);
907 $line_class = 'line-other';
909 $ret['portconfig'][$port_name][] = array ('type' => $line_class, 'line' => $line);
914 function vrp85Read8021QConfig ($input)
918 'vlanlist' => array(),
919 'portdata' => array(),
920 'portconfig' => array(),
925 foreach (explode ("\n", $input) as $line)
927 $line = rtrim ($line);
931 if (preg_match ('/^VLAN ID: (.*)/', $line, $m))
933 $current['vlanlist'] = ' ' . $m[1];
934 $state = 'vlans-nextline';
936 elseif (preg_match('/^-+$/', $line))
938 // commit $current into vlanlist
939 $range = preg_replace ('/\s+to\s+/', '-', $current['vlanlist']);
940 $range = trim (preg_replace('/\s+/', ',', $range), ',-');
941 $ret['vlanlist'] = $range == '' ?
array() : iosParseVLANString ($range);
947 case 'vlans-nextline':
948 if (preg_match('/^\s+(\d.*)/', $line, $m))
949 $current['vlanlist'] .= ' ' . $m[1];
957 if (isset ($current['name']))
959 if (preg_match('/^\s+(\d.*)/', $line, $m))
960 $current['allowed'] .= ' ' . $m[1];
963 // port-channel members are displayed in 'display port vlan' with PVID = 0.
964 if ($current['native'] >= VLAN_MIN_ID
&& $current['native'] <= VLAN_MAX_ID
)
966 // commit $current into portdata
969 'mode' => $current['mode'],
970 'native' => $current['native'],
971 'allowed' => array(),
973 $range = trim (preg_replace('/\s+/', ',', $current['allowed']), ',-');
974 $data['allowed'] = $range == '' ?
array() : iosParseVLANString ($range);
975 if ($data['mode'] == 'access')
976 $data['allowed'] = array ($current['native']);
977 elseif ($data['mode'] == 'trunk')
979 if (! in_array ($data['native'], $data['allowed']))
984 $data['allowed'] = array();
987 $ret['portdata'][$current['name']] = $data;
992 if (preg_match ('/^</', $line))
994 elseif (preg_match ('/^(\S+)\s+(\w+)\s+(\d+)\s+(.*)$/', $line, $m))
996 $current['name'] = shortenIfName ($m[1]);
997 $current['mode'] = ($m[2] == 'access' ||
$m[2] == 'trunk') ?
$m[2] : 'none';
998 $current['native'] = intval ($m[3]);
999 $current['allowed'] = $m[4];
1003 if (preg_match ('/^interface (\S+)$/', $line, $m))
1005 $current['name'] = shortenIfName ($m[1]);
1006 $current['lines'] = array (array ('type' => 'line-header', 'line' => $line));
1011 $line_class = ($line == '#') ?
'line-header' : 'line-other';
1012 if (preg_match ('/^\s*port (trunk|link-type|default vlan)/', $line))
1013 $line_class = 'line-8021q';
1014 $current['lines'][] = array ('type' => $line_class, 'line' => $line);
1017 // commit $current into portconfig
1018 $ret['portconfig'][$current['name']] = $current['lines'];
1024 throw new RackTablesError ("Unknown FSM state '$state'", RackTablesError
::INTERNAL
);
1033 D-Link VLAN info sample:
1034 ========================
1035 VID : 72 VLAN Name : v72
1036 VLAN Type : Static Advertisement : Disabled
1037 Member Ports : 1-16,25-28
1038 Static Ports : 1-16,25-28
1039 Current Tagged Ports : 25-28
1040 Current Untagged Ports : 1-16
1041 Static Tagged Ports : 25-28
1042 Static Untagged Ports : 1-16
1045 function dlinkReadVLANConfig ($input)
1049 'vlanlist' => array(),
1050 'portdata' => array(),
1053 $nextfunc = 'dlink-get8021q-top';
1054 foreach (explode ("\n", $input) as $line)
1055 $nextfunc = $breedfunc[$nextfunc] ($ret, $line);
1059 function dlinkScanTopLevel (&$work, $line)
1064 case preg_match ('@^\s*VID\s*:\s*(\d+)\s+.*name\s*:\s*(.+)$@i', $line, $matches):
1065 $work['current'] = array
1067 'vlan_id' => $matches[1],
1068 'vlan_name' => $matches[2],
1069 'tagged_ports' => '',
1070 'untagged_ports' => '',
1072 return 'dlink-get8021q-pickvlan';
1074 return 'dlink-get8021q-top';
1078 function dlinkPickVLANCommand (&$work, $line)
1082 case preg_match ('@END OF VLAN LIST@', $line):
1083 case trim ($line) === '':
1084 if (!isset($work['current']))
1086 $work['vlanlist'][] = $work['current']['vlan_id'];
1087 # portlist = range[,range..]
1089 foreach (iosParseVLANString ($work['current']['tagged_ports']) as $port_name)
1090 dlinkStorePortInfo ($work, $port_name, 'trunk', 'trunk');
1091 foreach (iosParseVLANString ($work['current']['untagged_ports']) as $port_name)
1092 dlinkStorePortInfo ($work, $port_name, 'access');
1093 unset ($work['current']);
1094 return 'dlink-get8021q-top';
1095 case preg_match ('@current tagged ports\s*:\s*([[:digit:]]+.*)$@i', $line, $matches):
1096 $work['current']['tagged_ports'] = $matches[1];
1098 case preg_match ('@current untagged ports\s*:\s*([[:digit:]]+.*)$@i', $line, $matches):
1099 $work['current']['untagged_ports'] = $matches[1];
1102 return 'dlink-get8021q-pickvlan';
1105 function dlinkStorePortInfo (&$work, $port_name, $new_mode, $overwrite_mode = '')
1107 if (! array_key_exists ($port_name, $work['portdata']))
1109 $work['portdata'][$port_name] = array
1111 'mode' => $new_mode,
1112 'allowed' => array ($work['current']['vlan_id']),
1113 'native' => $work['current']['vlan_id']
1117 $work['portdata'][$port_name]['allowed'][] = $work['current']['vlan_id'];
1118 if ($overwrite_mode !== '')
1119 $work['portdata'][$port_name]['mode'] = $overwrite_mode;
1122 function linuxReadVLANConfig ($input)
1126 'vlanlist' => array (VLAN_DFL_ID
),
1127 'portdata' => array(),
1129 foreach (explode ("\n", $input) as $line)
1131 // 13: vlan11@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP \ link/ether 00:1e:34:ae:75:21 brd ff:ff:ff:ff:ff:ff
1133 if (! preg_match ('/^[[:digit:]]+:\s+([^\s]+):\s.*\slink\/ether\s/', $line, $matches))
1135 $iface = $matches[1];
1136 if (preg_match ('/^(eth[[:digit:]]+)\.0*([[:digit:]]+):?$/', $iface, $matches))
1137 linuxStoreVLANInfo ($ret, 'vlan'.$matches[2], $matches[1], $matches[2]);
1138 elseif (preg_match('/^vlan0*([[:digit:]]+)\@(.*)$/', $iface, $matches))
1139 linuxStoreVLANInfo ($ret, 'vlan'.$matches[1], $matches[2], $matches[1]);
1140 elseif (! array_key_exists ($iface, $ret['portdata']))
1141 $ret['portdata'][$iface] = array ('mode' => 'access', 'native' => 0, 'allowed' => array());
1146 function linuxStoreVLANInfo (&$ret, $iface, $baseport, $vid)
1148 $ret['vlanlist'][] = $vid;
1149 if (! array_key_exists ($baseport, $ret['portdata']))
1150 $ret['portdata'][$baseport] = array ('mode' => 'trunk', 'native' => 0, 'allowed' => array ($vid));
1153 $ret['portdata'][$baseport]['mode'] = 'trunk';
1154 $ret['portdata'][$baseport]['allowed'][] = $vid;
1156 if (! array_key_exists ($iface, $ret['portdata']))
1157 $ret['portdata'][$iface] = array ('mode' => 'access', 'native' => $vid, 'allowed' => array ($vid));
1160 // most of the commands are compatible with IOS12, so are generated by ios12TranslatePushQueue
1161 // Only Nexus-specific commands are generated here (eg., lldp)
1162 function nxos4TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1166 foreach ($queue as $cmd)
1167 switch ($cmd['opcode'])
1170 if ($cmd['arg2'] == 'trunk')
1172 // some NX-OS platforms ask for confirmation if user tries to
1173 // overwrite allowed vlan list. Hence, we need to use
1174 // the differentiative remove syntax here
1175 $ret .= "interface ${cmd['arg1']}\n";
1176 $ret .= "switchport trunk encapsulation dot1q\n";
1177 $ret .= "switchport mode ${cmd['arg2']}\n";
1178 $ret .= "no switchport trunk native vlan\n";
1179 $ret .= "switchport trunk allowed vlan remove 1-4094\n";
1184 $ret .= ios12TranslatePushQueue ($dummy_object_id, array ($cmd), $dummy_vlan_names);
1190 // Get a list of VLAN management pseudo-commands and return a text
1191 // of real vendor-specific commands, which implement the work.
1192 // This work is done in two rounds:
1193 // 1. For "add allowed" and "rem allowed" commands detect continuous
1194 // sequences of VLAN IDs and replace them with ranges of form "A-B",
1196 // 2. Iterate over the resulting list and produce real CLI commands.
1197 function ios12TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1200 foreach ($queue as $cmd)
1201 switch ($cmd['opcode'])
1204 $ret .= "vlan ${cmd['arg1']}\nexit\n";
1206 case 'destroy VLAN':
1207 $ret .= "no vlan ${cmd['arg1']}\n";
1211 $clause = $cmd['opcode'] == 'add allowed' ?
'add' : 'remove';
1212 $ret .= "interface ${cmd['port']}\n";
1213 foreach (listToRanges ($cmd['vlans']) as $range)
1214 $ret .= "switchport trunk allowed vlan ${clause} " .
1215 ($range['from'] == $range['to'] ?
$range['to'] : "${range['from']}-${range['to']}") .
1220 $ret .= "interface ${cmd['arg1']}\nswitchport trunk native vlan ${cmd['arg2']}\nexit\n";
1222 case 'unset native':
1223 $ret .= "interface ${cmd['arg1']}\nno switchport trunk native vlan ${cmd['arg2']}\nexit\n";
1226 $ret .= "interface ${cmd['arg1']}\nswitchport access vlan ${cmd['arg2']}\nexit\n";
1228 case 'unset access':
1229 $ret .= "interface ${cmd['arg1']}\nno switchport access vlan\nexit\n";
1232 $ret .= "interface ${cmd['arg1']}\n";
1233 if ($cmd['arg2'] == 'trunk')
1234 $ret .= "switchport trunk encapsulation dot1q\n";
1235 $ret .= "switchport mode ${cmd['arg2']}\n";
1236 if ($cmd['arg2'] == 'trunk')
1237 $ret .= "no switchport trunk native vlan\nswitchport trunk allowed vlan none\n";
1240 case 'begin configuration':
1241 $ret .= "configure terminal\n";
1243 case 'end configuration':
1246 case 'save configuration':
1247 $ret .= "copy running-config startup-config\n\n";
1250 $ret .= $cmd['arg1'];
1255 'show interface switchport | incl Name:|Switchport:
1256 ! END OF SWITCHPORTS
1263 case 'getcdpstatus':
1264 $ret .= "show cdp neighbors detail\n";
1266 case 'getlldpstatus':
1267 $ret .= "show lldp neighbors\n";
1269 case 'getportstatus':
1270 $ret .= "show int status\n";
1273 $ret .= "show mac address-table dynamic\n";
1276 $ret .= "show running-config\n";
1279 throw new InvalidArgException ('opcode', $cmd['opcode']);
1284 function fdry5TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1287 foreach ($queue as $cmd)
1288 switch ($cmd['opcode'])
1291 $ret .= "vlan ${cmd['arg1']}\nexit\n";
1293 case 'destroy VLAN':
1294 $ret .= "no vlan ${cmd['arg1']}\n";
1297 foreach ($cmd['vlans'] as $vlan_id)
1298 $ret .= "vlan ${vlan_id}\ntagged ${cmd['port']}\nexit\n";
1301 foreach ($cmd['vlans'] as $vlan_id)
1302 $ret .= "vlan ${vlan_id}\nno tagged ${cmd['port']}\nexit\n";
1305 $ret .= "interface ${cmd['arg1']}\ndual-mode ${cmd['arg2']}\nexit\n";
1307 case 'unset native':
1308 $ret .= "interface ${cmd['arg1']}\nno dual-mode ${cmd['arg2']}\nexit\n";
1311 $ret .= "vlan ${cmd['arg2']}\nuntagged ${cmd['arg1']}\nexit\n";
1313 case 'unset access':
1314 $ret .= "vlan ${cmd['arg2']}\nno untagged ${cmd['arg1']}\nexit\n";
1316 case 'set mode': // NOP
1318 case 'begin configuration':
1321 case 'end configuration':
1324 case 'save configuration':
1325 $ret .= "write memory\n";
1328 $ret .= $cmd['arg1'];
1332 $ret .= "show running-config\n";
1335 $ret .= "show running-config\n";
1338 throw new InvalidArgException ('opcode', $cmd['opcode']);
1343 function vrp53TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1346 foreach ($queue as $cmd)
1347 switch ($cmd['opcode'])
1350 $ret .= "vlan ${cmd['arg1']}\nquit\n";
1352 case 'destroy VLAN':
1353 $ret .= "undo vlan ${cmd['arg1']}\n";
1357 $clause = $cmd['opcode'] == 'add allowed' ?
'' : 'undo ';
1358 $ret .= "interface ${cmd['port']}\n";
1359 foreach (listToRanges ($cmd['vlans']) as $range)
1360 $ret .= "${clause}port trunk allow-pass vlan " .
1361 ($range['from'] == $range['to'] ?
$range['to'] : "${range['from']} to ${range['to']}") .
1367 $ret .= "interface ${cmd['arg1']}\nport default vlan ${cmd['arg2']}\nquit\n";
1369 case 'unset native':
1370 case 'unset access':
1371 $ret .= "interface ${cmd['arg1']}\nundo port default vlan\nquit\n";
1374 $modemap = array ('access' => 'access', 'trunk' => 'hybrid');
1375 $ret .= "interface ${cmd['arg1']}\nport link-type " . $modemap[$cmd['arg2']] . "\n";
1376 if ($cmd['arg2'] == 'hybrid')
1377 $ret .= "undo port default vlan\nundo port trunk allow-pass vlan all\n";
1380 case 'begin configuration':
1381 $ret .= "system-view\n";
1383 case 'end configuration':
1386 case 'save configuration':
1387 $ret .= "save\nY\n";
1390 $ret .= $cmd['arg1'];
1394 $ret .= "display current-configuration\n";
1396 case 'getlldpstatus':
1397 $ret .= "display lldp neighbor\n";
1399 case 'getportstatus':
1400 $ret .= "display interface brief\n";
1403 $ret .= "display mac-address dynamic\n";
1406 $ret .= "display current-configuration\n";
1409 throw new InvalidArgException ('opcode', $cmd['opcode']);
1414 function vrp55TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1417 foreach ($queue as $cmd)
1418 switch ($cmd['opcode'])
1421 if ($cmd['arg1'] != 1)
1422 $ret .= "vlan ${cmd['arg1']}\nquit\n";
1424 case 'destroy VLAN':
1425 if ($cmd['arg1'] != 1)
1426 $ret .= "undo vlan ${cmd['arg1']}\n";
1430 $undo = $cmd['opcode'] == 'add allowed' ?
'' : 'undo ';
1431 $ret .= "interface ${cmd['port']}\n";
1432 foreach (listToRanges ($cmd['vlans']) as $range)
1433 $ret .= "${undo}port trunk allow-pass vlan " .
1434 ($range['from'] == $range['to'] ?
$range['to'] : "${range['from']} to ${range['to']}") .
1439 $ret .= "interface ${cmd['arg1']}\nport trunk pvid vlan ${cmd['arg2']}\nquit\n";
1442 $ret .= "interface ${cmd['arg1']}\nport default vlan ${cmd['arg2']}\nquit\n";
1444 case 'unset native':
1445 $ret .= "interface ${cmd['arg1']}\nundo port trunk pvid vlan\nquit\n";
1447 case 'unset access':
1448 $ret .= "interface ${cmd['arg1']}\nundo port default vlan\nquit\n";
1451 // VRP 5.50's meaning of "trunk" is much like the one of IOS
1452 // (unlike the way VRP 5.30 defines "trunk" and "hybrid"),
1453 // but it is necessary to undo configured VLANs on a port
1454 // for mode change command to succeed.
1457 'access' => "undo port trunk allow-pass vlan all\n" .
1458 "port trunk allow-pass vlan 1\n" .
1459 "undo port trunk pvid vlan\n",
1460 'trunk' => "undo port default vlan\n",
1465 'trunk' => "undo port trunk allow-pass vlan 1\n",
1467 $ret .= "interface ${cmd['arg1']}\n";
1468 $ret .= $before[$cmd['arg2']];
1469 $ret .= "port link-type ${cmd['arg2']}\n";
1470 $ret .= $after[$cmd['arg2']];
1473 case 'begin configuration':
1474 $ret .= "system-view\n";
1476 case 'end configuration':
1479 case 'save configuration':
1480 $ret .= "save\nY\n";
1483 $ret .= $cmd['arg1'];
1487 $ret .= "display current-configuration\n";
1489 case 'getlldpstatus':
1490 $ret .= "display lldp neighbor\n";
1492 case 'getportstatus':
1493 $ret .= "display interface brief\n";
1496 $ret .= "display mac-address dynamic\n";
1499 $ret .= "display current-configuration\n";
1502 throw new InvalidArgException ('opcode', $cmd['opcode']);
1507 function vrp85TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1510 foreach ($queue as $cmd)
1511 switch ($cmd['opcode'])
1514 if ($cmd['arg1'] != 1)
1515 $ret .= "vlan ${cmd['arg1']}\nquit\n";
1517 case 'destroy VLAN':
1518 if ($cmd['arg1'] != 1)
1519 $ret .= "undo vlan ${cmd['arg1']}\n";
1523 $undo = $cmd['opcode'] == 'add allowed' ?
'' : 'undo ';
1524 $ret .= "interface ${cmd['port']}\n";
1525 foreach (listToRanges ($cmd['vlans']) as $range)
1526 $ret .= "${undo}port trunk allow-pass vlan " .
1527 ($range['from'] == $range['to'] ?
$range['to'] : "${range['from']} to ${range['to']}") .
1532 $ret .= "interface ${cmd['arg1']}\nport trunk pvid vlan ${cmd['arg2']}\nquit\n";
1535 $ret .= "interface ${cmd['arg1']}\nport default vlan ${cmd['arg2']}\nquit\n";
1537 case 'unset native':
1538 $ret .= "interface ${cmd['arg1']}\nundo port trunk pvid vlan\nquit\n";
1540 case 'unset access':
1541 $ret .= "interface ${cmd['arg1']}\nundo port default vlan\nquit\n";
1544 // VRP 5.50's meaning of "trunk" is much like the one of IOS
1545 // (unlike the way VRP 5.30 defines "trunk" and "hybrid"),
1546 // but it is necessary to undo configured VLANs on a port
1547 // for mode change command to succeed.
1550 'access' => "undo port trunk allow-pass vlan all\n" .
1551 "port trunk allow-pass vlan 1\n" .
1552 "undo port trunk pvid vlan\n",
1553 'trunk' => "undo port default vlan\n",
1558 'trunk' => "undo port trunk allow-pass vlan 1\n",
1560 $ret .= "interface ${cmd['arg1']}\n";
1561 $ret .= $before[$cmd['arg2']];
1562 $ret .= "port link-type ${cmd['arg2']}\n";
1563 $ret .= $after[$cmd['arg2']];
1566 case 'begin configuration':
1567 $ret .= "system-view immediately\n";
1569 case 'end configuration':
1572 case 'save configuration':
1573 $ret .= "save\nY\n";
1576 $ret .= $cmd['arg1'];
1580 $ret .= "display vlan summary\n";
1581 $ret .= "display port vlan\n";
1582 $ret .= "display current-configuration\n";
1584 case 'getlldpstatus':
1585 $ret .= "display lldp neighbor\n";
1587 case 'getportstatus':
1588 $ret .= "display interface brief\n";
1591 $ret .= "display mac-address dynamic\n";
1594 $ret .= "display current-configuration\n";
1597 throw new InvalidArgException ('opcode', $cmd['opcode']);
1602 function xos12TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1605 foreach ($queue as $cmd)
1606 switch ($cmd['opcode'])
1609 $ret .= "create vlan VLAN${cmd['arg1']}\n";
1610 $ret .= "configure vlan VLAN${cmd['arg1']} tag ${cmd['arg1']}\n";
1612 case 'destroy VLAN':
1613 $ret .= "delete vlan VLAN${cmd['arg1']}\n";
1616 foreach ($cmd['vlans'] as $vlan_id)
1618 $vlan_name = $vlan_id == 1 ?
'Default' : "VLAN${vlan_id}";
1619 $ret .= "configure vlan ${vlan_name} add ports ${cmd['port']} tagged\n";
1623 foreach ($cmd['vlans'] as $vlan_id)
1625 $vlan_name = $vlan_id == 1 ?
'Default' : "VLAN${vlan_id}";
1626 $ret .= "configure vlan ${vlan_name} delete ports ${cmd['port']}\n";
1630 $vlan_name = $cmd['arg2'] == 1 ?
'Default' : "VLAN${cmd['arg2']}";
1631 $ret .= "configure vlan ${vlan_name} delete ports ${cmd['arg1']}\n";
1632 $ret .= "configure vlan ${vlan_name} add ports ${cmd['arg1']} untagged\n";
1634 case 'unset native':
1635 $vlan_name = $cmd['arg2'] == 1 ?
'Default' : "VLAN${cmd['arg2']}";
1636 $ret .= "configure vlan ${vlan_name} delete ports ${cmd['arg1']}\n";
1637 $ret .= "configure vlan ${vlan_name} add ports ${cmd['arg1']} tagged\n";
1640 $vlan_name = $cmd['arg2'] == 1 ?
'Default' : "VLAN${cmd['arg2']}";
1641 $ret .= "configure vlan ${vlan_name} add ports ${cmd['arg1']} untagged\n";
1643 case 'unset access':
1644 $vlan_name = $cmd['arg2'] == 1 ?
'Default' : "VLAN${cmd['arg2']}";
1645 $ret .= "configure vlan ${vlan_name} delete ports ${cmd['arg1']}\n";
1648 case 'begin configuration':
1649 case 'end configuration':
1651 case 'save configuration':
1652 $ret .= "save configuration\ny\n";
1655 $ret .= $cmd['arg1'];
1659 $ret .= 'show configuration "vlan"' . "\n";
1661 case 'getlldpstatus':
1662 $ret .= "show lldp neighbors detailed\n";
1665 $ret .= "show configuration\n";
1668 throw new InvalidArgException ('opcode', $cmd['opcode']);
1673 function jun10TranslatePushQueue ($dummy_object_id, $queue, $vlan_names)
1677 foreach ($queue as $cmd)
1678 switch ($cmd['opcode'])
1681 $ret .= "set vlans VLAN${cmd['arg1']} vlan-id ${cmd['arg1']}\n";
1683 case 'destroy VLAN':
1684 if (isset ($vlan_names[$cmd['arg1']]))
1685 $ret .= "delete vlans " . $vlan_names[$cmd['arg1']] . "\n";
1689 $del = ($cmd['opcode'] == 'rem allowed');
1690 $pre = ($del ?
'delete' : 'set') .
1691 " interfaces ${cmd['port']} unit 0 family ethernet-switching vlan members";
1692 if (count ($cmd['vlans']) > VLAN_MAX_ID
- VLAN_MIN_ID
)
1693 $ret .= "$pre " . ($del ?
'' : 'all') . "\n";
1695 while (! empty ($cmd['vlans']))
1697 $vlan = array_shift ($cmd['vlans']);
1698 $ret .= "$pre $vlan\n";
1699 if ($del and isset ($vlan_names[$vlan]))
1700 $ret .= "$pre ${vlan_names[$vlan]}\n";
1704 $ret .= "set interfaces ${cmd['arg1']} unit 0 family ethernet-switching native-vlan-id ${cmd['arg2']}\n";
1705 $pre = "delete interfaces ${cmd['arg1']} unit 0 family ethernet-switching vlan members";
1706 $vlan = $cmd['arg2'];
1707 $ret .= "$pre $vlan\n";
1708 if (isset ($vlan_names[$vlan]))
1709 $ret .= "$pre ${vlan_names[$vlan]}\n";
1711 case 'unset native':
1712 $ret .= "delete interfaces ${cmd['arg1']} unit 0 family ethernet-switching native-vlan-id\n";
1713 $pre = "interfaces ${cmd['arg1']} unit 0 family ethernet-switching vlan members";
1714 $vlan = $cmd['arg2'];
1715 if (isset ($vlan_names[$vlan]))
1716 $ret .= "delete $pre ${vlan_names[$vlan]}\n";
1717 $ret .= "set $pre $vlan\n";
1720 $ret .= "set interfaces ${cmd['arg1']} unit 0 family ethernet-switching vlan members ${cmd['arg2']}\n";
1722 case 'unset access':
1723 $ret .= "delete interfaces ${cmd['arg1']} unit 0 family ethernet-switching vlan members\n";
1726 $ret .= "set interfaces ${cmd['arg1']} unit 0 family ethernet-switching port-mode ${cmd['arg2']}\n";
1728 case 'begin configuration':
1729 $ret .= "configure exclusive\n";
1731 case 'end configuration':
1733 $ret .= "rollback 0\n"; // discard all changes if commit failed
1735 case 'save configuration':
1736 break; // JunOS can`t apply configuration without saving it
1738 $ret .= $cmd['arg1'];
1745 show configuration groups
1747 show configuration interfaces
1752 $ret .= "show configuration\n";
1754 case 'getlldpstatus':
1755 $ret .= "show lldp neighbors\n";
1756 $ret .= "# object_id=$dummy_object_id";
1759 throw new InvalidArgException ('opcode', $cmd['opcode']);
1764 function ftos8TranslatePushQueue ($dummy_object_id, $queue, $vlan_names)
1767 foreach ($queue as $cmd)
1768 switch ($cmd['opcode'])
1770 case 'begin configuration':
1771 $ret .= "configure terminal\n";
1773 case 'end configuration':
1776 case 'save configuration':
1777 $ret .= "write memory\n";
1780 $ret .= $cmd['arg1'];
1782 case 'getlldpstatus':
1783 $ret .= "show lldp neighbors detail\n";
1785 case 'getportstatus':
1786 $ret .= "show interfaces status\n";
1789 $ret .= "show mac-address-table dynamic\n";
1792 $ret .= "show running-config interface\n";
1795 $ret .= "int vlan ${cmd['arg1']}\nexit\n";
1797 case 'destroy VLAN':
1798 $ret .= "no int vlan ${cmd['arg1']}\n";
1801 while (! empty ($cmd['vlans']))
1803 $vlan = array_shift ($cmd['vlans']);
1804 $ret .= "int vlan $vlan\n";
1805 $ret .= "no tagged ${cmd['port']}\n";
1810 while (! empty ($cmd['vlans']))
1812 $vlan = array_shift ($cmd['vlans']);
1813 $ret .= "int vlan $vlan\n";
1814 $ret .= "tagged ${cmd['port']}\n";
1818 case 'unset native':
1819 $ret .= "int vlan ${cmd['arg2']}\n";
1820 $ret .= "no untagged ${cmd['arg1']}\n";
1821 $ret .= "tagged ${cmd['arg1']}\n";
1824 case 'unset access':
1825 $ret .= "int vlan ${cmd['arg2']}\n";
1826 $ret .= "no untagged ${cmd['arg1']}\n";
1830 $ret .= "int vlan ${cmd['arg2']}\n";
1831 $ret .= "no tagged ${cmd['arg1']}\n";
1832 $ret .= "untagged ${cmd['arg1']}\n";
1836 $ret .= "int vlan ${cmd['arg2']}\n";
1837 $ret .= "untagged ${cmd['arg1']}\n";
1843 $ret .= "show running-config\n";
1846 throw new InvalidArgException ('opcode', $cmd['opcode']);
1851 function air12TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1854 foreach ($queue as $cmd)
1855 switch ($cmd['opcode'])
1857 case 'begin configuration':
1858 $ret .= "configure terminal\n";
1860 case 'end configuration':
1863 case 'save configuration':
1864 $ret .= "copy running-config startup-config\n\n";
1867 $ret .= $cmd['arg1'];
1869 case 'getcdpstatus':
1870 $ret .= "show cdp neighbors detail\n";
1873 $ret .= "show running-config\n";
1876 throw new InvalidArgException ('opcode', $cmd['opcode']);
1881 function eos4TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1884 foreach ($queue as $cmd)
1885 switch ($cmd['opcode'])
1887 case 'begin configuration':
1888 $ret .= "enable\nconfigure terminal\n";
1890 case 'end configuration':
1893 case 'save configuration':
1894 $ret .= "copy running-config startup-config\n\n";
1897 $ret .= "vlan ${cmd['arg1']}\nexit\n";
1899 case 'destroy VLAN':
1900 if (isset ($vlan_names[$cmd['arg1']]))
1901 $ret .= "no vlan ${cmd['arg1']}\n";
1904 $ret .= "interface ${cmd['arg1']}\nswitchport access vlan ${cmd['arg2']}\nexit\n";
1906 case 'unset access':
1907 $ret .= "interface ${cmd['arg1']}\nno switchport access vlan\nexit\n";
1910 $ret .= "interface ${cmd['arg1']}\n";
1911 $ret .= "switchport mode ${cmd['arg2']}\n";
1912 if ($cmd['arg2'] == 'trunk')
1913 $ret .= "no switchport trunk native vlan\nswitchport trunk allowed vlan none\n";
1918 $clause = $cmd['opcode'] == 'add allowed' ?
'add' : 'remove';
1919 $ret .= "interface ${cmd['port']}\n";
1920 foreach (listToRanges ($cmd['vlans']) as $range)
1921 $ret .= "switchport trunk allowed vlan ${clause} " .
1922 ($range['from'] == $range['to'] ?
$range['to'] : "${range['from']}-${range['to']}") .
1927 $ret .= "interface ${cmd['arg1']}\nswitchport trunk native vlan ${cmd['arg2']}\nexit\n";
1929 case 'unset native':
1930 $ret .= "interface ${cmd['arg1']}\nswitchport trunk native vlan tag\nexit\n";
1932 case 'getlldpstatus':
1933 $ret .= "show lldp neighbors detail\n";
1935 case 'getportstatus':
1936 $ret .= "show interfaces status\n";
1939 $ret .= "show mac-address-table dynamic\n";
1942 $ret .= $cmd['arg1'];
1946 $ret .= "show running-config\n";
1949 throw new InvalidArgException ('opcode', $cmd['opcode']);
1954 function ros11TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1957 foreach ($queue as $cmd)
1958 switch ($cmd['opcode'])
1960 case 'begin configuration':
1961 $ret .= "configure terminal\n";
1963 case 'end configuration':
1966 case 'save configuration':
1967 $ret .= "copy running-config startup-config\nY\n";
1970 $ret .= "vlan database\nvlan ${cmd['arg1']}\nexit\n";
1972 case 'destroy VLAN':
1973 if (isset ($vlan_names[$cmd['arg1']]))
1974 $ret .= "vlan database\nno vlan ${cmd['arg1']}\nexit\n";
1977 $ret .= "interface ${cmd['arg1']}\nswitchport access vlan ${cmd['arg2']}\nexit\n";
1979 case 'unset access':
1980 $ret .= "interface ${cmd['arg1']}\nno switchport access vlan\nexit\n";
1983 $ret .= "interface ${cmd['arg1']}\n";
1984 $ret .= "switchport mode ${cmd['arg2']}\n";
1985 if ($cmd['arg2'] == 'trunk')
1986 $ret .= "no switchport trunk native vlan\nswitchport trunk allowed vlan remove all\n";
1991 $ret .= "interface ${cmd['port']}\n";
1992 # default VLAN special case
1993 $ordinary = array();
1994 foreach ($cmd['vlans'] as $vid)
1995 if ($vid == VLAN_DFL_ID
)
1996 $ret .= $cmd['opcode'] == 'add allowed' ?
1997 "no switchport forbidden default-vlan\nswitchport default-vlan tagged\n" :
1998 "switchport forbidden default-vlan\nno switchport default-vlan tagged\n";
2001 foreach (listToRanges ($ordinary) as $range)
2002 $ret .= 'switchport trunk allowed vlan ' .
2003 ($cmd['opcode'] == 'add allowed' ?
'add ' : 'remove ') .
2004 ($range['from'] == $range['to'] ?
$range['to'] : "${range['from']}-${range['to']}") .
2009 $ret .= "interface ${cmd['arg1']}\n";
2010 # default VLAN special case
2011 if ($cmd['arg2'] == VLAN_DFL_ID
)
2012 $ret .= "no switchport default-vlan tagged\n";
2014 $ret .= "switchport trunk native vlan ${cmd['arg2']}\n";
2017 case 'unset native':
2018 $ret .= "interface ${cmd['arg1']}\n";
2019 # default VLAN special case
2020 if ($cmd['arg2'] == VLAN_DFL_ID
)
2021 $ret .= "switchport default-vlan tagged\n";
2023 # Although a native VLAN is always one of the allowed VLANs in ROS (as seen in the
2024 # output of "show interfaces switchport"), the config text doesn't display the
2025 # native VLAN in the list of allowed VLANs. Respectively, setting the current
2026 # native VLAN as allowed leaves it allowed, but not native any more.
2027 $ret .= "switchport trunk allowed vlan add ${cmd['arg2']}\n";
2030 case 'getlldpstatus':
2031 $ret .= "show lldp neighbors detail\n";
2033 case 'getportstatus':
2034 $ret .= "show interfaces status\n";
2037 $ret .= "show mac address-table dynamic\n";
2040 $ret .= $cmd['arg1'];
2044 $ret .= "show running-config\n";
2047 throw new InvalidArgException ('opcode', $cmd['opcode']);
2052 function dlinkTranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
2055 foreach ($queue as $cmd)
2056 switch ($cmd['opcode'])
2058 case 'getportstatus':
2059 $ret .= "show ports\n";
2062 $ret .= "show fdb\n";
2065 $ret .= "show vlan\n";
2068 $ret .= $cmd['arg1'];
2071 throw new InvalidArgException ('opcode', $cmd['opcode']);
2076 function linuxTranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
2079 foreach ($queue as $cmd)
2080 switch ($cmd['opcode'])
2082 case 'getportstatus':
2083 $ret .= "cd /sys/class/net && for d in eth*; do sudo /sbin/ethtool \$d; done\n";
2086 $ret .= "sudo /usr/sbin/arp -an\n";
2089 $ret .= "sudo /sbin/ip -o a\n";
2092 $ret .= $cmd['arg1'];
2095 throw new InvalidArgException ('opcode', $cmd['opcode']);
2100 function xos12Read8021QConfig ($input)
2104 'vlanlist' => array (1),
2105 'portdata' => array(),
2106 'portconfig' => array(),
2108 foreach (explode ("\n", $input) as $line)
2113 case (preg_match ('/^create vlan "([[:alnum:]]+)"$/', $line, $matches)):
2114 if (!preg_match ('/^VLAN[[:digit:]]+$/', $matches[1]))
2115 throw new RTGatewayError ('unsupported VLAN name ' . $matches[1]);
2117 case (preg_match ('/^configure vlan ([[:alnum:]]+) tag ([[:digit:]]+)$/', $line, $matches)):
2118 if (strtolower ($matches[1]) == 'default')
2119 throw new RTGatewayError ('default VLAN tag must be 1');
2120 if ($matches[1] != 'VLAN' . $matches[2])
2121 throw new RTGatewayError ("VLAN name ${matches[1]} does not match its tag ${matches[2]}");
2122 $ret['vlanlist'][] = $matches[2];
2124 case (preg_match ('/^configure vlan ([[:alnum:]]+) add ports (.+) (tagged|untagged) */', $line, $matches)):
2125 $submatch = array();
2126 if ($matches[1] == 'Default')
2127 $matches[1] = 'VLAN1';
2128 if (!preg_match ('/^VLAN([[:digit:]]+)$/', $matches[1], $submatch))
2129 throw new RTGatewayError ('unsupported VLAN name ' . $matches[1]);
2130 $vlan_id = $submatch[1];
2131 foreach (iosParseVLANString ($matches[2]) as $port_name)
2133 if (!array_key_exists ($port_name, $ret['portdata']))
2134 $ret['portdata'][$port_name] = array
2137 'allowed' => array(),
2140 $ret['portdata'][$port_name]['allowed'][] = $vlan_id;
2141 if ($matches[3] == 'untagged')
2142 $ret['portdata'][$port_name]['native'] = $vlan_id;
2151 function jun10Read8021QConfig ($input)
2155 'vlanlist' => array (1),
2156 'vlannames' => array (1 => 'default'),
2157 'portdata' => array(),
2158 'portconfig' => array(),
2160 $lines = explode ("\n", $input);
2163 $vlans = array('default' => 1);
2165 while (count ($lines))
2167 $line = trim (array_shift ($lines));
2168 if (FALSE !== strpos ($line, '# END OF VLAN LIST'))
2170 if (preg_match ('/^VLAN: (.*), 802.1Q Tag: (\d+)/', $line, $m))
2172 $ret['vlannames'][$m[2]] = $m[1];
2173 $vlans[$m[1]] = $m[2];
2176 $ret['vlanlist'] = array_values ($vlans);
2178 // get config groups list - throw an exception if a group contains ether-switching config
2179 $current_group = NULL;
2180 while (count ($lines))
2182 $line = array_shift ($lines);
2183 if (FALSE !== strpos ($line, '# END OF GROUP LIST'))
2185 elseif (preg_match ('/^(\S+)(?:\s+{|;)$/', $line, $m))
2186 $current_group = $m[1];
2187 elseif (isset ($current_group) and preg_match ('/^\s*family ethernet-switching\b/', $line))
2188 throw new RTGatewayError ("Config-group '$current_group' contains switchport commands, which is not supported");
2191 // get interfaces config
2194 'is_range' => FALSE,
2195 'is_ethernet' => FALSE,
2200 while (count ($lines))
2202 $line = array_shift ($lines);
2203 $line_class = 'line-other';
2204 if (preg_match ('/# END OF CONFIG|^(interface-range )?(\S+)\s+{$/', $line, $m)) // line starts with interface name
2205 { // found interface section opening, or end-of-file
2206 if (isset ($current['name']) and $current['is_ethernet'])
2208 // add previous interface to the results
2209 if (! isset ($current['config']['mode']))
2210 $current['config']['mode'] = 'access';
2211 if (! isset ($current['config']['native']))
2212 $current['config']['native'] = $current['config']['native'] = 0;
2213 if (! isset ($current['config']['allowed']))
2215 if ($current['config']['mode'] == 'access')
2216 $current['config']['allowed'] = array (1);
2218 $current['config']['allowed'] = array();
2221 $current['config']['mode'] == 'trunk' and
2222 $current['config']['native'] != 0 and
2223 ! in_array ($current['config']['native'], $current['config']['allowed'])
2225 $current['config']['allowed'][] = $current['config']['native'];
2226 elseif ($current['config']['mode'] == 'access')
2227 $current['config']['native'] = $current['config']['allowed'][0];
2228 $ret['portdata'][$current['name']] = $current['config'];
2231 if (! empty ($m[2]))
2232 { // new interface section begins
2233 $current['is_ethernet'] = FALSE;
2234 $current['is_range'] = ! empty ($m[1]);
2235 $current['name'] = $m[2];
2236 $current['config'] = array (
2240 'config' => array(),
2242 $line_class = 'line-header';
2243 $current['indent'] = NULL;
2246 elseif (preg_match ('/^(\s+)family ethernet-switching\b/', $line, $m))
2248 if ($current['is_range'])
2249 throw new RTGatewayError ("interface-range '${current['name']}' contains switchport commands, which is not supported");
2250 $current['is_ethernet'] = TRUE;
2251 $current['indent'] = $m[1];
2253 elseif (isset ($current['indent']) and $line == $current['indent'] . '}')
2254 $current['indent'] = NULL;
2255 elseif ($current['is_ethernet'] and isset ($current['indent']))
2257 $line_class = 'line-8021q';
2258 if (preg_match ('/^\s+port-mode (trunk|access);/', $line, $m))
2259 $current['config']['mode'] = $m[1];
2260 elseif (preg_match ('/^\s+native-vlan-id (\d+);/', $line, $m))
2261 $current['config']['native'] = $m[1];
2262 elseif (preg_match ('/^\s+members \[?(.*)\]?;$/', $line, $m))
2265 foreach (explode (' ', $m[1]) as $item)
2267 $item = trim ($item);
2268 if (preg_match ('/^(\d+)(?:-(\d+))?$/', $item, $m))
2270 if (isset ($m[2]) and $m[2] > $m[1])
2271 $members = array_merge (range ($m[1], $m[2]), $members);
2275 elseif (isset ($vlans[$item]))
2276 $members[] = $vlans[$item];
2277 elseif ($item == 'all')
2278 $members = array_merge (range (VLAN_MIN_ID
, VLAN_MAX_ID
), $members);
2280 $current['config']['allowed'] = array_unique ($members);
2283 $line_class = 'line-other';
2285 if (isset ($current['name']))
2288 $line_class = 'line-header';
2289 $ret['portconfig'][$current['name']][] = array ('type' => $line_class, 'line' => $line);
2296 function ftos8Read8021QConfig ($input)
2300 'vlanlist' => array (),
2301 'vlannames' => array (),
2302 'portdata' => array(),
2303 'portconfig' => array(),
2306 foreach (explode ("\n", $input) as $line)
2308 if (preg_match ('/^interface (\S.*?)\s*$/', $line, $m))
2312 'name' => shortenIfName (str_replace (' ', '', $m[1])),
2314 'is_switched' => FALSE,
2315 'vlan' => 1 === preg_match ('/^Vlan (\d+)$/', $m[1], $m2) ?
$m2[1] : 0,
2320 $iface['lines'][] = array ('type' => 'line-other', 'line' => $line);
2322 if ($line == ' switchport')
2324 $iface['is_switched'] = TRUE;
2325 # In "no default-vlan disable" mode (active by default) FTOS monitors
2326 # switchport/VLAN configuration and once a port is removed from all
2327 # VLANs, the software assigns it to the default VLAN in access mode.
2328 # In this case every port is guaranteed to belong to at least one VLAN
2329 # and assuming "access" mode is a reasonable default, but see below.
2330 $ret['portdata'][$iface['name']] = array
2332 'allowed' => array (),
2337 elseif ($line == '!')
2339 $ret['portconfig'][$iface['name']] = $iface['lines'];
2342 elseif ($iface['vlan'])
2344 $ret['vlanlist'][] = $iface['vlan'];
2345 if (preg_match ('/^[ !](un)?tagged (\S+) (\S+)/', $line, $m))
2347 list ($untagged, $pref, $list) = array ($m[1], $m[2], $m[3]);
2348 if (preg_match ('#^(\d+/)#', $list, $m))
2351 $list = substr ($list, strlen ($m[1]));
2353 foreach (explode (',', $list) as $range)
2355 $constraints = explode ('-', $range);
2356 if (count ($constraints) == 1)
2357 $constraints[] = $constraints[0];
2358 if ($constraints[0] <= $constraints[1])
2359 for ($i = $constraints[0]; $i <= $constraints[1]; $i++
)
2361 $if_name = shortenIfName ($pref . $i);
2362 $ret['portdata'][$if_name]['allowed'][] = $iface['vlan'];
2364 $ret['portdata'][$if_name]['native'] = $iface['vlan'];
2366 $ret['portdata'][$if_name]['mode'] = 'trunk';
2373 # In "default-vlan disable" mode a port can be removed from all VLANs and
2374 # still remain a switchport without a bridge group. If that was the case,
2375 # this extra round makes sure all ports without allowed VLANs are "T" ports,
2376 # because pure "A" mode is defined illegal in RackTables 802.1Q data model.
2377 foreach (array_keys ($ret['portdata']) as $if_name)
2378 if (! count ($ret['portdata'][$if_name]['allowed']))
2379 $ret['portdata'][$if_name]['mode'] = 'trunk';
2383 function eos4BuildSwitchport ($mined)
2387 case ! array_key_exists ('mode', $mined):
2388 case $mined['mode'] == 'access':
2389 if (! array_key_exists ('access', $mined))
2390 $mined['access'] = VLAN_DFL_ID
;
2394 'allowed' => array ($mined['access']),
2395 'native' => $mined['access'],
2397 case $mined['mode'] == 'trunk':
2398 if (! array_key_exists ('native', $mined))
2399 $mined['native'] = ! array_key_exists ('allowed', $mined) ||
in_array (VLAN_DFL_ID
, $mined['allowed']) ? VLAN_DFL_ID
: 0;
2400 if (! array_key_exists ('allowed', $mined))
2401 $mined['allowed'] = range (VLAN_MIN_ID
, VLAN_MAX_ID
);
2405 'allowed' => $mined['allowed'],
2406 'native' => $mined['native'],
2408 case $mined['mode'] == 'none':
2412 'allowed' => array(),
2416 throw new RackTablesError ('malformed switchport data', RackTablesError
::INTERNAL
);
2420 function eos4Read8021QConfig ($input)
2424 'vlanlist' => array (VLAN_DFL_ID
),
2425 'vlannames' => array (),
2426 'portdata' => array(),
2427 'portconfig' => array(),
2429 foreach (explode ("\n", $input) as $line)
2432 if (! array_key_exists ('current', $ret))
2436 case preg_match ('/^vlan ([\d,-]+)$/', $line, $matches):
2437 foreach (iosParseVLANString ($matches[1]) as $vlan_id)
2438 if ($vlan_id != VLAN_DFL_ID
)
2439 $ret['vlanlist'][] = $vlan_id;
2441 case preg_match ('/^interface ((Ethernet|Port-Channel)\d+)$/', $line, $matches):
2442 $portname = shortenIfName ($matches[1]);
2443 $ret['current'] = array
2445 'port_name' => $portname,
2449 $ret['portconfig'][$portname][] = array ('type' => 'line-header', 'line' => $line);
2454 # $portname == $ret['current']['port_name']
2457 case $line == ' switchport mode dot1q-tunnel':
2458 throw new RTGatewayError ('unsupported switchport mode for port ' . $ret['current']['portname']);
2459 case $line == ' no switchport':
2460 $ret['current']['mode'] = 'none';
2461 $ret['portconfig'][$portname][] = array ('type' => 'line-other', 'line' => $line);
2463 case $line == ' switchport mode trunk':
2464 $ret['current']['mode'] = 'trunk';
2465 $ret['portconfig'][$portname][] = array ('type' => 'line-8021q', 'line' => $line);
2467 case $line == ' switchport trunk native vlan tag':
2468 $ret['current']['default1'] = FALSE;
2469 $ret['portconfig'][$portname][] = array ('type' => 'line-8021q', 'line' => $line);
2471 case preg_match ('/^ switchport trunk native vlan (\d+)$/', $line, $matches):
2472 $ret['current']['native'] = $matches[1];
2473 $ret['portconfig'][$portname][] = array ('type' => 'line-8021q', 'line' => $line);
2475 case $line == ' switchport trunk allowed vlan none':
2476 $ret['current']['allowed'] = array();
2477 $ret['portconfig'][$portname][] = array ('type' => 'line-8021q', 'line' => $line);
2479 case preg_match ('/^ switchport trunk allowed vlan (\S+)$/', $line, $matches):
2480 $ret['current']['allowed'] = iosParseVLANString ($matches[1]);
2481 $ret['portconfig'][$portname][] = array ('type' => 'line-8021q', 'line' => $line);
2483 case preg_match ('/^ switchport trunk allowed vlan add (\S+)$/', $line, $matches):
2484 $ret['current']['allowed'] = array_merge ($ret['current']['allowed'], iosParseVLANString ($matches[1]));
2485 $ret['portconfig'][$portname][] = array ('type' => 'line-8021q', 'line' => $line);
2487 case preg_match ('/^ switchport access vlan (\d+)$/', $line, $matches):
2488 $ret['current']['access'] = $matches[1];
2489 $ret['portconfig'][$portname][] = array ('type' => 'line-8021q', 'line' => $line);
2491 case $line == '!': # end of interface section
2492 if (! array_key_exists ('current', $ret))
2494 $ret['portdata'][$ret['current']['port_name']] = eos4BuildSwitchport ($ret['current']);
2495 unset ($ret['current']);
2496 $ret['portconfig'][$portname][] = array ('type' => 'line-header', 'line' => $line);
2499 $ret['portconfig'][$portname][] = array ('type' => 'line-other', 'line' => $line);
2503 unset ($ret['current']);
2507 # ROS 1.1 config file sytax is derived from that of IOS, but has a few configuration
2508 # traits regarding 802.1Q.
2510 # In IOS there is one "interface" section for each port with all 802.1Q configuration
2511 # maintained as text lines in the first place. These lines are eventually translated
2512 # into effective configuration of the port. E.g. access and trunk VLAN settings can
2513 # co-exist in IOS, it is switchport mode (set either statically or dynamically)
2514 # defining, which settings are used by the port. Likewise, it is possible to "assign"
2515 # any VLAN to any port regardless if the VLAN itself exists.
2517 # In ROS the configuration is maintained in port's effective switchport state in the
2518 # first place, making trunk and access settings mutually exclusive. A VLAN that does
2519 # not exist cannot be assigned to a port. Finally, typically there are multiple
2520 # "interface" sections in the configuration text referring to the same port. A single
2521 # section would typically configure a range of ports with a single configuration line
2523 # * switchport default-vlan tagged
2524 # * switchport forbidden default-vlan
2525 # * switchport mode trunk
2526 # * switchport trunk allowed vlan add (one "interface" section per each VLAN)
2527 # * switchport trunk native vlan (idem)
2528 # * switchport access vlan (idem)
2530 # ROS CLI allows configuring a port in access mode without an access VLAN. Such
2531 # configuration is not supported.
2532 function ros11Read8021QConfig ($input)
2536 'vlanlist' => array (VLAN_DFL_ID
),
2537 'portdata' => array(),
2538 'portconfig' => array(),
2540 $nextfunc = 'ros11-get8021q-scantop';
2542 foreach (explode ("\n", $input) as $line)
2543 $nextfunc = $breedfunc[$nextfunc] ($ret, $line);
2544 # process any clauses buffered by ros11Read8021QPorts()
2545 foreach ($ret['portdata'] as $portname => $port)
2547 if (! array_key_exists ('mode', $port))
2548 throw new RTGatewayError ("unsupported configuration of port ${portname}");
2551 ! array_key_exists ('switchport forbidden default-vlan', $port)
2552 && array_key_exists ('switchport default-vlan tagged', $port)
2554 $ret['portdata'][$portname]['allowed'][] = VLAN_DFL_ID
;
2557 ! $port['native'] # a configured native VLAN preempts untagged default VLAN
2558 && ! array_key_exists ('switchport forbidden default-vlan', $port)
2559 && ! array_key_exists ('switchport default-vlan tagged', $port)
2562 $ret['portdata'][$portname]['allowed'][] = VLAN_DFL_ID
;
2563 $ret['portdata'][$portname]['native'] = VLAN_DFL_ID
;
2565 foreach (array ('switchport forbidden default-vlan', 'switchport default-vlan tagged') as $line)
2566 if (array_key_exists ($line, $port))
2568 unset ($ret['portdata'][$portname][$line]);
2569 $work['portconfig'][$portname][] = array ('type' => 'line-8021q', 'line' => $line);
2575 function iosxr4TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
2578 foreach ($queue as $cmd)
2579 switch ($cmd['opcode'])
2582 $ret .= $cmd['arg1'];
2585 $ret .= "show running-config\n";
2587 case 'getlldpstatus':
2588 $ret .= "show lldp neighbors\n";
2591 throw new InvalidArgException ('opcode', $cmd['opcode']);
2596 function ucsTranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
2599 foreach ($queue as $cmd)
2600 switch ($cmd['opcode'])
2603 $ret .= $cmd['arg1'];
2605 case 'getinventory':
2609 throw new InvalidArgException ('opcode', $cmd['opcode']);
2614 function ros11Read8021QScanTop (&$work, $line)
2618 case $line == 'vlan database':
2619 return 'ros11-get8021q-vlandb';
2620 case 1 == preg_match ('@^interface\s+(range\s+)?([a-z0-9/,-]+)$@', $line, $m):
2621 $ports = ros11ParsePortString ($m[2]);
2622 $work['current'] = array ('config' => array(), 'ports' => $ports);
2623 foreach ($ports as $portname)
2624 $work['portconfig'][$portname][] = array ('type' => 'line-header', 'line' => $line);
2625 return 'ros11-get8021q-readports';
2627 return 'ros11-get8021q-scantop';
2631 function ros11Read8021QVLANDatabase (&$work, $line)
2633 if (1 != preg_match ('/^vlan ([-,0-9]+)$/', $line, $m))
2634 return 'ros11-get8021q-scantop';
2635 $work['vlanlist'] = array_merge ($work['vlanlist'], iosParseVLANString ($m[1]));
2636 return 'ros11-get8021q-vlandb';
2639 function ros11Read8021QPorts (&$work, $line)
2643 case 1 == preg_match ('/^switchport mode ([a-z]+)$/', $line, $m):
2644 if ($m[1] != 'trunk' && $m[1] != 'access')
2645 throw new RTGatewayError ("unsupported switchport mode '${m[1]}'");
2646 $work['current']['config']['mode'] = $m[1];
2647 $work['current']['config']['allowed'] = array();
2648 $work['current']['config']['native'] = 0;
2649 $work['current']['lines'][] = array ('type' => 'line-8021q', 'line' => $line);
2650 return 'ros11-get8021q-readports';
2651 case 1 == preg_match ('/^switchport access vlan (\d+)$/', $line, $m):
2652 $work['current']['config']['mode'] = 'access';
2653 $work['current']['config']['allowed'] = array ($m[1]);
2654 $work['current']['config']['native'] = $m[1];
2655 $work['current']['lines'][] = array ('type' => 'line-8021q', 'line' => $line);
2656 return 'ros11-get8021q-readports';
2657 # ROS accepts multiple allowed VLANs per a "allowed vlan add" line, but generates
2658 # a single "allowed vlan add" line per VLAN on output.
2659 case 1 == preg_match ('/^switchport trunk allowed vlan add (\d+)$/', $line, $m):
2660 $work['current']['config']['allowed'] = array ($m[1]);
2661 $work['current']['lines'][] = array ('type' => 'line-8021q', 'line' => $line);
2662 return 'ros11-get8021q-readports';
2663 case 1 == preg_match ('/^switchport trunk native vlan (\d+)$/', $line, $m):
2664 $work['current']['config']['allowed']= array ($m[1]); # native wasn't in the allowed list
2665 $work['current']['config']['native']= $m[1];
2666 $work['current']['lines'][] = array ('type' => 'line-8021q', 'line' => $line);
2667 return 'ros11-get8021q-readports';
2668 # "switchport default-vlan tagged" and "switchport forbidden default-vlan" are buffered
2669 # to be processed only after the complete configuration of each port is collected.
2670 case $line == 'switchport default-vlan tagged':
2671 case $line == 'switchport forbidden default-vlan':
2672 $work['current']['config'][$line] = TRUE;
2673 $work['current']['lines'][] = array ('type' => 'line-8021q', 'line' => $line);
2674 return 'ros11-get8021q-readports';
2675 case $line == 'exit':
2676 $work['current']['lines'][] = array ('type' => 'line-header', 'line' => $line);
2677 # Since an "interface" line may stand both for a single interface and
2678 # an interface range, the result is always a product of two sets.
2679 foreach ($work['current']['ports'] as $portname)
2681 # 802.1Q configuration text uses the short form of interface names, other
2682 # configuration text may use the long form. Translate to merge the latter.
2683 $work['portconfig'][shortenIfName ($portname)] = array_merge ($work['portconfig'][$portname], $work['current']['lines']);
2684 foreach ($work['current']['config'] as $param => $val)
2685 if ($param != 'allowed') # overwrite
2686 $work['portdata'][$portname][$param] = $val;
2687 else # initialize and merge
2689 if (! array_key_exists ('allowed', $work['portdata'][$portname]))
2690 $work['portdata'][$portname]['allowed'] = array();
2692 $work['portdata'][$portname]['allowed'][] = current ($val);
2695 unset ($work['current']);
2696 return 'ros11-get8021q-scantop';
2698 $work['current']['lines'][] = array ('type' => 'line-other', 'line' => $line);
2699 return 'ros11-get8021q-readports';
2703 function ciscoReadInterfaceStatus ($text)
2706 $state = 'headerSearch';
2707 foreach (explode ("\n", $text) as $line)
2711 case 'headerSearch':
2712 if (preg_match('/^Port\s+Name\s+Status/', $line))
2714 $name_field_borders = getColumnCoordinates($line, 'Name');
2715 if (isset ($name_field_borders['from']))
2716 $state = 'readPort';
2720 $portname = shortenIfName (trim (substr ($line, 0, $name_field_borders['from'])));
2721 $rest = trim (substr ($line, $name_field_borders['from'] +
$name_field_borders['length'] +
1));
2722 $field_list = preg_split('/\s+/', $rest);
2723 if (count ($field_list) < 4)
2725 list ($status_raw, $vlan, $duplex, $speed) = $field_list;
2726 if ($status_raw == 'connected' ||
$status_raw == 'up')
2728 elseif (0 === strpos ($status_raw, 'notconn') ||
$status_raw == 'down' ||
$status_raw == 'sfpAbsent')
2731 $status = 'disabled';
2732 $result[$portname] = array
2734 'status' => $status,
2736 'duplex' => $duplex,
2744 function vrpReadInterfaceStatus ($text)
2747 $state = 'headerSearch';
2748 foreach (explode ("\n", $text) as $line)
2752 case 'headerSearch':
2753 if (preg_match('/^Interface\s+Phy\w*\s+Protocol/i', $line))
2754 $state = 'readPort';
2757 if (preg_match('/[\$><\]]/', $line))
2759 $field_list = preg_split('/\s+/', $line);
2760 if (count ($field_list) < 7)
2762 if ($field_list[0] == '')
2763 array_shift ($field_list);
2764 list ($portname, $status_raw) = $field_list;
2765 $portname = shortenIfName ($portname);
2767 if ($status_raw == 'up' ||
$status_raw == 'down')
2768 $status = $status_raw;
2770 $status = 'disabled';
2771 $result[$portname] = array
2773 'status' => $status,
2782 D-Link "show ports" output sample
2783 =================================
2785 Port State/ Settings Connection Address
2786 MDI Speed/Duplex/FlowCtrl Speed/Duplex/FlowCtrl Learning
2787 ----- -------- --------------------- --------------------- --------
2788 1 Enabled Auto/Disabled 100M/Full/None Enabled
2791 26(C) Enabled Auto/Disabled LinkDown Enabled
2793 26(F) Enabled Auto/Disabled LinkDown Enabled
2795 function dlinkReadInterfaceStatus ($text)
2798 foreach (preg_split ("/\n\r?/", $text) as $line)
2800 if (!preg_match ('/^\s*\d+/', $line))
2802 $w = preg_split ('/\s+/', strtolower($line));
2806 if ($w[1] != 'enabled')
2807 $result[$portname] = array ('status'=>'disabled', 'speed'=>0, 'duplex'=>'');
2808 elseif ($w[3] == 'linkdown')
2809 $result[$portname] = array ('status'=>'down', 'speed'=>0, 'duplex'=>'');
2812 $s = split('/', $w[3]);
2813 $result[$portname] = array ('status'=>'up', 'speed'=>$s[0], 'duplex'=>$s[1]);
2820 Linux "ethtool" output sample
2821 =============================
2824 Supported ports: [ TP ]
2825 Supported link modes: 10baseT/Half 10baseT/Full
2826 100baseT/Half 100baseT/Full
2828 Supports auto-negotiation: Yes
2829 Advertised link modes: 10baseT/Half 10baseT/Full
2830 100baseT/Half 100baseT/Full
2832 Advertised pause frame use: No
2833 Advertised auto-negotiation: Yes
2838 Transceiver: internal
2839 Auto-negotiation: on
2841 Supports Wake-on: pumbg
2843 Current message level: 0x00000001 (1)
2847 Supported ports: [ TP ]
2848 Supported link modes: 10baseT/Half 10baseT/Full
2849 100baseT/Half 100baseT/Full
2851 Supports auto-negotiation: Yes
2852 Advertised link modes: 10baseT/Half 10baseT/Full
2853 100baseT/Half 100baseT/Full
2855 Advertised pause frame use: No
2856 Advertised auto-negotiation: Yes
2858 Duplex: Unknown! (255)
2861 Transceiver: internal
2862 Auto-negotiation: on
2864 Supports Wake-on: pumbg
2866 Current message level: 0x00000001 (1)
2869 function linuxReadInterfaceStatus ($text)
2876 foreach (explode ("\n", $text) as $line)
2879 if (preg_match ('/^[^\s].* (.*):$/', $line, $m))
2882 $result[$iface] = array ('status' => $status, 'speed' => $speed, 'duplex' => $duplex);