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