Skip to content

Instantly share code, notes, and snippets.

@willjasen
Forked from spali/10-wancarp
Last active July 12, 2024 15:43
Show Gist options
  • Save willjasen/6ae0f47bca36ced2bd52b2fefc2bc21e to your computer and use it in GitHub Desktop.
Save willjasen/6ae0f47bca36ced2bd52b2fefc2bc21e to your computer and use it in GitHub Desktop.
Disable WAN interfaces when CARP is down
#!/usr/local/bin/php
<?php
/*
This script can be used with OPNsense when using CARP in certain circumstances where CARP is desired on the LAN side
but where CARP cannot run on the WAN side. This script runs each time an event by CARP is generated. In the event that
there are multiple LAN interfaces where CARP is enabled, this script will check that all interfaces where CARP is
enabled are in a particular state (MASTER/BACKUP) before enabling or disabling its WAN interfaces.
*/
// this file should be saved to /usr/local/etc/rc.syshook.d/carp/10-wancarp
// some ideas taken from https://gist.github.com/taxilian/eecdc1fb17cf70e8080118cf6d8af412
/* --- EDIT AS NEEDED --- */
// my primary WAN is on the opt2 interface and secondary WAN is on the wan interface
$interfaces = array('opt2', 'wan');
require_once("config.inc");
require_once("interfaces.inc");
require_once("util.inc");
$subsystem = !empty($argv[1]) ? $argv[1] : '';
$type = !empty($argv[2]) ? $argv[2] : '';
if ($type != 'MASTER' && $type != 'BACKUP' && $type != 'INIT' && $type != 'DISABLED') {
log_error("CARP '$type' event unknown from source '{$subsystem}'");
exit(1);
}
if (!strstr($subsystem, '@')) {
log_error("CARP '$type' event triggered from wrong source '{$subsystem}'");
exit(1);
}
// Get a list of CARP statuses across interfaces that have it
$cmd = "/sbin/ifconfig -m -v | grep 'carp:' | awk '{print $2}'";
$cmd2 = "/sbin/ifconfig -m -v | grep 'carp:' | awk '{print $2}' | wc -l | tr -d ' '";
exec($cmd, $ifconfig_data, $ret);
exec($cmd2, $ifconfig_num, $ret);
$activeCARPInterfaces = $ifconfig_num[0];
// Log what the CARP subsystem is doing
// After INIT, the subsystem will become BACKUP, then MASTER if needed
if($type == 'INIT' && $activeCARPInterfaces > 0) {
log_error("CARP on '{$subsystem}' is now INIT, but other subsystems are still active");
} else if($type == 'INIT' && $activeCARPInterfaces == 0) {
log_error("CARP on '{$subsystem}' is now INIT, and appears to be the last active");
}
else if($type == 'MASTER' && $activeCARPInterfaces > 0) {
log_error("CARP on '{$subsystem}' is now MASTER, but other subsystems are still active");
} else if($type == 'MASTER' && $activeCARPInterfaces == 0) {
log_error("CARP on '{$subsystem}' is now MASTER, and appears to be the last active");
}
else if($type == 'BACKUP' && $activeCARPInterfaces > 0) {
log_error("CARP on '{$subsystem}' is now BACKUP, other subsystems are still active");
} else if($type == 'BACKUP' && $activeCARPInterfaces == 0) {
log_error("CARP on '{$subsystem}' is now BACKUP, and appears to be the last active");
}
// If no CARP subsystems/interfaces are active, then disable WAN interfaces
// This will generally apply when CARP is disabled and reports no statuses
if($activeCARPInterfaces == 0) {
log_error("No CARP subsystems are active, deactivating WAN interfaces");
foreach ($interfaces as $ifkey) {
unset($config['interfaces'][$ifkey]['enable']);
interface_configure(false, $ifkey, false, false);
write_config("disable interface '$ifkey' due CARP event '$type'", false);
}
exit(0);
}
// Keep track of active MASTER and BACKUP instances
$masterCount = 0;
$backupCount = 0;
// Loop over $ifconfig_data and count how many are "MASTER" and how many are "BACKUP"
foreach ($ifconfig_data as $line) {
if (strpos($line, 'MASTER') !== false) {
$masterCount++;
} else if (strpos($line, 'BACKUP') !== false) {
$backupCount++;
}
}
// Toggle WAN interfaces depending if all interfaces are MASTER or BACKUP
if ($masterCount == $activeCARPInterfaces && $masterCount > 0) {
// The current node is all MASTER
log_error("All CARP subsystems are MASTER, activating WAN interfaces");
foreach ($interfaces as $ifkey) {
$config['interfaces'][$ifkey]['enable'] = '1';
interface_configure(false, $ifkey, false, false);
write_config("enable interface '$ifkey' due CARP event '$type'", false);
}
} else if ($backupCount == $activeCARPInterfaces && $backupCount > 0) {
// The current node is all BACKUP
log_error("Not all CARP subsystems are MASTER, deactivating WAN interfaces");
foreach ($interfaces as $ifkey) {
unset($config['interfaces'][$ifkey]['enable']);
interface_configure(false, $ifkey, false, false);
write_config("disable interface '$ifkey' due CARP event '$type'", false);
}
}
?>
@skl283
Copy link

skl283 commented Apr 20, 2024

Did you find a solution? Perhaps it is the same problem which i mentioned here?

@oasis9
Copy link

oasis9 commented Apr 22, 2024

@skl283 I'm having a similar issue; IPv4 works instantly but my backup router won't pick up IPv6 information even if I reload using the command or gui. I have a very limited understanding of IPv6, not really sure how Router Advertisements work/why my backup router isn't working when my primary works each time. I have my IPv6 DUID set the same on each machine and I use the same MAC on my single WAN on both machines. Is it wise to use the same DUID for IPv6 in this scenario? I have Interfaces > Settings > IPv6 DHCP > Prevent Release enabled on both machines.

I also notice when I enter persistent CARP maintenance mode, I get a warning from OPNsense that an issue was detected with the machine and it has been demoted to backup, which usually doesn't show up, the machine would normally just go into backup. I have to enter persistent CARP maintenance mode again for it to properly engage, then press the button again to leave it. Not sure what's causing that warning, though a require_once("system.inc"); helped me fix some errors I was seeing reported by OPNsense, though not all.

@willjasen
Copy link
Author

Hey there everyone looking at this thread - I hope this script has helped out in some way!

I am softly abandoning my further development however. My setup where this was used was running two OPNsense instances virtually acting as my primary home firewall/router, but I have since replaced it with a Ubiquiti firewall - the two main reasons for myself being that it would be less time developing and troubleshooting this script as well as I could foreshadow an edge case where my Proxmox cluster might not gain quorum and be able to boot up virtual machines (including the OPNsenses) and thus would be a catch 22 and very bad. I still could have a use case for this within my dedicated cloud server, but one OPNsense there is enough and two aren't necessary.

Thank you, namaste, and good luck!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment