]>
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 | ||
29 | my $css = <<END | |
30 | <style> | |
31 | table { | |
32 | width: 100%; | |
0ec8e31a FB |
33 | border-collapse: collapse; |
34 | table-layout: fixed; | |
1dcf513a FB |
35 | } |
36 | ||
37 | tr { | |
38 | height: 4em; | |
39 | } | |
40 | ||
0ec8e31a FB |
41 | tr.thin { |
42 | height: 3em; | |
43 | } | |
44 | ||
45 | td.narrow { | |
46 | width: 11em; | |
1dcf513a FB |
47 | } |
48 | ||
49 | td { | |
50 | padding: 5px; | |
51 | padding-left: 10px; | |
52 | padding-right: 10px; | |
53 | border: 0.5px solid black; | |
54 | } | |
55 | ||
7478903f FB |
56 | td.slightlygrey { |
57 | background-color: #F0F0F0; | |
58 | } | |
59 | ||
1dcf513a FB |
60 | td.h { |
61 | background-color: grey; | |
62 | color: white; | |
63 | font-weight: 800; | |
64 | } | |
65 | ||
66 | td.green { | |
67 | background-color: $Header::colourgreen; | |
68 | } | |
69 | ||
70 | td.red { | |
71 | background-color: $Header::colourred; | |
72 | } | |
73 | ||
74 | td.blue { | |
75 | background-color: $Header::colourblue; | |
76 | } | |
77 | ||
78 | td.orange { | |
79 | background-color: $Header::colourorange; | |
80 | } | |
81 | ||
82 | td.topleft { | |
83 | background-color: white; | |
84 | border-top-style: none; | |
85 | border-left-style: none; | |
86 | } | |
87 | ||
88 | td.disabled { | |
89 | background-color: #cccccc; | |
90 | } | |
91 | ||
92 | td.textcenter { | |
93 | text-align: center; | |
94 | } | |
95 | ||
96 | #submit-container { | |
1dcf513a | 97 | width: 100%; |
0ec8e31a | 98 | padding-top: 20px; |
1d6bc7a0 | 99 | text-align: right; |
7478903f | 100 | color: red; |
1dcf513a FB |
101 | } |
102 | ||
103 | #submit-container.input { | |
104 | margin-left: auto; | |
105 | } | |
106 | ||
107 | button { | |
108 | margin-top: 1em; | |
109 | } | |
1dcf513a FB |
110 | </style> |
111 | END | |
112 | ; | |
113 | ||
114 | my %ethsettings = (); | |
115 | my %vlansettings = (); | |
116 | my %cgiparams = (); | |
117 | ||
7478903f FB |
118 | my $restart_notice = ""; |
119 | ||
1dcf513a FB |
120 | &General::readhash("${General::swroot}/ethernet/settings",\%ethsettings); |
121 | &General::readhash("${General::swroot}/ethernet/vlans",\%vlansettings); | |
122 | ||
123 | &Header::getcgihash(\%cgiparams); | |
124 | &Header::showhttpheaders(); | |
125 | ||
126 | # Define all zones we will check for NIC assignment | |
127 | my @zones = ("green", "red", "orange", "blue"); | |
128 | ||
129 | # Get all physical NICs present | |
130 | opendir(my $dh, "/sys/class/net/"); | |
131 | my @nics = (); | |
132 | ||
133 | while (my $nic = readdir($dh)) { | |
134 | if (-e "/sys/class/net/$nic/device") { # Indicates that the NIC is physical | |
135 | push(@nics, [&Network::get_nic_property($nic, "address"), $nic, 0]); | |
136 | } | |
137 | } | |
138 | ||
139 | closedir($dh); | |
140 | ||
141 | @nics = sort {$a->[0] cmp $b->[0]} @nics; # Sort nics by their MAC address | |
142 | ||
143 | # Name the physical NICs | |
144 | # Even though they may not be really named like this, we will name them ethX or wlanX | |
145 | my $ethcount = 0; | |
146 | my $wlancount = 0; | |
147 | ||
148 | foreach (@nics) { | |
149 | my $nic = $_->[1]; | |
150 | ||
151 | if (-e "/sys/class/net/$nic/wireless") { | |
152 | $_->[1] = "wlan$wlancount"; | |
153 | $_->[2] = 1; | |
154 | $wlancount++; | |
155 | } else { | |
156 | $_->[1] = "eth$ethcount"; | |
157 | $ethcount++; | |
158 | } | |
159 | } | |
160 | ||
161 | &Header::openpage($Lang::tr{"zoneconf title"}, 1, $css); | |
162 | &Header::openbigbox('100%', 'center'); | |
163 | ||
164 | ### Evaluate POST parameters ### | |
165 | ||
166 | if ($cgiparams{"ACTION"} eq $Lang::tr{"save"}) { | |
167 | my %VALIDATE_nic_check = (); | |
168 | my $VALIDATE_error = ""; | |
169 | ||
170 | foreach (@zones) { | |
171 | my $uc = uc $_; | |
172 | my $slave_string = ""; | |
173 | my $zone_mode = $cgiparams{"MODE $uc"}; | |
174 | my $VALIDATE_vlancount = 0; | |
f60b61e0 FB |
175 | my $VALIDATE_zoneslaves = 0; |
176 | ||
1dcf513a FB |
177 | $ethsettings{"${uc}_MACADDR"} = ""; |
178 | $ethsettings{"${uc}_MODE"} = ""; | |
179 | $ethsettings{"${uc}_SLAVES"} = ""; | |
180 | $vlansettings{"${uc}_PARENT_DEV"} = ""; | |
181 | $vlansettings{"${uc}_VLAN_ID"} = ""; | |
182 | $vlansettings{"${uc}_MAC_ADDRESS"} = ""; | |
183 | ||
184 | # If RED is not in DHCP or static mode, we only set its MACADDR property | |
185 | if ($uc eq "RED" && ! $cgiparams{"PPPACCESS"} eq "") { | |
186 | foreach (@nics) { | |
187 | my $mac = $_->[0]; | |
188 | ||
189 | if ($mac eq $cgiparams{"PPPACCESS"}) { | |
190 | $ethsettings{"${uc}_MACADDR"} = $mac; | |
191 | ||
192 | # Check if this interface is already accessed by any other zone | |
193 | # If this is the case, show an error message | |
194 | if ($VALIDATE_nic_check{"ACC $mac"}) { | |
195 | $VALIDATE_error = $Lang::tr{"zoneconf val ppp assignment error"}; | |
196 | } | |
197 | ||
198 | $VALIDATE_nic_check{"RESTRICT $mac"} = 1; | |
199 | last; | |
200 | } | |
201 | } | |
202 | ||
203 | next; | |
204 | } | |
205 | ||
206 | foreach (@nics) { | |
207 | my $mac = $_->[0]; | |
208 | my $nic_access = $cgiparams{"ACCESS $uc $mac"}; | |
209 | ||
a6695868 | 210 | if ($nic_access ne "NONE") { |
1dcf513a FB |
211 | if ($VALIDATE_nic_check{"RESTRICT $mac"}) { # If this interface is already assigned to RED in PPP mode, throw an error |
212 | $VALIDATE_error = $Lang::tr{"zoneconf val ppp assignment error"}; | |
f60b61e0 FB |
213 | last; |
214 | } | |
215 | ||
a6695868 | 216 | if ($zone_mode ne "BRIDGE" && $VALIDATE_zoneslaves > 0 && $nic_access ne "") { |
f60b61e0 FB |
217 | $VALIDATE_error = $Lang::tr{"zoneconf val zoneslave amount error"}; |
218 | last; | |
1dcf513a FB |
219 | } |
220 | ||
221 | $VALIDATE_nic_check{"ACC $mac"} = 1; | |
f60b61e0 | 222 | $VALIDATE_zoneslaves++; |
1dcf513a FB |
223 | } |
224 | ||
225 | if ($nic_access eq "NATIVE") { | |
226 | if ($VALIDATE_nic_check{"NATIVE $mac"}) { | |
227 | $VALIDATE_error = $Lang::tr{"zoneconf val native assignment error"}; | |
f60b61e0 | 228 | last; |
1dcf513a FB |
229 | } |
230 | ||
231 | $VALIDATE_nic_check{"NATIVE $mac"} = 1; | |
232 | ||
233 | if ($zone_mode eq "BRIDGE") { | |
234 | $slave_string = "${slave_string}${mac} "; | |
235 | } else { | |
236 | $ethsettings{"${uc}_MACADDR"} = $mac; | |
237 | } | |
238 | } elsif ($nic_access eq "VLAN") { | |
239 | my $vlan_tag = $cgiparams{"TAG $uc $mac"}; | |
240 | ||
241 | if ($VALIDATE_nic_check{"VLAN $mac $vlan_tag"}) { | |
242 | $VALIDATE_error = $Lang::tr{"zoneconf val vlan tag assignment error"}; | |
f60b61e0 | 243 | last; |
1dcf513a FB |
244 | } |
245 | ||
246 | $VALIDATE_nic_check{"VLAN $mac $vlan_tag"} = 1; | |
247 | ||
248 | if (! looks_like_number($vlan_tag)) { | |
f60b61e0 | 249 | last; |
1dcf513a FB |
250 | } |
251 | if ($vlan_tag < 1 || $vlan_tag > 4095) { | |
f60b61e0 | 252 | last; |
1dcf513a FB |
253 | } |
254 | ||
255 | my $rnd_mac = &Network::random_mac(); | |
256 | ||
257 | $vlansettings{"${uc}_PARENT_DEV"} = $mac; | |
258 | $vlansettings{"${uc}_VLAN_ID"} = $vlan_tag; | |
259 | $vlansettings{"${uc}_MAC_ADDRESS"} = $rnd_mac; | |
260 | ||
261 | if ($zone_mode eq "BRIDGE") { | |
262 | $slave_string = "${slave_string}${rnd_mac} "; | |
263 | } | |
264 | ||
265 | $VALIDATE_vlancount++; # We can't allow more than one VLAN per zone | |
266 | } | |
267 | } | |
268 | ||
269 | if ($VALIDATE_vlancount > 1) { | |
270 | $VALIDATE_error = $Lang::tr{"zoneconf val vlan amount assignment error"}; | |
f60b61e0 | 271 | last; |
1dcf513a FB |
272 | } |
273 | ||
274 | chop($slave_string); | |
275 | ||
276 | if ($zone_mode eq "BRIDGE") { | |
277 | $ethsettings{"${uc}_MODE"} = "bridge"; | |
278 | $ethsettings{"${uc}_SLAVES"} = $slave_string; | |
279 | } elsif ($zone_mode eq "MACVTAP") { | |
280 | $ethsettings{"${uc}_MODE"} = "macvtap"; | |
281 | } | |
282 | } | |
283 | ||
284 | if ($VALIDATE_error) { | |
285 | &Header::openbox('100%', 'left', $Lang::tr{"error"}); | |
286 | ||
287 | print "$VALIDATE_error<br><a href='/cgi-bin/zoneconf.cgi'><button>$Lang::tr{'ok'}</button></a>"; | |
288 | ||
289 | &Header::closebox(); | |
290 | &Header::closebigbox(); | |
291 | &Header::closepage(); | |
292 | ||
293 | exit 0; | |
294 | } | |
295 | ||
296 | &General::writehash("${General::swroot}/ethernet/settings",\%ethsettings); | |
297 | &General::writehash("${General::swroot}/ethernet/vlans",\%vlansettings); | |
7478903f FB |
298 | |
299 | $restart_notice = $Lang::tr{'zoneconf notice reboot'}; | |
1dcf513a FB |
300 | } |
301 | ||
302 | &Header::openbox('100%', 'left', $Lang::tr{"zoneconf nic assignment"}); | |
303 | ||
304 | ### START OF TABLE ### | |
305 | ||
306 | print <<END | |
0ec8e31a FB |
307 | <form method='post' enctype='multipart/form-data'> |
308 | <table> | |
309 | <tr> | |
310 | <td class="h narrow topleft" /td> | |
1dcf513a FB |
311 | END |
312 | ; | |
313 | ||
0ec8e31a | 314 | # Fill the table header with all activated zones |
1dcf513a | 315 | foreach (@zones) { |
1dcf513a | 316 | my $uc = uc $_; |
0ec8e31a | 317 | my $dev_name = $ethsettings{"${uc}_DEV"}; |
1dcf513a | 318 | |
0ec8e31a FB |
319 | if ($dev_name eq "") { # If the zone is not activated, don't show it |
320 | next; | |
321 | } | |
f60b61e0 | 322 | |
0ec8e31a FB |
323 | # If the zone is in PPP mode, don't show a mode dropdown |
324 | if ($uc eq "RED") { | |
1dcf513a FB |
325 | my $red_type = $ethsettings{"RED_TYPE"}; |
326 | my $red_restricted = ($uc eq "RED" && ! ($red_type eq "STATIC" || $red_type eq "DHCP")); | |
327 | ||
1dcf513a | 328 | if ($red_restricted) { |
0ec8e31a | 329 | print "<td class='h textcenter $_'>$uc ($red_type)</td>"; |
1dcf513a | 330 | |
1dcf513a FB |
331 | next; # We're done here |
332 | } | |
333 | } | |
334 | ||
335 | my %mode_selected = (); | |
336 | my $zone_mode = $ethsettings{"${uc}_MODE"}; | |
337 | ||
338 | if ($zone_mode eq "") { | |
339 | $mode_selected{"DEFAULT"} = "selected"; | |
340 | } elsif ($zone_mode eq "bridge") { | |
341 | $mode_selected{"BRIDGE"} = "selected"; | |
342 | } elsif ($zone_mode eq "macvtap") { | |
343 | $mode_selected{"MACVTAP"} = "selected"; | |
344 | } | |
345 | ||
346 | print <<END | |
0ec8e31a | 347 | <td class='h textcenter $_'>$uc</br> |
1dcf513a FB |
348 | <select name="MODE $uc"> |
349 | <option value="DEFAULT" $mode_selected{"DEFAULT"}>$Lang::tr{"zoneconf nicmode default"}</option> | |
350 | <option value="BRIDGE" $mode_selected{"BRIDGE"}>$Lang::tr{"zoneconf nicmode bridge"}</option> | |
351 | <option value="MACVTAP" $mode_selected{"MACVTAP"}>$Lang::tr{"zoneconf nicmode macvtap"}</option> | |
352 | </select> | |
353 | </td> | |
354 | END | |
355 | ; | |
0ec8e31a FB |
356 | } |
357 | ||
358 | print "</tr>"; | |
359 | ||
7478903f FB |
360 | my $slightlygrey = ""; |
361 | ||
0ec8e31a FB |
362 | foreach (@nics) { |
363 | my $mac = $_->[0]; | |
364 | my $nic = $_->[1]; | |
365 | my $wlan = $_->[2]; | |
366 | ||
367 | print "<tr><td class='h narrow textcenter'>$nic<br>$mac</td>"; | |
368 | ||
369 | # Iterate through all zones and check if the current NIC is assigned to it | |
370 | foreach (@zones) { | |
371 | my $uc = uc $_; | |
372 | my $dev_name = $ethsettings{"${uc}_DEV"}; | |
373 | ||
374 | if ($dev_name eq "") { # Again, skip the zone if it is not activated | |
375 | next; | |
376 | } | |
1dcf513a | 377 | |
0ec8e31a FB |
378 | if ($uc eq "RED") { |
379 | my $red_type = $ethsettings{"RED_TYPE"}; | |
380 | my $red_restricted = ($uc eq "RED" && ! ($red_type eq "STATIC" || $red_type eq "DHCP")); | |
1dcf513a | 381 | |
0ec8e31a FB |
382 | # VLANs/Bridging is not possible if the RED interface is set to PPP, PPPoE, VDSL, ... |
383 | if ($red_restricted) { | |
384 | my $checked = ""; | |
1dcf513a | 385 | |
0ec8e31a FB |
386 | if ($mac eq $ethsettings{"${uc}_MACADDR"}) { |
387 | $checked = "checked"; | |
388 | } | |
389 | ||
7478903f | 390 | print "<td class='textcenter $slightlygrey'><input type='radio' id='PPPACCESS $mac' name='PPPACCESS' value='$mac' $checked></td>"; |
0ec8e31a FB |
391 | next; # We're done here |
392 | } | |
393 | } | |
394 | ||
395 | my %access_selected = (); | |
396 | my $zone_mode = $ethsettings{"${uc}_MODE"}; | |
397 | my $zone_parent_dev = $vlansettings{"${uc}_PARENT_DEV"}; # ZONE_PARENT_DEV is set if this zone accesses any interface via a VLAN | |
398 | my $field_disabled = "disabled"; # Only enable the VLAN ID input field if the current access mode is VLAN | |
1dcf513a FB |
399 | my $zone_vlan_id = ""; |
400 | ||
0ec8e31a FB |
401 | # 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 |
402 | $zone_parent_dev = &Network::get_mac_by_name($zone_parent_dev); | |
403 | ||
404 | # If the current NIC is accessed by the current zone via a VLAN, the ZONE_PARENT_DEV option corresponds to the current NIC | |
405 | if ($mac eq $zone_parent_dev) { | |
1dcf513a FB |
406 | $access_selected{"VLAN"} = "selected"; |
407 | $field_disabled = ""; | |
408 | $zone_vlan_id = $vlansettings{"${uc}_VLAN_ID"}; | |
0ec8e31a | 409 | } 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 |
410 | my @slaves = split(/ /, $ethsettings{"${uc}_SLAVES"}); |
411 | ||
412 | foreach (@slaves) { | |
413 | # Slaves can be set to a NICs name so we have to find out its MAC address | |
414 | $_ = &Network::get_mac_by_name($_); | |
415 | ||
416 | if ($_ eq $mac) { | |
417 | $access_selected{"NATIVE"} = "selected"; | |
418 | last; | |
419 | } | |
420 | } | |
0ec8e31a FB |
421 | } 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 |
422 | $access_selected{"NATIVE"} = "selected"; | |
1dcf513a FB |
423 | } |
424 | ||
0ec8e31a | 425 | $access_selected{"NONE"} = ($access_selected{"NATIVE"} eq "") && ($access_selected{"VLAN"} eq "") ? "selected" : ""; |
1dcf513a FB |
426 | my $vlan_disabled = ($wlan) ? "disabled" : ""; |
427 | ||
0ec8e31a | 428 | print <<END |
7478903f | 429 | <td class="textcenter $slightlygrey"> |
0ec8e31a FB |
430 | <select name="ACCESS $uc $mac" onchange="document.getElementById('TAG $uc $mac').disabled = (this.value === 'VLAN' ? false : true)"> |
431 | <option value="NONE" $access_selected{"NONE"}>- $Lang::tr{"zoneconf access none"} -</option> | |
432 | <option value="NATIVE" $access_selected{"NATIVE"}>$Lang::tr{"zoneconf access native"}</option> | |
433 | <option value="VLAN" $access_selected{"VLAN"} $vlan_disabled>$Lang::tr{"zoneconf access vlan"}</option> | |
434 | </select> | |
435 | <input type="number" id="TAG $uc $mac" name="TAG $uc $mac" min="1" max="4095" value="$zone_vlan_id" $field_disabled> | |
436 | </td> | |
1dcf513a FB |
437 | END |
438 | ; | |
0ec8e31a | 439 | } |
1dcf513a | 440 | |
0ec8e31a | 441 | print "</tr>"; |
7478903f FB |
442 | |
443 | if ($slightlygrey) { | |
444 | $slightlygrey = ""; | |
445 | } else { | |
446 | $slightlygrey = "slightlygrey"; | |
447 | } | |
1dcf513a FB |
448 | } |
449 | ||
450 | print <<END | |
451 | </table> | |
1d6bc7a0 MT |
452 | |
453 | <div id="submit-container"> | |
7478903f | 454 | $restart_notice |
1d6bc7a0 MT |
455 | <input type="submit" name="ACTION" value="$Lang::tr{"save"}"> |
456 | </div> | |
457 | </form> | |
1dcf513a FB |
458 | END |
459 | ; | |
460 | ||
461 | ### END OF TABLE ### | |
462 | ||
463 | &Header::closebox(); | |
464 | &Header::closebigbox(); | |
465 | &Header::closepage(); |