#!/usr/bin/perl ############################################################################### # # # IPFire.org - A linux based firewall # # Copyright (C) 2007-2023 IPFire Team # # Copyright (C) 2024 BPFire # # # # This program is free software: you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # # the Free Software Foundation, either version 3 of the License, or # # (at your option) any later version. # # # # This program is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # # GNU General Public License for more details. # # # # You should have received a copy of the GNU General Public License # # along with this program. If not, see . # # # ############################################################################### use strict; use experimental 'smartmatch'; # enable only the following on debugging purpose #use warnings; #use CGI::Carp 'fatalsToBrowser'; require '/var/ipfire/general-functions.pl'; require "${General::swroot}/lang.pl"; require "${General::swroot}/header.pl"; #workaround to suppress a warning when a variable is used only once my @dummy = ( ${Header::colouryellow} ); undef (@dummy); our %hasettings=(); our %netsettings=(); my %mainsettings=(); my %timesettings=(); my $setting = "${General::swroot}/keepalived/settings"; my $runsetting = "${General::swroot}/keepalived/runsettings"; my $loxilbipfile = "${General::swroot}/loxilb/ipconfigfile"; # because we need commas in the some data my $errormessage = ''; #remove 'ENABLE_HA' from '/var/ipfire/keepalived/settings' as it could affect keepalived running state my @nosaved=('ENABLE_HA'); my %color = (); $hasettings{'ENABLE_HA'} = 'off'; # Load multiline data our @current = (); if (open(FILE, "$loxilbipfile")) { @current = ; close (FILE); } &Header::showhttpheaders(); our @ITFs=('RED', 'GREEN'); my @STATE= ('MASTER', 'BACKUP'); my $DUMMY_IP = '192.0.2.1'; #for HA master/backup state tracking #Settings1 for the first screen box foreach my $itf (@ITFs) { $hasettings{"ENABLE_${itf}"} = 'off'; $hasettings{"state_${itf}"} = ''; $hasettings{"garp_master_delay_${itf}"} = ''; $hasettings{"virtual_router_id_${itf}"} = ''; $hasettings{"priority_${itf}"} = ''; $hasettings{"advert_int_${itf}"} = ''; $hasettings{"auth_pass_${itf}"} = ''; $hasettings{"unicast_peer_${itf}"} = ''; $hasettings{"virtual_ipaddress_${itf}"} = ''; } # Read Ipcop settings &General::readhash("${General::swroot}/ethernet/settings", \%netsettings); &General::readhash("${General::swroot}/main/settings", \%mainsettings); &General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", \%color); #Get GUI values &Header::getcgihash(\%hasettings); if ($hasettings{'ACTION'} eq $Lang::tr{'enable'}) { #remove @nosaved from $hasettings before writehash to 'runsettings' file since 'runsetting' is only for keepalived running state foreach my $itf (@ITFs) { my @nosaved = ("virtual_router_id_${itf}", "priority_${itf}", "unicast_peer_${itf}", "auth_pass_${itf}", "garp_master_delay_${itf}", "advert_int_${itf}", "virtual_ipaddress_${itf}", "state_${itf}", "ENABLE_${itf}"); map (delete ($hasettings{$_}) ,(@nosaved)); } &General::writehash("$runsetting", \%hasettings); if ($hasettings{'ENABLE_HA'} eq 'on') { &General::system('/usr/bin/touch', "${General::swroot}/keepalived/enable_ha"); &General::system('/usr/local/bin/keepalivedctrl', 'start'); } else { &General::system('/usr/local/bin/keepalivedctrl', 'stop'); unlink "${General::swroot}/keepalived/enable_ha"; } } # Check Settings1 first because they are needed by &buildconf if ($hasettings{'ACTION'} eq $Lang::tr{'save'}) { foreach my $itf (@ITFs) { if ($hasettings{"ENABLE_${itf}"} eq 'on' ) { if (!(&General::validnum($hasettings{"virtual_router_id_${itf}"})) || ($hasettings{"virtual_router_id_${itf}"} eq '')) { $errormessage = "virtual_router_id" . " is $Lang::tr{'required field'}" . " or not valid num"; goto ERROR; } if (!(&General::validnum($hasettings{"priority_${itf}"})) || ($hasettings{"priority_${itf}"} eq '')) { $errormessage = "priority" . " is $Lang::tr{'required field'}" . " or not valid num"; goto ERROR; } if (!(&General::validnum($hasettings{"advert_int_${itf}"})) || ($hasettings{"advert_int_${itf}"} eq '')) { $errormessage = "advert_int" . " is $Lang::tr{'required field'}" . " or not valid num"; goto ERROR; } if (!(&General::validnum($hasettings{"garp_master_delay_${itf}"})) || ($hasettings{"garp_master_delay_${itf}"} eq '')) { $errormessage = "garp master delay" . " is $Lang::tr{'required field'}" . " or not valid num"; goto ERROR; } } } map (delete ($hasettings{$_}) ,@nosaved,'ACTION','KEY1','KEY2','q'); # Must not be saved &General::writehash($setting, \%hasettings); # Save good settings $hasettings{'ACTION'} = $Lang::tr{'save'}; # create an 'ACTION' map ($hasettings{$_} = '',@nosaved,'KEY1','KEY2'); # and reinit vars to empty &buildconf; ERROR: } if ($hasettings{'ACTION'} eq '' ) { # First launch from GUI # Set default DHCP values only if blank and disabled foreach my $itf (@ITFs) { if ($hasettings{"ENABLE_${itf}"} ne 'on' ) { $hasettings{"virtual_router_id_${itf}"} = '50'; $hasettings{"priority_${itf}"} = '100'; $hasettings{"advert_int_${itf}"} = '1'; $hasettings{"auth_pass_${itf}"} = ''; $hasettings{"unicast_peer_${itf}"} = ''; $hasettings{"garp_master_delay_${itf}"} = '10'; } } } ### START PAGE ### &Header::openpage($Lang::tr{'keepalived configuration'}, 1, $Header::extraHead); &Header::openbigbox('100%', 'left', '', $errormessage); if ($errormessage) { &Header::openbox('100%', 'left', $Lang::tr{'error messages'}); print "$errormessage \n"; &Header::closebox(); } # Read configuration file. &General::readhash("$runsetting", \%hasettings); # Checkbox pre-selection. my $checked; if ($hasettings{'ENABLE_HA'} eq "on") { $checked = "checked='checked'"; } my $hastate = "
$Lang::tr{'standby'}
"; my @ips = &General::get_ipaddresses_from_interface("green0"); if (grep { $_ eq $DUMMY_IP } @ips) { $hastate = "
$Lang::tr{'active'}
"; } my $sactive = "
$Lang::tr{'stopped'}
"; my @status = &General::system_output('/usr/local/bin/keepalivedctrl', 'status'); if (grep(/is running/, @status)){ $sactive = "
$Lang::tr{'running'}
"; } &Header::openbox('100%', 'center', $Lang::tr{'keepalived status'}); print <
      $Lang::tr{'keepalived status'} $sactive $hastate   $Lang::tr{'enable'} END print "
\n"; &Header::closebox(); # &General::readhash($setting, \%hasettings); # Get saved settings and reset to good if needed &Header::openbox('100%', 'left', $Lang::tr{'keepalived config'}); print "
"; foreach my $itf (@ITFs) { my %checked = (); my @vips; my $lc_itf = lc($itf); my $current_state = $hasettings{"state_${itf}"}; my @current_vips = split(/\|/, $hasettings{"virtual_ipaddress_${itf}"}); #multi selected value is separated by pipe | foreach my $line (@current) { chomp($line); my @temp = split(/\,/, $line); if ($temp[1] eq $netsettings{"${itf}_DEV"}) { push(@vips, $temp[0]); } } $checked{'ENABLE'}{'on'} = ($hasettings{"ENABLE_${itf}"} ne 'on') ? '' : "checked='checked'"; print < $Lang::tr{"$lc_itf interface"} $Lang::tr{'enabled'} $Lang::tr{'keepalived state'}:  $Lang::tr{'keepalived priority'} * $Lang::tr{'keepalived advert int'} * $Lang::tr{'keepalived garp master delay'}  $Lang::tr{'keepalived auth pass'} * $Lang::tr{'keepalived unicast peer'} * $Lang::tr{'keepalived virtual address'}: * END &Header::closebox(); &Header::closebigbox(); &Header::closepage(); # Build the configuration file mixing settings, fixed leases and advanced options sub buildconf { open(FILE, ">/${General::swroot}/keepalived/keepalived.conf") or die "Unable to write keepalived.conf file"; flock(FILE, 2); # Global settings print FILE "global_defs {\n"; print FILE "\trouter_id BPFire_DEVEL\n"; print FILE "}\n"; print FILE "\n"; #Subnet range definition foreach my $itf (@ITFs) { my $lc_itf=lc($itf); if ($hasettings{"ENABLE_${itf}"} eq 'on' ){ print FILE "vrrp_instance VI_$lc_itf {" . "\n"; print FILE "\tstate " . $hasettings{"state_${itf}"} . "\n"; print FILE "\tinterface " . $netsettings{"${itf}_DEV"} . "\n"; print FILE "\tvirtual_router_id " . $hasettings{"virtual_router_id_${itf}"} . "\n"; print FILE "\tpriority " . $hasettings{"priority_${itf}"} . "\n"; print FILE "\tadvert_int " . $hasettings{"advert_int_${itf}"} . "\n"; print FILE "\tgarp_master_delay " . $hasettings{"garp_master_delay_${itf}"} . "\n"; #unicast peer, red0 does not support multicast print FILE "\tunicast_peer {" . "\n"; print FILE "\t\t" . $hasettings{"unicast_peer_${itf}"} . "\n"; print FILE "\t}" . "\n"; # authentication print FILE "\tauthentication {" . "\n"; print FILE "\t\tauth_type PASS" . "\n"; print FILE "\t\tauth_pass " . $hasettings{"auth_pass_${itf}"} . "\n"; print FILE "\t}" . "\n"; # virtual ipaddress print FILE "\tvirtual_ipaddress {" . "\n"; print FILE "\t\t$DUMMY_IP" . "\n"; my @vips = split(/\|/, $hasettings{"virtual_ipaddress_${itf}"}); foreach my $ip (@vips) { print FILE "\t\t$ip" . "\n"; } print FILE "\t}" . "\n"; print FILE "} #$itf\n\n"; &General::system('/usr/bin/touch', "${General::swroot}/keepalived/enable_${lc_itf}"); } else { unlink "${General::swroot}/keepalived/enable_${lc_itf}"; } } close FILE; &General::system_background('/usr/local/bin/keepalivedctrl', 'restart'); }