]>
Commit | Line | Data |
---|---|---|
1dcf513a FB |
1 | #!/usr/bin/perl |
2 | ############################################################################### | |
3 | # # | |
4 | # VLAN Management for IPFire # | |
5 | # Copyright (C) 2019 Florian Bührle <fbuehrle@ipfire.org> # | |
6 | # # | |
7 | # This program is free software: you can redistribute it and/or modify # | |
8 | # it under the terms of the GNU General Public License as published by # | |
9 | # the Free Software Foundation, either version 3 of the License, or # | |
10 | # (at your option) any later version. # | |
11 | # # | |
12 | # This program is distributed in the hope that it will be useful, # | |
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
15 | # GNU General Public License for more details. # | |
16 | # # | |
17 | # You should have received a copy of the GNU General Public License # | |
18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
19 | # # | |
20 | ############################################################################### | |
21 | ||
22 | use strict; | |
23 | use Scalar::Util qw(looks_like_number); | |
24 | ||
25 | require '/var/ipfire/general-functions.pl'; | |
26 | require "${General::swroot}/lang.pl"; | |
27 | require "${General::swroot}/header.pl"; | |
28 | ||
63a1c81a | 29 | ###--- HTML HEAD ---### |
5c33a761 | 30 | my $extraHead = <<END |
1dcf513a | 31 | <style> |
fc31c28d | 32 | table#zoneconf { |
1dcf513a | 33 | width: 100%; |
23b26ce5 MT |
34 | border-collapse: collapse; |
35 | table-layout: fixed; | |
1dcf513a FB |
36 | } |
37 | ||
fc31c28d | 38 | #zoneconf tr { |
1dcf513a FB |
39 | height: 4em; |
40 | } | |
41 | ||
fc31c28d LAH |
42 | #zoneconf td { |
43 | padding: 5px 10px; | |
44 | border: 0.5px solid black; | |
45 | text-align: center; | |
1dcf513a FB |
46 | } |
47 | ||
fc31c28d LAH |
48 | /* dark grey header cells */ |
49 | #zoneconf td.heading { | |
50 | background-color: grey; | |
51 | color: white; | |
52 | } | |
53 | #zoneconf td.heading::first-line { | |
54 | font-weight: bold; | |
55 | line-height: 1.6; | |
1dcf513a FB |
56 | } |
57 | ||
fc31c28d LAH |
58 | /* narrow left column */ |
59 | #zoneconf tr > td:first-child { | |
60 | width: 11em; | |
23b26ce5 | 61 | } |
7478903f | 62 | |
fc31c28d LAH |
63 | /* alternating row background color */ |
64 | #zoneconf tr:nth-child(2n+3) { | |
65 | background-color: #F0F0F0; | |
1dcf513a FB |
66 | } |
67 | ||
fc31c28d | 68 | #zoneconf td.green { |
1dcf513a FB |
69 | background-color: $Header::colourgreen; |
70 | } | |
71 | ||
fc31c28d | 72 | #zoneconf td.red { |
1dcf513a FB |
73 | background-color: $Header::colourred; |
74 | } | |
75 | ||
fc31c28d | 76 | #zoneconf td.blue { |
1dcf513a FB |
77 | background-color: $Header::colourblue; |
78 | } | |
79 | ||
fc31c28d | 80 | #zoneconf td.orange { |
1dcf513a FB |
81 | background-color: $Header::colourorange; |
82 | } | |
83 | ||
fc31c28d LAH |
84 | #zoneconf td.topleft { |
85 | background-color: $Header::pagecolour; | |
1dcf513a FB |
86 | border-top-style: none; |
87 | border-left-style: none; | |
88 | } | |
89 | ||
7f44ec04 AK |
90 | input.vlanid { |
91 | width: 4em; | |
92 | } | |
93 | ||
1dcf513a | 94 | #submit-container { |
1dcf513a | 95 | width: 100%; |
23b26ce5 | 96 | padding-top: 20px; |
1d6bc7a0 | 97 | text-align: right; |
23b26ce5 | 98 | color: red; |
1dcf513a FB |
99 | } |
100 | ||
101 | #submit-container.input { | |
102 | margin-left: auto; | |
103 | } | |
1dcf513a | 104 | </style> |
5c33a761 LAH |
105 | |
106 | <script src="/include/zoneconf.js"></script> | |
1dcf513a FB |
107 | END |
108 | ; | |
63a1c81a | 109 | ###--- END HTML HEAD ---### |
1dcf513a | 110 | |
63a1c81a | 111 | ### Read configuration ### |
1dcf513a FB |
112 | my %ethsettings = (); |
113 | my %vlansettings = (); | |
114 | my %cgiparams = (); | |
115 | ||
7478903f FB |
116 | my $restart_notice = ""; |
117 | ||
1dcf513a FB |
118 | &General::readhash("${General::swroot}/ethernet/settings",\%ethsettings); |
119 | &General::readhash("${General::swroot}/ethernet/vlans",\%vlansettings); | |
120 | ||
121 | &Header::getcgihash(\%cgiparams); | |
122 | &Header::showhttpheaders(); | |
123 | ||
124 | # Define all zones we will check for NIC assignment | |
63a1c81a | 125 | my @zones = ("red", "green", "orange", "blue"); |
1dcf513a FB |
126 | |
127 | # Get all physical NICs present | |
128 | opendir(my $dh, "/sys/class/net/"); | |
129 | my @nics = (); | |
130 | ||
131 | while (my $nic = readdir($dh)) { | |
132 | if (-e "/sys/class/net/$nic/device") { # Indicates that the NIC is physical | |
133 | push(@nics, [&Network::get_nic_property($nic, "address"), $nic, 0]); | |
134 | } | |
135 | } | |
136 | ||
137 | closedir($dh); | |
138 | ||
139 | @nics = sort {$a->[0] cmp $b->[0]} @nics; # Sort nics by their MAC address | |
140 | ||
141 | # Name the physical NICs | |
142 | # Even though they may not be really named like this, we will name them ethX or wlanX | |
143 | my $ethcount = 0; | |
144 | my $wlancount = 0; | |
145 | ||
146 | foreach (@nics) { | |
147 | my $nic = $_->[1]; | |
148 | ||
149 | if (-e "/sys/class/net/$nic/wireless") { | |
150 | $_->[1] = "wlan$wlancount"; | |
151 | $_->[2] = 1; | |
152 | $wlancount++; | |
153 | } else { | |
154 | $_->[1] = "eth$ethcount"; | |
155 | $ethcount++; | |
156 | } | |
157 | } | |
158 | ||
63a1c81a LAH |
159 | ### Functions ### |
160 | ||
161 | # Check if a zone is in IP mode or in PPP, PPPoE, VDSL, ... mode | |
162 | sub is_zonetype_ip { | |
163 | my $zone_type = shift; | |
164 | return ($zone_type eq "STATIC" || $zone_type eq "DHCP"); | |
165 | } | |
166 | ||
167 | # Check if a zone is activated (device assigned) | |
168 | sub is_zone_activated { | |
169 | my $zone = uc shift; | |
170 | return ($ethsettings{"${zone}_DEV"} ne ""); | |
171 | } | |
172 | ||
173 | ### START PAGE ### | |
5c33a761 | 174 | &Header::openpage($Lang::tr{"zoneconf title"}, 1, $extraHead); |
1dcf513a FB |
175 | &Header::openbigbox('100%', 'center'); |
176 | ||
177 | ### Evaluate POST parameters ### | |
178 | ||
179 | if ($cgiparams{"ACTION"} eq $Lang::tr{"save"}) { | |
180 | my %VALIDATE_nic_check = (); | |
181 | my $VALIDATE_error = ""; | |
182 | ||
183 | foreach (@zones) { | |
184 | my $uc = uc $_; | |
185 | my $slave_string = ""; | |
186 | my $zone_mode = $cgiparams{"MODE $uc"}; | |
187 | my $VALIDATE_vlancount = 0; | |
f60b61e0 FB |
188 | my $VALIDATE_zoneslaves = 0; |
189 | ||
1dcf513a FB |
190 | $ethsettings{"${uc}_MACADDR"} = ""; |
191 | $ethsettings{"${uc}_MODE"} = ""; | |
192 | $ethsettings{"${uc}_SLAVES"} = ""; | |
193 | $vlansettings{"${uc}_PARENT_DEV"} = ""; | |
194 | $vlansettings{"${uc}_VLAN_ID"} = ""; | |
195 | $vlansettings{"${uc}_MAC_ADDRESS"} = ""; | |
196 | ||
197 | # If RED is not in DHCP or static mode, we only set its MACADDR property | |
198 | if ($uc eq "RED" && ! $cgiparams{"PPPACCESS"} eq "") { | |
199 | foreach (@nics) { | |
200 | my $mac = $_->[0]; | |
201 | ||
202 | if ($mac eq $cgiparams{"PPPACCESS"}) { | |
203 | $ethsettings{"${uc}_MACADDR"} = $mac; | |
204 | ||
205 | # Check if this interface is already accessed by any other zone | |
206 | # If this is the case, show an error message | |
207 | if ($VALIDATE_nic_check{"ACC $mac"}) { | |
208 | $VALIDATE_error = $Lang::tr{"zoneconf val ppp assignment error"}; | |
209 | } | |
210 | ||
211 | $VALIDATE_nic_check{"RESTRICT $mac"} = 1; | |
212 | last; | |
213 | } | |
214 | } | |
215 | ||
63a1c81a | 216 | # skip NIC/VLAN assignment and additional zone options for RED in PPP mode |
1dcf513a FB |
217 | next; |
218 | } | |
219 | ||
220 | foreach (@nics) { | |
221 | my $mac = $_->[0]; | |
222 | my $nic_access = $cgiparams{"ACCESS $uc $mac"}; | |
223 | ||
bb90622c MT |
224 | next unless ($nic_access); |
225 | ||
a6695868 | 226 | if ($nic_access ne "NONE") { |
1dcf513a FB |
227 | if ($VALIDATE_nic_check{"RESTRICT $mac"}) { # If this interface is already assigned to RED in PPP mode, throw an error |
228 | $VALIDATE_error = $Lang::tr{"zoneconf val ppp assignment error"}; | |
f60b61e0 FB |
229 | last; |
230 | } | |
231 | ||
a6695868 | 232 | if ($zone_mode ne "BRIDGE" && $VALIDATE_zoneslaves > 0 && $nic_access ne "") { |
f60b61e0 FB |
233 | $VALIDATE_error = $Lang::tr{"zoneconf val zoneslave amount error"}; |
234 | last; | |
1dcf513a FB |
235 | } |
236 | ||
237 | $VALIDATE_nic_check{"ACC $mac"} = 1; | |
f60b61e0 | 238 | $VALIDATE_zoneslaves++; |
1dcf513a FB |
239 | } |
240 | ||
241 | if ($nic_access eq "NATIVE") { | |
242 | if ($VALIDATE_nic_check{"NATIVE $mac"}) { | |
243 | $VALIDATE_error = $Lang::tr{"zoneconf val native assignment error"}; | |
f60b61e0 | 244 | last; |
1dcf513a FB |
245 | } |
246 | ||
247 | $VALIDATE_nic_check{"NATIVE $mac"} = 1; | |
248 | ||
249 | if ($zone_mode eq "BRIDGE") { | |
250 | $slave_string = "${slave_string}${mac} "; | |
251 | } else { | |
252 | $ethsettings{"${uc}_MACADDR"} = $mac; | |
253 | } | |
254 | } elsif ($nic_access eq "VLAN") { | |
255 | my $vlan_tag = $cgiparams{"TAG $uc $mac"}; | |
256 | ||
257 | if ($VALIDATE_nic_check{"VLAN $mac $vlan_tag"}) { | |
258 | $VALIDATE_error = $Lang::tr{"zoneconf val vlan tag assignment error"}; | |
f60b61e0 | 259 | last; |
1dcf513a FB |
260 | } |
261 | ||
262 | $VALIDATE_nic_check{"VLAN $mac $vlan_tag"} = 1; | |
263 | ||
264 | if (! looks_like_number($vlan_tag)) { | |
f60b61e0 | 265 | last; |
1dcf513a FB |
266 | } |
267 | if ($vlan_tag < 1 || $vlan_tag > 4095) { | |
f60b61e0 | 268 | last; |
1dcf513a FB |
269 | } |
270 | ||
271 | my $rnd_mac = &Network::random_mac(); | |
272 | ||
273 | $vlansettings{"${uc}_PARENT_DEV"} = $mac; | |
274 | $vlansettings{"${uc}_VLAN_ID"} = $vlan_tag; | |
275 | $vlansettings{"${uc}_MAC_ADDRESS"} = $rnd_mac; | |
276 | ||
277 | if ($zone_mode eq "BRIDGE") { | |
278 | $slave_string = "${slave_string}${rnd_mac} "; | |
279 | } | |
280 | ||
281 | $VALIDATE_vlancount++; # We can't allow more than one VLAN per zone | |
282 | } | |
283 | } | |
284 | ||
285 | if ($VALIDATE_vlancount > 1) { | |
286 | $VALIDATE_error = $Lang::tr{"zoneconf val vlan amount assignment error"}; | |
f60b61e0 | 287 | last; |
1dcf513a FB |
288 | } |
289 | ||
290 | chop($slave_string); | |
291 | ||
292 | if ($zone_mode eq "BRIDGE") { | |
293 | $ethsettings{"${uc}_MODE"} = "bridge"; | |
294 | $ethsettings{"${uc}_SLAVES"} = $slave_string; | |
295 | } elsif ($zone_mode eq "MACVTAP") { | |
296 | $ethsettings{"${uc}_MODE"} = "macvtap"; | |
297 | } | |
298 | } | |
299 | ||
63a1c81a | 300 | # validation failed, show error message and exit |
1dcf513a FB |
301 | if ($VALIDATE_error) { |
302 | &Header::openbox('100%', 'left', $Lang::tr{"error"}); | |
303 | ||
8797526d | 304 | print "$VALIDATE_error<br><br><a href='$ENV{'SCRIPT_NAME'}'>$Lang::tr{'back'}</a>\n"; |
1dcf513a FB |
305 | |
306 | &Header::closebox(); | |
307 | &Header::closebigbox(); | |
308 | &Header::closepage(); | |
309 | ||
310 | exit 0; | |
311 | } | |
312 | ||
63a1c81a | 313 | # new settings are valid, write configuration files |
1dcf513a FB |
314 | &General::writehash("${General::swroot}/ethernet/settings",\%ethsettings); |
315 | &General::writehash("${General::swroot}/ethernet/vlans",\%vlansettings); | |
7478903f | 316 | |
23b26ce5 | 317 | $restart_notice = $Lang::tr{'zoneconf notice reboot'}; |
1dcf513a FB |
318 | } |
319 | ||
1dcf513a FB |
320 | ### START OF TABLE ### |
321 | ||
63a1c81a LAH |
322 | &Header::openbox('100%', 'left', $Lang::tr{"zoneconf nic assignment"}); |
323 | ||
1dcf513a | 324 | print <<END |
0ec8e31a | 325 | <form method='post' enctype='multipart/form-data'> |
fc31c28d | 326 | <table id="zoneconf"> |
96d0c761 | 327 | <tr> |
fc31c28d | 328 | <td class="topleft"></td> |
1dcf513a FB |
329 | END |
330 | ; | |
331 | ||
0ec8e31a | 332 | # Fill the table header with all activated zones |
1dcf513a | 333 | foreach (@zones) { |
1dcf513a | 334 | my $uc = uc $_; |
1dcf513a | 335 | |
63a1c81a LAH |
336 | # If the zone is not activated, don't show it |
337 | next unless is_zone_activated($_); | |
f60b61e0 | 338 | |
63a1c81a | 339 | # If the red zone is in PPP mode, don't show a mode dropdown |
23b26ce5 | 340 | if ($uc eq "RED") { |
1dcf513a | 341 | my $red_type = $ethsettings{"RED_TYPE"}; |
1dcf513a | 342 | |
63a1c81a LAH |
343 | unless (is_zonetype_ip($red_type)) { |
344 | print "\t\t<td class='heading bold $_'>$uc ($red_type)</td>\n"; | |
1dcf513a | 345 | |
1dcf513a FB |
346 | next; # We're done here |
347 | } | |
348 | } | |
349 | ||
350 | my %mode_selected = (); | |
351 | my $zone_mode = $ethsettings{"${uc}_MODE"}; | |
352 | ||
353 | if ($zone_mode eq "") { | |
354 | $mode_selected{"DEFAULT"} = "selected"; | |
355 | } elsif ($zone_mode eq "bridge") { | |
356 | $mode_selected{"BRIDGE"} = "selected"; | |
357 | } elsif ($zone_mode eq "macvtap") { | |
358 | $mode_selected{"MACVTAP"} = "selected"; | |
359 | } | |
360 | ||
361 | print <<END | |
fc31c28d | 362 | <td class='heading $_'>$uc<br> |
1dcf513a FB |
363 | <select name="MODE $uc"> |
364 | <option value="DEFAULT" $mode_selected{"DEFAULT"}>$Lang::tr{"zoneconf nicmode default"}</option> | |
365 | <option value="BRIDGE" $mode_selected{"BRIDGE"}>$Lang::tr{"zoneconf nicmode bridge"}</option> | |
366 | <option value="MACVTAP" $mode_selected{"MACVTAP"}>$Lang::tr{"zoneconf nicmode macvtap"}</option> | |
367 | </select> | |
368 | </td> | |
369 | END | |
370 | ; | |
0ec8e31a FB |
371 | } |
372 | ||
96d0c761 | 373 | print "\t</tr>\n"; |
0ec8e31a | 374 | |
63a1c81a | 375 | # NIC assignment matrix |
0ec8e31a | 376 | foreach (@nics) { |
23b26ce5 | 377 | my $mac = $_->[0]; |
0ec8e31a | 378 | my $nic = $_->[1]; |
23b26ce5 | 379 | my $wlan = $_->[2]; |
0ec8e31a | 380 | |
96d0c761 | 381 | print "\t<tr>\n"; |
fc31c28d | 382 | print "\t\t<td class='heading'>$nic<br>$mac</td>\n"; |
0ec8e31a | 383 | |
23b26ce5 MT |
384 | # Iterate through all zones and check if the current NIC is assigned to it |
385 | foreach (@zones) { | |
386 | my $uc = uc $_; | |
5c33a761 | 387 | my $highlight = ""; |
0ec8e31a | 388 | |
63a1c81a LAH |
389 | # If the zone is not activated, don't show it |
390 | next unless is_zone_activated($_); | |
1dcf513a | 391 | |
23b26ce5 | 392 | if ($uc eq "RED") { |
23b26ce5 | 393 | # VLANs/Bridging is not possible if the RED interface is set to PPP, PPPoE, VDSL, ... |
63a1c81a | 394 | unless (is_zonetype_ip($ethsettings{"RED_TYPE"})) { |
23b26ce5 | 395 | my $checked = ""; |
1dcf513a | 396 | |
0ec8e31a FB |
397 | if ($mac eq $ethsettings{"${uc}_MACADDR"}) { |
398 | $checked = "checked"; | |
5c33a761 | 399 | $highlight = $_; |
0ec8e31a FB |
400 | } |
401 | ||
96d0c761 | 402 | print <<END |
5c33a761 LAH |
403 | <td class="$highlight"> |
404 | <input type="radio" name="PPPACCESS" value="$mac" data-zone="RED" data-mac="$mac" onchange="highlightAccess(this)" $checked> | |
96d0c761 LAH |
405 | </td> |
406 | END | |
407 | ; | |
23b26ce5 MT |
408 | next; # We're done here |
409 | } | |
410 | } | |
411 | ||
412 | my %access_selected = (); | |
413 | my $zone_mode = $ethsettings{"${uc}_MODE"}; | |
414 | my $zone_parent_dev = $vlansettings{"${uc}_PARENT_DEV"}; # ZONE_PARENT_DEV is set if this zone accesses any interface via a VLAN | |
415 | my $field_disabled = "disabled"; # Only enable the VLAN ID input field if the current access mode is VLAN | |
1dcf513a FB |
416 | my $zone_vlan_id = ""; |
417 | ||
23b26ce5 MT |
418 | # If ZONE_PARENT_DEV is set to a NICs name (e.g. green0 or eth0) instead of a MAC address, we have to find out this NICs MAC address |
419 | $zone_parent_dev = &Network::get_mac_by_name($zone_parent_dev); | |
0ec8e31a | 420 | |
23b26ce5 MT |
421 | # If the current NIC is accessed by the current zone via a VLAN, the ZONE_PARENT_DEV option corresponds to the current NIC |
422 | if ($mac eq $zone_parent_dev) { | |
1dcf513a FB |
423 | $access_selected{"VLAN"} = "selected"; |
424 | $field_disabled = ""; | |
425 | $zone_vlan_id = $vlansettings{"${uc}_VLAN_ID"}; | |
0ec8e31a | 426 | } elsif ($zone_mode eq "bridge") { # If the current zone is in bridge mode, all corresponding NICs (Native as well as VLAN) are set via the ZONE_SLAVES option |
1dcf513a FB |
427 | my @slaves = split(/ /, $ethsettings{"${uc}_SLAVES"}); |
428 | ||
429 | foreach (@slaves) { | |
430 | # Slaves can be set to a NICs name so we have to find out its MAC address | |
431 | $_ = &Network::get_mac_by_name($_); | |
432 | ||
433 | if ($_ eq $mac) { | |
434 | $access_selected{"NATIVE"} = "selected"; | |
435 | last; | |
436 | } | |
437 | } | |
0ec8e31a FB |
438 | } elsif ($mac eq $ethsettings{"${uc}_MACADDR"}) { # Native access via ZONE_MACADDR is only set if the zone does not access a NIC via a VLAN and the zone is not in bridge mode |
439 | $access_selected{"NATIVE"} = "selected"; | |
1dcf513a FB |
440 | } |
441 | ||
23b26ce5 | 442 | $access_selected{"NONE"} = ($access_selected{"NATIVE"} eq "") && ($access_selected{"VLAN"} eq "") ? "selected" : ""; |
1dcf513a FB |
443 | my $vlan_disabled = ($wlan) ? "disabled" : ""; |
444 | ||
5c33a761 LAH |
445 | # If the interface is assigned, hightlight table cell |
446 | if ($access_selected{"NONE"} eq "") { | |
447 | $highlight = $_; | |
448 | } | |
449 | ||
23b26ce5 | 450 | print <<END |
5c33a761 LAH |
451 | <td class="$highlight"> |
452 | <select name="ACCESS $uc $mac" data-zone="$uc" data-mac="$mac" onchange="highlightAccess(this)"> | |
96d0c761 LAH |
453 | <option value="NONE" $access_selected{"NONE"}>- $Lang::tr{"zoneconf access none"} -</option> |
454 | <option value="NATIVE" $access_selected{"NATIVE"}>$Lang::tr{"zoneconf access native"}</option> | |
455 | <option value="VLAN" $access_selected{"VLAN"} $vlan_disabled>$Lang::tr{"zoneconf access vlan"}</option> | |
456 | </select> | |
8797526d | 457 | <input type="number" class="vlanid" id="TAG-$uc-$mac" name="TAG $uc $mac" min="1" max="4095" value="$zone_vlan_id" $field_disabled> |
96d0c761 | 458 | </td> |
1dcf513a FB |
459 | END |
460 | ; | |
23b26ce5 | 461 | } |
1dcf513a | 462 | |
96d0c761 | 463 | print "\t</tr>\n"; |
1dcf513a FB |
464 | } |
465 | ||
63a1c81a | 466 | # footer and submit button |
1dcf513a FB |
467 | print <<END |
468 | </table> | |
1d6bc7a0 MT |
469 | |
470 | <div id="submit-container"> | |
23b26ce5 | 471 | $restart_notice |
1d6bc7a0 MT |
472 | <input type="submit" name="ACTION" value="$Lang::tr{"save"}"> |
473 | </div> | |
474 | </form> | |
1dcf513a FB |
475 | END |
476 | ; | |
477 | ||
478 | ### END OF TABLE ### | |
479 | ||
480 | &Header::closebox(); | |
481 | &Header::closebigbox(); | |
482 | &Header::closepage(); |