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