Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding OSPF cost-shifting as a CARP failover mechanism for scenarios requiring rapid failover in HA pairs. #557

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion net/pfSense-pkg-Quagga_OSPF/Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# $FreeBSD$

PORTNAME= pfSense-pkg-Quagga_OSPF
PORTVERSION= 0.6.21
PORTVERSION= 0.6.22
PORTREVISION= 3
CATEGORIES= net
MASTER_SITES= # empty
Expand Down
22 changes: 22 additions & 0 deletions net/pfSense-pkg-Quagga_OSPF/files/usr/local/bin/quaggactl
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ zebra)
bgpr*)
daemon_command ${ZEBRA_PORT} ${ZEBRA_PASSWORD} "show ip route bgp"
;;
run*)
daemon_command ${ZEBRA_PORT} ${ZEBRA_PASSWORD} "enable\nshow running-config "
;;
esac ;;
ospf*)
if [ "`pgrep ospfd`" = "" ]; then
Expand Down Expand Up @@ -87,6 +90,22 @@ ospf*)
rou*)
daemon_command ${OSPF_PORT} ${OSPF_PASSWORD} "show ip ospf route"
;;
run*)
daemon_command ${OSPF_PORT} ${OSPF_PASSWORD} "enable\nshow running-config"
;;
cos*)
if [ -z "$3" ] || [ -z "$4" ]; then
echo "interface or cost not properly specified."
exit 1
fi
case $4 in
0)
daemon_command ${OSPF_PORT} ${OSPF_PASSWORD} "enable\nconfig terminal\ninterface $3\nno ip ospf cost\nend"
;;
*)
daemon_command ${OSPF_PORT} ${OSPF_PASSWORD} "enable\nconfig terminal\ninterface $3\nip ospf cost $4\nend"
;;
esac ;;
esac ;;
bgp6*)
if [ "`pgrep bgpd`" = "" ]; then
Expand Down Expand Up @@ -115,5 +134,8 @@ bgp*)
shift; shift;
daemon_command ${BGP_PORT} ${BGP_PASSWORD} "show ip bgp summary $*"
;;
run*)
daemon_command ${BGP_PORT} ${BGP_PASSWORD} "enable\nshow running-config"
;;
esac ;;
esac
178 changes: 141 additions & 37 deletions net/pfSense-pkg-Quagga_OSPF/files/usr/local/pkg/quagga_ospfd.inc
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,17 @@ function quagga_ospfd_install_conf() {
// generate ospfd.conf based on the assistant
if (is_array($config['installedpackages']['quaggaospfd']['config'])) {
$ospfd_conf = &$config['installedpackages']['quaggaospfd']['config'][0];
} elseif (isset($config['installedpackages']['quaggaospfdraw']['config'][0]['ospfd']) ||
isset($config['installedpackages']['quaggaospfdraw']['config'][0]['ospf6d']) ||
isset($config['installedpackages']['quaggaospfdraw']['config'][0]['bgpd'])) {
} elseif (isset($config['installedpackages']['quaggaospfdraw']['config'][0]['ospfd'])
|| isset($config['installedpackages']['quaggaospfdraw']['config'][0]['ospf6d'])
|| isset($config['installedpackages']['quaggaospfdraw']['config'][0]['bgpd'])) {
log_error("Quagga: No assistant generated config for OSPF, but found raw config for one or more daemon");
} else {
log_error("Quagga OSPFd: No config data found.");
return;
}

if (isset($config['installedpackages']['quaggaospfdraw']['config'][0]['ospfd'])
&& !empty($config['installedpackages']['quaggaospfdraw']['config'][0]['ospfd'])) {
&& !empty($config['installedpackages']['quaggaospfdraw']['config'][0]['ospfd'])) {
// if there is a raw config specifyed in tthe config.xml use that instead of the assisted config
$conffile = str_replace("\r","",base64_decode($config['installedpackages']['quaggaospfdraw']['config'][0]['ospfd']));
} else {
Expand Down Expand Up @@ -266,7 +266,7 @@ function quagga_ospfd_install_conf() {

/* Make zebra config */
if (isset($config['installedpackages']['quaggaospfdraw']['config'][0]['zebra'])
&& !empty($config['installedpackages']['quaggaospfdraw']['config'][0]['zebra'])) {
&& !empty($config['installedpackages']['quaggaospfdraw']['config'][0]['zebra'])) {
// if there is a raw config specifyed in tthe config.xml use that instead of the assisted config
$zebraconffile = str_replace("\r", "", base64_decode($config['installedpackages']['quaggaospfdraw']['config'][0]['zebra']));
} else {
Expand All @@ -291,11 +291,10 @@ function quagga_ospfd_install_conf() {

/* Make bgpd config, add password and logging */
if (isset($config['installedpackages']['quaggaospfdraw']['config'][0]['bgpd'])
&& !empty($config['installedpackages']['quaggaospfdraw']['config'][0]['bgpd'])) {
&& !empty($config['installedpackages']['quaggaospfdraw']['config'][0]['bgpd'])) {
// if there is a raw config specified in the config.xml use that instead of the assisted config
$bgpdconffile = str_replace("\r","",base64_decode($config['installedpackages']['quaggaospfdraw']['config'][0]['bgpd']));
}
else {
} else {
$bgpdconffile = "# This file was created by the pfSense package manager. Do not edit!\n\n";
if ($ospfd_conf['password']) {
$bgpdconffile .= "password {$ospfd_conf['password']}\n";
Expand Down Expand Up @@ -324,39 +323,81 @@ function quagga_ospfd_install_conf() {
fwrite($fd, $bgpdaddmd5file);
fclose($fd);
}

if ($bgpddelmd5file != "") {
$fd = fopen("{$quagga_config_base}/bgpddelmd5pw.conf", "w");
fwrite($fd, $bgpddelmd5file);
fclose($fd);
}
}
/* Make ospf6 config */
/* Make ospf6 config */
if (isset($config['installedpackages']['quaggaospfdraw']['config'][0]['ospf6d'])
&& !empty($config['installedpackages']['quaggaospfdraw']['config'][0]['ospf6d'])) {
// if there is a raw config specified in the config.xml use that instead of the assisted config
$ospf6dconffile = str_replace("\r","",base64_decode($config['installedpackages']['quaggaospfdraw']['config'][0]['ospf6d']));
} else {
$ospf6dconffile = "";
$ospf6dconffile = "";
}

$fd = fopen("{$quagga_config_base}/ospf6d.conf", "w");
fwrite($fd, $ospf6dconffile);
fclose($fd);

//UPGRADE PATH - Checks if legacy "CARP Disable" mode was previously in use proior to 0.6.20; if previously in use, sets "carpmode" to "quaggadisable". If not in use, sets "carpmode" to "none".
if (!isset($ospfd_conf['carpmode'])) {
syslog(LOG_NOTICE, "Quagga: CARP failover mode upgrade initializing");
if (strpos($ospfd_conf['carpstatusvid'],'_vip') !== false) {
$ospfd_conf['carpmode'] = "quaggadisable";
syslog(LOG_NOTICE, "Quagga: CARP failover mode established as 'quaggadisable'");
} else {
$ospfd_conf['carpmode'] = "none";
syslog(LOG_NOTICE, "Quagga: CARP failover mode established as 'none'");
}
write_config( $desc = gettext("Quagga_OSPFd: Upgraded failover method for legacy configurations of Quagga_OSPFD") );
syslog(LOG_NOTICE, "Quagga: CARP failover upgrade complete");
}

$carp_ip_status_check = "";
if (isset($ospfd_conf['carpstatusvid']) && $ospfd_conf['carpstatusvid'] != "none") {
$vip = get_configured_vip($ospfd_conf['carpstatusvid']);
$carpcheckinterface = escapeshellarg(get_real_interface($vip['interface']));
$vhid = escapeshellarg("vhid {$vip['vhid']}");
$carp_ip_status_check = <<<EOF
if ((isset($ospfd_conf['carpmode']) && $ospfd_conf['carpmode'] == "quaggadisable") && (isset($ospfd_conf['carpstatusvid']) && $ospfd_conf['carpstatusvid'] != "none")) {
$vip = get_configured_vip($ospfd_conf['carpstatusvid']);
$carpcheckinterface = escapeshellarg(get_real_interface($vip['interface']));
$vhid = escapeshellarg("vhid {$vip['vhid']}");
$carp_ip_status_check = <<<EOF

CARP_STATUS=`/sbin/ifconfig {$carpcheckinterface} | /usr/bin/grep 'carp:' | /usr/bin/grep {$vhid} | /usr/bin/awk '{print \$2;}'`
if [ \${CARP_STATUS} != "MASTER" ]; then
logger "Quagga: CARP \"Quagga Disable\" Mode - Interface {$carpcheckinterface} is NOT in MASTER state, exiting";
exit;
fi
EOF;
}
}

$carp_ospfcost_status_check = "";
if ((isset($ospfd_conf['carpmode']) && $ospfd_conf['carpmode'] == "ospfcost")
&& (isset($ospfd_conf['carpcostvid']) && substr($ospfd_conf['carpcostvid'],0,4) != "none")
&& (isset($ospfd_conf['carpactivecost']) && isset($ospfd_conf['carpbackupcost']))
&& ($ospfd_conf['carpactivecost'] >= 0 && $ospfd_conf['carpactivecost'] <= 65535)
&& ($ospfd_conf['carpbackupcost'] >= 0 && $ospfd_conf['carpbackupcost'] <= 65535)) {
$control_script = "/usr/local/bin/quaggactl";
$carpvips = explode(",",$ospfd_conf['carpcostvid']);
foreach ($carpvips as $vips) {
$vip = get_configured_vip($vips);
$phyint = get_real_interface($vip['interface']);
$carpcheckinterface = escapeshellarg(get_real_interface($vip['interface']));
$vhid = escapeshellarg("vhid {$vip['vhid']}");
$carp_ospfcost_status_check .= <<<EOF

CARP_STATUS=`/sbin/ifconfig {$carpcheckinterface} | /usr/bin/grep 'carp:' | /usr/bin/grep {$vhid} | /usr/bin/awk '{print \$2;}'`
if [ \${CARP_STATUS} == "MASTER" ]; then
{$control_script} ospf cost {$phyint} {$ospfd_conf['carpactivecost']};
logger "Quagga: CARP \"OSPF Cost\" Mode - Interface {$carpcheckinterface} is in MASTER state, OSPF cost set to {$ospfd_conf['carpactivecost']}";
else
{$control_script} ospf cost {$phyint} {$ospfd_conf['carpbackupcost']};
logger "Quagga: CARP \"OSPF Cost\" Mode - Interface {$carpcheckinterface} is NOT in MASTER state, OSPF cost set to {$ospfd_conf['carpbackupcost']}";
fi
EOF;

}
}


// Create rc.d file
Expand Down Expand Up @@ -422,6 +463,7 @@ fi
[ -s {$quagga_config_base}/ospf6d.conf ] && /usr/local/sbin/ospf6d -d -f {$quagga_config_base}/ospf6d.conf
[ -s {$quagga_config_base}/bgpdaddmd5pw.conf ] && /sbin/setkey -f {$quagga_config_base}/bgpdaddmd5pw.conf
[ -s {$quagga_config_base}/bgpd.conf ] && /usr/local/sbin/bgpd -d -f {$quagga_config_base}/bgpd.conf
{$carp_ospfcost_status_check}

EOF;
write_rcfile(array(
Expand All @@ -439,18 +481,20 @@ EOF;
mwexec("/bin/chmod u+rw,go-rw {$quagga_config_base}/bgpd.conf");

// Kick off newly created rc.d script
if (isset($ospfd_conf['carpstatusvid']) && $ospfd_conf['carpstatusvid'] != "none") {
if ((isset($ospfd_conf['carpmode']) && $ospfd_conf['carpmode'] == "quaggadisable") && (isset($ospfd_conf['carpstatusvid']) && $ospfd_conf['carpstatusvid'] != "none")) {
$status = get_carp_interface_status($ospfd_conf['carpstatusvid']);
switch (strtoupper($status)) {
// Stop the service if the VIP is in BACKUP or INIT state.
case "BACKUP":
case "INIT":
mwexec_bg("/usr/local/etc/rc.d/quagga.sh stop");
syslog(LOG_NOTICE, "Quagga: CARP \"Quagga Disable\" Mode - Interface {$carpcheckinterface} is NOT in MASTER state, exiting");
break;
// Start the service if the VIP is MASTER state.
case "MASTER":
// Assume it's up if the status can't be determined.
default:
syslog(LOG_NOTICE, "Quagga: CARP \"Quagga Disable\" Mode - Interface {$carpcheckinterface} is in MASTER state, starting");
mwexec_bg("/usr/local/etc/rc.d/quagga.sh restart");
break;
}
Expand Down Expand Up @@ -517,22 +561,83 @@ function quagga_ospfd_plugin_carp($pluginparams) {
return null;
}
/* If there is no properly configured CARP status check IP, then stop */
if (!isset($ospfd_conf['carpstatusvid']) || $ospfd_conf['carpstatusvid'] == "none") {
return null;
}
list($vhid, $iface) = explode("@", trim($pluginparams['interface']));
$friendly = convert_real_interface_to_friendly_interface_name($iface);
$vip = get_configured_vip($ospfd_conf['carpstatusvid']);
if ($vip['vhid'] != $vhid || $vip['interface'] != $friendly) {
return null;
}

/* Start or stop the service as needed based on the CARP transition. */
if ($pluginparams['event'] == "rc.carpmaster") {
start_service("Quagga OSPFd");
} elseif ($pluginparams['event'] == "rc.carpbackup") {
stop_service("Quagga OSPFd");
}
if (!isset($ospfd_conf['carpmode']) || $ospfd_conf['carpmode'] == "none") {
syslog(LOG_ALERT, "Quagga: CARP failover mode not set.");
return null;
}
/* Verify "quaggadisable" parameters are properly set.*/
if ( $ospfd_conf['carpmode'] == "quaggadisable" && (!isset($ospfd_conf['carpstatusvid']) || $ospfd_conf['carpstatusvid'] == "none")) {
syslog(LOG_ALERT, "Quagga: Failed checking CARP mode parameters: {$ospfd_conf['carpmode']}");
return null;
}
/* Verify "carpmode" parameters are properly set.*/
if ( $ospfd_conf['carpmode'] == "ospfcost" && (!isset($ospfd_conf['carpcostvid'])
|| substr($ospfd_conf['carpcostvid'],0,4) == "none")
|| !isset($ospfd_conf['carpactivecost']) || !isset($ospfd_conf['carpbackupcost'])
|| ($ospfd_conf['carpactivecost'] < 0 || $ospfd_conf['carpactivecost'] > 65535)
|| ($ospfd_conf['carpbackupcost'] < 0 || $ospfd_conf['carpbackupcost'] > 65535)) {
syslog(LOG_ALERT, "Quagga: Failed checking CARP mode parameters: {$ospfd_conf['carpmode']}");
return null;
}

list($vhid, $iface) = explode("@", trim($pluginparams['interface']));
$friendly = convert_real_interface_to_friendly_interface_name($iface);

switch ($ospfd_conf['carpmode']) {
case "quaggadisable":
syslog(LOG_INFO, "Quagga: CARP mode {$ospfd_conf['carpmode']}");
$vip = get_configured_vip($ospfd_conf['carpstatusvid']);
syslog(LOG_INFO, "Quagga: CARP VHID and Interface {$vip['vhid']},{$vip['interface']}");
if ($vip['vhid'] != $vhid || $vip['interface'] != $friendly) {
return null;
}

/* Start or stop the service as needed based on the CARP transition. */
if ((isset($ospfd_conf['carpmode']) && $ospfd_conf['carpmode'] == "quaggadisable") && (isset($ospfd_conf['carpstatusvid']) && $ospfd_conf['carpstatusvid'] != "none")) {
if ($pluginparams['event'] == "rc.carpmaster") {
start_service("Quagga OSPFd");
} elseif ($pluginparams['event'] == "rc.carpbackup") {
stop_service("Quagga OSPFd");
}
}
break;
case "ospfcost":
syslog(LOG_INFO, "Quagga: CARP mode {$ospfd_conf['carpmode']}");
$carpvips = explode(",",$ospfd_conf['carpcostvid']);
$match = FALSE;
$i = 0;
foreach ($carpvips as $vip) {
$vips[$i] = get_configured_vip($vip);
if (($vips[$i]['vhid'] == $vhid) && ($vips[$i]['interface'] == $friendly)){
$match = TRUE;
syslog(LOG_INFO, "Quagga: CARP VHID and Interface {$vips[$i]['vhid']} {$vips[$i]['interface']}");
$phyint = get_real_interface($vips[$i]['interface']);
syslog(LOG_INFO, "Quagga: CARP Interface identified {$phyint}");
}
$i++;
}
unset($vip);

if ($match != TRUE){
syslog(LOG_NOTICE, "Quagga: Couldn't find an Interface or VHID match!");
return null;
}

/* Shift OSPF cost up or downas needed based on the CARP transition. */
$control_script = "/usr/local/bin/quaggactl";
if ((isset($ospfd_conf['carpmode']) && $ospfd_conf['carpmode'] == "ospfcost") && (isset($ospfd_conf['carpcostvid']) && substr($ospfd_conf['carpcostvid'],0,4) != "none")) {
if ($pluginparams['event'] == "rc.carpmaster") {
syslog(LOG_INFO, "Quagga: Shifted OSPF Master cost on {$phyint} to {$ospfd_conf['carpactivecost']}");
mwexec("{$control_script} ospf cost {$phyint} {$ospfd_conf['carpactivecost']}");
return null;
} elseif ($pluginparams['event'] == "rc.carpbackup") {
syslog(LOG_INFO, "Quagga: Shifted OSPF Backup cost on {$phyint} to {$ospfd_conf['carpbackupcost']}");
mwexec("{$control_script} ospf cost {$phyint} {$ospfd_conf['carpbackupcost']}");
return null;
}
}
break;
}
}

/* The following function checks for the presence of and writes the contents of "/var/etc/quagga/$module.conf" into the $module . "running" field within the config.xml file */
Expand All @@ -547,11 +652,10 @@ function write_quagga_running_config($module) {
if ( file_exists( $moduleRunningFile ) && ( filesize( $moduleRunningFile ) > 0 ) ) {
$moduleRunning = fopen( "$moduleRunningFile", "r" );
$config['installedpackages']['quaggaospfdraw']['config'][0][$module . "running"] = base64_encode( fread( $moduleRunning, filesize($moduleRunningFile) ));
}
else {
} else {
$config['installedpackages']['quaggaospfdraw']['config'][0][$module . "running"] = base64_encode("!!!!! {$module}.conf does not exist or is empty.");
}
write_config( $write_config_only = true );
write_config( $desc = gettext("Quagga_OSPFd: Wrote {$module}.conf startup-config to pfSense config file."), $backup = false, $write_config_only = true );
conf_mount_ro();
}

Expand Down
Loading