#!/usr/local/bin/php * OPNsense CARP event script * - Enables/disables the WAN interface only when needed * - Avoids reapplying config when CARP triggers multiple times */ require_once("config.inc"); require_once("interfaces.inc"); require_once("util.inc"); require_once("system.inc"); // Read CARP event arguments $subsystem = !empty($argv[1]) ? $argv[1] : ''; $type = !empty($argv[2]) ? $argv[2] : ''; // Accept only MASTER/BACKUP events if (!in_array($type, ['MASTER', 'BACKUP'])) { // Ignore CARP INIT, DEMOTED, etc. exit(0); } // Validate subsystem name format, expected pattern: @ if (!preg_match('/^[a-z0-9_]+@\S+$/i', $subsystem)) { log_error("Malformed subsystem argument: '{$subsystem}'."); exit(0); } // Only react to the primary VHID list($vhid, $iface) = explode('@', $subsystem); $primary_vhid = '1'; // if ($vhid !== $primary_vhid) { exit(0); // ignore events from other VHIDs } // Interface key to manage $ifkey = 'wan'; $real_if = get_real_interface('wan'); // Fallback gateway name $gw_name = 'LAN_GW'; // Determine whether WAN interface is currently enabled $ifkey_enabled = !empty($config['interfaces'][$ifkey]['enable']) ? true : false; // Lock file to prevent interface flapping $lock_file = '/tmp/carp_wan_disable_lock'; $lock_default_age = 5; $lock_max_age = 10; // MASTER event if ($type === "MASTER") { // Enable WAN only if it's currently disabled if (!$ifkey_enabled) { // Check if lock file is present if (file_exists($lock_file)) { $lock_age = time() - (int)file_get_contents($lock_file); if ($lock_age < $lock_max_age) { log_msg("CARP event: WAN disable lock present ({$lock_age}s old), waiting..."); $elapsed = 0; while (file_exists($lock_file) && $elapsed < 5000) { usleep(500000); $elapsed += 500; } } else { log_msg("CARP event: removing stale WAN disable lock."); @unlink($lock_file); } } log_msg("CARP event: switching to '$type', enabling interface '$ifkey'.", LOG_WARNING); $config['interfaces'][$ifkey]['enable'] = '1'; write_config("enable interface '$ifkey' due CARP event '$type'", false); interface_configure(false, $ifkey, false, false); } else { log_msg("CARP event: already 'MASTER' for interface '$ifkey', nothing to do."); } // BACKUP event } else { // Disable WAN only if it's currently enabled if ($ifkey_enabled) { log_msg("CARP event: switching to '$type', disabling interface '$ifkey'.", LOG_WARNING); unset($config['interfaces'][$ifkey]['enable']); write_config("disable interface '$ifkey' due CARP event '$type'", false); interface_configure(false, $ifkey, false, false); // Remove WAN default gateway mwexec("/sbin/route delete default"); foreach ($config['OPNsense']['Gateways']['gateway_item'] as $gw) { if ($gw['name'] === $gw_name) { $gw_ip = $gw['gateway']; break; } } // Shutdown WAN interface mwexec("/sbin/ifconfig {$real_if} down") // Add fallback default gateway mwexec("/sbin/route add default {$gw_ip}"); // Create lock file file_put_contents($lock_file, time()); sleep($lock_default_age); @unlink($lock_file); } else { log_msg("CARP event: already '$type' for interface '$ifkey', nothing to do."); } }