bugfix: Native vlan on Huawei trunk ports no longer gets to the allowed vlans list
[racktables-incomplete-works] / wwwroot / inc / deviceconfig.php
1 <?php
2
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.
6
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)
11 {
12 $ret = array();
13 foreach (explode ("\n", $input) as $line)
14 {
15 $matches = array();
16 switch (TRUE)
17 {
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];
21 break;
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
25 (
26 'device' => $ret['current']['device'],
27 'port' => $matches[2],
28 );
29 unset ($ret['current']);
30 break;
31 default:
32 }
33 }
34 unset ($ret['current']);
35 return $ret;
36 }
37
38 function ios12ReadLLDPStatus ($input)
39 {
40 $ret = array();
41 $got_header = FALSE;
42 foreach (explode ("\n", $input) as $line)
43 {
44 if (preg_match ("/^Device ID/", $line))
45 $got_header = TRUE;
46
47 if (!$got_header)
48 continue;
49
50 $matches = preg_split ('/\s+/', trim ($line));
51
52 switch (count ($matches))
53 {
54 case 5:
55 list ($remote_name, $local_port, $ttl, $caps, $remote_port) = $matches;
56 $local_port = shortenIfName ($local_port);
57 $ret[$local_port][] = array
58 (
59 'device' => $remote_name,
60 'port' => $remote_port,
61 );
62 break;
63 default:
64 }
65 }
66 return $ret;
67 }
68
69 function xos12ReadLLDPStatus ($input)
70 {
71 $ret = array();
72 foreach (explode ("\n", $input) as $line)
73 {
74 $matches = array();
75 switch (TRUE)
76 {
77 case preg_match ('/^LLDP Port ([[:digit:]]+) detected \d+ neighbor$/', $line, $matches):
78 $ret['current']['local_port'] = shortenIfName ($matches[1]);
79 break;
80 case preg_match ('/^ Port ID : "(.+)"$/', $line, $matches):
81 $ret['current']['remote_port'] = $matches[1];
82 break;
83 case preg_match ('/^ - System Name: "(.+)"$/', $line, $matches):
84 if
85 (
86 array_key_exists ('current', $ret) and
87 array_key_exists ('local_port', $ret['current']) and
88 array_key_exists ('remote_port', $ret['current'])
89 )
90 $ret[$ret['current']['local_port']][] = array
91 (
92 'device' => $matches[1],
93 'port' => $ret['current']['remote_port'],
94 );
95 unset ($ret['current']);
96 default:
97 }
98 }
99 unset ($ret['current']);
100 return $ret;
101 }
102
103 function vrpReadLLDPStatus ($input)
104 {
105 $ret = array();
106 $valid_subtypes = array
107 (
108 'interfaceName',
109 'Interface Name',
110 'interfaceAlias',
111 'Interface Alias',
112 'local',
113 'Local',
114 );
115 foreach (explode ("\n", $input) as $line)
116 {
117 $matches = array();
118 switch (TRUE)
119 {
120 case preg_match ('/^(.+) has \d+ neighbor(\(s\)|s):$/', $line, $matches):
121 $ret['current']['local_port'] = shortenIfName (trim ($matches[1]));
122 break;
123 case preg_match ('/^Port ?ID ?(?:sub)?type\s*:\s*(.*)$/i', $line, $matches):
124 $ret['current']['PortIdSubtype'] = trim ($matches[1]);
125 break;
126 case preg_match ('/^Port ?ID\s*:\s*(.+)$/i', $line, $matches):
127 $ret['current']['PortId'] = trim ($matches[1]);
128 break;
129 case preg_match ('/^Port description\s*:\s*(.*)$/i', $line, $matches):
130 $ret['current']['PortDescription'] = trim ($matches[1]);
131 break;
132 case preg_match ('/^Sys(?:tem)? ?name\s*:\s*(.+)$/i', $line, $matches):
133 if
134 (
135 array_key_exists ('current', $ret) and
136 array_key_exists ('PortIdSubtype', $ret['current']) and
137 array_key_exists ('local_port', $ret['current'])
138 )
139 {
140 $port = NULL;
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'];
145 if (isset ($port))
146 $ret[$ret['current']['local_port']][] = array
147 (
148 'device' => trim ($matches[1]),
149 'port' => $port,
150 );
151 }
152 unset ($ret['current']);
153 break;
154 default:
155 }
156 }
157 unset ($ret['current']);
158 return $ret;
159 }
160
161 function ftos8ReadLLDPStatus ($input)
162 {
163 $ret = array();
164 $valid_subtypes = array
165 (
166 'Interface name (5)',
167 'Interface Alias (1)',
168 'Locally assigned (7)',
169 );
170 foreach (explode ("\n", $input) as $line)
171 {
172 $matches = array();
173 switch (TRUE)
174 {
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"
177 break;
178 case preg_match ('/^ Remote Port Subtype: (.+)$/', $line, $matches):
179 $ret['current']['remote_subtype'] = $matches[1];
180 break;
181 case preg_match ('/^ Remote Port ID: (.+)$/i', $line, $matches):
182 $ret['current']['remote_port'] = $matches[1];
183 break;
184 case preg_match ('/^ Remote System Name: (.+)$/', $line, $matches):
185 if
186 (
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'])
192 )
193 $ret[$ret['current']['local_port']][] = array
194 (
195 'device' => $matches[1],
196 'port' => $ret['current']['remote_port'],
197 );
198 unset ($ret['current']['remote_subtype']);
199 unset ($ret['current']['remote_port']);
200 break;
201 default:
202 }
203 }
204 unset ($ret['current']);
205 return $ret;
206 }
207
208 function eos4ReadLLDPStatus ($input)
209 {
210 $ret = array();
211 $valid_subtypes = array
212 (
213 'Interface name (5)',
214 );
215 foreach (explode ("\n", $input) as $line)
216 {
217 $matches = array();
218 switch (TRUE)
219 {
220 case preg_match ('/^Interface (.+) detected \d+ LLDP neighbors/', $line, $matches):
221 $ret['current']['local_port'] = shortenIfName ($matches[1]);
222 break;
223 case preg_match ('/^ - Port ID type: (.+)$/', $line, $matches):
224 $ret['current']['remote_subtype'] = $matches[1];
225 break;
226 case preg_match ('/^ Port ID : "(.+)"$/', $line, $matches):
227 $ret['current']['remote_port'] = $matches[1];
228 break;
229 case preg_match ('/^ - System Name: "(.+)"$/', $line, $matches):
230 if
231 (
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'])
237 )
238 $ret[$ret['current']['local_port']][] = array
239 (
240 'device' => $matches[1],
241 'port' => $ret['current']['remote_port'],
242 );
243 unset ($ret['current']['remote_subtype']);
244 unset ($ret['current']['remote_port']);
245 break;
246 default:
247 }
248 }
249 unset ($ret['current']);
250 return $ret;
251 }
252
253 function ros11ReadLLDPStatus ($input)
254 {
255 $ret = array();
256 foreach (explode ("\n", $input) as $line)
257 {
258 switch (1)
259 {
260 case preg_match ('/^Local port: (.+)$/', $line, $m):
261 $ret['current']['local_port'] = shortenIfName ($m[1]);
262 break;
263 case preg_match ('/^Port ID: (.+)$/', $line, $m):
264 $ret['current']['remote_port'] = $m[1];
265 break;
266 case preg_match ('/^System Name: (.+)$/', $line, $m):
267 if
268 (
269 array_key_exists ('current', $ret) and
270 array_key_exists ('remote_port', $ret['current']) and
271 array_key_exists ('local_port', $ret['current'])
272 )
273 $ret[$ret['current']['local_port']][] = array
274 (
275 'device' => $m[1],
276 'port' => $ret['current']['remote_port'],
277 );
278 unset ($ret['current']['remote_port']);
279 break;
280 default: # NOP
281 }
282 }
283 unset ($ret['current']);
284 return $ret;
285 }
286
287 function ios12ReadVLANConfig ($input)
288 {
289 $ret = array
290 (
291 'vlanlist' => array(),
292 'portdata' => array(),
293 'portconfig' => array(),
294 );
295 $schema = $ret;
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");
298
299 global $breedfunc;
300 $nextfunc = 'ios12-get8021q-swports';
301 foreach (explode ("\n", $input) as $line)
302 $nextfunc = $breedfunc[$nextfunc] ($ret, $line);
303
304 // clear $ret from temporary keys created by parser functions
305 foreach ($ret as $key => $value)
306 if (! isset ($schema[$key]))
307 unset ($ret[$key]);
308 return $ret;
309 }
310
311 function ios12ScanTopLevel (&$work, $line)
312 {
313 $matches = array();
314 switch (TRUE)
315 {
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';
323 default:
324 return 'ios12-get8021q-top'; // continue scan
325 }
326 }
327
328 function ios12ReadSwitchPortList (&$work, $line)
329 {
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']))
335 {
336 $work['switchports'][] = shortenIfName ($work['current_switchport']);
337 unset ($work['current_switchport']);
338 }
339 return 'ios12-get8021q-swports';
340 }
341
342 function ios12PickSwitchportCommand (&$work, $line)
343 {
344 $port_name = $work['current']['port_name'];
345 if (! strlen ($line) || $line[0] != ' ') // end of interface section
346 {
347 $work['portconfig'][$port_name][] = array ('type' => 'line-header', 'line' => $line);
348
349 // save work, if it makes sense
350 if (! in_array ($port_name, $work['switchports']))
351 $work['current']['mode'] = 'SKIP'; // skip not switched ports
352 else
353 {
354 if (! isset ($work['current']['mode']))
355 $work['current']['mode'] = 'access';
356 }
357 switch (@$work['current']['mode'])
358 {
359 case 'access':
360 if (!array_key_exists ('access vlan', $work['current']))
361 $work['current']['access vlan'] = 1;
362 $work['portdata'][$port_name] = array
363 (
364 'mode' => 'access',
365 'allowed' => array ($work['current']['access vlan']),
366 'native' => $work['current']['access vlan'],
367 );
368 break;
369 case 'trunk':
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
377 (
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
382 (
383 'mode' => 'trunk',
384 'allowed' => $work['current']['trunk allowed vlan'],
385 'native' => $effective_native,
386 );
387 break;
388 case 'SKIP':
389 case 'fex-fabric': // associated port-channel
390 case 'IP':
391 break;
392 default:
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
396 // by means of VST.
397 $work['portdata'][$port_name] = array
398 (
399 'mode' => 'none',
400 'allowed' => array(),
401 'native' => 0,
402 );
403 break;
404 }
405 unset ($work['current']);
406 return 'ios12-get8021q-top';
407 }
408 // not yet
409 $matches = array();
410 $line_class = 'line-8021q';
411 switch (TRUE)
412 {
413 case (preg_match ('@^\s+switchport mode (.+)$@', $line, $matches)):
414 $work['current']['mode'] = $matches[1];
415 break;
416 case (preg_match ('@^\s+switchport access vlan (.+)$@', $line, $matches)):
417 $work['current']['access vlan'] = $matches[1];
418 break;
419 case (preg_match ('@^\s+switchport trunk native vlan (.+)$@', $line, $matches)):
420 $work['current']['trunk native vlan'] = $matches[1];
421 break;
422 case (preg_match ('@^\s+switchport trunk allowed vlan add (.+)$@', $line, $matches)):
423 $work['current']['trunk allowed vlan'] = array_merge
424 (
425 $work['current']['trunk allowed vlan'],
426 iosParseVLANString ($matches[1])
427 );
428 break;
429 case preg_match ('@^\s+switchport trunk allowed vlan none$@', $line, $matches):
430 $work['current']['trunk allowed vlan'] = array();
431 break;
432 case (preg_match ('@^\s+switchport trunk allowed vlan (.+)$@', $line, $matches)):
433 $work['current']['trunk allowed vlan'] = iosParseVLANString ($matches[1]);
434 break;
435 case preg_match ('@^\s+channel-group @', $line):
436 // port-channel subinterface config follows that of the master interface
437 $work['current']['mode'] = 'SKIP';
438 break;
439 case preg_match ('@^\s+ip address @', $line):
440 // L3 interface does no switchport functions
441 $work['current']['mode'] = 'IP';
442 break;
443 default: // suppress warning on irrelevant config clause
444 $line_class = 'line-other';
445 }
446 $work['portconfig'][$port_name][] = array ('type' => $line_class, 'line' => $line);
447 return 'ios12-get8021q-readport';
448 }
449
450 function ios12PickVLANCommand (&$work, $line)
451 {
452 $matches = array();
453 switch (TRUE)
454 {
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];
459 break;
460 default:
461 }
462 return 'ios12-get8021q-readvlan';
463 }
464
465 // Another finite automata to read a dialect of Foundry configuration.
466 function fdry5ReadVLANConfig ($input)
467 {
468 $ret = array
469 (
470 'vlanlist' => array(),
471 'portdata' => array(),
472 'portconfig' => array(),
473 );
474 global $breedfunc;
475 $nextfunc = 'fdry5-get8021q-top';
476 foreach (explode ("\n", $input) as $line)
477 $nextfunc = $breedfunc[$nextfunc] ($ret, $line);
478 return $ret;
479 }
480
481 function fdry5ScanTopLevel (&$work, $line)
482 {
483 $matches = array();
484 switch (TRUE)
485 {
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';
496 default:
497 return 'fdry5-get8021q-top';
498 }
499 }
500
501 function fdry5PickVLANSubcommand (&$work, $line)
502 {
503 if ($line[0] != ' ') // end of VLAN section
504 {
505 unset ($work['current']);
506 return 'fdry5-get8021q-top';
507 }
508 // not yet
509 $matches = array();
510 switch (TRUE)
511 {
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'];
517 else
518 $work['portdata'][$port_name] = array
519 (
520 'mode' => 'trunk',
521 'allowed' => array ($work['current']['vlan_id']),
522 'native' => 0, // can be updated later
523 );
524 $work['portdata'][$port_name]['mode'] = 'trunk';
525 break;
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)
529 {
530 if (array_key_exists ($port_name, $work['portdata']))
531 {
532 $work['portdata'][$port_name]['native'] = $work['current']['vlan_id'];
533 $work['portdata'][$port_name]['allowed'][] = $work['current']['vlan_id'];
534 }
535 else
536 $work['portdata'][$port_name] = array
537 (
538 'mode' => 'access',
539 'allowed' => array ($work['current']['vlan_id']),
540 'native' => $work['current']['vlan_id'],
541 );
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';
547 }
548 break;
549 default: // nom-nom
550 }
551 return 'fdry5-get8021q-readvlan';
552 }
553
554 function fdry5PickInterfaceSubcommand (&$work, $line)
555 {
556 $port_name = $work['current']['port_name'];
557 if ($line[0] != ' ') // end of interface section
558 {
559 $work['portconfig'][$port_name][] = array ('type' => 'line-header', 'line' => $line);
560 if (array_key_exists ('dual-mode', $work['current']))
561 {
562 if (array_key_exists ($port_name, $work['portdata']))
563 // update existing record
564 $work['portdata'][$port_name]['native'] = $work['current']['dual-mode'];
565 else
566 // add new
567 $work['portdata'][$port_name] = array
568 (
569 'allowed' => array ($work['current']['dual-mode']),
570 'native' => $work['current']['dual-mode'],
571 );
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';
576 }
577 unset ($work['current']);
578 return 'fdry5-get8021q-top';
579 }
580 $matches = array();
581 switch (TRUE)
582 {
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;
586 break;
587 // FIXME: trunk/link-aggregate/ip address pulls port from 802.1Q field
588 default: // nom-nom
589 }
590 $work['portconfig'][$port_name][] = array ('type' => 'line-other', 'line' => $line);
591 return 'fdry5-get8021q-readport';
592 }
593
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)
599 {
600 $ret = array();
601 $tokens = explode (' ', trim ($string));
602 while (count ($tokens))
603 {
604 $letters = array_shift ($tokens); // "ethe", "to"
605 $numbers = array_shift ($tokens); // "x", "x/x", "x/x/x"
606 switch ($letters)
607 {
608 case 'ethe':
609 if ($prev_numbers != NULL)
610 $ret[] = 'e' . $prev_numbers;
611 $prev_numbers = $numbers;
612 break;
613 case 'to':
614 $ret = array_merge ($ret, fdry5GenPortRange ($prev_numbers, $numbers));
615 $prev_numbers = NULL; // no action on next token
616 break;
617 default: // ???
618 throw new InvalidArgException ('string', $string, 'format mismatch');
619 }
620 }
621 // flush delayed item
622 if ($prev_numbers != NULL)
623 $ret[] = 'e' . $prev_numbers;
624 return $ret;
625 }
626
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)
632 {
633 $matches = array();
634 if (1 !== preg_match ('@^([[:digit:]]+/)?([[:digit:]]+/)?([[:digit:]]+)$@', $from, $matches))
635 return array();
636 $prefix = 'e' . $matches[1] . $matches[2];
637 $from_idx = $matches[3];
638 if (1 !== preg_match ('@^([[:digit:]]+/)?([[:digit:]]+/)?([[:digit:]]+)$@', $to, $matches))
639 return array();
640 $to_idx = $matches[3];
641 for ($i = $from_idx; $i <= $to_idx; $i++)
642 $ret[] = $prefix . $i;
643 return $ret;
644 }
645
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)
649 {
650 $ret = array();
651 foreach (explode (',', $string) as $item)
652 if (preg_match ('#^[a-z]+\d+/\d+$#', $item)) # a single interface
653 $ret[] = $item;
654 elseif (preg_match ('#^([a-z]+\d+/)(\d+)-(\d+)$#', $item, $matches)) # a range
655 {
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}";
662 }
663 else
664 throw new InvalidArgException ('string', $string, "format error in '${item}'");
665 return $ret;
666 }
667
668 // an implementation for Huawei syntax
669 function vrp53ReadVLANConfig ($input)
670 {
671 $ret = array
672 (
673 'vlanlist' => array(),
674 'portdata' => array(),
675 'portconfig' => array(),
676 );
677 global $breedfunc;
678 $nextfunc = 'vrp53-get8021q-top';
679 foreach (explode ("\n", $input) as $line)
680 $nextfunc = $breedfunc[$nextfunc] ($ret, $line);
681 return $ret;
682 }
683
684 function vrp53ScanTopLevel (&$work, $line)
685 {
686 $matches = array();
687 switch (TRUE)
688 {
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';
698 default:
699 return 'vrp53-get8021q-top';
700 }
701 }
702
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)
706 {
707 $string = preg_replace ('/ to /', '-', $string);
708 $string = preg_replace ('/ /', ',', $string);
709 return iosParseVLANString ($string);
710 }
711
712 function vrp53PickInterfaceSubcommand (&$work, $line)
713 {
714 $port_name = $work['current']['port_name'];
715 if ($line[0] == '#') // end of interface section
716 {
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'])
727 {
728 case 'access':
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
733 (
734 'allowed' => $work['current']['allowed'],
735 'native' => $work['current']['native'],
736 'mode' => 'access',
737 ) : array
738 (
739 'mode' => 'none',
740 'allowed' => array(),
741 'native' => 0,
742 );
743 break;
744 case 'trunk':
745 $work['portdata'][$port_name] = array
746 (
747 'allowed' => $work['current']['allowed'],
748 'native' => 0,
749 'mode' => 'trunk',
750 );
751 break;
752 case 'hybrid':
753 $work['portdata'][$port_name] = array
754 (
755 'allowed' => $work['current']['allowed'],
756 'native' => in_array ($work['current']['native'], $work['current']['allowed']) ? $work['current']['native'] : 0,
757 'mode' => 'trunk',
758 );
759 break;
760 case 'SKIP':
761 default: // dot1q-tunnel ?
762 }
763 unset ($work['current']);
764 return 'vrp53-get8021q-top';
765 }
766 $matches = array();
767 $line_class = 'line-8021q';
768 switch (TRUE)
769 {
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();
774 break;
775 case (preg_match ('@^ port link-type (.+)$@', $line, $matches)):
776 $work['current']['link-type'] = $matches[1];
777 break;
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;
784 break;
785 case preg_match ('/^\s*eth-trunk \d+/', $line):
786 $work['current']['link-type'] = 'SKIP';
787 break;
788 default: // nom-nom
789 $line_class = 'line-other';
790 }
791 $work['portconfig'][$port_name][] = array('type' => $line_class, 'line' => $line);
792 return 'vrp53-get8021q-readport';
793 }
794
795 function vrp55Read8021QConfig ($input)
796 {
797 $ret = array
798 (
799 'vlanlist' => array (1), // VRP 5.50 hides VLAN1 from config text
800 'portdata' => array(),
801 'portconfig' => array(),
802 );
803 foreach (explode ("\n", $input) as $line)
804 {
805 $matches = array();
806 // top level
807 if (!array_key_exists ('current', $ret))
808 {
809 switch (TRUE)
810 {
811 case (preg_match ('@^ vlan batch (.+)$@', $line, $matches)):
812 foreach (vrp53ParseVLANString ($matches[1]) as $vlan_id)
813 $ret['vlanlist'][] = $vlan_id;
814 break;
815 case (preg_match ('@^interface ((Ethernet|GigabitEthernet|XGigabitEthernet|Eth-Trunk)([[:digit:]]+(/[[:digit:]]+)*))$@', $line, $matches)):
816 $port_name = shortenIfName ($matches[1]);
817 $ret['current'] = array
818 (
819 'port_name' => $port_name,
820 'allowed' => array (VLAN_DFL_ID),
821 'native' => VLAN_DFL_ID,
822 );
823 $ret['portconfig'][$port_name][] = array ('type' => 'line-header', 'line' => $line);
824 break;
825 }
826 continue;
827 }
828 $port_name = $ret['current']['port_name'];
829 // inside an interface block
830 $line_class = 'line-8021q';
831 switch (TRUE)
832 {
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];
837 break;
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];
845 break;
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;
850 break;
851 case preg_match ('/^ undo port trunk allow-pass vlan (.+)$/', $line, $matches):
852 $ret['current']['allowed'] = array_diff ($ret['current']['allowed'], vrp53ParseVLANString ($matches[1]));
853 break;
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';
858 break;
859 case preg_match ('/^ eth-trunk /', $line):
860 $ret['current']['link-type'] = 'SKIP';
861 break;
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'])
867 {
868 case 'access':
869 // In VRP 5.50 an access port has default VLAN ID == 1
870 $ret['portdata'][$port_name] =
871 $ret['current']['native'] ? array
872 (
873 'mode' => 'access',
874 'allowed' => array ($ret['current']['native']),
875 'native' => $ret['current']['native'],
876 ) : array
877 (
878 'mode' => 'access',
879 'allowed' => array (VLAN_DFL_ID),
880 'native' => VLAN_DFL_ID,
881 );
882 break;
883 case 'trunk':
884 $ret['portdata'][$port_name] = array
885 (
886 'mode' => 'trunk',
887 'allowed' => $ret['current']['allowed'],
888 'native' => in_array ($ret['current']['native'], $ret['current']['allowed']) ? $ret['current']['native'] : 0,
889 );
890 break;
891 case 'IP':
892 case 'SKIP':
893 break;
894 case 'hybrid': // hybrid ports are not supported
895 default: // dot1q-tunnel ?
896 $ret['portdata'][$port_name] = array
897 (
898 'mode' => 'none',
899 'allowed' => array(),
900 'native' => 0,
901 );
902 break;
903 }
904 unset ($ret['current']);
905 break;
906 default: // nom-nom
907 $line_class = 'line-other';
908 }
909 $ret['portconfig'][$port_name][] = array ('type' => $line_class, 'line' => $line);
910 }
911 return $ret;
912 }
913
914 function vrp85Read8021QConfig ($input)
915 {
916 $ret = array
917 (
918 'vlanlist' => array(),
919 'portdata' => array(),
920 'portconfig' => array(),
921 );
922 $state = 'vlans';
923 $current = array();
924
925 foreach (explode ("\n", $input) as $line)
926 {
927 $line = rtrim ($line);
928 do switch ($state)
929 {
930 case 'vlans':
931 if (preg_match ('/^VLAN ID: (.*)/', $line, $m))
932 {
933 $current['vlanlist'] = ' ' . $m[1];
934 $state = 'vlans-nextline';
935 }
936 elseif (preg_match('/^-+$/', $line))
937 {
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);
942 $current = array();
943
944 $state = 'ports';
945 }
946 break;
947 case 'vlans-nextline':
948 if (preg_match('/^\s+(\d.*)/', $line, $m))
949 $current['vlanlist'] .= ' ' . $m[1];
950 else
951 {
952 $state = 'vlans';
953 continue 2;
954 }
955 break;
956 case 'ports':
957 if (isset ($current['name']))
958 {
959 if (preg_match('/^\s+(\d.*)/', $line, $m))
960 $current['allowed'] .= ' ' . $m[1];
961 else
962 {
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)
965 {
966 // commit $current into portdata
967 $data = array
968 (
969 'mode' => $current['mode'],
970 'native' => $current['native'],
971 'allowed' => array(),
972 );
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')
978 {
979 if (! in_array ($data['native'], $data['allowed']))
980 $data['native'] = 0;
981 }
982 else
983 {
984 $data['allowed'] = array();
985 $data['native'] = 0;
986 }
987 $ret['portdata'][$current['name']] = $data;
988 }
989 $current = array();
990 }
991 }
992 if (preg_match ('/^</', $line))
993 $state = 'conf';
994 elseif (preg_match ('/^(\S+)\s+(\w+)\s+(\d+)\s+(.*)$/', $line, $m))
995 {
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];
1000 }
1001 break;
1002 case 'conf':
1003 if (preg_match ('/^interface (\S+)$/', $line, $m))
1004 {
1005 $current['name'] = shortenIfName ($m[1]);
1006 $current['lines'] = array (array ('type' => 'line-header', 'line' => $line));
1007 $state = 'iface';
1008 }
1009 break;
1010 case 'iface':
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);
1015 if ($line == '#')
1016 {
1017 // commit $current into portconfig
1018 $ret['portconfig'][$current['name']] = $current['lines'];
1019 $current = array();
1020 $state = 'conf';
1021 }
1022 break;
1023 default:
1024 throw new RackTablesError ("Unknown FSM state '$state'", RackTablesError::INTERNAL);
1025 }
1026 while (FALSE);
1027 }
1028
1029 return $ret;
1030 }
1031
1032 /*
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
1043 Forbidden Ports :
1044 */
1045 function dlinkReadVLANConfig ($input)
1046 {
1047 $ret = array
1048 (
1049 'vlanlist' => array(),
1050 'portdata' => array(),
1051 );
1052 global $breedfunc;
1053 $nextfunc = 'dlink-get8021q-top';
1054 foreach (explode ("\n", $input) as $line)
1055 $nextfunc = $breedfunc[$nextfunc] ($ret, $line);
1056 return $ret;
1057 }
1058
1059 function dlinkScanTopLevel (&$work, $line)
1060 {
1061 $matches = array();
1062 switch (TRUE)
1063 {
1064 case preg_match ('@^\s*VID\s*:\s*(\d+)\s+.*name\s*:\s*(.+)$@i', $line, $matches):
1065 $work['current'] = array
1066 (
1067 'vlan_id' => $matches[1],
1068 'vlan_name' => $matches[2],
1069 'tagged_ports' => '',
1070 'untagged_ports' => '',
1071 );
1072 return 'dlink-get8021q-pickvlan';
1073 default:
1074 return 'dlink-get8021q-top';
1075 }
1076 }
1077
1078 function dlinkPickVLANCommand (&$work, $line)
1079 {
1080 switch (TRUE)
1081 {
1082 case preg_match ('@END OF VLAN LIST@', $line):
1083 case trim ($line) === '':
1084 if (!isset($work['current']))
1085 break;
1086 $work['vlanlist'][] = $work['current']['vlan_id'];
1087 # portlist = range[,range..]
1088 # range = N[-N]
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];
1097 break;
1098 case preg_match ('@current untagged ports\s*:\s*([[:digit:]]+.*)$@i', $line, $matches):
1099 $work['current']['untagged_ports'] = $matches[1];
1100 break;
1101 }
1102 return 'dlink-get8021q-pickvlan';
1103 }
1104
1105 function dlinkStorePortInfo (&$work, $port_name, $new_mode, $overwrite_mode = '')
1106 {
1107 if (! array_key_exists ($port_name, $work['portdata']))
1108 {
1109 $work['portdata'][$port_name] = array
1110 (
1111 'mode' => $new_mode,
1112 'allowed' => array ($work['current']['vlan_id']),
1113 'native' => $work['current']['vlan_id']
1114 );
1115 return;
1116 }
1117 $work['portdata'][$port_name]['allowed'][] = $work['current']['vlan_id'];
1118 if ($overwrite_mode !== '')
1119 $work['portdata'][$port_name]['mode'] = $overwrite_mode;
1120 }
1121
1122 function linuxReadVLANConfig ($input)
1123 {
1124 $ret = array
1125 (
1126 'vlanlist' => array (VLAN_DFL_ID),
1127 'portdata' => array(),
1128 );
1129 foreach (explode ("\n", $input) as $line)
1130 {
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
1132 $matches = array();
1133 if (! preg_match ('/^[[:digit:]]+:\s+([^\s]+):\s.*\slink\/ether\s/', $line, $matches))
1134 continue;
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());
1142 }
1143 return $ret;
1144 }
1145
1146 function linuxStoreVLANInfo (&$ret, $iface, $baseport, $vid)
1147 {
1148 $ret['vlanlist'][] = $vid;
1149 if (! array_key_exists ($baseport, $ret['portdata']))
1150 $ret['portdata'][$baseport] = array ('mode' => 'trunk', 'native' => 0, 'allowed' => array ($vid));
1151 else
1152 {
1153 $ret['portdata'][$baseport]['mode'] = 'trunk';
1154 $ret['portdata'][$baseport]['allowed'][] = $vid;
1155 }
1156 if (! array_key_exists ($iface, $ret['portdata']))
1157 $ret['portdata'][$iface] = array ('mode' => 'access', 'native' => $vid, 'allowed' => array ($vid));
1158 }
1159
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)
1163 {
1164 $ret = '';
1165
1166 foreach ($queue as $cmd)
1167 switch ($cmd['opcode'])
1168 {
1169 case 'set mode':
1170 if ($cmd['arg2'] == 'trunk')
1171 {
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";
1180 break;
1181 }
1182 // fall-through
1183 default:
1184 $ret .= ios12TranslatePushQueue ($dummy_object_id, array ($cmd), $dummy_vlan_names);
1185 break;
1186 }
1187 return $ret;
1188 }
1189
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",
1195 // where B>A.
1196 // 2. Iterate over the resulting list and produce real CLI commands.
1197 function ios12TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1198 {
1199 $ret = '';
1200 foreach ($queue as $cmd)
1201 switch ($cmd['opcode'])
1202 {
1203 case 'create VLAN':
1204 $ret .= "vlan ${cmd['arg1']}\nexit\n";
1205 break;
1206 case 'destroy VLAN':
1207 $ret .= "no vlan ${cmd['arg1']}\n";
1208 break;
1209 case 'add allowed':
1210 case 'rem allowed':
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']}") .
1216 "\n";
1217 $ret .= "exit\n";
1218 break;
1219 case 'set native':
1220 $ret .= "interface ${cmd['arg1']}\nswitchport trunk native vlan ${cmd['arg2']}\nexit\n";
1221 break;
1222 case 'unset native':
1223 $ret .= "interface ${cmd['arg1']}\nno switchport trunk native vlan ${cmd['arg2']}\nexit\n";
1224 break;
1225 case 'set access':
1226 $ret .= "interface ${cmd['arg1']}\nswitchport access vlan ${cmd['arg2']}\nexit\n";
1227 break;
1228 case 'unset access':
1229 $ret .= "interface ${cmd['arg1']}\nno switchport access vlan\nexit\n";
1230 break;
1231 case 'set mode':
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";
1238 $ret .= "exit\n";
1239 break;
1240 case 'begin configuration':
1241 $ret .= "configure terminal\n";
1242 break;
1243 case 'end configuration':
1244 $ret .= "end\n";
1245 break;
1246 case 'save configuration':
1247 $ret .= "copy running-config startup-config\n\n";
1248 break;
1249 case 'cite':
1250 $ret .= $cmd['arg1'];
1251 break;
1252 // query list
1253 case 'get8021q':
1254 $ret .=
1255 'show interface switchport | incl Name:|Switchport:
1256 ! END OF SWITCHPORTS
1257 show run
1258 ! END OF CONFIG
1259 show vlan brief
1260 ! END OF VLAN LIST
1261 ';
1262 break;
1263 case 'getcdpstatus':
1264 $ret .= "show cdp neighbors detail\n";
1265 break;
1266 case 'getlldpstatus':
1267 $ret .= "show lldp neighbors\n";
1268 break;
1269 case 'getportstatus':
1270 $ret .= "show int status\n";
1271 break;
1272 case 'getmaclist':
1273 $ret .= "show mac address-table dynamic\n";
1274 break;
1275 case 'getallconf':
1276 $ret .= "show running-config\n";
1277 break;
1278 default:
1279 throw new InvalidArgException ('opcode', $cmd['opcode']);
1280 }
1281 return $ret;
1282 }
1283
1284 function fdry5TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1285 {
1286 $ret = '';
1287 foreach ($queue as $cmd)
1288 switch ($cmd['opcode'])
1289 {
1290 case 'create VLAN':
1291 $ret .= "vlan ${cmd['arg1']}\nexit\n";
1292 break;
1293 case 'destroy VLAN':
1294 $ret .= "no vlan ${cmd['arg1']}\n";
1295 break;
1296 case 'add allowed':
1297 foreach ($cmd['vlans'] as $vlan_id)
1298 $ret .= "vlan ${vlan_id}\ntagged ${cmd['port']}\nexit\n";
1299 break;
1300 case 'rem allowed':
1301 foreach ($cmd['vlans'] as $vlan_id)
1302 $ret .= "vlan ${vlan_id}\nno tagged ${cmd['port']}\nexit\n";
1303 break;
1304 case 'set native':
1305 $ret .= "interface ${cmd['arg1']}\ndual-mode ${cmd['arg2']}\nexit\n";
1306 break;
1307 case 'unset native':
1308 $ret .= "interface ${cmd['arg1']}\nno dual-mode ${cmd['arg2']}\nexit\n";
1309 break;
1310 case 'set access':
1311 $ret .= "vlan ${cmd['arg2']}\nuntagged ${cmd['arg1']}\nexit\n";
1312 break;
1313 case 'unset access':
1314 $ret .= "vlan ${cmd['arg2']}\nno untagged ${cmd['arg1']}\nexit\n";
1315 break;
1316 case 'set mode': // NOP
1317 break;
1318 case 'begin configuration':
1319 $ret .= "conf t\n";
1320 break;
1321 case 'end configuration':
1322 $ret .= "end\n";
1323 break;
1324 case 'save configuration':
1325 $ret .= "write memory\n";
1326 break;
1327 case 'cite':
1328 $ret .= $cmd['arg1'];
1329 break;
1330 // query list
1331 case 'get8021q':
1332 $ret .= "show running-config\n";
1333 break;
1334 case 'getallconf':
1335 $ret .= "show running-config\n";
1336 break;
1337 default:
1338 throw new InvalidArgException ('opcode', $cmd['opcode']);
1339 }
1340 return $ret;
1341 }
1342
1343 function vrp53TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1344 {
1345 $ret = '';
1346 foreach ($queue as $cmd)
1347 switch ($cmd['opcode'])
1348 {
1349 case 'create VLAN':
1350 $ret .= "vlan ${cmd['arg1']}\nquit\n";
1351 break;
1352 case 'destroy VLAN':
1353 $ret .= "undo vlan ${cmd['arg1']}\n";
1354 break;
1355 case 'add allowed':
1356 case 'rem allowed':
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']}") .
1362 "\n";
1363 $ret .= "quit\n";
1364 break;
1365 case 'set native':
1366 case 'set access':
1367 $ret .= "interface ${cmd['arg1']}\nport default vlan ${cmd['arg2']}\nquit\n";
1368 break;
1369 case 'unset native':
1370 case 'unset access':
1371 $ret .= "interface ${cmd['arg1']}\nundo port default vlan\nquit\n";
1372 break;
1373 case 'set mode':
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";
1378 $ret .= "quit\n";
1379 break;
1380 case 'begin configuration':
1381 $ret .= "system-view\n";
1382 break;
1383 case 'end configuration':
1384 $ret .= "return\n";
1385 break;
1386 case 'save configuration':
1387 $ret .= "save\nY\n";
1388 break;
1389 case 'cite':
1390 $ret .= $cmd['arg1'];
1391 break;
1392 // query list
1393 case 'get8021q':
1394 $ret .= "display current-configuration\n";
1395 break;
1396 case 'getlldpstatus':
1397 $ret .= "display lldp neighbor\n";
1398 break;
1399 case 'getportstatus':
1400 $ret .= "display interface brief\n";
1401 break;
1402 case 'getmaclist':
1403 $ret .= "display mac-address dynamic\n";
1404 break;
1405 case 'getallconf':
1406 $ret .= "display current-configuration\n";
1407 break;
1408 default:
1409 throw new InvalidArgException ('opcode', $cmd['opcode']);
1410 }
1411 return $ret;
1412 }
1413
1414 function vrp55TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1415 {
1416 $ret = '';
1417 foreach ($queue as $cmd)
1418 switch ($cmd['opcode'])
1419 {
1420 case 'create VLAN':
1421 if ($cmd['arg1'] != 1)
1422 $ret .= "vlan ${cmd['arg1']}\nquit\n";
1423 break;
1424 case 'destroy VLAN':
1425 if ($cmd['arg1'] != 1)
1426 $ret .= "undo vlan ${cmd['arg1']}\n";
1427 break;
1428 case 'add allowed':
1429 case 'rem allowed':
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']}") .
1435 "\n";
1436 $ret .= "quit\n";
1437 break;
1438 case 'set native':
1439 $ret .= "interface ${cmd['arg1']}\nport trunk pvid vlan ${cmd['arg2']}\nquit\n";
1440 break;
1441 case 'set access':
1442 $ret .= "interface ${cmd['arg1']}\nport default vlan ${cmd['arg2']}\nquit\n";
1443 break;
1444 case 'unset native':
1445 $ret .= "interface ${cmd['arg1']}\nundo port trunk pvid vlan\nquit\n";
1446 break;
1447 case 'unset access':
1448 $ret .= "interface ${cmd['arg1']}\nundo port default vlan\nquit\n";
1449 break;
1450 case 'set mode':
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.
1455 $before = array
1456 (
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",
1461 );
1462 $after = array
1463 (
1464 'access' => '',
1465 'trunk' => "undo port trunk allow-pass vlan 1\n",
1466 );
1467 $ret .= "interface ${cmd['arg1']}\n";
1468 $ret .= $before[$cmd['arg2']];
1469 $ret .= "port link-type ${cmd['arg2']}\n";
1470 $ret .= $after[$cmd['arg2']];
1471 $ret .= "quit\n";
1472 break;
1473 case 'begin configuration':
1474 $ret .= "system-view\n";
1475 break;
1476 case 'end configuration':
1477 $ret .= "return\n";
1478 break;
1479 case 'save configuration':
1480 $ret .= "save\nY\n";
1481 break;
1482 case 'cite':
1483 $ret .= $cmd['arg1'];
1484 break;
1485 // query list
1486 case 'get8021q':
1487 $ret .= "display current-configuration\n";
1488 break;
1489 case 'getlldpstatus':
1490 $ret .= "display lldp neighbor\n";
1491 break;
1492 case 'getportstatus':
1493 $ret .= "display interface brief\n";
1494 break;
1495 case 'getmaclist':
1496 $ret .= "display mac-address dynamic\n";
1497 break;
1498 case 'getallconf':
1499 $ret .= "display current-configuration\n";
1500 break;
1501 default:
1502 throw new InvalidArgException ('opcode', $cmd['opcode']);
1503 }
1504 return $ret;
1505 }
1506
1507 function vrp85TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1508 {
1509 $ret = '';
1510 foreach ($queue as $cmd)
1511 switch ($cmd['opcode'])
1512 {
1513 case 'create VLAN':
1514 if ($cmd['arg1'] != 1)
1515 $ret .= "vlan ${cmd['arg1']}\nquit\n";
1516 break;
1517 case 'destroy VLAN':
1518 if ($cmd['arg1'] != 1)
1519 $ret .= "undo vlan ${cmd['arg1']}\n";
1520 break;
1521 case 'add allowed':
1522 case 'rem allowed':
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']}") .
1528 "\n";
1529 $ret .= "quit\n";
1530 break;
1531 case 'set native':
1532 $ret .= "interface ${cmd['arg1']}\nport trunk pvid vlan ${cmd['arg2']}\nquit\n";
1533 break;
1534 case 'set access':
1535 $ret .= "interface ${cmd['arg1']}\nport default vlan ${cmd['arg2']}\nquit\n";
1536 break;
1537 case 'unset native':
1538 $ret .= "interface ${cmd['arg1']}\nundo port trunk pvid vlan\nquit\n";
1539 break;
1540 case 'unset access':
1541 $ret .= "interface ${cmd['arg1']}\nundo port default vlan\nquit\n";
1542 break;
1543 case 'set mode':
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.
1548 $before = array
1549 (
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",
1554 );
1555 $after = array
1556 (
1557 'access' => '',
1558 'trunk' => "undo port trunk allow-pass vlan 1\n",
1559 );
1560 $ret .= "interface ${cmd['arg1']}\n";
1561 $ret .= $before[$cmd['arg2']];
1562 $ret .= "port link-type ${cmd['arg2']}\n";
1563 $ret .= $after[$cmd['arg2']];
1564 $ret .= "quit\n";
1565 break;
1566 case 'begin configuration':
1567 $ret .= "system-view immediately\n";
1568 break;
1569 case 'end configuration':
1570 $ret .= "return\n";
1571 break;
1572 case 'save configuration':
1573 $ret .= "save\nY\n";
1574 break;
1575 case 'cite':
1576 $ret .= $cmd['arg1'];
1577 break;
1578 // query list
1579 case 'get8021q':
1580 $ret .= "display vlan summary\n";
1581 $ret .= "display port vlan\n";
1582 $ret .= "display current-configuration\n";
1583 break;
1584 case 'getlldpstatus':
1585 $ret .= "display lldp neighbor\n";
1586 break;
1587 case 'getportstatus':
1588 $ret .= "display interface brief\n";
1589 break;
1590 case 'getmaclist':
1591 $ret .= "display mac-address dynamic\n";
1592 break;
1593 case 'getallconf':
1594 $ret .= "display current-configuration\n";
1595 break;
1596 default:
1597 throw new InvalidArgException ('opcode', $cmd['opcode']);
1598 }
1599 return $ret;
1600 }
1601
1602 function xos12TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1603 {
1604 $ret = '';
1605 foreach ($queue as $cmd)
1606 switch ($cmd['opcode'])
1607 {
1608 case 'create VLAN':
1609 $ret .= "create vlan VLAN${cmd['arg1']}\n";
1610 $ret .= "configure vlan VLAN${cmd['arg1']} tag ${cmd['arg1']}\n";
1611 break;
1612 case 'destroy VLAN':
1613 $ret .= "delete vlan VLAN${cmd['arg1']}\n";
1614 break;
1615 case 'add allowed':
1616 foreach ($cmd['vlans'] as $vlan_id)
1617 {
1618 $vlan_name = $vlan_id == 1 ? 'Default' : "VLAN${vlan_id}";
1619 $ret .= "configure vlan ${vlan_name} add ports ${cmd['port']} tagged\n";
1620 }
1621 break;
1622 case 'rem allowed':
1623 foreach ($cmd['vlans'] as $vlan_id)
1624 {
1625 $vlan_name = $vlan_id == 1 ? 'Default' : "VLAN${vlan_id}";
1626 $ret .= "configure vlan ${vlan_name} delete ports ${cmd['port']}\n";
1627 }
1628 break;
1629 case 'set native':
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";
1633 break;
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";
1638 break;
1639 case 'set access':
1640 $vlan_name = $cmd['arg2'] == 1 ? 'Default' : "VLAN${cmd['arg2']}";
1641 $ret .= "configure vlan ${vlan_name} add ports ${cmd['arg1']} untagged\n";
1642 break;
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";
1646 break;
1647 case 'set mode':
1648 case 'begin configuration':
1649 case 'end configuration':
1650 break; // NOP
1651 case 'save configuration':
1652 $ret .= "save configuration\ny\n";
1653 break;
1654 case 'cite':
1655 $ret .= $cmd['arg1'];
1656 break;
1657 // query list
1658 case 'get8021q':
1659 $ret .= 'show configuration "vlan"' . "\n";
1660 break;
1661 case 'getlldpstatus':
1662 $ret .= "show lldp neighbors detailed\n";
1663 break;
1664 case 'getallconf':
1665 $ret .= "show configuration\n";
1666 break;
1667 default:
1668 throw new InvalidArgException ('opcode', $cmd['opcode']);
1669 }
1670 return $ret;
1671 }
1672
1673 function jun10TranslatePushQueue ($dummy_object_id, $queue, $vlan_names)
1674 {
1675 $ret = '';
1676
1677 foreach ($queue as $cmd)
1678 switch ($cmd['opcode'])
1679 {
1680 case 'create VLAN':
1681 $ret .= "set vlans VLAN${cmd['arg1']} vlan-id ${cmd['arg1']}\n";
1682 break;
1683 case 'destroy VLAN':
1684 if (isset ($vlan_names[$cmd['arg1']]))
1685 $ret .= "delete vlans " . $vlan_names[$cmd['arg1']] . "\n";
1686 break;
1687 case 'add allowed':
1688 case 'rem allowed':
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";
1694 else
1695 while (! empty ($cmd['vlans']))
1696 {
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";
1701 }
1702 break;
1703 case 'set native':
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";
1710 break;
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";
1718 break;
1719 case 'set access':
1720 $ret .= "set interfaces ${cmd['arg1']} unit 0 family ethernet-switching vlan members ${cmd['arg2']}\n";
1721 break;
1722 case 'unset access':
1723 $ret .= "delete interfaces ${cmd['arg1']} unit 0 family ethernet-switching vlan members\n";
1724 break;
1725 case 'set mode':
1726 $ret .= "set interfaces ${cmd['arg1']} unit 0 family ethernet-switching port-mode ${cmd['arg2']}\n";
1727 break;
1728 case 'begin configuration':
1729 $ret .= "configure exclusive\n";
1730 break;
1731 case 'end configuration':
1732 $ret .= "commit\n";
1733 $ret .= "rollback 0\n"; // discard all changes if commit failed
1734 break;
1735 case 'save configuration':
1736 break; // JunOS can`t apply configuration without saving it
1737 case 'cite':
1738 $ret .= $cmd['arg1'];
1739 break;
1740 // query list
1741 case 'get8021q':
1742 $ret .=
1743 'show vlans detail
1744 # END OF VLAN LIST
1745 show configuration groups
1746 # END OF GROUP LIST
1747 show configuration interfaces
1748 # END OF CONFIG
1749 ';
1750 break;
1751 case 'getallconf':
1752 $ret .= "show configuration\n";
1753 break;
1754 case 'getlldpstatus':
1755 $ret .= "show lldp neighbors\n";
1756 $ret .= "# object_id=$dummy_object_id";
1757 break;
1758 default:
1759 throw new InvalidArgException ('opcode', $cmd['opcode']);
1760 }
1761 return $ret;
1762 }
1763
1764 function ftos8TranslatePushQueue ($dummy_object_id, $queue, $vlan_names)
1765 {
1766 $ret = '';
1767 foreach ($queue as $cmd)
1768 switch ($cmd['opcode'])
1769 {
1770 case 'begin configuration':
1771 $ret .= "configure terminal\n";
1772 break;
1773 case 'end configuration':
1774 $ret .= "end\n";
1775 break;
1776 case 'save configuration':
1777 $ret .= "write memory\n";
1778 break;
1779 case 'cite':
1780 $ret .= $cmd['arg1'];
1781 break;
1782 case 'getlldpstatus':
1783 $ret .= "show lldp neighbors detail\n";
1784 break;
1785 case 'getportstatus':
1786 $ret .= "show interfaces status\n";
1787 break;
1788 case 'getmaclist':
1789 $ret .= "show mac-address-table dynamic\n";
1790 break;
1791 case 'get8021q':
1792 $ret .= "show running-config interface\n";
1793 break;
1794 case 'create VLAN':
1795 $ret .= "int vlan ${cmd['arg1']}\nexit\n";
1796 break;
1797 case 'destroy VLAN':
1798 $ret .= "no int vlan ${cmd['arg1']}\n";
1799 break;
1800 case 'rem allowed':
1801 while (! empty ($cmd['vlans']))
1802 {
1803 $vlan = array_shift ($cmd['vlans']);
1804 $ret .= "int vlan $vlan\n";
1805 $ret .= "no tagged ${cmd['port']}\n";
1806 $ret .= "exit\n";
1807 }
1808 break;
1809 case 'add allowed':
1810 while (! empty ($cmd['vlans']))
1811 {
1812 $vlan = array_shift ($cmd['vlans']);
1813 $ret .= "int vlan $vlan\n";
1814 $ret .= "tagged ${cmd['port']}\n";
1815 $ret .= "exit\n";
1816 }
1817 break;
1818 case 'unset native':
1819 $ret .= "int vlan ${cmd['arg2']}\n";
1820 $ret .= "no untagged ${cmd['arg1']}\n";
1821 $ret .= "tagged ${cmd['arg1']}\n";
1822 $ret .= "exit\n";
1823 break;
1824 case 'unset access':
1825 $ret .= "int vlan ${cmd['arg2']}\n";
1826 $ret .= "no untagged ${cmd['arg1']}\n";
1827 $ret .= "exit\n";
1828 break;
1829 case 'set native':
1830 $ret .= "int vlan ${cmd['arg2']}\n";
1831 $ret .= "no tagged ${cmd['arg1']}\n";
1832 $ret .= "untagged ${cmd['arg1']}\n";
1833 $ret .= "exit\n";
1834 break;
1835 case 'set access':
1836 $ret .= "int vlan ${cmd['arg2']}\n";
1837 $ret .= "untagged ${cmd['arg1']}\n";
1838 $ret .= "exit\n";
1839 break;
1840 case 'set mode':
1841 break;
1842 case 'getallconf':
1843 $ret .= "show running-config\n";
1844 break;
1845 default:
1846 throw new InvalidArgException ('opcode', $cmd['opcode']);
1847 }
1848 return $ret;
1849 }
1850
1851 function air12TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1852 {
1853 $ret = '';
1854 foreach ($queue as $cmd)
1855 switch ($cmd['opcode'])
1856 {
1857 case 'begin configuration':
1858 $ret .= "configure terminal\n";
1859 break;
1860 case 'end configuration':
1861 $ret .= "end\n";
1862 break;
1863 case 'save configuration':
1864 $ret .= "copy running-config startup-config\n\n";
1865 break;
1866 case 'cite':
1867 $ret .= $cmd['arg1'];
1868 break;
1869 case 'getcdpstatus':
1870 $ret .= "show cdp neighbors detail\n";
1871 break;
1872 case 'getallconf':
1873 $ret .= "show running-config\n";
1874 break;
1875 default:
1876 throw new InvalidArgException ('opcode', $cmd['opcode']);
1877 }
1878 return $ret;
1879 }
1880
1881 function eos4TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1882 {
1883 $ret = '';
1884 foreach ($queue as $cmd)
1885 switch ($cmd['opcode'])
1886 {
1887 case 'begin configuration':
1888 $ret .= "enable\nconfigure terminal\n";
1889 break;
1890 case 'end configuration':
1891 $ret .= "end\n";
1892 break;
1893 case 'save configuration':
1894 $ret .= "copy running-config startup-config\n\n";
1895 break;
1896 case 'create VLAN':
1897 $ret .= "vlan ${cmd['arg1']}\nexit\n";
1898 break;
1899 case 'destroy VLAN':
1900 if (isset ($vlan_names[$cmd['arg1']]))
1901 $ret .= "no vlan ${cmd['arg1']}\n";
1902 break;
1903 case 'set access':
1904 $ret .= "interface ${cmd['arg1']}\nswitchport access vlan ${cmd['arg2']}\nexit\n";
1905 break;
1906 case 'unset access':
1907 $ret .= "interface ${cmd['arg1']}\nno switchport access vlan\nexit\n";
1908 break;
1909 case 'set mode':
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";
1914 $ret .= "exit\n";
1915 break;
1916 case 'add allowed':
1917 case 'rem allowed':
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']}") .
1923 "\n";
1924 $ret .= "exit\n";
1925 break;
1926 case 'set native':
1927 $ret .= "interface ${cmd['arg1']}\nswitchport trunk native vlan ${cmd['arg2']}\nexit\n";
1928 break;
1929 case 'unset native':
1930 $ret .= "interface ${cmd['arg1']}\nswitchport trunk native vlan tag\nexit\n";
1931 break;
1932 case 'getlldpstatus':
1933 $ret .= "show lldp neighbors detail\n";
1934 break;
1935 case 'getportstatus':
1936 $ret .= "show interfaces status\n";
1937 break;
1938 case 'getmaclist':
1939 $ret .= "show mac-address-table dynamic\n";
1940 break;
1941 case 'cite':
1942 $ret .= $cmd['arg1'];
1943 break;
1944 case 'getallconf':
1945 case 'get8021q':
1946 $ret .= "show running-config\n";
1947 break;
1948 default:
1949 throw new InvalidArgException ('opcode', $cmd['opcode']);
1950 }
1951 return $ret;
1952 }
1953
1954 function ros11TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1955 {
1956 $ret = '';
1957 foreach ($queue as $cmd)
1958 switch ($cmd['opcode'])
1959 {
1960 case 'begin configuration':
1961 $ret .= "configure terminal\n";
1962 break;
1963 case 'end configuration':
1964 $ret .= "end\n";
1965 break;
1966 case 'save configuration':
1967 $ret .= "copy running-config startup-config\nY\n";
1968 break;
1969 case 'create VLAN':
1970 $ret .= "vlan database\nvlan ${cmd['arg1']}\nexit\n";
1971 break;
1972 case 'destroy VLAN':
1973 if (isset ($vlan_names[$cmd['arg1']]))
1974 $ret .= "vlan database\nno vlan ${cmd['arg1']}\nexit\n";
1975 break;
1976 case 'set access':
1977 $ret .= "interface ${cmd['arg1']}\nswitchport access vlan ${cmd['arg2']}\nexit\n";
1978 break;
1979 case 'unset access':
1980 $ret .= "interface ${cmd['arg1']}\nno switchport access vlan\nexit\n";
1981 break;
1982 case 'set mode':
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";
1987 $ret .= "exit\n";
1988 break;
1989 case 'add allowed':
1990 case 'rem allowed':
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";
1999 else
2000 $ordinary[] = $vid;
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']}") .
2005 "\n";
2006 $ret .= "exit\n";
2007 break;
2008 case 'set native':
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";
2013 else
2014 $ret .= "switchport trunk native vlan ${cmd['arg2']}\n";
2015 $ret .= "exit\n";
2016 break;
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";
2022 else
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";
2028 $ret .= "exit\n";
2029 break;
2030 case 'getlldpstatus':
2031 $ret .= "show lldp neighbors detail\n";
2032 break;
2033 case 'getportstatus':
2034 $ret .= "show interfaces status\n";
2035 break;
2036 case 'getmaclist':
2037 $ret .= "show mac address-table dynamic\n";
2038 break;
2039 case 'cite':
2040 $ret .= $cmd['arg1'];
2041 break;
2042 case 'getallconf':
2043 case 'get8021q':
2044 $ret .= "show running-config\n";
2045 break;
2046 default:
2047 throw new InvalidArgException ('opcode', $cmd['opcode']);
2048 }
2049 return $ret;
2050 }
2051
2052 function dlinkTranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
2053 {
2054 $ret = '';
2055 foreach ($queue as $cmd)
2056 switch ($cmd['opcode'])
2057 {
2058 case 'getportstatus':
2059 $ret .= "show ports\n";
2060 break;
2061 case 'getmaclist':
2062 $ret .= "show fdb\n";
2063 break;
2064 case 'get8021q':
2065 $ret .= "show vlan\n";
2066 break;
2067 case 'cite':
2068 $ret .= $cmd['arg1'];
2069 break;
2070 default:
2071 throw new InvalidArgException ('opcode', $cmd['opcode']);
2072 }
2073 return $ret;
2074 }
2075
2076 function linuxTranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
2077 {
2078 $ret = '';
2079 foreach ($queue as $cmd)
2080 switch ($cmd['opcode'])
2081 {
2082 case 'getportstatus':
2083 $ret .= "cd /sys/class/net && for d in eth*; do sudo /sbin/ethtool \$d; done\n";
2084 break;
2085 case 'getmaclist':
2086 $ret .= "sudo /usr/sbin/arp -an\n";
2087 break;
2088 case 'get8021q':
2089 $ret .= "sudo /sbin/ip -o a\n";
2090 break;
2091 case 'cite':
2092 $ret .= $cmd['arg1'];
2093 break;
2094 default:
2095 throw new InvalidArgException ('opcode', $cmd['opcode']);
2096 }
2097 return $ret;
2098 }
2099
2100 function xos12Read8021QConfig ($input)
2101 {
2102 $ret = array
2103 (
2104 'vlanlist' => array (1),
2105 'portdata' => array(),
2106 'portconfig' => array(),
2107 );
2108 foreach (explode ("\n", $input) as $line)
2109 {
2110 $matches = array();
2111 switch (TRUE)
2112 {
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]);
2116 break;
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];
2123 break;
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)
2132 {
2133 if (!array_key_exists ($port_name, $ret['portdata']))
2134 $ret['portdata'][$port_name] = array
2135 (
2136 'mode' => 'trunk',
2137 'allowed' => array(),
2138 'native' => 0,
2139 );
2140 $ret['portdata'][$port_name]['allowed'][] = $vlan_id;
2141 if ($matches[3] == 'untagged')
2142 $ret['portdata'][$port_name]['native'] = $vlan_id;
2143 }
2144 break;
2145 default:
2146 }
2147 }
2148 return $ret;
2149 }
2150
2151 function jun10Read8021QConfig ($input)
2152 {
2153 $ret = array
2154 (
2155 'vlanlist' => array (1),
2156 'vlannames' => array (1 => 'default'),
2157 'portdata' => array(),
2158 'portconfig' => array(),
2159 );
2160 $lines = explode ("\n", $input);
2161
2162 // get vlan list
2163 $vlans = array('default' => 1);
2164 $names = array();
2165 while (count ($lines))
2166 {
2167 $line = trim (array_shift ($lines));
2168 if (FALSE !== strpos ($line, '# END OF VLAN LIST'))
2169 break;
2170 if (preg_match ('/^VLAN: (.*), 802.1Q Tag: (\d+)/', $line, $m))
2171 {
2172 $ret['vlannames'][$m[2]] = $m[1];
2173 $vlans[$m[1]] = $m[2];
2174 }
2175 }
2176 $ret['vlanlist'] = array_values ($vlans);
2177
2178 // get config groups list - throw an exception if a group contains ether-switching config
2179 $current_group = NULL;
2180 while (count ($lines))
2181 {
2182 $line = array_shift ($lines);
2183 if (FALSE !== strpos ($line, '# END OF GROUP LIST'))
2184 break;
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");
2189 }
2190
2191 // get interfaces config
2192 $current = array
2193 (
2194 'is_range' => FALSE,
2195 'is_ethernet' => FALSE,
2196 'name' => NULL,
2197 'config' => NULL,
2198 'indent' => NULL,
2199 );
2200 while (count ($lines))
2201 {
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'])
2207 {
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']))
2214 {
2215 if ($current['config']['mode'] == 'access')
2216 $current['config']['allowed'] = array (1);
2217 else
2218 $current['config']['allowed'] = array();
2219 }
2220 if (
2221 $current['config']['mode'] == 'trunk' and
2222 $current['config']['native'] != 0 and
2223 ! in_array ($current['config']['native'], $current['config']['allowed'])
2224 )
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'];
2229 }
2230
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 (
2237 'mode' => NULL,
2238 'allowed' => NULL,
2239 'native' => NULL,
2240 'config' => array(),
2241 );
2242 $line_class = 'line-header';
2243 $current['indent'] = NULL;
2244 }
2245 }
2246 elseif (preg_match ('/^(\s+)family ethernet-switching\b/', $line, $m))
2247 {
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];
2252 }
2253 elseif (isset ($current['indent']) and $line == $current['indent'] . '}')
2254 $current['indent'] = NULL;
2255 elseif ($current['is_ethernet'] and isset ($current['indent']))
2256 {
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))
2263 {
2264 $members = array();
2265 foreach (explode (' ', $m[1]) as $item)
2266 {
2267 $item = trim ($item);
2268 if (preg_match ('/^(\d+)(?:-(\d+))?$/', $item, $m))
2269 {
2270 if (isset ($m[2]) and $m[2] > $m[1])
2271 $members = array_merge (range ($m[1], $m[2]), $members);
2272 else
2273 $members[] = $m[1];
2274 }
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);
2279 }
2280 $current['config']['allowed'] = array_unique ($members);
2281 }
2282 else
2283 $line_class = 'line-other';
2284 }
2285 if (isset ($current['name']))
2286 {
2287 if ($line == '}')
2288 $line_class = 'line-header';
2289 $ret['portconfig'][$current['name']][] = array ('type' => $line_class, 'line' => $line);
2290 }
2291 }
2292
2293 return $ret;
2294 }
2295
2296 function ftos8Read8021QConfig ($input)
2297 {
2298 $ret = array
2299 (
2300 'vlanlist' => array (),
2301 'vlannames' => array (),
2302 'portdata' => array(),
2303 'portconfig' => array(),
2304 );
2305 $iface = NULL;
2306 foreach (explode ("\n", $input) as $line)
2307 {
2308 if (preg_match ('/^interface (\S.*?)\s*$/', $line, $m))
2309 {
2310 $iface = array
2311 (
2312 'name' => shortenIfName (str_replace (' ', '', $m[1])),
2313 'lines' => array(),
2314 'is_switched' => FALSE,
2315 'vlan' => 1 === preg_match ('/^Vlan (\d+)$/', $m[1], $m2) ? $m2[1] : 0,
2316 );
2317 }
2318 if (isset ($iface))
2319 {
2320 $iface['lines'][] = array ('type' => 'line-other', 'line' => $line);
2321
2322 if ($line == ' switchport')
2323 {
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
2331 (
2332 'allowed' => array (),
2333 'native' => 0,
2334 'mode' => 'access',
2335 );
2336 }
2337 elseif ($line == '!')
2338 {
2339 $ret['portconfig'][$iface['name']] = $iface['lines'];
2340 unset ($iface);
2341 }
2342 elseif ($iface['vlan'])
2343 {
2344 $ret['vlanlist'][] = $iface['vlan'];
2345 if (preg_match ('/^[ !](un)?tagged (\S+) (\S+)/', $line, $m))
2346 {
2347 list ($untagged, $pref, $list) = array ($m[1], $m[2], $m[3]);
2348 if (preg_match ('#^(\d+/)#', $list, $m))
2349 {
2350 $pref .= $m[1];
2351 $list = substr ($list, strlen ($m[1]));
2352 }
2353 foreach (explode (',', $list) as $range)
2354 {
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++)
2360 {
2361 $if_name = shortenIfName ($pref . $i);
2362 $ret['portdata'][$if_name]['allowed'][] = $iface['vlan'];
2363 if ($untagged)
2364 $ret['portdata'][$if_name]['native'] = $iface['vlan'];
2365 else
2366 $ret['portdata'][$if_name]['mode'] = 'trunk';
2367 }
2368 }
2369 }
2370 }
2371 }
2372 }
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';
2380 return $ret;
2381 }
2382
2383 function eos4BuildSwitchport ($mined)
2384 {
2385 switch (TRUE)
2386 {
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;
2391 return array
2392 (
2393 'mode' => 'access',
2394 'allowed' => array ($mined['access']),
2395 'native' => $mined['access'],
2396 );
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);
2402 return array
2403 (
2404 'mode' => 'trunk',
2405 'allowed' => $mined['allowed'],
2406 'native' => $mined['native'],
2407 );
2408 case $mined['mode'] == 'none':
2409 return array
2410 (
2411 'mode' => 'none',
2412 'allowed' => array(),
2413 'native' => 0,
2414 );
2415 default:
2416 throw new RackTablesError ('malformed switchport data', RackTablesError::INTERNAL);
2417 }
2418 }
2419
2420 function eos4Read8021QConfig ($input)
2421 {
2422 $ret = array
2423 (
2424 'vlanlist' => array (VLAN_DFL_ID),
2425 'vlannames' => array (),
2426 'portdata' => array(),
2427 'portconfig' => array(),
2428 );
2429 foreach (explode ("\n", $input) as $line)
2430 {
2431 $matches = array();
2432 if (! array_key_exists ('current', $ret))
2433 {
2434 switch (TRUE)
2435 {
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;
2440 break;
2441 case preg_match ('/^interface ((Ethernet|Port-Channel)\d+)$/', $line, $matches):
2442 $portname = shortenIfName ($matches[1]);
2443 $ret['current'] = array
2444 (
2445 'port_name' => $portname,
2446 'mode' => 'access',
2447 'default1' => TRUE,
2448 );
2449 $ret['portconfig'][$portname][] = array ('type' => 'line-header', 'line' => $line);
2450 break;
2451 }
2452 continue;
2453 }
2454 # $portname == $ret['current']['port_name']
2455 switch (TRUE)
2456 {
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);
2462 break;
2463 case $line == ' switchport mode trunk':
2464 $ret['current']['mode'] = 'trunk';
2465 $ret['portconfig'][$portname][] = array ('type' => 'line-8021q', 'line' => $line);
2466 break;
2467 case $line == ' switchport trunk native vlan tag':
2468 $ret['current']['default1'] = FALSE;
2469 $ret['portconfig'][$portname][] = array ('type' => 'line-8021q', 'line' => $line);
2470 break;
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);
2474 break;
2475 case $line == ' switchport trunk allowed vlan none':
2476 $ret['current']['allowed'] = array();
2477 $ret['portconfig'][$portname][] = array ('type' => 'line-8021q', 'line' => $line);
2478 break;
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);
2482 break;
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);
2486 break;
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);
2490 break;
2491 case $line == '!': # end of interface section
2492 if (! array_key_exists ('current', $ret))
2493 break;
2494 $ret['portdata'][$ret['current']['port_name']] = eos4BuildSwitchport ($ret['current']);
2495 unset ($ret['current']);
2496 $ret['portconfig'][$portname][] = array ('type' => 'line-header', 'line' => $line);
2497 break;
2498 default:
2499 $ret['portconfig'][$portname][] = array ('type' => 'line-other', 'line' => $line);
2500 break;
2501 }
2502 }
2503 unset ($ret['current']);
2504 return $ret;
2505 }
2506
2507 # ROS 1.1 config file sytax is derived from that of IOS, but has a few configuration
2508 # traits regarding 802.1Q.
2509 #
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.
2516 #
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
2522 # as follows:
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)
2529 #
2530 # ROS CLI allows configuring a port in access mode without an access VLAN. Such
2531 # configuration is not supported.
2532 function ros11Read8021QConfig ($input)
2533 {
2534 $ret = array
2535 (
2536 'vlanlist' => array (VLAN_DFL_ID),
2537 'portdata' => array(),
2538 'portconfig' => array(),
2539 );
2540 $nextfunc = 'ros11-get8021q-scantop';
2541 global $breedfunc;
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)
2546 {
2547 if (! array_key_exists ('mode', $port))
2548 throw new RTGatewayError ("unsupported configuration of port ${portname}");
2549 if
2550 (
2551 ! array_key_exists ('switchport forbidden default-vlan', $port)
2552 && array_key_exists ('switchport default-vlan tagged', $port)
2553 )
2554 $ret['portdata'][$portname]['allowed'][] = VLAN_DFL_ID;
2555 elseif
2556 (
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)
2560 )
2561 {
2562 $ret['portdata'][$portname]['allowed'][] = VLAN_DFL_ID;
2563 $ret['portdata'][$portname]['native'] = VLAN_DFL_ID;
2564 }
2565 foreach (array ('switchport forbidden default-vlan', 'switchport default-vlan tagged') as $line)
2566 if (array_key_exists ($line, $port))
2567 {
2568 unset ($ret['portdata'][$portname][$line]);
2569 $work['portconfig'][$portname][] = array ('type' => 'line-8021q', 'line' => $line);
2570 }
2571 }
2572 return $ret;
2573 }
2574
2575 function iosxr4TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
2576 {
2577 $ret = '';
2578 foreach ($queue as $cmd)
2579 switch ($cmd['opcode'])
2580 {
2581 case 'cite':
2582 $ret .= $cmd['arg1'];
2583 break;
2584 case 'getallconf':
2585 $ret .= "show running-config\n";
2586 break;
2587 case 'getlldpstatus':
2588 $ret .= "show lldp neighbors\n";
2589 break;
2590 default:
2591 throw new InvalidArgException ('opcode', $cmd['opcode']);
2592 }
2593 return $ret;
2594 }
2595
2596 function ucsTranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
2597 {
2598 $ret = '';
2599 foreach ($queue as $cmd)
2600 switch ($cmd['opcode'])
2601 {
2602 case 'cite':
2603 $ret .= $cmd['arg1'];
2604 break;
2605 case 'getinventory':
2606 $ret .= "getmo\n";
2607 break;
2608 default:
2609 throw new InvalidArgException ('opcode', $cmd['opcode']);
2610 }
2611 return $ret;
2612 }
2613
2614 function ros11Read8021QScanTop (&$work, $line)
2615 {
2616 switch (TRUE)
2617 {
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';
2626 default:
2627 return 'ros11-get8021q-scantop';
2628 }
2629 }
2630
2631 function ros11Read8021QVLANDatabase (&$work, $line)
2632 {
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';
2637 }
2638
2639 function ros11Read8021QPorts (&$work, $line)
2640 {
2641 switch (TRUE)
2642 {
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)
2680 {
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
2688 {
2689 if (! array_key_exists ('allowed', $work['portdata'][$portname]))
2690 $work['portdata'][$portname]['allowed'] = array();
2691 if (count ($val))
2692 $work['portdata'][$portname]['allowed'][] = current ($val);
2693 }
2694 }
2695 unset ($work['current']);
2696 return 'ros11-get8021q-scantop';
2697 default:
2698 $work['current']['lines'][] = array ('type' => 'line-other', 'line' => $line);
2699 return 'ros11-get8021q-readports';
2700 }
2701 }
2702
2703 function ciscoReadInterfaceStatus ($text)
2704 {
2705 $result = array();
2706 $state = 'headerSearch';
2707 foreach (explode ("\n", $text) as $line)
2708 {
2709 switch ($state)
2710 {
2711 case 'headerSearch':
2712 if (preg_match('/^Port\s+Name\s+Status/', $line))
2713 {
2714 $name_field_borders = getColumnCoordinates($line, 'Name');
2715 if (isset ($name_field_borders['from']))
2716 $state = 'readPort';
2717 }
2718 break;
2719 case '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)
2724 break;
2725 list ($status_raw, $vlan, $duplex, $speed) = $field_list;
2726 if ($status_raw == 'connected' || $status_raw == 'up')
2727 $status = 'up';
2728 elseif (0 === strpos ($status_raw, 'notconn') || $status_raw == 'down' || $status_raw == 'sfpAbsent')
2729 $status = 'down';
2730 else
2731 $status = 'disabled';
2732 $result[$portname] = array
2733 (
2734 'status' => $status,
2735 'speed' => $speed,
2736 'duplex' => $duplex,
2737 );
2738 break;
2739 }
2740 }
2741 return $result;
2742 }
2743
2744 function vrpReadInterfaceStatus ($text)
2745 {
2746 $result = array();
2747 $state = 'headerSearch';
2748 foreach (explode ("\n", $text) as $line)
2749 {
2750 switch ($state)
2751 {
2752 case 'headerSearch':
2753 if (preg_match('/^Interface\s+Phy\w*\s+Protocol/i', $line))
2754 $state = 'readPort';
2755 break;
2756 case 'readPort':
2757 if (preg_match('/[\$><\]]/', $line))
2758 break 2;
2759 $field_list = preg_split('/\s+/', $line);
2760 if (count ($field_list) < 7)
2761 break;
2762 if ($field_list[0] == '')
2763 array_shift ($field_list);
2764 list ($portname, $status_raw) = $field_list;
2765 $portname = shortenIfName ($portname);
2766
2767 if ($status_raw == 'up' || $status_raw == 'down')
2768 $status = $status_raw;
2769 else
2770 $status = 'disabled';
2771 $result[$portname] = array
2772 (
2773 'status' => $status,
2774 );
2775 break;
2776 }
2777 }
2778 return $result;
2779 }
2780
2781 /*
2782 D-Link "show ports" output sample
2783 =================================
2784
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
2789 Auto
2790 ...
2791 26(C) Enabled Auto/Disabled LinkDown Enabled
2792 Auto
2793 26(F) Enabled Auto/Disabled LinkDown Enabled
2794 */
2795 function dlinkReadInterfaceStatus ($text)
2796 {
2797 $result = array();
2798 foreach (preg_split ("/\n\r?/", $text) as $line)
2799 {
2800 if (!preg_match ('/^\s*\d+/', $line))
2801 continue;
2802 $w = preg_split ('/\s+/', strtolower($line));
2803 if (count($w) != 5)
2804 continue;
2805 $port_name = $w[0];
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'=>'');
2810 else
2811 {
2812 $s = split('/', $w[3]);
2813 $result[$portname] = array ('status'=>'up', 'speed'=>$s[0], 'duplex'=>$s[1]);
2814 }
2815 }
2816 return $result;
2817 }
2818
2819 /*
2820 Linux "ethtool" output sample
2821 =============================
2822
2823 Settings for eth0:
2824 Supported ports: [ TP ]
2825 Supported link modes: 10baseT/Half 10baseT/Full
2826 100baseT/Half 100baseT/Full
2827 1000baseT/Full
2828 Supports auto-negotiation: Yes
2829 Advertised link modes: 10baseT/Half 10baseT/Full
2830 100baseT/Half 100baseT/Full
2831 1000baseT/Full
2832 Advertised pause frame use: No
2833 Advertised auto-negotiation: Yes
2834 Speed: 1000Mb/s
2835 Duplex: Full
2836 Port: Twisted Pair
2837 PHYAD: 2
2838 Transceiver: internal
2839 Auto-negotiation: on
2840 MDI-X: off
2841 Supports Wake-on: pumbg
2842 Wake-on: g
2843 Current message level: 0x00000001 (1)
2844 Link detected: yes
2845
2846 Settings for eth1:
2847 Supported ports: [ TP ]
2848 Supported link modes: 10baseT/Half 10baseT/Full
2849 100baseT/Half 100baseT/Full
2850 1000baseT/Full
2851 Supports auto-negotiation: Yes
2852 Advertised link modes: 10baseT/Half 10baseT/Full
2853 100baseT/Half 100baseT/Full
2854 1000baseT/Full
2855 Advertised pause frame use: No
2856 Advertised auto-negotiation: Yes
2857 Speed: Unknown!
2858 Duplex: Unknown! (255)
2859 Port: Twisted Pair
2860 PHYAD: 1
2861 Transceiver: internal
2862 Auto-negotiation: on
2863 MDI-X: Unknown
2864 Supports Wake-on: pumbg
2865 Wake-on: g
2866 Current message level: 0x00000001 (1)
2867 Link detected: no
2868 */
2869 function linuxReadInterfaceStatus ($text)
2870 {
2871 $result = array();
2872 $iface = '';
2873 $status = 'down';
2874 $speed = '0';
2875 $duplex = '';
2876 foreach (explode ("\n", $text) as $line)
2877 {
2878 $m = array();
2879 if (preg_match ('/^[^\s].* (.*):$/', $line, $m))
2880 {
2881 if ($iface !== '')
2882 $result[$iface] = array ('status' => $status, 'speed' => $speed, 'duplex' => $duplex);
2883 $iface = $m[1];
2884 $status = 'down';