2881b853c0acca8b22bcd7450962e899326d574b
[racktables] / inc / gateways.php
1 <?php
2 /*
3 *
4 * This file contains gateway functions for RackTables.
5 * A gateway is an external executable, which provides
6 * read-only or read-write access to some external entities.
7 * Each gateway accepts its own list of command-line args
8 * and then reads its stdin for requests. Each request consists
9 * of one line and results in exactly one line of reply.
10 * The replies must have the following syntax:
11 * OK<space>any text up to the end of the line
12 * ERR<space>any text up to the end of the line
13 *
14 */
15
16 // translating functions maps
17 $gwrxlator = array();
18 $gwrxlator['getcdpstatus']['ios12'] = 'ios12ReadCDPStatus';
19 $gwrxlator['getlldpstatus']['xos12'] = 'xos12ReadLLDPStatus';
20 $gwrxlator['get8021q'] = array
21 (
22 'ios12' => 'ios12ReadVLANConfig',
23 'fdry5' => 'fdry5ReadVLANConfig',
24 'vrp53' => 'vrp53ReadVLANConfig',
25 'nxos4' => 'nxos4Read8021QConfig',
26 'xos12' => 'xos12Read8021QConfig',
27 );
28
29 $gwpushxlator = array
30 (
31 'ios12' => 'ios12TranslatePushQueue',
32 'fdry5' => 'fdry5TranslatePushQueue',
33 'vrp53' => 'vrp53TranslatePushQueue',
34 'nxos4' => 'ios12TranslatePushQueue', // employ syntax compatibility
35 'xos12' => 'xos12TranslatePushQueue',
36 );
37
38 // This function launches specified gateway with specified
39 // command-line arguments and feeds it with the commands stored
40 // in the second arg as array.
41 // The answers are stored in another array, which is returned
42 // by this function. In the case when a gateway cannot be found,
43 // finishes prematurely or exits with non-zero return code,
44 // a single-item array is returned with the only "ERR" record,
45 // which explains the reason.
46 function queryGateway ($gwname, $questions)
47 {
48 $execpath = "./gateways/{$gwname}/main";
49 $dspec = array
50 (
51 0 => array ("pipe", "r"),
52 1 => array ("pipe", "w"),
53 2 => array ("file", "/dev/null", "a")
54 );
55 $pipes = array();
56 $gateway = proc_open ($execpath, $dspec, $pipes);
57 if (!is_resource ($gateway))
58 return array ('ERR proc_open() failed in ' . __FUNCTION__);
59
60 // Dialogue starts. Send all questions.
61 foreach ($questions as $q)
62 fwrite ($pipes[0], "$q\n");
63 fclose ($pipes[0]);
64
65 // Fetch replies.
66 $answers = array ();
67 while (!feof($pipes[1]))
68 {
69 $a = fgets ($pipes[1]);
70 if (!strlen ($a))
71 continue;
72 // Somehow I got a space appended at the end. Kick it.
73 $answers[] = trim ($a);
74 }
75 fclose($pipes[1]);
76
77 $retval = proc_close ($gateway);
78 if ($retval != 0)
79 throw new Exception ("gateway failed with code ${retval}", E_GW_FAILURE);
80 if (!count ($answers))
81 throw new Exception ('no response from gateway', E_GW_FAILURE);
82 if (count ($answers) != count ($questions))
83 throw new Exception ('protocol violation', E_GW_FAILURE);
84 foreach ($answers as $a)
85 if (strpos ($a, 'OK!') !== 0)
86 throw new Exception ("subcommand failed with status: ${a}", E_GW_FAILURE);
87 return $answers;
88 }
89
90 // This functions returns an array for VLAN list, and an array for port list (both
91 // form another array themselves) and another one with MAC address list.
92 // The ports in the latter array are marked with either VLAN ID or 'trunk'.
93 // We don't sort the port list, as the gateway is believed to have done this already
94 // (or at least the underlying switch software ought to). This is important, as the
95 // port info is transferred to/from form not by names, but by numbers.
96 function getSwitchVLANs ($object_id = 0)
97 {
98 global $remote_username;
99 $objectInfo = spotEntity ('object', $object_id);
100 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
101 if (count ($endpoints) == 0)
102 throw new Exception ('no management address set', E_GW_FAILURE);
103 if (count ($endpoints) > 1)
104 throw new Exception ('cannot pick management address', E_GW_FAILURE);
105 $hwtype = $swtype = 'unknown';
106 foreach (getAttrValues ($object_id) as $record)
107 {
108 if ($record['name'] == 'SW type' && strlen ($record['o_value']))
109 $swtype = str_replace (' ', '+', execGMarker ($record['o_value']));
110 if ($record['name'] == 'HW type' && strlen ($record['o_value']))
111 $hwtype = str_replace (' ', '+', execGMarker ($record['o_value']));
112 }
113 $endpoint = str_replace (' ', '+', $endpoints[0]);
114 $commands = array
115 (
116 "connect ${endpoint} ${hwtype} ${swtype} ${remote_username}",
117 'listvlans',
118 'listports',
119 'listmacs'
120 );
121 $data = queryGateway ('switchvlans', $commands);
122 if (strpos ($data[0], 'OK!') !== 0)
123 throw new Exception ("gateway failed with status: ${data[0]}.", E_GW_FAILURE);
124 // Now we have VLAN list in $data[1] and port list in $data[2]. Let's sort this out.
125 $tmp = array_unique (explode (';', substr ($data[1], strlen ('OK!'))));
126 if (count ($tmp) == 0)
127 throw new Exception ('gateway returned no records', E_GW_FAILURE);
128 $vlanlist = array();
129 foreach ($tmp as $record)
130 {
131 list ($vlanid, $vlandescr) = explode ('=', $record);
132 $vlanlist[$vlanid] = $vlandescr;
133 }
134 $portlist = array();
135 foreach (explode (';', substr ($data[2], strlen ('OK!'))) as $pair)
136 {
137 list ($portname, $pair2) = explode ('=', $pair);
138 list ($status, $vlanid) = explode (',', $pair2);
139 $portlist[] = array ('portname' => $portname, 'status' => $status, 'vlanid' => $vlanid);
140 }
141 if (count ($portlist) == 0)
142 throw new Exception ('gateway returned no records', E_GW_FAILURE);
143 $maclist = array();
144 foreach (explode (';', substr ($data[3], strlen ('OK!'))) as $pair)
145 {
146 list ($macaddr, $pair2) = explode ('=', $pair);
147 if (!strlen ($pair2))
148 continue;
149 list ($vlanid, $ifname) = explode ('@', $pair2);
150 $maclist[$ifname][$vlanid][] = $macaddr;
151 }
152 return array ($vlanlist, $portlist, $maclist);
153 }
154
155 function setSwitchVLANs ($object_id = 0, $setcmd)
156 {
157 global $remote_username;
158 if ($object_id <= 0)
159 return oneLiner (160); // invalid arguments
160 $objectInfo = spotEntity ('object', $object_id);
161 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
162 if (count ($endpoints) == 0)
163 return oneLiner (161); // endpoint not found
164 if (count ($endpoints) > 1)
165 return oneLiner (162); // can't pick an address
166 $hwtype = $swtype = 'unknown';
167 foreach (getAttrValues ($object_id) as $record)
168 {
169 if ($record['name'] == 'SW type' && strlen ($record['o_value']))
170 $swtype = strtr (execGMarker ($record['o_value']), ' ', '+');
171 if ($record['name'] == 'HW type' && strlen ($record['o_value']))
172 $hwtype = strtr (execGMarker ($record['o_value']), ' ', '+');
173 }
174 $endpoint = str_replace (' ', '+', $endpoints[0]);
175 $data = queryGateway
176 (
177 'switchvlans',
178 array ("connect ${endpoint} ${hwtype} ${swtype} ${remote_username}", $setcmd)
179 );
180 // Finally we can parse the response into message array.
181 $log_m = array();
182 foreach (explode (';', substr ($data[1], strlen ('OK!'))) as $text)
183 {
184 if (strpos ($text, 'C!') === 0)
185 {
186 $tmp = explode ('!', $text);
187 array_shift ($tmp);
188 $code = array_shift ($tmp);
189 $log_m[] = count ($tmp) ? array ('c' => $code, 'a' => $tmp) : array ('c' => $code); // gateway-encoded message
190 }
191 elseif (strpos ($text, 'I!') === 0)
192 $log_m[] = array ('c' => 62, 'a' => array (substr ($text, 2))); // generic gateway success
193 elseif (strpos ($text, 'W!') === 0)
194 $log_m[] = array ('c' => 202, 'a' => array (substr ($text, 2))); // generic gateway warning
195 else // All improperly formatted messages must be treated as error conditions.
196 $log_m[] = array ('c' => 166, 'a' => array (substr ($text, 2))); // generic gateway error
197 }
198 return $log_m;
199 }
200
201 // Drop a file off RackTables platform. The gateway will catch the file and pass it to the given
202 // installer script.
203 function gwSendFile ($endpoint, $handlername, $filetext = array())
204 {
205 global $remote_username;
206 $tmpnames = array();
207 $endpoint = str_replace (' ', '\ ', $endpoint); // the gateway dispatcher uses read (1) to assign arguments
208 $command = "submit ${remote_username} ${endpoint} ${handlername}";
209 foreach ($filetext as $text)
210 {
211 $name = tempnam ('', 'RackTables-sendfile-');
212 $tmpnames[] = $name;
213 if (FALSE === $name or FALSE === file_put_contents ($name, $text))
214 {
215 foreach ($tmpnames as $name)
216 unlink ($name);
217 throw new Exception ('failed to write to temporary file', E_GW_FAILURE);
218 }
219 $command .= " ${name}";
220 }
221 $outputlines = queryGateway
222 (
223 'sendfile',
224 array ($command)
225 );
226 foreach ($tmpnames as $name)
227 unlink ($name);
228 }
229
230 // Query something through a gateway and get some text in return. Return that text.
231 function gwRecvFile ($endpoint, $handlername, &$output)
232 {
233 global $remote_username;
234 $tmpfilename = tempnam ('', 'RackTables-sendfile-');
235 $endpoint = str_replace (' ', '\ ', $endpoint); // the gateway dispatcher uses read (1) to assign arguments
236 $outputlines = queryGateway
237 (
238 'sendfile',
239 array ("submit ${remote_username} ${endpoint} ${handlername} ${tmpfilename}")
240 );
241 $output = file_get_contents ($tmpfilename);
242 unlink ($tmpfilename);
243 // Being here means having 'OK!' in the response.
244 return oneLiner (66, array ($handlername)); // ignore provided "Ok" text
245 }
246
247 function gwSendFileToObject ($object_id = 0, $handlername, $filetext = '')
248 {
249 $objectInfo = spotEntity ('object', $object_id);
250 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
251 if (count ($endpoints) == 0)
252 throw new Exception ('no management address set', E_GW_FAILURE);
253 if (count ($endpoints) > 1)
254 throw new Exception ('cannot pick management address', E_GW_FAILURE);
255 gwSendFile (str_replace (' ', '+', $endpoints[0]), $handlername, array ($filetext));
256 }
257
258 function gwRecvFileFromObject ($object_id = 0, $handlername, &$output)
259 {
260 global $remote_username;
261 if ($object_id <= 0 or !strlen ($handlername))
262 return oneLiner (160); // invalid arguments
263 $objectInfo = spotEntity ('object', $object_id);
264 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
265 if (count ($endpoints) == 0)
266 return oneLiner (161); // endpoint not found
267 if (count ($endpoints) > 1)
268 return oneLiner (162); // can't pick an address
269 $endpoint = str_replace (' ', '+', $endpoints[0]);
270 return gwRecvFile ($endpoint, $handlername, $output);
271 }
272
273 function detectDeviceBreed ($object_id)
274 {
275 foreach (getAttrValues ($object_id) as $record)
276 {
277 if
278 (
279 $record['name'] == 'SW type' &&
280 strlen ($record['o_value']) &&
281 preg_match ('/^Cisco IOS 12\./', execGMarker ($record['o_value']))
282 )
283 return 'ios12';
284 if
285 (
286 $record['name'] == 'SW type' &&
287 strlen ($record['o_value']) &&
288 preg_match ('/^Cisco NX-OS 4\./', execGMarker ($record['o_value']))
289 )
290 return 'nxos4';
291 if
292 (
293 $record['id'] == 4 &&
294 $record['key'] == 1352
295 )
296 return 'xos12';
297 if
298 (
299 $record['name'] == 'HW type' &&
300 strlen ($record['o_value']) &&
301 preg_match ('/^Foundry FastIron GS /', execGMarker ($record['o_value']))
302 )
303 return 'fdry5';
304 if
305 (
306 $record['name'] == 'HW type' &&
307 strlen ($record['o_value']) &&
308 preg_match ('/^Huawei Quidway S53/', execGMarker ($record['o_value']))
309 )
310 return 'vrp53';
311 }
312 return '';
313 }
314
315 function getRunning8021QConfig ($object_id)
316 {
317 $ret = gwRetrieveDeviceConfig ($object_id, 'get8021q');
318 // Once there is no default VLAN in the parsed data, it means
319 // something else was parsed instead of config text.
320 if (!in_array (VLAN_DFL_ID, $ret['vlanlist']))
321 throw new Exception ('communication with device failed', E_GW_FAILURE);
322 return $ret;
323 }
324
325 function setDevice8021QConfig ($object_id, $pseudocode)
326 {
327 if ('' == $breed = detectDeviceBreed ($object_id))
328 throw new Exception ('device breed unknown', E_GW_FAILURE);
329 global $gwpushxlator;
330 gwDeployDeviceConfig ($object_id, $breed, unix2dos ($gwpushxlator[$breed] ($pseudocode)));
331 }
332
333 function gwRetrieveDeviceConfig ($object_id, $command)
334 {
335 global $gwrxlator;
336 if (!array_key_exists ($command, $gwrxlator))
337 throw new Exception ('command unknown', E_GW_FAILURE);
338 $breed = detectDeviceBreed ($object_id);
339 if (!array_key_exists ($breed, $gwrxlator[$command]))
340 throw new Exception ('device breed unknown', E_GW_FAILURE);
341 $objectInfo = spotEntity ('object', $object_id);
342 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
343 if (count ($endpoints) == 0)
344 throw new Exception ('no management address set', E_GW_FAILURE);
345 if (count ($endpoints) > 1)
346 throw new Exception ('cannot pick management address', E_GW_FAILURE);
347 $endpoint = str_replace (' ', '\ ', str_replace (' ', '+', $endpoints[0]));
348 $tmpfilename = tempnam ('', 'RackTables-deviceconfig-');
349 $outputlines = queryGateway
350 (
351 'deviceconfig',
352 array ("${command} ${endpoint} ${breed} ${tmpfilename}")
353 );
354 $configtext = dos2unix (file_get_contents ($tmpfilename));
355 unlink ($tmpfilename);
356 // Being here means it was alright.
357 return $gwrxlator[$command][$breed] ($configtext);
358 }
359
360 function gwDeployDeviceConfig ($object_id, $breed, $text)
361 {
362 $objectInfo = spotEntity ('object', $object_id);
363 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
364 if (count ($endpoints) == 0)
365 throw new Exception ('no management address set', E_GW_FAILURE);
366 if (count ($endpoints) > 1)
367 throw new Exception ('cannot pick management address', E_GW_FAILURE);
368 $endpoint = str_replace (' ', '\ ', str_replace (' ', '+', $endpoints[0]));
369 $tmpfilename = tempnam ('', 'RackTables-deviceconfig-');
370 if (FALSE === file_put_contents ($tmpfilename, $text))
371 {
372 unlink ($tmpfilename);
373 throw new Exception ('failed to write to temporary file', E_GW_FAILURE);
374 }
375 $outputlines = queryGateway
376 (
377 'deviceconfig',
378 array ("deploy ${endpoint} ${breed} ${tmpfilename}")
379 );
380 unlink ($tmpfilename);
381 }
382
383 // Read provided output of "show cdp neighbors detail" command and
384 // return a list of records with (translated) local port name,
385 // remote device name and (translated) remote port name.
386 function ios12ReadCDPStatus ($input)
387 {
388 $ret = array();
389 $procfunc = 'ios12ScanCDPTopLevel';
390 foreach (explode ("\n", $input) as $line)
391 $procfunc = $procfunc ($ret, $line);
392 return $ret;
393 }
394
395 function xos12ReadLLDPStatus ($input)
396 {
397 $ret = array();
398 foreach (explode ("\n", $input) as $line)
399 {
400 $matches = array();
401 switch (TRUE)
402 {
403 case preg_match ('/^LLDP Port ([[:digit:]]+) detected 1 neighbor$/', $line, $matches):
404 $ret['current']['local_port'] = ios12ShortenIfName ($matches[1]);
405 break;
406 case preg_match ('/^ Port ID : "(.+)"$/', $line, $matches):
407 $ret['current']['remote_port'] = ios12ShortenIfName ($matches[1]);
408 break;
409 case preg_match ('/^ - System Name: "(.+)"$/', $line, $matches):
410 if
411 (
412 array_key_exists ('current', $ret) and
413 array_key_exists ('local_port', $ret['current']) and
414 array_key_exists ('remote_port', $ret['current'])
415 )
416 $ret[$ret['current']['local_port']] = array
417 (
418 'device' => $matches[1],
419 'port' => $ret['current']['remote_port'],
420 );
421 unset ($ret['current']);
422 default:
423 }
424 }
425 unset ($ret['current']);
426 return $ret;
427 }
428
429 function ios12ReadVLANConfig ($input)
430 {
431 $ret = array
432 (
433 'vlanlist' => array(),
434 'portdata' => array(),
435 );
436 $procfunc = 'ios12ScanTopLevel';
437 foreach (explode ("\n", $input) as $line)
438 $procfunc = $procfunc ($ret, $line);
439 return $ret;
440 }
441
442 function ios12ScanTopLevel (&$work, $line)
443 {
444 $matches = array();
445 switch (TRUE)
446 {
447 case (preg_match ('@^interface ((Ethernet|FastEthernet|GigabitEthernet|TenGigabitEthernet|Port-channel)[[:digit:]]+(/[[:digit:]]+)*)$@', $line, $matches)):
448 $work['current'] = array ('port_name' => ios12ShortenIfName ($matches[1]));
449 return 'ios12PickSwitchportCommand'; // switch to interface block reading
450 case (preg_match ('/^VLAN Name Status Ports$/', $line, $matches)):
451 return 'ios12PickVLANCommand';
452 default:
453 return __FUNCTION__; // continue scan
454 }
455 }
456
457 function ios12PickSwitchportCommand (&$work, $line)
458 {
459 if ($line[0] != ' ') // end of interface section
460 {
461 // save work, if it makes sense
462 switch (TRUE)
463 {
464 case $work['current']['ignore']:
465 $work['portdata'][$work['current']['port_name']] = array
466 (
467 'mode' => 'none',
468 'allowed' => array(),
469 'native' => 0,
470 );
471 break;
472 case 'access' == $work['current']['mode']:
473 if (!array_key_exists ('access vlan', $work['current']))
474 $work['current']['access vlan'] = 1;
475 $work['portdata'][$work['current']['port_name']] = array
476 (
477 'mode' => 'access',
478 'allowed' => array ($work['current']['access vlan']),
479 'native' => $work['current']['access vlan'],
480 );
481 break;
482 case 'trunk' == $work['current']['mode']:
483 if (!array_key_exists ('trunk native vlan', $work['current']))
484 $work['current']['trunk native vlan'] = 1;
485 if (!array_key_exists ('trunk allowed vlan', $work['current']))
486 $work['current']['trunk allowed vlan'] = range (VLAN_MIN_ID, VLAN_MAX_ID);
487 // Having configured VLAN as "native" doesn't mean anything
488 // as long as it's not listed on the "allowed" line.
489 $effective_native = in_array
490 (
491 $work['current']['trunk native vlan'],
492 $work['current']['trunk allowed vlan']
493 ) ? $work['current']['trunk native vlan'] : 0;
494 $work['portdata'][$work['current']['port_name']] = array
495 (
496 'mode' => 'trunk',
497 'allowed' => $work['current']['trunk allowed vlan'],
498 'native' => $effective_native,
499 );
500 break;
501 default:
502 // dot1q-tunnel, dynamic, private-vlan or even none --
503 // show in returned config and let user decide, if they
504 // want to fix device config or work around these ports
505 // by means of VST.
506 $work['portdata'][$work['current']['port_name']] = array
507 (
508 'mode' => 'none',
509 'allowed' => array(),
510 'native' => 0,
511 );
512 break;
513 }
514 unset ($work['current']);
515 return 'ios12ScanTopLevel';
516 }
517 // not yet
518 $matches = array();
519 switch (TRUE)
520 {
521 case (preg_match ('@^ switchport mode (.+)$@', $line, $matches)):
522 $work['current']['mode'] = $matches[1];
523 break;
524 case (preg_match ('@^ switchport access vlan (.+)$@', $line, $matches)):
525 $work['current']['access vlan'] = $matches[1];
526 break;
527 case (preg_match ('@^ switchport trunk native vlan (.+)$@', $line, $matches)):
528 $work['current']['trunk native vlan'] = $matches[1];
529 break;
530 case (preg_match ('@^ switchport trunk allowed vlan add (.+)$@', $line, $matches)):
531 $work['current']['trunk allowed vlan'] = array_merge
532 (
533 $work['current']['trunk allowed vlan'],
534 iosParseVLANString ($matches[1])
535 );
536 break;
537 case (preg_match ('@^ switchport trunk allowed vlan (.+)$@', $line, $matches)):
538 $work['current']['trunk allowed vlan'] = iosParseVLANString ($matches[1]);
539 break;
540 case preg_match ('@^ channel-group @', $line):
541 // port-channel subinterface config follows that of the master interface
542 case preg_match ('@^ ip address @', $line):
543 // L3 interface does no switchport functions
544 $work['current']['ignore'] = TRUE;
545 break;
546 default: // suppress warning on irrelevant config clause
547 }
548 return __FUNCTION__;
549 }
550
551 function ios12PickVLANCommand (&$work, $line)
552 {
553 $matches = array();
554 switch (TRUE)
555 {
556 case ($line == '---- -------------------------------- --------- -------------------------------'):
557 // ignore the rest of VLAN table header;
558 break;
559 case (preg_match ('@! END OF VLAN LIST$@', $line)):
560 return 'ios12ScanTopLevel';
561 case (preg_match ('@^([[:digit:]]+) {1,4}.{32} active @', $line, $matches)):
562 if (!array_key_exists ($matches[1], $work['vlanlist']))
563 $work['vlanlist'][] = $matches[1];
564 break;
565 default:
566 }
567 return __FUNCTION__;
568 }
569
570 // Another finite automata to read a dialect of Foundry configuration.
571 function fdry5ReadVLANConfig ($input)
572 {
573 $ret = array
574 (
575 'vlanlist' => array(),
576 'portdata' => array(),
577 );
578 $procfunc = 'fdry5ScanTopLevel';
579 foreach (explode ("\n", $input) as $line)
580 $procfunc = $procfunc ($ret, $line);
581 return $ret;
582 }
583
584 function fdry5ScanTopLevel (&$work, $line)
585 {
586 $matches = array();
587 switch (TRUE)
588 {
589 case (preg_match ('@^vlan ([[:digit:]]+)( name .+)? (by port)$@', $line, $matches)):
590 if (!array_key_exists ($matches[1], $work['vlanlist']))
591 $work['vlanlist'][] = $matches[1];
592 $work['current'] = array ('vlan_id' => $matches[1]);
593 return 'fdry5PickVLANSubcommand';
594 case (preg_match ('@^interface ethernet ([[:digit:]]+/[[:digit:]]+/[[:digit:]]+)$@', $line, $matches)):
595 $work['current'] = array ('port_name' => 'e' . $matches[1]);
596 return 'fdry5PickInterfaceSubcommand';
597 default:
598 return __FUNCTION__;
599 }
600 }
601
602 function fdry5PickVLANSubcommand (&$work, $line)
603 {
604 if ($line[0] != ' ') // end of VLAN section
605 {
606 unset ($work['current']);
607 return 'fdry5ScanTopLevel';
608 }
609 // not yet
610 $matches = array();
611 switch (TRUE)
612 {
613 case (preg_match ('@^ tagged (.+)$@', $line, $matches)):
614 // add current VLAN to 'allowed' list of each mentioned port
615 foreach (fdry5ParsePortString ($matches[1]) as $port_name)
616 if (array_key_exists ($port_name, $work['portdata']))
617 $work['portdata'][$port_name]['allowed'][] = $work['current']['vlan_id'];
618 else
619 $work['portdata'][$port_name] = array
620 (
621 'mode' => 'trunk',
622 'allowed' => array ($work['current']['vlan_id']),
623 'native' => 0, // can be updated later
624 );
625 $work['portdata'][$port_name]['mode'] = 'trunk';
626 break;
627 case (preg_match ('@^ untagged (.+)$@', $line, $matches)):
628 // replace 'native' column of each mentioned port with current VLAN ID
629 foreach (fdry5ParsePortString ($matches[1]) as $port_name)
630 {
631 if (array_key_exists ($port_name, $work['portdata']))
632 {
633 $work['portdata'][$port_name]['native'] = $work['current']['vlan_id'];
634 $work['portdata'][$port_name]['allowed'][] = $work['current']['vlan_id'];
635 }
636 else
637 $work['portdata'][$port_name] = array
638 (
639 'mode' => 'access',
640 'allowed' => array ($work['current']['vlan_id']),
641 'native' => $work['current']['vlan_id'],
642 );
643 // Untagged ports are initially assumed to be access ports, and
644 // when this assumption is right, this is the final port mode state.
645 // When the port is dual-mode one, this is detected and justified
646 // later in "interface" section of config text.
647 $work['portdata'][$port_name]['mode'] = 'access';
648 }
649 break;
650 default: // nom-nom
651 }
652 return __FUNCTION__;
653 }
654
655 function fdry5PickInterfaceSubcommand (&$work, $line)
656 {
657 if ($line[0] != ' ') // end of interface section
658 {
659 if (array_key_exists ('dual-mode', $work['current']))
660 {
661 if (array_key_exists ($work['current']['port_name'], $work['portdata']))
662 // update existing record
663 $work['portdata'][$work['current']['port_name']]['native'] = $work['current']['dual-mode'];
664 else
665 // add new
666 $work['portdata'][$work['current']['port_name']] = array
667 (
668 'allowed' => array ($work['current']['dual-mode']),
669 'native' => $work['current']['dual-mode'],
670 );
671 // a dual-mode port is always considered a trunk port
672 // (but not in the IronWare's meaning of "trunk") regardless of
673 // number of assigned tagged VLANs
674 $work['portdata'][$work['current']['port_name']]['mode'] = 'trunk';
675 }
676 unset ($work['current']);
677 return 'fdry5ScanTopLevel';
678 }
679 $matches = array();
680 switch (TRUE)
681 {
682 case (preg_match ('@^ dual-mode( +[[:digit:]]+ *)?$@', $line, $matches)):
683 // default VLAN ID for dual-mode command is 1
684 $work['current']['dual-mode'] = strlen (trim ($matches[1])) ? trim ($matches[1]) : 1;
685 break;
686 // FIXME: trunk/link-aggregate/ip address pulls port from 802.1Q field
687 default: // nom-nom
688 }
689 return __FUNCTION__;
690 }
691
692 function fdry5ParsePortString ($string)
693 {
694 $ret = array();
695 $tokens = explode (' ', trim ($string));
696 while (count ($tokens))
697 {
698 $letters = array_shift ($tokens); // "ethe", "to"
699 $numbers = array_shift ($tokens); // "x", "x/x", "x/x/x"
700 switch ($letters)
701 {
702 case 'ethe':
703 if ($prev_numbers != NULL)
704 $ret[] = 'e' . $prev_numbers;
705 $prev_numbers = $numbers;
706 break;
707 case 'to':
708 $ret = array_merge ($ret, fdry5GenPortRange ($prev_numbers, $numbers));
709 $prev_numbers = NULL; // no action on next token
710 break;
711 default: // ???
712 return array();
713 }
714 }
715 // flush delayed item
716 if ($prev_numbers != NULL)
717 $ret[] = 'e' . $prev_numbers;
718 return $ret;
719 }
720
721 // Take two indices in form "x", "x/x" or "x/x/x" and return the range of
722 // ports spanning from the first to the last. The switch software makes it
723 // easier to perform, because "ethe x/x/x to y/y/y" ranges never cross
724 // unit/slot boundary (every index except the last remains constant).
725 function fdry5GenPortRange ($from, $to)
726 {
727 $matches = array();
728 if (1 !== preg_match ('@^([[:digit:]]+/)?([[:digit:]]+/)?([[:digit:]]+)$@', $from, $matches))
729 return array();
730 $prefix = 'e' . $matches[1] . $matches[2];
731 $from_idx = $matches[3];
732 if (1 !== preg_match ('@^([[:digit:]]+/)?([[:digit:]]+/)?([[:digit:]]+)$@', $to, $matches))
733 return array();
734 $to_idx = $matches[3];
735 for ($i = $from_idx; $i <= $to_idx; $i++)
736 $ret[] = $prefix . $i;
737 return $ret;
738 }
739
740 // an implementation for Huawei syntax
741 function vrp53ReadVLANConfig ($input)
742 {
743 $ret = array
744 (
745 'vlanlist' => array(),
746 'portdata' => array(),
747 );
748 $procfunc = 'vrp53ScanTopLevel';
749 foreach (explode ("\n", $input) as $line)
750 $procfunc = $procfunc ($ret, $line);
751 return $ret;
752 }
753
754 function vrp53ScanTopLevel (&$work, $line)
755 {
756 $matches = array();
757 switch (TRUE)
758 {
759 case (preg_match ('@^ vlan batch (.+)$@', $line, $matches)):
760 foreach (vrp53ParseVLANString ($matches[1]) as $vlan_id)
761 $work['vlanlist'][] = $vlan_id;
762 return __FUNCTION__;
763 case (preg_match ('@^interface ((GigabitEthernet|XGigabitEthernet|Eth-Trunk)([[:digit:]]+(/[[:digit:]]+)*))$@', $line, $matches)):
764 $matches[1] = preg_replace ('@^GigabitEthernet(.+)$@', 'gi\\1', $matches[1]);
765 $matches[1] = preg_replace ('@^XGigabitEthernet(.+)$@', 'xg\\1', $matches[1]);
766 $matches[1] = preg_replace ('@^Eth-Trunk(.+)$@', 'et\\1', $matches[1]);
767 $work['current'] = array ('port_name' => $matches[1]);
768 return 'vrp53PickInterfaceSubcommand';
769 default:
770 return __FUNCTION__;
771 }
772 }
773
774 function vrp53ParseVLANString ($string)
775 {
776 $string = preg_replace ('/ to /', '-', $string);
777 $string = preg_replace ('/ /', ',', $string);
778 return iosParseVLANString ($string);
779 }
780
781 function vrp53PickInterfaceSubcommand (&$work, $line)
782 {
783 if ($line[0] == '#') // end of interface section
784 {
785 // Configuration Guide - Ethernet 3.3.4:
786 // "By default, the interface type is hybrid."
787 if (!array_key_exists ('link-type', $work['current']))
788 $work['current']['link-type'] = 'hybrid';
789 if (!array_key_exists ('allowed', $work['current']))
790 $work['current']['allowed'] = array();
791 if (!array_key_exists ('native', $work['current']))
792 $work['current']['native'] = 0;
793 switch ($work['current']['link-type'])
794 {
795 case 'access':
796 // VRP does not assign access ports to VLAN1 by default,
797 // leaving them blocked.
798 $work['portdata'][$work['current']['port_name']] =
799 $work['current']['native'] ? array
800 (
801 'allowed' => $work['current']['allowed'],
802 'native' => $work['current']['native'],
803 'mode' => 'access',
804 ) : array
805 (
806 'mode' => 'none',
807 'allowed' => array(),
808 'native' => 0,
809 );
810 break;
811 case 'trunk':
812 $work['portdata'][$work['current']['port_name']] = array
813 (
814 'allowed' => $work['current']['allowed'],
815 'native' => 0,
816 'mode' => 'trunk',
817 );
818 break;
819 case 'hybrid':
820 $work['portdata'][$work['current']['port_name']] = array
821 (
822 'allowed' => $work['current']['allowed'],
823 'native' => $work['current']['native'],
824 'mode' => 'trunk',
825 );
826 break;
827 default: // dot1q-tunnel ?
828 }
829 unset ($work['current']);
830 return 'vrp53ScanTopLevel';
831 }
832 $matches = array();
833 switch (TRUE)
834 {
835 case (preg_match ('@^ port default vlan ([[:digit:]]+)$@', $line, $matches)):
836 $work['current']['native'] = $matches[1];
837 if (!array_key_exists ('allowed', $work['current']))
838 $work['current']['allowed'] = array();
839 if (!in_array ($matches[1], $work['current']['allowed']))
840 $work['current']['allowed'][] = $matches[1];
841 break;
842 case (preg_match ('@^ port link-type (.+)$@', $line, $matches)):
843 $work['current']['link-type'] = $matches[1];
844 break;
845 case (preg_match ('@^ port trunk allow-pass vlan (.+)$@', $line, $matches)):
846 if (!array_key_exists ('allowed', $work['current']))
847 $work['current']['allowed'] = array();
848 foreach (vrp53ParseVLANString ($matches[1]) as $vlan_id)
849 if (!in_array ($vlan_id, $work['current']['allowed']))
850 $work['current']['allowed'][] = $vlan_id;
851 break;
852 // TODO: make sure, that a port with "eth-trunk" clause always ends up in "none" mode
853 default: // nom-nom
854 }
855 return __FUNCTION__;
856 }
857
858 function nxos4Read8021QConfig ($input)
859 {
860 $ret = array
861 (
862 'vlanlist' => array(),
863 'portdata' => array(),
864 );
865 $procfunc = 'nxos4ScanTopLevel';
866 foreach (explode ("\n", $input) as $line)
867 $procfunc = $procfunc ($ret, $line);
868 return $ret;
869 }
870
871 function nxos4ScanTopLevel (&$work, $line)
872 {
873 $matches = array();
874 switch (TRUE)
875 {
876 case (preg_match ('@^interface ((Ethernet)[[:digit:]]+(/[[:digit:]]+)*)$@', $line, $matches)):
877 $matches[1] = preg_replace ('@^Ethernet(.+)$@', 'e\\1', $matches[1]);
878 $work['current'] = array ('port_name' => $matches[1]);
879 return 'nxos4PickSwitchportCommand';
880 case (preg_match ('@^vlan ([[:digit:]]+)$@', $line, $matches)):
881 $work['vlanlist'][] = $matches[1];
882 return 'nxos4PickVLANs';
883 default:
884 return __FUNCTION__; // continue scan
885 }
886 }
887
888 function nxos4PickVLANs (&$work, $line)
889 {
890 switch (TRUE)
891 {
892 case ($line == ''): // end of VLAN list
893 return 'nxos4ScanTopLevel';
894 case (preg_match ('@^vlan ([[:digit:]]+)$@', $line, $matches)):
895 $work['vlanlist'][] = $matches[1];
896 default: // VLAN name or any other text
897 return __FUNCTION__;
898 }
899 }
900
901 function nxos4PickSwitchportCommand (&$work, $line)
902 {
903 if ($line == '') // end of interface section
904 {
905 // fill in defaults
906 // below assumes "system default switchport" mode set on the device
907 if (!array_key_exists ('mode', $work['current']))
908 $work['current']['mode'] = 'access';
909 // save work, if it makes sense
910 switch ($work['current']['mode'])
911 {
912 case 'access':
913 if (!array_key_exists ('access vlan', $work['current']))
914 $work['current']['access vlan'] = 1;
915 $work['portdata'][$work['current']['port_name']] = array
916 (
917 'mode' => 'access',
918 'allowed' => array ($work['current']['access vlan']),
919 'native' => $work['current']['access vlan'],
920 );
921 break;
922 case 'trunk':
923 if (!array_key_exists ('trunk native vlan', $work['current']))
924 $work['current']['trunk native vlan'] = 1;
925 if (!array_key_exists ('trunk allowed vlan', $work['current']))
926 $work['current']['trunk allowed vlan'] = range (VLAN_MIN_ID, VLAN_MAX_ID);
927 // Having configured VLAN as "native" doesn't mean anything
928 // as long as it's not listed on the "allowed" line.
929 $effective_native = in_array
930 (
931 $work['current']['trunk native vlan'],
932 $work['current']['trunk allowed vlan']
933 ) ? $work['current']['trunk native vlan'] : 0;
934 $work['portdata'][$work['current']['port_name']] = array
935 (
936 'mode' => 'trunk',
937 'allowed' => $work['current']['trunk allowed vlan'],
938 'native' => $effective_native,
939 );
940 break;
941 default:
942 // dot1q-tunnel, dynamic, private-vlan --- skip these
943 }
944 unset ($work['current']);
945 return 'nxos4ScanTopLevel';
946 }
947 // not yet
948 $matches = array();
949 switch (TRUE)
950 {
951 case (preg_match ('@^ switchport mode (.+)$@', $line, $matches)):
952 $work['current']['mode'] = $matches[1];
953 break;
954 case (preg_match ('@^ switchport access vlan (.+)$@', $line, $matches)):
955 $work['current']['access vlan'] = $matches[1];
956 break;
957 case (preg_match ('@^ switchport trunk native vlan (.+)$@', $line, $matches)):
958 $work['current']['trunk native vlan'] = $matches[1];
959 break;
960 case (preg_match ('@^ switchport trunk allowed vlan add (.+)$@', $line, $matches)):
961 $work['current']['trunk allowed vlan'] = array_merge
962 (
963 $work['current']['trunk allowed vlan'],
964 iosParseVLANString ($matches[1])
965 );
966 break;
967 case (preg_match ('@^ switchport trunk allowed vlan (.+)$@', $line, $matches)):
968 $work['current']['trunk allowed vlan'] = iosParseVLANString ($matches[1]);
969 break;
970 default: // suppress warning on irrelevant config clause
971 }
972 return __FUNCTION__;
973 }
974
975 // Get a list of VLAN management pseudo-commands and return a text
976 // of real vendor-specific commands, which implement the work.
977 // This work is done in two rounds:
978 // 1. For "add allowed" and "rem allowed" commands detect continuous
979 // sequences of VLAN IDs and replace them with ranges of form "A-B",
980 // where B>A.
981 // 2. Iterate over the resulting list and produce real CLI commands.
982 function ios12TranslatePushQueue ($queue)
983 {
984 $ret = "configure terminal\n";
985 foreach ($queue as $cmd)
986 switch ($cmd['opcode'])
987 {
988 case 'create VLAN':
989 $ret .= "vlan ${cmd['arg1']}\nexit\n";
990 break;
991 case 'destroy VLAN':
992 $ret .= "no vlan ${cmd['arg1']}\n";
993 break;
994 case 'add allowed':
995 case 'rem allowed':
996 $clause = $cmd['opcode'] == 'add allowed' ? 'add' : 'remove';
997 $ret .= "interface ${cmd['port']}\n";
998 foreach (listToRanges ($cmd['vlans']) as $range)
999 $ret .= "switchport trunk allowed vlan ${clause} " .
1000 ($range['from'] == $range['to'] ? $range['to'] : "${range['from']}-${range['to']}") .
1001 "\n";
1002 $ret .= "exit\n";
1003 break;
1004 case 'set native':
1005 $ret .= "interface ${cmd['arg1']}\nswitchport trunk native vlan ${cmd['arg2']}\nexit\n";
1006 break;
1007 case 'unset native':
1008 $ret .= "interface ${cmd['arg1']}\nno switchport trunk native vlan ${cmd['arg2']}\nexit\n";
1009 break;
1010 case 'set access':
1011 $ret .= "interface ${cmd['arg1']}\nswitchport access vlan ${cmd['arg2']}\nexit\n";
1012 break;
1013 case 'unset access':
1014 $ret .= "interface ${cmd['arg1']}\nno switchport access vlan\nexit\n";
1015 break;
1016 case 'set mode':
1017 $ret .= "interface ${cmd['arg1']}\nswitchport mode ${cmd['arg2']}\n";
1018 if ($cmd['arg2'] == 'trunk')
1019 $ret .= "no switchport trunk native vlan\nswitchport trunk allowed vlan none\n";
1020 $ret .= "exit\n";
1021 break;
1022 }
1023 $ret .= "end\n";
1024 if (getConfigVar ('8021Q_WRI_AFTER_CONFT') == 'yes')
1025 $ret .= "write memory\n";
1026 return $ret;
1027 }
1028
1029 function fdry5TranslatePushQueue ($queue)
1030 {
1031 $ret = "conf t\n";
1032 foreach ($queue as $cmd)
1033 switch ($cmd['opcode'])
1034 {
1035 case 'create VLAN':
1036 $ret .= "vlan ${cmd['arg1']}\nexit\n";
1037 break;
1038 case 'destroy VLAN':
1039 $ret .= "no vlan ${cmd['arg1']}\n";
1040 break;
1041 case 'add allowed':
1042 foreach ($cmd['vlans'] as $vlan_id)
1043 $ret .= "vlan ${vlan_id}\ntagged ${cmd['port']}\nexit\n";
1044 break;
1045 case 'rem allowed':
1046 foreach ($cmd['vlans'] as $vlan_id)
1047 $ret .= "vlan ${vlan_id}\nno tagged ${cmd['port']}\nexit\n";
1048 break;
1049 case 'set native':
1050 $ret .= "interface ${cmd['arg1']}\ndual-mode ${cmd['arg2']}\nexit\n";
1051 break;
1052 case 'unset native':
1053 $ret .= "interface ${cmd['arg1']}\nno dual-mode ${cmd['arg2']}\nexit\n";
1054 break;
1055 case 'set access':
1056 $ret .= "vlan ${cmd['arg2']}\nuntagged ${cmd['arg1']}\nexit\n";
1057 break;
1058 case 'unset access':
1059 $ret .= "vlan ${cmd['arg2']}\nno untagged ${cmd['arg1']}\nexit\n";
1060 break;
1061 case 'set mode': // NOP
1062 break;
1063 }
1064 $ret .= "end\n";
1065 if (getConfigVar ('8021Q_WRI_AFTER_CONFT') == 'yes')
1066 $ret .= "write memory\n";
1067 return $ret;
1068 }
1069
1070 function vrp53TranslatePushQueue ($queue)
1071 {
1072 $ret = "system-view\n";
1073 foreach ($queue as $cmd)
1074 switch ($cmd['opcode'])
1075 {
1076 case 'create VLAN':
1077 $ret .= "vlan ${cmd['arg1']}\nquit\n";
1078 break;
1079 case 'destroy VLAN':
1080 $ret .= "undo vlan ${cmd['arg1']}\n";
1081 break;
1082 case 'add allowed':
1083 case 'rem allowed':
1084 $clause = $cmd['opcode'] == 'add allowed' ? '' : 'undo ';
1085 $ret .= "interface ${cmd['port']}\n";
1086 foreach (listToRanges ($cmd['vlans']) as $range)
1087 $ret .= "${clause}port trunk allow-pass vlan " .
1088 ($range['from'] == $range['to'] ? $range['to'] : "${range['from']} to ${range['to']}") .
1089 "\n";
1090 $ret .= "quit\n";
1091 break;
1092 case 'set native':
1093 case 'set access':
1094 $ret .= "interface ${cmd['arg1']}\nport default vlan ${cmd['arg2']}\nquit\n";
1095 break;
1096 case 'unset native':
1097 case 'unset access':
1098 $ret .= "interface ${cmd['arg1']}\nundo port default vlan\nquit\n";
1099 break;
1100 case 'set mode':
1101 $modemap = array ('access' => 'access', 'trunk' => 'hybrid');
1102 $ret .= "interface ${cmd['arg1']}\nport link-type " . $modemap[$cmd['arg2']] . "\n";
1103 if ($cmd['arg2'] == 'hybrid')
1104 $ret .= "undo port default vlan\nundo port trunk allow-pass vlan all\n";
1105 $ret .= "quit\n";
1106 break;
1107 }
1108 $ret .= "return\n";
1109 if (getConfigVar ('8021Q_WRI_AFTER_CONFT') == 'yes')
1110 $ret .= "save\nY\n";
1111 return $ret;
1112 }
1113
1114 function xos12TranslatePushQueue ($queue)
1115 {
1116 $ret = '';
1117 foreach ($queue as $cmd)
1118 switch ($cmd['opcode'])
1119 {
1120 case 'create VLAN':
1121 $ret .= "create vlan VLAN${cmd['arg1']}\n";
1122 $ret .= "configure vlan VLAN${cmd['arg1']} tag ${cmd['arg1']}\n";
1123 break;
1124 case 'destroy VLAN':
1125 $ret .= "delete vlan VLAN${cmd['arg1']}\n";
1126 break;
1127 case 'add allowed':
1128 foreach ($cmd['vlans'] as $vlan_id)
1129 {
1130 $vlan_name = $vlan_id == 1 ? 'Default' : "VLAN${vlan_id}";
1131 $ret .= "configure vlan ${vlan_name} add ports ${cmd['port']} tagged\n";
1132 }
1133 break;
1134 case 'rem allowed':
1135 foreach ($cmd['vlans'] as $vlan_id)
1136 {
1137 $vlan_name = $vlan_id == 1 ? 'Default' : "VLAN${vlan_id}";
1138 $ret .= "configure vlan ${vlan_name} delete ports ${cmd['port']}\n";
1139 }
1140 break;
1141 case 'set native':
1142 $vlan_name = $cmd['arg2'] == 1 ? 'Default' : "VLAN${cmd['arg2']}";
1143 $ret .= "configure vlan ${vlan_name} delete ports ${cmd['arg1']}\n";
1144 $ret .= "configure vlan ${vlan_name} add ports ${cmd['arg1']} untagged\n";
1145 break;
1146 case 'unset native':
1147 $vlan_name = $cmd['arg2'] == 1 ? 'Default' : "VLAN${cmd['arg2']}";
1148 $ret .= "configure vlan ${vlan_name} delete ports ${cmd['arg1']}\n";
1149 $ret .= "configure vlan ${vlan_name} add ports ${cmd['arg1']} tagged\n";
1150 break;
1151 case 'set access':
1152 $vlan_name = $cmd['arg2'] == 1 ? 'Default' : "VLAN${cmd['arg2']}";
1153 $ret .= "configure vlan ${vlan_name} add ports ${cmd['arg1']} untagged\n";
1154 break;
1155 case 'unset access':
1156 $vlan_name = $cmd['arg2'] == 1 ? 'Default' : "VLAN${cmd['arg2']}";
1157 $ret .= "configure vlan ${vlan_name} delete ports ${cmd['arg1']}\n";
1158 break;
1159 case 'set mode': // NOP
1160 break;
1161 }
1162 if (getConfigVar ('8021Q_WRI_AFTER_CONFT') == 'yes')
1163 $ret .= "save configuration\ny\n";
1164 return $ret;
1165 }
1166
1167 function ios12ScanCDPTopLevel (&$work, $line)
1168 {
1169 $matches = array();
1170 switch (TRUE)
1171 {
1172 case preg_match ('/^Device ID: (.+)$/', $line, $matches):
1173 $work['current'] = array ('device' => $matches[1]);
1174 return 'ios12ScanCDPEntry';
1175 default:
1176 return __FUNCTION__; // continue scan
1177 }
1178 }
1179
1180 function ios12ScanCDPEntry (&$work, $line)
1181 {
1182 $matches = array();
1183 switch (TRUE)
1184 {
1185 case preg_match ('/^Interface: (.+), Port ID \(outgoing port\): (.+)$/', $line, $matches):
1186 $work[ios12ShortenIfName ($matches[1])] = array
1187 (
1188 'device' => $work['current']['device'],
1189 'port' => ios12ShortenIfName ($matches[2]),
1190 );
1191 unset ($work['current']);
1192 return 'ios12ScanCDPTopLevel';
1193 default:
1194 }
1195 return __FUNCTION__;
1196 }
1197
1198 function xos12Read8021QConfig ($input)
1199 {
1200 $ret = array
1201 (
1202 'vlanlist' => array (1),
1203 'portdata' => array(),
1204 );
1205 foreach (explode ("\n", $input) as $line)
1206 {
1207 $matches = array();
1208 switch (TRUE)
1209 {
1210 case (preg_match ('/^create vlan "([[:alnum:]]+)"$/', $line, $matches)):
1211 if (!preg_match ('/^VLAN[[:digit:]]+$/', $matches[1]))
1212 throw new Exception ('unsupported VLAN name ' . $matches[1], E_GW_FAILURE);
1213 break;
1214 case (preg_match ('/^configure vlan ([[:alnum:]]+) tag ([[:digit:]]+)$/', $line, $matches)):
1215 if (strtolower ($matches[1]) == 'default')
1216 throw new Exception ('default VLAN tag must be 1', E_GW_FAILURE);
1217 if ($matches[1] != 'VLAN' . $matches[2])
1218 throw new Exception ("VLAN name ${matches[1]} does not match its tag ${matches[2]}", E_GW_FAILURE);
1219 $ret['vlanlist'][] = $matches[2];
1220 break;
1221 case (preg_match ('/^configure vlan ([[:alnum:]]+) add ports (.+) (tagged|untagged) */', $line, $matches)):
1222 $submatch = array();
1223 if ($matches[1] == 'Default')
1224 $matches[1] = 'VLAN1';
1225 if (!preg_match ('/^VLAN([[:digit:]]+)$/', $matches[1], $submatch))
1226 throw new Exception ('unsupported VLAN name ' . $matches[1], E_GW_FAILURE);
1227 $vlan_id = $submatch[1];
1228 foreach (iosParseVLANString ($matches[2]) as $port_name)
1229 {
1230 if (!array_key_exists ($port_name, $ret['portdata']))
1231 $ret['portdata'][$port_name] = array
1232 (
1233 'mode' => 'trunk',
1234 'allowed' => array(),
1235 'native' => 0,
1236 );
1237 $ret['portdata'][$port_name]['allowed'][] = $vlan_id;
1238 if ($matches[3] == 'untagged')
1239 $ret['portdata'][$port_name]['native'] = $vlan_id;
1240 }
1241 break;
1242 default:
1243 }
1244 }
1245 return $ret;
1246 }
1247
1248 ?>