bugfix: Native vlan on Huawei trunk ports no longer gets to the allowed vlans list
[racktables-incomplete-works] / wwwroot / inc / deviceconfig.php
CommitLineData
b519e37e
DO
1<?php
2
cddbb9fd
DO
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
b519e37e
DO
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.
10function 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']))
3af9b06f 24 $ret[shortenIfName ($matches[1])][] = array
b519e37e
DO
25 (
26 'device' => $ret['current']['device'],
023df3f8 27 'port' => $matches[2],
b519e37e
DO
28 );
29 unset ($ret['current']);
30 break;
31 default:
32 }
33 }
34 unset ($ret['current']);
35 return $ret;
36}
37
38function 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
34940566 50 $matches = preg_split ('/\s+/', trim ($line));
dec748f6 51
b519e37e
DO
52 switch (count ($matches))
53 {
54 case 5:
55 list ($remote_name, $local_port, $ttl, $caps, $remote_port) = $matches;
3af9b06f 56 $local_port = shortenIfName ($local_port);
b519e37e
DO
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
69function 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):
3af9b06f 78 $ret['current']['local_port'] = shortenIfName ($matches[1]);
b519e37e
DO
79 break;
80 case preg_match ('/^ Port ID : "(.+)"$/', $line, $matches):
023df3f8 81 $ret['current']['remote_port'] = $matches[1];
b519e37e
DO
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
d89632f6 103function vrpReadLLDPStatus ($input)
b519e37e
DO
104{
105 $ret = array();
e610e9d5
AA
106 $valid_subtypes = array
107 (
108 'interfaceName',
04682db0 109 'Interface Name',
e610e9d5 110 'interfaceAlias',
04682db0 111 'Interface Alias',
7b4ba2ed 112 'local',
04682db0 113 'Local',
e610e9d5 114 );
b519e37e
DO
115 foreach (explode ("\n", $input) as $line)
116 {
117 $matches = array();
118 switch (TRUE)
119 {
39244f29 120 case preg_match ('/^(.+) has \d+ neighbor(\(s\)|s):$/', $line, $matches):
04682db0 121 $ret['current']['local_port'] = shortenIfName (trim ($matches[1]));
b519e37e 122 break;
04682db0
AA
123 case preg_match ('/^Port ?ID ?(?:sub)?type\s*:\s*(.*)$/i', $line, $matches):
124 $ret['current']['PortIdSubtype'] = trim ($matches[1]);
b519e37e 125 break;
e610e9d5 126 case preg_match ('/^Port ?ID\s*:\s*(.+)$/i', $line, $matches):
04682db0 127 $ret['current']['PortId'] = trim ($matches[1]);
b519e37e 128 break;
4977f0cc 129 case preg_match ('/^Port description\s*:\s*(.*)$/i', $line, $matches):
04682db0 130 $ret['current']['PortDescription'] = trim ($matches[1]);
4977f0cc 131 break;
e610e9d5 132 case preg_match ('/^Sys(?:tem)? ?name\s*:\s*(.+)$/i', $line, $matches):
b519e37e
DO
133 if
134 (
135 array_key_exists ('current', $ret) and
136 array_key_exists ('PortIdSubtype', $ret['current']) and
b519e37e
DO
137 array_key_exists ('local_port', $ret['current'])
138 )
4977f0cc
AA
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 (
04682db0 148 'device' => trim ($matches[1]),
023df3f8 149 'port' => $port,
4977f0cc
AA
150 );
151 }
b519e37e
DO
152 unset ($ret['current']);
153 break;
154 default:
155 }
156 }
157 unset ($ret['current']);
158 return $ret;
159}
160
4cd9fe27
DO
161function ftos8ReadLLDPStatus ($input)
162{
163 $ret = array();
164 $valid_subtypes = array
165 (
166 'Interface name (5)',
167 'Interface Alias (1)',
9294ebe6 168 'Locally assigned (7)',
4cd9fe27
DO
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],
023df3f8 196 'port' => $ret['current']['remote_port'],
4cd9fe27
DO
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
85703049
DO
208function 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):
3af9b06f 221 $ret['current']['local_port'] = shortenIfName ($matches[1]);
85703049
DO
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],
023df3f8 241 'port' => $ret['current']['remote_port'],
85703049
DO
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
919bb8b6
DO
253function 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):
3af9b06f 261 $ret['current']['local_port'] = shortenIfName ($m[1]);
919bb8b6
DO
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],
023df3f8 276 'port' => $ret['current']['remote_port'],
919bb8b6
DO
277 );
278 unset ($ret['current']['remote_port']);
279 break;
280 default: # NOP
281 }
282 }
283 unset ($ret['current']);
284 return $ret;
285}
286
b519e37e
DO
287function ios12ReadVLANConfig ($input)
288{
289 $ret = array
290 (
291 'vlanlist' => array(),
292 'portdata' => array(),
fa9e68cd 293 'portconfig' => array(),
b519e37e 294 );
cfc28da9 295 $schema = $ret;
40ee43a1
AA
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");
cfc28da9 298
b2148be4 299 global $breedfunc;
cfc28da9 300 $nextfunc = 'ios12-get8021q-swports';
b519e37e 301 foreach (explode ("\n", $input) as $line)
b2148be4 302 $nextfunc = $breedfunc[$nextfunc] ($ret, $line);
cfc28da9
AA
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]);
b519e37e
DO
308 return $ret;
309}
310
311function ios12ScanTopLevel (&$work, $line)
312{
313 $matches = array();
314 switch (TRUE)
315 {
cfc28da9 316 case (preg_match ('@^interface ((Ethernet|FastEthernet|GigabitEthernet|TenGigabitEthernet|[Pp]ort-channel)[[:digit:]]+(/[[:digit:]]+)*)$@', $line, $matches)):
3af9b06f 317 $port_name = shortenIfName ($matches[1]);
fa9e68cd
AA
318 $work['current'] = array ('port_name' => $port_name);
319 $work['portconfig'][$port_name][] = array ('type' => 'line-header', 'line' => $line);
b2148be4 320 return 'ios12-get8021q-readport'; // switch to interface block reading
b519e37e 321 case (preg_match ('/^VLAN Name Status Ports$/', $line, $matches)):
b2148be4 322 return 'ios12-get8021q-readvlan';
b519e37e 323 default:
b2148be4 324 return 'ios12-get8021q-top'; // continue scan
b519e37e
DO
325 }
326}
327
cfc28da9
AA
328function ios12ReadSwitchPortList (&$work, $line)
329{
330 if (0 < strpos ($line, '! END OF SWITCHPORTS'))
331 return 'ios12-get8021q-top';
8586a5e8 332 if (preg_match ('@^(?:\s*|vdc .*)Name:\s+(\S+)@', $line, $m))
cfc28da9
AA
333 $work['current_switchport'] = $m[1];
334 elseif (preg_match ('@^\s*Switchport:\s+(Enabled)@', $line, $m) && isset ($work['current_switchport']))
335 {
3af9b06f 336 $work['switchports'][] = shortenIfName ($work['current_switchport']);
cfc28da9
AA
337 unset ($work['current_switchport']);
338 }
339 return 'ios12-get8021q-swports';
340}
341
b519e37e
DO
342function ios12PickSwitchportCommand (&$work, $line)
343{
fa9e68cd 344 $port_name = $work['current']['port_name'];
cfc28da9 345 if (! strlen ($line) || $line[0] != ' ') // end of interface section
b519e37e 346 {
fa9e68cd 347 $work['portconfig'][$port_name][] = array ('type' => 'line-header', 'line' => $line);
cfc28da9 348
b519e37e 349 // save work, if it makes sense
cfc28da9
AA
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 }
999eaf31 357 switch (@$work['current']['mode'])
b519e37e
DO
358 {
359 case 'access':
360 if (!array_key_exists ('access vlan', $work['current']))
361 $work['current']['access vlan'] = 1;
fa9e68cd 362 $work['portdata'][$port_name] = array
b519e37e
DO
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;
fa9e68cd 381 $work['portdata'][$port_name] = array
b519e37e
DO
382 (
383 'mode' => 'trunk',
384 'allowed' => $work['current']['trunk allowed vlan'],
385 'native' => $effective_native,
386 );
387 break;
388 case 'SKIP':
cfc28da9 389 case 'fex-fabric': // associated port-channel
b519e37e 390 case 'IP':
cfc28da9 391 break;
b519e37e
DO
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.
fa9e68cd 397 $work['portdata'][$port_name] = array
b519e37e
DO
398 (
399 'mode' => 'none',
400 'allowed' => array(),
401 'native' => 0,
402 );
403 break;
404 }
b519e37e 405 unset ($work['current']);
b2148be4 406 return 'ios12-get8021q-top';
b519e37e
DO
407 }
408 // not yet
409 $matches = array();
410 $line_class = 'line-8021q';
411 switch (TRUE)
412 {
cfc28da9 413 case (preg_match ('@^\s+switchport mode (.+)$@', $line, $matches)):
b519e37e
DO
414 $work['current']['mode'] = $matches[1];
415 break;
cfc28da9 416 case (preg_match ('@^\s+switchport access vlan (.+)$@', $line, $matches)):
b519e37e
DO
417 $work['current']['access vlan'] = $matches[1];
418 break;
cfc28da9 419 case (preg_match ('@^\s+switchport trunk native vlan (.+)$@', $line, $matches)):
b519e37e
DO
420 $work['current']['trunk native vlan'] = $matches[1];
421 break;
cfc28da9 422 case (preg_match ('@^\s+switchport trunk allowed vlan add (.+)$@', $line, $matches)):
b519e37e
DO
423 $work['current']['trunk allowed vlan'] = array_merge
424 (
425 $work['current']['trunk allowed vlan'],
426 iosParseVLANString ($matches[1])
427 );
428 break;
cfc28da9 429 case preg_match ('@^\s+switchport trunk allowed vlan none$@', $line, $matches):
e60e6798
DO
430 $work['current']['trunk allowed vlan'] = array();
431 break;
cfc28da9 432 case (preg_match ('@^\s+switchport trunk allowed vlan (.+)$@', $line, $matches)):
b519e37e
DO
433 $work['current']['trunk allowed vlan'] = iosParseVLANString ($matches[1]);
434 break;
cfc28da9 435 case preg_match ('@^\s+channel-group @', $line):
b519e37e
DO
436 // port-channel subinterface config follows that of the master interface
437 $work['current']['mode'] = 'SKIP';
438 break;
cfc28da9 439 case preg_match ('@^\s+ip address @', $line):
b519e37e
DO
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 }
fa9e68cd 446 $work['portconfig'][$port_name][] = array ('type' => $line_class, 'line' => $line);
b2148be4 447 return 'ios12-get8021q-readport';
b519e37e
DO
448}
449
450function ios12PickVLANCommand (&$work, $line)
451{
452 $matches = array();
453 switch (TRUE)
454 {
b519e37e 455 case (preg_match ('@! END OF VLAN LIST$@', $line)):
b2148be4 456 return 'ios12-get8021q-top';
b519e37e 457 case (preg_match ('@^([[:digit:]]+) {1,4}.{32} active @', $line, $matches)):
cb021344 458 $work['vlanlist'][] = $matches[1];
b519e37e
DO
459 break;
460 default:
461 }
b2148be4 462 return 'ios12-get8021q-readvlan';
b519e37e
DO
463}
464
465// Another finite automata to read a dialect of Foundry configuration.
466function fdry5ReadVLANConfig ($input)
467{
468 $ret = array
469 (
470 'vlanlist' => array(),
471 'portdata' => array(),
fa9e68cd 472 'portconfig' => array(),
b519e37e 473 );
b2148be4
DO
474 global $breedfunc;
475 $nextfunc = 'fdry5-get8021q-top';
b519e37e 476 foreach (explode ("\n", $input) as $line)
b2148be4 477 $nextfunc = $breedfunc[$nextfunc] ($ret, $line);
b519e37e
DO
478 return $ret;
479}
480
481function 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]);
b2148be4 490 return 'fdry5-get8021q-readvlan';
b519e37e 491 case (preg_match ('@^interface ethernet ([[:digit:]]+/[[:digit:]]+/[[:digit:]]+)$@', $line, $matches)):
fa9e68cd
AA
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);
b2148be4 495 return 'fdry5-get8021q-readport';
b519e37e 496 default:
b2148be4 497 return 'fdry5-get8021q-top';
b519e37e
DO
498 }
499}
500
501function fdry5PickVLANSubcommand (&$work, $line)
502{
503 if ($line[0] != ' ') // end of VLAN section
504 {
505 unset ($work['current']);
b2148be4 506 return 'fdry5-get8021q-top';
b519e37e
DO
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 }
b2148be4 551 return 'fdry5-get8021q-readvlan';
b519e37e
DO
552}
553
554function fdry5PickInterfaceSubcommand (&$work, $line)
555{
fa9e68cd 556 $port_name = $work['current']['port_name'];
b519e37e
DO
557 if ($line[0] != ' ') // end of interface section
558 {
fa9e68cd 559 $work['portconfig'][$port_name][] = array ('type' => 'line-header', 'line' => $line);
b519e37e
DO
560 if (array_key_exists ('dual-mode', $work['current']))
561 {
fa9e68cd 562 if (array_key_exists ($port_name, $work['portdata']))
b519e37e 563 // update existing record
fa9e68cd 564 $work['portdata'][$port_name]['native'] = $work['current']['dual-mode'];
b519e37e
DO
565 else
566 // add new
fa9e68cd 567 $work['portdata'][$port_name] = array
b519e37e
DO
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
fa9e68cd 575 $work['portdata'][$port_name]['mode'] = 'trunk';
b519e37e
DO
576 }
577 unset ($work['current']);
b2148be4 578 return 'fdry5-get8021q-top';
b519e37e
DO
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 }
fa9e68cd 590 $work['portconfig'][$port_name][] = array ('type' => 'line-other', 'line' => $line);
b2148be4 591 return 'fdry5-get8021q-readport';
b519e37e
DO
592}
593
3af65689
DO
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
b519e37e
DO
598function 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: // ???
3af65689 618 throw new InvalidArgException ('string', $string, 'format mismatch');
b519e37e
DO
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).
631function 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
919bb8b6
DO
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
648function 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
b519e37e
DO
668// an implementation for Huawei syntax
669function vrp53ReadVLANConfig ($input)
670{
671 $ret = array
672 (
673 'vlanlist' => array(),
674 'portdata' => array(),
fa9e68cd 675 'portconfig' => array(),
b519e37e 676 );
b2148be4
DO
677 global $breedfunc;
678 $nextfunc = 'vrp53-get8021q-top';
b519e37e 679 foreach (explode ("\n", $input) as $line)
b2148be4 680 $nextfunc = $breedfunc[$nextfunc] ($ret, $line);
b519e37e
DO
681 return $ret;
682}
683
684function 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;
b2148be4 692 return 'vrp53-get8021q-top';
d240e9b2 693 case (preg_match ('@^interface ((Ethernet|GigabitEthernet|XGigabitEthernet|Eth-Trunk)([[:digit:]]+(/[[:digit:]]+)*))$@', $line, $matches)):
3af9b06f 694 $port_name = shortenIfName ($matches[1]);
fa9e68cd
AA
695 $work['current'] = array ('port_name' => $port_name);
696 $work['portconfig'][$port_name][] = array ('type' => 'line-header', 'line' => $line);
b2148be4 697 return 'vrp53-get8021q-readport';
b519e37e 698 default:
b2148be4 699 return 'vrp53-get8021q-top';
b519e37e
DO
700 }
701}
702
3af65689
DO
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 ...
b519e37e
DO
705function vrp53ParseVLANString ($string)
706{
707 $string = preg_replace ('/ to /', '-', $string);
708 $string = preg_replace ('/ /', ',', $string);
709 return iosParseVLANString ($string);
710}
711
712function vrp53PickInterfaceSubcommand (&$work, $line)
713{
fa9e68cd 714 $port_name = $work['current']['port_name'];
b519e37e
DO
715 if ($line[0] == '#') // end of interface section
716 {
fa9e68cd 717 $work['portconfig'][$port_name][] = array ('type' => 'line-header', 'line' => $line);
b519e37e
DO
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.
fa9e68cd 731 $work['portdata'][$port_name] =
b519e37e
DO
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':
fa9e68cd 745 $work['portdata'][$port_name] = array
b519e37e
DO
746 (
747 'allowed' => $work['current']['allowed'],
748 'native' => 0,
749 'mode' => 'trunk',
750 );
751 break;
752 case 'hybrid':
fa9e68cd 753 $work['portdata'][$port_name] = array
b519e37e
DO
754 (
755 'allowed' => $work['current']['allowed'],
26ac34ff 756 'native' => in_array ($work['current']['native'], $work['current']['allowed']) ? $work['current']['native'] : 0,
b519e37e
DO
757 'mode' => 'trunk',
758 );
759 break;
8fda53de 760 case 'SKIP':
b519e37e
DO
761 default: // dot1q-tunnel ?
762 }
b519e37e 763 unset ($work['current']);
b2148be4 764 return 'vrp53-get8021q-top';
b519e37e
DO
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();
b519e37e
DO
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;
8fda53de
AA
785 case preg_match ('/^\s*eth-trunk \d+/', $line):
786 $work['current']['link-type'] = 'SKIP';
787 break;
b519e37e
DO
788 default: // nom-nom
789 $line_class = 'line-other';
790 }
fa9e68cd 791 $work['portconfig'][$port_name][] = array('type' => $line_class, 'line' => $line);
b2148be4 792 return 'vrp53-get8021q-readport';
b519e37e
DO
793}
794
795function vrp55Read8021QConfig ($input)
796{
797 $ret = array
798 (
799 'vlanlist' => array (1), // VRP 5.50 hides VLAN1 from config text
800 'portdata' => array(),
fa9e68cd 801 'portconfig' => array(),
b519e37e
DO
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;
d240e9b2 815 case (preg_match ('@^interface ((Ethernet|GigabitEthernet|XGigabitEthernet|Eth-Trunk)([[:digit:]]+(/[[:digit:]]+)*))$@', $line, $matches)):
3af9b06f 816 $port_name = shortenIfName ($matches[1]);
ce919b6e
AA
817 $ret['current'] = array
818 (
819 'port_name' => $port_name,
820 'allowed' => array (VLAN_DFL_ID),
821 'native' => VLAN_DFL_ID,
822 );
fa9e68cd 823 $ret['portconfig'][$port_name][] = array ('type' => 'line-header', 'line' => $line);
b519e37e
DO
824 break;
825 }
826 continue;
827 }
fa9e68cd 828 $port_name = $ret['current']['port_name'];
b519e37e
DO
829 // inside an interface block
830 $line_class = 'line-8021q';
831 switch (TRUE)
832 {
833 case preg_match ('/^ port (link-type )?hybrid /', $line):
fa9e68cd 834 throw new RTGatewayError ("unsupported hybrid link-type for $port_name: ${line}");
b519e37e
DO
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
75e7c0c6 839 // VRP is known to filter off clauses that don't make sense for
b519e37e
DO
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];
b519e37e
DO
845 break;
846 case preg_match ('/^ port trunk allow-pass vlan (.+)$/', $line, $matches):
b519e37e
DO
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;
ce919b6e
AA
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;
b519e37e
DO
854 case $line == ' undo portswitch':
855 case preg_match ('/^ ip address /', $line):
e12eb8c2 856 case preg_match ('/^ service type /', $line):
b519e37e
DO
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
fa9e68cd 863 $line_class = 'line-header';
b519e37e 864 if (!array_key_exists ('link-type', $ret['current']))
ff77af61 865 $ret['current']['link-type'] = 'hybrid';
b519e37e
DO
866 switch ($ret['current']['link-type'])
867 {
868 case 'access':
869 // In VRP 5.50 an access port has default VLAN ID == 1
fa9e68cd 870 $ret['portdata'][$port_name] =
b519e37e
DO
871 $ret['current']['native'] ? array
872 (
873 'mode' => 'access',
ce919b6e 874 'allowed' => array ($ret['current']['native']),
b519e37e
DO
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':
fa9e68cd 884 $ret['portdata'][$port_name] = array
b519e37e
DO
885 (
886 'mode' => 'trunk',
887 'allowed' => $ret['current']['allowed'],
ce919b6e 888 'native' => in_array ($ret['current']['native'], $ret['current']['allowed']) ? $ret['current']['native'] : 0,
b519e37e
DO
889 );
890 break;
891 case 'IP':
cfc28da9
AA
892 case 'SKIP':
893 break;
894 case 'hybrid': // hybrid ports are not supported
895 default: // dot1q-tunnel ?
fa9e68cd 896 $ret['portdata'][$port_name] = array
b519e37e
DO
897 (
898 'mode' => 'none',
899 'allowed' => array(),
900 'native' => 0,
901 );
902 break;
b519e37e 903 }
b519e37e 904 unset ($ret['current']);
fa9e68cd 905 break;
b519e37e
DO
906 default: // nom-nom
907 $line_class = 'line-other';
908 }
fa9e68cd 909 $ret['portconfig'][$port_name][] = array ('type' => $line_class, 'line' => $line);
b519e37e
DO
910 }
911 return $ret;
912}
913
d89632f6
AA
914function 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
ca3b35b8
AA
939 $range = preg_replace ('/\s+to\s+/', '-', $current['vlanlist']);
940 $range = trim (preg_replace('/\s+/', ',', $range), ',-');
d89632f6
AA
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
fc3a6bd0
IE
1032/*
1033D-Link VLAN info sample:
1034========================
1035VID : 72 VLAN Name : v72
1036VLAN Type : Static Advertisement : Disabled
1037Member Ports : 1-16,25-28
1038Static Ports : 1-16,25-28
1039Current Tagged Ports : 25-28
1040Current Untagged Ports : 1-16
1041Static Tagged Ports : 25-28
1042Static Untagged Ports : 1-16
1043Forbidden Ports :
1044*/
1045function 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
1059function 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
1078function 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
1105function 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
1122function 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
1146function 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
c56449ed
AA
1160// most of the commands are compatible with IOS12, so are generated by ios12TranslatePushQueue
1161// Only Nexus-specific commands are generated here (eg., lldp)
1162function nxos4TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
1163{
1164 $ret = '';
1165
1166 foreach ($queue as $cmd)
1167 switch ($cmd['opcode'])
1168 {
c681c13f
AA
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
c56449ed
AA
1183 default:
1184 $ret .= ios12TranslatePushQueue ($dummy_object_id, array ($cmd), $dummy_vlan_names);
1185 break;
1186 }
1187 return $ret;
1188}
1189
b519e37e
DO
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.
28fe3555 1197function ios12TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
b519e37e
DO
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':
0cb3cc88
AA
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";
b519e37e
DO
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;
a6db869e
DO
1249 case 'cite':
1250 $ret .= $cmd['arg1'];
1251 break;
7cb57a03
AA
1252 // query list
1253 case 'get8021q':
1254 $ret .=
cfc28da9
AA
1255'show interface switchport | incl Name:|Switchport:
1256! END OF SWITCHPORTS
1257show run
7cb57a03
AA
1258! END OF CONFIG
1259show 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;
1e22a6fa
DO
1275 case 'getallconf':
1276 $ret .= "show running-config\n";
1277 break;
b519e37e
DO
1278 default:
1279 throw new InvalidArgException ('opcode', $cmd['opcode']);
1280 }
1281 return $ret;
1282}
1283
28fe3555 1284function fdry5TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
b519e37e
DO
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;
a6db869e
DO
1327 case 'cite':
1328 $ret .= $cmd['arg1'];
1329 break;
7cb57a03
AA
1330 // query list
1331 case 'get8021q':
1332 $ret .= "show running-config\n";
1333 break;
1e22a6fa
DO
1334 case 'getallconf':
1335 $ret .= "show running-config\n";
1336 break;
b519e37e
DO
1337 default:
1338 throw new InvalidArgException ('opcode', $cmd['opcode']);
1339 }
1340 return $ret;
1341}
1342
28fe3555 1343function vrp53TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
b519e37e
DO
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;
a6db869e
DO
1389 case 'cite':
1390 $ret .= $cmd['arg1'];
1391 break;
7cb57a03
AA
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;
1e22a6fa
DO
1405 case 'getallconf':
1406 $ret .= "display current-configuration\n";
1407 break;
b519e37e
DO
1408 default:
1409 throw new InvalidArgException ('opcode', $cmd['opcode']);
1410 }
1411 return $ret;
1412}
1413
28fe3555 1414function vrp55TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
b519e37e
DO
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.
c528491d 1455 $before = array
b519e37e
DO
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 );
c528491d
AA
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";
b519e37e
DO
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;
a6db869e
DO
1482 case 'cite':
1483 $ret .= $cmd['arg1'];
1484 break;
7cb57a03
AA
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;
1e22a6fa
DO
1498 case 'getallconf':
1499 $ret .= "display current-configuration\n";
1500 break;
b519e37e
DO
1501 default:
1502 throw new InvalidArgException ('opcode', $cmd['opcode']);
1503 }
1504 return $ret;
1505}
1506
d89632f6
AA
1507function 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
28fe3555 1602function xos12TranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
b519e37e
DO
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;
a6db869e
DO
1654 case 'cite':
1655 $ret .= $cmd['arg1'];
1656 break;
7cb57a03
AA
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;
1e22a6fa
DO
1664 case 'getallconf':
1665 $ret .= "show configuration\n";
1666 break;
b519e37e
DO
1667 default:
1668 throw new InvalidArgException ('opcode', $cmd['opcode']);
1669 }
1670 return $ret;
1671}
1672
28fe3555 1673function jun10TranslatePushQueue ($dummy_object_id, $queue, $vlan_names)
8d6c3668
AA
1674{
1675 $ret = '';
8d6c3668
AA
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";
e289babf
AA
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";
8d6c3668
AA
1710 break;
1711 case 'unset native':
1712 $ret .= "delete interfaces ${cmd['arg1']} unit 0 family ethernet-switching native-vlan-id\n";
e289babf
AA
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";
8d6c3668
AA
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':
dc68fd36
AA
1732 $ret .= "commit\n";
1733 $ret .= "rollback 0\n"; // discard all changes if commit failed
8d6c3668
AA
1734 break;
1735 case 'save configuration':
1736 break; // JunOS can`t apply configuration without saving it
a6db869e
DO
1737 case 'cite':
1738 $ret .= $cmd['arg1'];
1739 break;
7cb57a03
AA
1740 // query list
1741 case 'get8021q':
1742 $ret .=
1743'show vlans detail
1744# END OF VLAN LIST
1745show configuration groups
1746# END OF GROUP LIST
1747show configuration interfaces
1748# END OF CONFIG
1749';
1750 break;
1e22a6fa
DO
1751 case 'getallconf':
1752 $ret .= "show configuration\n";
1753 break;
1ff39492
AA
1754 case 'getlldpstatus':
1755 $ret .= "show lldp neighbors\n";
1756 $ret .= "# object_id=$dummy_object_id";
1757 break;
8d6c3668
AA
1758 default:
1759 throw new InvalidArgException ('opcode', $cmd['opcode']);
1760 }
1761 return $ret;
1762}
1763
4cd9fe27
DO
1764function 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;
dcc34f52
AA
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':
44245b01 1798 $ret .= "no int vlan ${cmd['arg1']}\n";
dcc34f52
AA
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";
dcc34f52
AA
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':
44245b01
AA
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;
dcc34f52
AA
1835 case 'set access':
1836 $ret .= "int vlan ${cmd['arg2']}\n";
dcc34f52
AA
1837 $ret .= "untagged ${cmd['arg1']}\n";
1838 $ret .= "exit\n";
1839 break;
1840 case 'set mode':
1841 break;
1e22a6fa
DO
1842 case 'getallconf':
1843 $ret .= "show running-config\n";
1844 break;
4cd9fe27
DO
1845 default:
1846 throw new InvalidArgException ('opcode', $cmd['opcode']);
1847 }
1848 return $ret;
1849}
1850
52bf6129
DO
1851function 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
85703049
DO
1881function 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
919bb8b6
DO
1954function 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
fc3a6bd0
IE
2052function 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
2076function linuxTranslatePushQueue ($dummy_object_id, $queue, $dummy_vlan_names)
2077{
2078 $ret = '';
2079 foreach ($queue as $cmd)
2080 switch ($cmd['opcode'])
2081 {
2082 case 'getportstatus':
2622aaf3 2083 $ret .= "cd /sys/class/net && for d in eth*; do sudo /sbin/ethtool \$d; done\n";
fc3a6bd0
IE
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
b519e37e
DO
2100function xos12Read8021QConfig ($input)
2101{
2102 $ret = array
2103 (
2104 'vlanlist' => array (1),
2105 'portdata' => array(),
fa9e68cd 2106 'portconfig' => array(),
b519e37e
DO
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
8d6c3668
AA
2151function jun10Read8021QConfig ($input)
2152{
2153 $ret = array
2154 (
2155 'vlanlist' => array (1),
2156 'vlannames' => array (1 => 'default'),
2157 'portdata' => array(),
fa9e68cd 2158 'portconfig' => array(),
8d6c3668
AA
2159 );
2160 $lines = explode ("\n", $input);
dec748f6 2161
8d6c3668
AA
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);
927f4921 2203 $line_class = 'line-other';
8d6c3668
AA
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'])
dec748f6 2207 {
8d6c3668
AA
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
336eded6
AA
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,
927f4921 2240 'config' => array(),
336eded6 2241 );
927f4921 2242 $line_class = 'line-header';
336eded6
AA
2243 $current['indent'] = NULL;
2244 }
8d6c3668
AA
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 }
336eded6 2253 elseif (isset ($current['indent']) and $line == $current['indent'] . '}')
8d6c3668
AA
2254 $current['indent'] = NULL;
2255 elseif ($current['is_ethernet'] and isset ($current['indent']))
2256 {
927f4921 2257 $line_class = 'line-8021q';
8d6c3668
AA
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 }
927f4921
AA
2282 else
2283 $line_class = 'line-other';
8d6c3668 2284 }
fa9e68cd
AA
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 }
8d6c3668 2291 }
dec748f6 2292
8d6c3668
AA
2293 return $ret;
2294}
2295
dcc34f52
AA
2296function ftos8Read8021QConfig ($input)
2297{
2298 $ret = array
2299 (
2300 'vlanlist' => array (),
2301 'vlannames' => array (),
2302 'portdata' => array(),
2303 'portconfig' => array(),
2304 );
dcc34f52
AA
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 (
3af9b06f 2312 'name' => shortenIfName (str_replace (' ', '', $m[1])),
dcc34f52
AA
2313 'lines' => array(),
2314 'is_switched' => FALSE,
f9c4a406 2315 'vlan' => 1 === preg_match ('/^Vlan (\d+)$/', $m[1], $m2) ? $m2[1] : 0,
dcc34f52 2316 );
dcc34f52
AA
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;
f9c4a406
DO
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.
dcc34f52
AA
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 {
3af9b06f 2361 $if_name = shortenIfName ($pref . $i);
dcc34f52
AA
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 }
f9c4a406
DO
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';
dcc34f52
AA
2380 return $ret;
2381}
2382
85703049
DO
2383function 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':
efb47a13
AA
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;
85703049
DO
2400 if (! array_key_exists ('allowed', $mined))
2401 $mined['allowed'] = range (VLAN_MIN_ID, VLAN_MAX_ID);
85703049
DO
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
2420function 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 {
6ab9c063 2436 case preg_match ('/^vlan ([\d,-]+)$/', $line, $matches):
85703049 2437 foreach (iosParseVLANString ($matches[1]) as $vlan_id)
6ab9c063
DO
2438 if ($vlan_id != VLAN_DFL_ID)
2439 $ret['vlanlist'][] = $vlan_id;
85703049
DO
2440 break;
2441 case preg_match ('/^interface ((Ethernet|Port-Channel)\d+)$/', $line, $matches):
3af9b06f 2442 $portname = shortenIfName ($matches[1]);
85703049
DO
2443 $ret['current'] = array
2444 (
a2b98684 2445 'port_name' => $portname,
85703049
DO
2446 'mode' => 'access',
2447 'default1' => TRUE,
2448 );
a2b98684 2449 $ret['portconfig'][$portname][] = array ('type' => 'line-header', 'line' => $line);
85703049
DO
2450 break;
2451 }
2452 continue;
2453 }
a2b98684 2454 # $portname == $ret['current']['port_name']
85703049
DO
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';
a2b98684 2461 $ret['portconfig'][$portname][] = array ('type' => 'line-other', 'line' => $line);
85703049
DO
2462 break;
2463 case $line == ' switchport mode trunk':
2464 $ret['current']['mode'] = 'trunk';
a2b98684 2465 $ret['portconfig'][$portname][] = array ('type' => 'line-8021q', 'line' => $line);
85703049
DO
2466 break;
2467 case $line == ' switchport trunk native vlan tag':
2468 $ret['current']['default1'] = FALSE;
a2b98684 2469 $ret['portconfig'][$portname][] = array ('type' => 'line-8021q', 'line' => $line);
85703049
DO
2470 break;
2471 case preg_match ('/^ switchport trunk native vlan (\d+)$/', $line, $matches):
2472 $ret['current']['native'] = $matches[1];
a2b98684 2473 $ret['portconfig'][$portname][] = array ('type' => 'line-8021q', 'line' => $line);
85703049 2474 break;
e60e6798
DO
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;
85703049
DO
2479 case preg_match ('/^ switchport trunk allowed vlan (\S+)$/', $line, $matches):
2480 $ret['current']['allowed'] = iosParseVLANString ($matches[1]);
a2b98684 2481 $ret['portconfig'][$portname][] = array ('type' => 'line-8021q', 'line' => $line);
85703049
DO
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]));
a2b98684 2485 $ret['portconfig'][$portname][] = array ('type' => 'line-8021q', 'line' => $line);
85703049
DO
2486 break;
2487 case preg_match ('/^ switchport access vlan (\d+)$/', $line, $matches):
2488 $ret['current']['access'] = $matches[1];
a2b98684 2489 $ret['portconfig'][$portname][] = array ('type' => 'line-8021q', 'line' => $line);
85703049
DO
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']);
a2b98684
DO
2496 $ret['portconfig'][$portname][] = array ('type' => 'line-header', 'line' => $line);
2497 break;
2498 default:
2499 $ret['portconfig'][$portname][] = array ('type' => 'line-other', 'line' => $line);
85703049 2500 break;
85703049
DO
2501 }
2502 }
2503 unset ($ret['current']);
2504 return $ret;
2505}
2506
919bb8b6
DO
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.
2532function 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
7a0e9964
AA
2575function 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;
816c0ab8
AA
2587 case 'getlldpstatus':
2588 $ret .= "show lldp neighbors\n";
2589 break;
7a0e9964
AA
2590 default:
2591 throw new InvalidArgException ('opcode', $cmd['opcode']);
2592 }
2593 return $ret;
2594}
2595
be91a564
JS
2596function 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
919bb8b6
DO
2614function 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
2631function 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
2639function 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.
3af9b06f 2683 $work['portconfig'][shortenIfName ($portname)] = array_merge ($work['portconfig'][$portname], $work['current']['lines']);
919bb8b6
DO
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
b519e37e
DO
2703function 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':
3af9b06f 2720 $portname = shortenIfName (trim (substr ($line, 0, $name_field_borders['from'])));
b519e37e
DO
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;
4da6bc56 2725 list ($status_raw, $vlan, $duplex, $speed) = $field_list;
b519e37e
DO
2726 if ($status_raw == 'connected' || $status_raw == 'up')
2727 $status = 'up';
585736a6 2728 elseif (0 === strpos ($status_raw, 'notconn') || $status_raw == 'down' || $status_raw == 'sfpAbsent')
b519e37e
DO
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
2744function 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;
680c6167
AA
2762 if ($field_list[0] == '')
2763 array_shift ($field_list);
b519e37e 2764 list ($portname, $status_raw) = $field_list;
3af9b06f 2765 $portname = shortenIfName ($portname);
b519e37e
DO
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
fc3a6bd0
IE
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*/
2795function 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];
a20a4e3c 2806 if ($w[1] != 'enabled')
fc3a6bd0 2807 $result[$portname] = array ('status'=>'disabled', 'speed'=>0, 'duplex'=>'');
a20a4e3c 2808 elseif ($w[3] == 'linkdown')
fc3a6bd0 2809 $result[$portname] = array ('status'=>'down', 'speed'=>0, 'duplex'=>'');
a20a4e3c
DO
2810 else
2811 {
fc3a6bd0
IE
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
2823Settings 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
2846Settings 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*/
2869function 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';
2885 $speed = 0;
2886 $duplex = '';
2887 }
2888 elseif (preg_match ('/^\s*Speed: (.*)$/', $line, $m))
2889 $speed = $m[1];
2890 elseif (preg_match ('/^\s*Duplex: (.*)$/', $line, $m))
2891 $duplex = $m[1];
2892 elseif (preg_match ('/^\s*Link detected: (.*)$/', $line, $m))
2893 $status = (($m[1] === 'yes') ? 'up' : 'down');
2894 }
2895 if ($iface !== '')
2896 $result[$iface] = array ('status' => $status, 'speed' => $speed, 'duplex' => $duplex);
2897 return $result;
2898}
2899
dcc34f52
AA
2900function ftos8ReadInterfaceStatus ($text)
2901{
2902 $result = array();
2903 $table_schema = array();
2904 foreach (explode ("\n", $text) as $line)
2905 if (empty ($table_schema))
2906 {
2907 if (preg_match('/^Port\s+Description\s+Status\s+Speed\s+Duplex\b/', $line))
2908 $table_schema = guessTableStructure ($line);
2909 }
2910 else
2911 {
2912 $fields = explodeTableLine ($line, $table_schema);
2913 if (! empty ($fields['Port']) and ! empty ($fields['Speed']) and ! empty ($fields['Duplex']))
2914 {
2915 $status = strtolower ($fields['Status']);
2916 if ($status != 'up' && $status != 'down')
2917 $status = 'disabled';
3af9b06f 2918 $portname = shortenIfName (str_replace (' ', '', $fields['Port']));
dcc34f52
AA
2919 $result[$portname] = array
2920 (
2921 'status' => $status,
2922 'speed' => $fields['Speed'],
2923 'duplex' => $fields['Duplex'],
2924 );
2925 }
2926 }
2927 return $result;
2928}
2929
85703049
DO
2930function eos4ReadInterfaceStatus ($text)
2931{
2932 $result = array();
2933 $table_schema = array();
2934 foreach (explode ("\n", $text) as $line)
2935 if (empty ($table_schema))
2936 {
2937 if (preg_match('/^Port\s+Name\s+Status\s+Vlan\s+Duplex\s+Speed\b/', $line))
2938 $table_schema = guessTableStructure ($line);
2939 }
2940 else
2941 {
2942 $fields = explodeTableLine ($line, $table_schema);
2943 if (! empty ($fields['Port']) and ! empty ($fields['Speed']) and ! empty ($fields['Duplex']))
2944 {
2945 $status = strtolower ($fields['Status']);
2946 if ($status == 'connected')
2947 $status = 'up';
2948 elseif ($status == 'notconnect')
2949 $status = 'down';
2950 else
2951 $status = 'disabled';
3af9b06f 2952 $result[shortenIfName ($fields['Port'])] = array
85703049
DO
2953 (
2954 'status' => $status,
2955 'speed' => $fields['Speed'],
2956 'duplex' => $fields['Duplex'],
2957 );
2958 }
2959 }
2960 return $result;
2961}
2962
919bb8b6
DO
2963function ros11ReadInterfaceStatus ($text)
2964{
12563ebb
DO
2965 $ret = array();
2966 $state = 'headerscan';
919bb8b6 2967 foreach (explode ("\n", $text) as $line)
12563ebb 2968 switch ($state)
919bb8b6 2969 {
12563ebb
DO
2970 case 'headerscan':
2971 if (preg_match ('/^Port\s+Type\s+Duplex\s+Speed\s+Neg\s+ctrl\s+State\s+Pressure Mode\b/', $line))
2972 $state = 'physical';
2973 elseif (preg_match ('/^Ch\s+Type\s+Duplex\s+Speed\s+Neg\s+control\s+State\b/', $line))
2974 $state = 'group';
2975 break;
2976 case 'physical':
2977 if (preg_match ('#^([a-z]+\d+/\d+)\s+\S+\s+(\S+)\s+(\S+)\s+\S+\s+\S+\s+(\S+)\s#', $line, $m))
2978 $ret[$m[1]] = array
919bb8b6 2979 (
12563ebb
DO
2980 'status' => strtolower ($m[4]),
2981 'speed' => $m[3],
2982 'duplex' => $m[2],
919bb8b6 2983 );
12563ebb
DO
2984 elseif (substr ($line, 0, 9) != '-------- ') # ruler
2985 $state = 'headerscan'; # end of first table
2986 break;
2987 case 'group':
d090df50
DO
2988 if (preg_match ('#^(Po\d+)\s+\S+\s+(\S+)\s+(\S+)\s+\S+\s+\S+\s+(\S.+)\s+$#', $line, $m))
2989 {
2990 if ($m[4] != 'Not Present')
2991 $ret[strtolower ($m[1])] = array
2992 (
2993 'status' => strtolower (trim ($m[4])),
2994 'speed' => $m[3],
2995 'duplex' => $m[2],
2996 );
2997 }
12563ebb
DO
2998 elseif (substr ($line, 0, 9) != '-------- ') # ruler
2999 break 2; # end of the last table
3000 break;
3001 default:
3002 throw new RackTablesError ('state error', RackTablesError::INTERNAL);
3003 }
3004 return $ret;
919bb8b6
DO
3005}
3006
b519e37e
DO
3007function maclist_sort ($a, $b)
3008{
3009 if ($a['vid'] == $b['vid'])
3010 return 0;
3011 return ($a['vid'] < $b['vid']) ? -1 : 1;
3012}
3013
3014function ios12ReadMacList ($text)
3015{
3016 $result = array();
3017 $state = 'headerSearch';
3018 foreach (explode ("\n", $text) as $line)
3019 {
3020 switch ($state)
3021 {
3022 case 'headerSearch':
3023 if (preg_match('/Vlan\s+Mac Address\s+Type.*Ports?\s*$/i', $line))
3024 $state = 'readPort';
3025 break;
3026 case 'readPort':
3027 if (! preg_match ('/(\d+)\s+([a-f0-9]{4}\.[a-f0-9]{4}\.[a-f0-9]{4})\s.*?(\S+)$/', trim ($line), $matches))
3028 break;
68026ff9
AA
3029 if ($matches[3] == 'Drop') // 802.1X issue - no port name
3030 break;
3af9b06f 3031 $portname = shortenIfName ($matches[3]);
b519e37e
DO
3032 $result[$portname][] = array
3033 (
3034 'mac' => $matches[2],
3035 'vid' => $matches[1],
3036 );
3037 break;
3038 }
3039 }
3040 foreach ($result as $portname => &$maclist)
3041 usort ($maclist, 'maclist_sort');
3042 return $result;
3043}
3044
3045function nxos4ReadMacList ($text)
3046{
3047 $result = array();
3048 $state = 'headerSearch';
3049 foreach (explode ("\n", $text) as $line)
3050 {
3051 switch ($state)
3052 {
3053 case 'headerSearch':
3054 if (preg_match('/VLAN\s+MAC Address\s+Type\s+age\s+Secure\s+NTFY\s+Ports/i', $line))
3055 $state = 'readPort';
3056 break;
3057 case 'readPort':
3058 if (! preg_match ('/(\d+)\s+([a-f0-9]{4}\.[a-f0-9]{4}\.[a-f0-9]{4})\s.*?(\S+)$/', trim ($line), $matches))
3059 break;
3af9b06f 3060 $portname = shortenIfName ($matches[3]);
b519e37e
DO
3061 $result[$portname][] = array
3062 (
3063 'mac' => $matches[2],
3064 'vid' => $matches[1],
3065 );
3066 break;
3067 }
3068 }
3069 foreach ($result as $portname => &$maclist)
3070 usort ($maclist, 'maclist_sort');
3071 return $result;
3072}
3073
3074function vrp53ReadMacList ($text)
3075{
3076 $result = array();
3077 $state = 'headerSearch';
3078 foreach (explode ("\n", $text) as $line)
3079 {
3080 switch ($state)
3081 {
3082 case 'headerSearch':
3083 if (preg_match('/MAC Address\s+VLAN\/VSI\s+Port/i', $line))
3084 $state = 'readPort';
3085 break;
3086 case 'readPort':
3087 if (! preg_match ('/([a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4})\s+(\d+)\s+(\S+)/', trim ($line), $matches))
3088 break;
3af9b06f 3089 $portname = shortenIfName ($matches[3]);
b519e37e
DO
3090 $result[$portname][] = array
3091 (
3092 'mac' => str_replace ('-', '.', $matches[1]),
3093 'vid' => $matches[2],
3094 );
3095 break;
3096 }
3097 }
3098 foreach ($result as $portname => &$maclist)
3099 usort ($maclist, 'maclist_sort');
3100 return $result;
3101}
3102
d89632f6 3103function vrpReadMacList ($text)
b519e37e
DO
3104{
3105 $result = array();
3106 $state = 'headerSearch';
3107 foreach (explode ("\n", $text) as $line)
3108 {
3109 switch ($state)
3110 {
3111 case 'headerSearch':
f57bb8f5 3112 if (preg_match('/^MAC Address\s+VLAN/i', $line))
b519e37e
DO
3113 $state = 'readPort';
3114 break;
3115 case 'readPort':
f57bb8f5 3116 if (! preg_match ('/([a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4})\s+(\d+)(?:(?:\s+\S+){2}|\/\S*)\s+(\S+)/', trim ($line), $matches))
b519e37e 3117 break;
3af9b06f 3118 $portname = shortenIfName ($matches[3]);
b519e37e
DO
3119 $result[$portname][] = array
3120 (
3121 'mac' => str_replace ('-', '.', $matches[1]),
3122 'vid' => $matches[2],
3123 );
3124 break;
3125 }
3126 }
3127 foreach ($result as $portname => &$maclist)
3128 usort ($maclist, 'maclist_sort');
3129 return $result;
3130}
3131
fc3a6bd0
IE
3132/*
3133D-Link "show fdb" output sample
3134===============================
3135
3136VID VLAN Name MAC Address Port Type
3137---- -------------------------------- ----------------- ---- ---------------
31381 default 00-1B-2C-5F-4E-AE 27 Dynamic
313999 switch 00-00-3E-11-B7-52 27 Dynamic
314099 switch 84-C9-B2-36-80-F2 27 Dynamic
3141*/
3142function dlinkReadMacList ($text)
3143{
3144 $result = array();
3145 foreach (preg_split ("/\n\r?/", $text) as $line)
3146 {
3147 if (! preg_match ('/^\s*\d+\s+/', $line))
3148 continue;
3149 $w = preg_split ('/\s+/', $line);
3150 if (count ($w) != 5)
3151 continue;
3152 $result[$w[3]][] = array
3153 (
3154 'mac' => $w[2],
3155 'vid' => $w[0],
3156 );
3157 }
3158 foreach ($result as $portname => &$maclist)
3159 usort ($maclist, 'maclist_sort');
3160 return $result;
3161}
3162
3163function linuxReadMacList ($text)
3164{
3165 $result = array();
3166 $passed = array();
3167 foreach (explode ("\n", $text) as $line)
3168 {
3169 $m = array();
3170 if (! preg_match ('/\(([^\s]+)\) at ([^\s]+) \[ether\] on (.*)$/', $line, $m))
3171 continue;
3172
3173 // prevent multiple additions
3174 if (array_key_exists ($m[2].$m[3], $passed))
3175 continue;
3176 $passed[$m[2].$m[3]] = 1;
3177
3178 $result[$m[3]][] = array ('mac' => $m[2], 'vid' => 1);
3179 }
3180 foreach ($result as $portname => &$maclist)
3181 usort ($maclist, 'maclist_sort');
3182 return $result;
3183}
3184
dcc34f52
AA
3185function ftos8ReadMacList ($text)
3186{
3187 $result = array();
3188 $state = 'headerSearch';
3189 foreach (explode ("\n", $text) as $line)
3190 switch ($state)
3191 {
3192 case 'headerSearch':
3193 if (preg_match('/^VlanId\s+Mac Address\s+Type\s+Interface\s+State/', $line))
3194 $state = 'readPort';
3195 break;
3196 case 'readPort':
3197 if (! preg_match ('/^(\d+)\s+((?:[a-f0-9]{2}:){5}[a-f0-9]{2})\s+Dynamic\s+(\S+ (?:\S+)?)/', trim ($line), $matches))
3198 break;
3af9b06f 3199 $portname = shortenIfName (str_replace (' ', '', $matches[3]));
dcc34f52
AA
3200 $mac = preg_replace ('/([a-f0-9]{2}):([a-f0-9]{2})/', '$1$2', $matches[2]);
3201 $result[$portname][] = array
3202 (
3203 'mac' => str_replace (':', '.', $mac),
3204 'vid' => $matches[1],
3205 );
3206 break;
3207 }
3208 foreach ($result as $portname => &$maclist)
3209 usort ($maclist, 'maclist_sort');
3210 return $result;
3211}
3212
85703049
DO
3213function eos4ReadMacList ($text)
3214{
3215 $result = array();
3216 $seen_header = FALSE;
3217 foreach (explode ("\n", $text) as $line)
3218 if (! $seen_header)
3219 $seen_header = $line == '---- ----------- ---- ----- ----- ---------';
3220 else
3221 {
3222 if (substr ($line, 0, 19) == 'Total Mac Addresses') # end of table
3223 break;
34a3426c 3224 if (preg_match ('/^ *(\d+)\s+(\S+)\s+DYNAMIC\s+(\S+)\s/', $line, $m))
3af9b06f 3225 $result[shortenIfName ($m[3])][] = array
85703049
DO
3226 (
3227 'mac' => $m[2],
3228 'vid' => $m[1],
3229 );
3230 }
3231 foreach (array_keys ($result) as $portname)
3232 usort ($result[$portname], 'maclist_sort');
3233 return $result;
3234}
3235
919bb8b6
DO
3236function ros11ReadMacList ($text)
3237{
3238 $result = array();
3239 $got_header = FALSE;
3240 foreach (explode ("\n", $text) as $line)
3241 if (! $got_header)
3242 $got_header = (1 == preg_match('/Vlan\s+Mac Address\s+Port\s+Type\b/', $line));
919bb8b6 3243 elseif (preg_match ('/\b(\d+)\s+([a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2})\s+(\S+)\b/', $line, $m))
3af9b06f 3244 $result[shortenIfName ($m[3])][] = array
919bb8b6
DO
3245 (
3246 'mac' => $m[2],
3247 'vid' => $m[1],
3248 );
12563ebb
DO
3249 elseif (! preg_match ('/^--------/', $line)) # ruler
3250 break; # end of table
919bb8b6
DO
3251 foreach (array_keys ($result) as $portname)
3252 usort ($result[$portname], 'maclist_sort');
3253 return $result;
3254}
3255
be91a564
JS
3256# The types of objects returned are 'NetworkElement', 'EquipmentChassis' and
3257# 'ComputeBlade', just like their respective UCS classes.
3258function ucsReadInventory ($text)
3259{
3260 # transform plain-text response into array
3261 $tmp = $replies = array();
3262 foreach (explode ("\n", $text) as $line)
3263 if (1 !== preg_match ('/^(OK|ERR)( .+)?$/', $line, $m))
3264 $tmp[] = $line;
3265 else
3266 {
3267 $replies[] = array ('code' => $m[1], 'text' => $m[2], 'body' => $tmp);
3268 $tmp = array();
3269 }
3270 # validate the array
9b74a484 3271 if (count ($replies) != 2 and count ($replies) != 1)
be91a564
JS
3272 throw new RTGatewayError ('replies count does not match commands count');
3273 if ($replies[0]['code'] != 'OK')
3274 throw new RTGatewayError ('UCS login failed');
3275 if ($replies[1]['code'] != 'OK')
3276 throw new RTGatewayError ('UCS enumeration failed');
3277 $ret = array();
3278 foreach ($replies[1]['body'] as $line)
3279 switch (1)
3280 {
3281 case preg_match ('/^COLUMNS (.+)$/', $line, $m):
3282 if (! count ($hcols = explode (',', $m[1])))
3283 throw new RTGatewayError ("UCS format error: '${line}'");
3284 break;
3285 case preg_match ('/^ROW (.+)$/', $line, $m):
3286 if (count ($cols = explode (',', $m[1])) != count ($hcols))
3287 throw new RTGatewayError ("UCS format error: '${line}'");
3288 # $hcols and $cols have same array keys
3289 $tmp = array();
3290 foreach ($cols as $key => $value)
3291 $tmp[$hcols[$key]] = $value;
3292 $ret[] = $tmp;
3293 break;
3294 default:
3295 throw new RTGatewayError ("Unrecognized line: '${line}'");
3296 }
3297 return $ret;
3298}
3299
eb8a4c05
DO
3300function ios12SpotConfigText ($input)
3301{
40ee43a1
AA
3302 if (preg_match ('/\nUnable to get configuration. Try again later/s', $input))
3303 throw new ERetryNeeded ("device is busy. 'show run' did not work");
eb45d531 3304 return preg_replace ('/.*?^Current configuration : \d+ bytes$\n(.*)^\S+#\s*\Z/sm', '$1', $input, 1);
eb8a4c05
DO
3305}
3306
3307function nxos4SpotConfigText ($input)
3308{
eb45d531 3309 return preg_replace ('/.*?^!Command: show running-config$\n(.*)^\S+#\s*\Z/sm', '$1', $input, 1);
eb8a4c05
DO
3310}
3311
3312function fdry5SpotConfigText ($input)
3313{
3314 return $input;
3315}
3316
d89632f6 3317function vrpSpotConfigText ($input)
eb8a4c05 3318{
eb45d531 3319 return preg_replace ('/.*?^!Software Version V\N*\n(.*)^return$.*/sm', '$1', $input, 1);
eb8a4c05
DO
3320}
3321
3322function xos12SpotConfigText ($input)
3323{
7f601a0b 3324 return preg_replace ('/.*?^(#\n^# Module \N+ configuration.$\n.*)^\S+\.\d+ # /sm', '$1', $input, 1);
eb8a4c05
DO
3325}
3326
3327function jun10SpotConfigText ($input)
3328{
eb45d531 3329 return preg_replace ('/.*?^## Last commit: \N*\n(.*)^\S+@\S+>\s*\Z/sm', '$1', $input, 1);
eb8a4c05
DO
3330}
3331
3332function ftos8SpotConfigText ($input)
3333{
eb45d531 3334 return preg_replace ('/.*?^! Version [0-9\.]+\n(.*)^end$.*/sm', '$1', $input, 1);
eb8a4c05
DO
3335}
3336
85703049
DO
3337function eos4SpotConfigText ($input)
3338{
eb45d531 3339 return preg_replace ('/.*?^! device: \N*EOS-\N*$\n(.*)^end$.*/sm', '$1', $input, 1);
85703049
DO
3340}
3341
919bb8b6
DO
3342function ros11SpotConfigText ($input)
3343{
3344 return $input;
3345}
3346
7a0e9964
AA
3347function iosxr4SpotConfigText ($input)
3348{
eb45d531 3349 return preg_replace ('/.*?^!! IOS XR Configuration [^\n]*$\n(.*)^\S+#\s*\Z/sm', '$1', $input, 1);
7a0e9964
AA
3350}
3351
1ff39492
AA
3352function jun10ReadLLDPStatus ($input)
3353{
3354 $ret = array();
3355
3356 $lldp_mode = FALSE;
3357 foreach (explode ("\n", $input) as $line)
3358 {
3359 $line = rtrim ($line);
3360 if (preg_match ('/^Local Interface\s+Chassis Id\s+Port info\s+System Name$/', $line))
3361 $lldp_mode = TRUE;
3362 elseif ($line == "")
3363 $lldp_mode = FALSE;
3364 elseif ($lldp_mode && preg_match ('/^(\S+)\s+([0-9a-f:]{17})\s+(.*?)\s+(\S+)\s*$/', $line, $m))
3af9b06f 3365 $ret[shortenIfName ($m[1])][] = array
1ff39492 3366 (
023df3f8 3367 'port' => $m[3],
1ff39492
AA
3368 'device' => $m[4],
3369 );
3370 }
3371
3372 return $ret;
3373}
3374
816c0ab8
AA
3375function iosxr4ReadLLDPStatus ($input)
3376{
3377 $ret = array();
3378
3379 $lldp_mode = FALSE;
3380 foreach (explode ("\n", $input) as $line)
3381 {
3382 $line = rtrim ($line);
3383 if (preg_match ('/^Device ID\s+Local Intf\s+Hold-time\s+Capability\s+Port ID$/', $line))
3384 $lldp_mode = TRUE;
3385 elseif ($line == "")
3386 $lldp_mode = FALSE;
3387 elseif ($lldp_mode && preg_match ('/^(\S+)\s+([^\s\[\]]+)[^\s]*\s+\d+\s+\S+\s+(.*)$/', $line, $m))
3388 {
3af9b06f 3389 $local_port = shortenIfName ($m[2]);
023df3f8 3390 $remote_port = $m[3];
816c0ab8
AA
3391 if (!preg_match ('@^bundle-ether\d+$@', $remote_port) || preg_match ('@^bundle-ether\d+$@', $local_port))
3392 $ret[$local_port][] = array
3393 (
3394 'port' => $remote_port,
3395 'device' => $m[1],
3396 );
3397 }
3398 }
3399
3400 return $ret;
3401}
3402
b519e37e 3403?>